aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml28
-rw-r--r--data/sql/customizations.sql4
-rw-r--r--data/sql/proj_db_table_defs.sql2
-rw-r--r--docs/source/apps/projsync.rst2
-rw-r--r--docs/source/community/rfc/rfc-2.rst2
-rw-r--r--docs/source/community/rfc/rfc-3.rst2
-rw-r--r--docs/source/community/rfc/rfc-4.rst16
-rw-r--r--docs/source/community/rfc/rfc-5.rst2
-rw-r--r--docs/source/development/reference/functions.rst2
-rw-r--r--docs/source/faq.rst2
-rw-r--r--docs/source/install.rst2
-rw-r--r--docs/source/news.rst10
-rw-r--r--docs/source/operations/operations_computation.rst12
-rw-r--r--docs/source/operations/transformations/helmert.rst2
-rw-r--r--docs/source/operations/transformations/molodensky.rst2
-rw-r--r--docs/source/resource_files.rst4
-rw-r--r--docs/source/specifications/geodetictiffgrids.rst6
-rw-r--r--docs/source/specifications/projjson.rst4
-rw-r--r--docs/source/usage/differences.rst8
-rw-r--r--docs/source/usage/environmentvars.rst2
-rw-r--r--docs/source/usage/network.rst2
-rw-r--r--docs/source/usage/projections.rst2
-rw-r--r--docs/source/usage/transformation.rst2
-rw-r--r--include/proj/coordinateoperation.hpp8
-rw-r--r--include/proj/crs.hpp3
-rw-r--r--include/proj/datum.hpp2
-rw-r--r--include/proj/internal/lru_cache.hpp2
-rw-r--r--include/proj/util.hpp2
-rwxr-xr-xscripts/fix_typos.sh26
-rw-r--r--scripts/typos_whitelist.txt1
-rw-r--r--src/4D_api.cpp2
-rw-r--r--src/apps/cs2cs.cpp4
-rw-r--r--src/ell_set.cpp2
-rw-r--r--src/geodesic.h2
-rw-r--r--src/grids.cpp2
-rw-r--r--src/internal.cpp2
-rw-r--r--src/iso19111/c_api.cpp14
-rw-r--r--src/iso19111/common.cpp2
-rw-r--r--src/iso19111/coordinateoperation.cpp319
-rw-r--r--src/iso19111/crs.cpp305
-rw-r--r--src/iso19111/datum.cpp14
-rw-r--r--src/iso19111/factory.cpp237
-rw-r--r--src/iso19111/io.cpp198
-rw-r--r--src/iso19111/metadata.cpp6
-rw-r--r--src/iso19111/util.cpp2
-rw-r--r--src/malloc.cpp2
-rw-r--r--src/networkfilemanager.cpp4
-rw-r--r--src/projections/isea.cpp2
-rw-r--r--src/projections/ocea.cpp4
-rw-r--r--src/projections/tmerc.cpp4
-rw-r--r--src/sqlite3_utils.cpp17
-rw-r--r--src/strtod.cpp2
-rw-r--r--src/transformations/vgridshift.cpp2
-rwxr-xr-xtest/cli/testvarious2
-rw-r--r--test/fuzzers/README.TXT2
-rw-r--r--test/gie/more_builtins.gie2
-rw-r--r--test/unit/test_c_api.cpp6
-rw-r--r--test/unit/test_crs.cpp80
-rw-r--r--test/unit/test_factory.cpp7
-rw-r--r--test/unit/test_io.cpp170
-rw-r--r--test/unit/test_operation.cpp340
-rwxr-xr-xtravis/linux_s390x/after_success.sh5
-rwxr-xr-xtravis/linux_s390x/before_install.sh8
-rwxr-xr-xtravis/linux_s390x/install.sh9
64 files changed, 1409 insertions, 534 deletions
diff --git a/.travis.yml b/.travis.yml
index d62d104f..891e18df 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -66,6 +66,14 @@ matrix:
- BUILD_NAME=linux_clang
- DETAILS="linux, clang"
+ - os: linux
+ dist: bionic
+ compiler: gcc
+ arch: s390x
+ env:
+ - BUILD_NAME=linux_s390x
+ - DETAILS="linux, s390x"
+
- os: osx
env:
- BUILD_NAME=osx
@@ -97,13 +105,13 @@ script:
after_success: ./travis/${BUILD_NAME}/after_success.sh
-notifications:
- #email:
- # recipients:
- # - gdal-commits@lists.osgeo.org
-
- irc:
- channels:
- - "irc.freenode.org#gdal"
- - "irc.freenode.org#proj"
- use_notice: true
+#notifications:
+# #email:
+# # recipients:
+# # - gdal-commits@lists.osgeo.org
+#
+# irc:
+# channels:
+# - "irc.freenode.org#gdal"
+# - "irc.freenode.org#proj"
+# use_notice: true
diff --git a/data/sql/customizations.sql b/data/sql/customizations.sql
index fd67b8a1..efb0a4bc 100644
--- a/data/sql/customizations.sql
+++ b/data/sql/customizations.sql
@@ -136,6 +136,10 @@ INSERT INTO "alias_name" VALUES('geodetic_datum','EPSG','6299','ire65','PROJ');
INSERT INTO "alias_name" VALUES('geodetic_datum','EPSG','6272','nzgd49','PROJ');
INSERT INTO "alias_name" VALUES('geodetic_datum','EPSG','6277','OSGB36','PROJ');
+-- Given that we have installed above a WGS84 alias to the datum, add also one
+-- to the EPSG:4326 CRS, as this is a common use case (https://github.com/OSGeo/PROJ/issues/2216)
+INSERT INTO "alias_name" VALUES('geodetic_crs','EPSG','4326','WGS84','PROJ');
+
---- PROJ unit short names -----
-- Linear units
diff --git a/data/sql/proj_db_table_defs.sql b/data/sql/proj_db_table_defs.sql
index df20de31..2d820d20 100644
--- a/data/sql/proj_db_table_defs.sql
+++ b/data/sql/proj_db_table_defs.sql
@@ -1340,6 +1340,8 @@ CREATE TABLE alias_name(
source TEXT
);
+CREATE INDEX idx_alias_name_code ON alias_name(code);
+
CREATE TRIGGER alias_name_insert_trigger
BEFORE INSERT ON alias_name
FOR EACH ROW BEGIN
diff --git a/docs/source/apps/projsync.rst b/docs/source/apps/projsync.rst
index 9756e0c4..d79bd87b 100644
--- a/docs/source/apps/projsync.rst
+++ b/docs/source/apps/projsync.rst
@@ -110,7 +110,7 @@ The following control parameters can appear in any order:
.. option:: --dry-run
- Simulate the behaviour of the tool without downloading resource files.
+ Simulate the behavior of the tool without downloading resource files.
.. option:: --list-files
diff --git a/docs/source/community/rfc/rfc-2.rst b/docs/source/community/rfc/rfc-2.rst
index df6f742d..4cea339d 100644
--- a/docs/source/community/rfc/rfc-2.rst
+++ b/docs/source/community/rfc/rfc-2.rst
@@ -405,7 +405,7 @@ be combined together.
- `alias_names`: list possible alias for the `name` field of object table
- `link_from_deprecated_to_non_deprecated`: to handle the link between old ESRI to new ESRI/EPSG codes
-- Commmon:
+- Common:
- `unit_of_measure`: table with UnitOfMeasure definitions.
- `area`: table with area-of-use (bounding boxes) applicable to CRS and coordinate operations.
diff --git a/docs/source/community/rfc/rfc-3.rst b/docs/source/community/rfc/rfc-3.rst
index d3e3d14c..ae4bff95 100644
--- a/docs/source/community/rfc/rfc-3.rst
+++ b/docs/source/community/rfc/rfc-3.rst
@@ -120,7 +120,7 @@ development, but it is allowed in case future development of PROJ warrants an
update. Changes in minimum version requirements are allowed to happen with
minor version releases of PROJ.
-At the time of writing the minimum version requried for SQLite it 3.7 which was
+At the time of writing the minimum version required for SQLite it 3.7 which was
released in 2010. CMake currently is required to be at least at version 2.8.3
which was also released in 2010.
diff --git a/docs/source/community/rfc/rfc-4.rst b/docs/source/community/rfc/rfc-4.rst
index 88abbe9e..0819c6f1 100644
--- a/docs/source/community/rfc/rfc-4.rst
+++ b/docs/source/community/rfc/rfc-4.rst
@@ -70,7 +70,7 @@ Summary of work planned by this RFC
implemented.
The use of grids locally available will of course still be available, and will
-be the default behaviour.
+be the default behavior.
Network access to grids
-------------------------------------------------------------------------------
@@ -220,7 +220,7 @@ The preliminary C API for the above is:
To make network access efficient, PROJ will internally have a in-memory cache
of file ranges to only issue network requests by chunks of 16 KB or multiple of them,
to limit the number of HTTP GET requests and minimize latency caused by network
-access. This is very similar to the behaviour of the GDAL
+access. This is very similar to the behavior of the GDAL
`/vsicurl/ <https://gdal.org/user/virtual_file_systems.html#vsicurl-http-https-ftp-files-random-access>`_
I/O layer. The plan is to mostly copy GDAL's vsicurl implementation inside PROJ, with
needed adjustmeents and proper namespacing of it.
@@ -495,7 +495,7 @@ Several formats exist depending on the ad-hoc needs and ideas of the original
data producer. It would be appropriate to converge on a common format able to
address the different use cases.
-- Not tiled. Tiling is a nice to have propery for cloud-friendly access to
+- Not tiled. Tiling is a nice to have property for cloud-friendly access to
large files.
- No support for compression
- The NTv2 structures is roughly: header of main grid, data of main grid,
@@ -511,13 +511,13 @@ Discussion on choice of format
We have been made recently aware of other initiatives from the industry to come
with a common format to store geodetic adjustment data. Some discussions have
happen recently within the OGC CRS Working group. Past efforts include the
-Esri's proposed Geodetic data Grid eXchange Format, GGXF, briefly mentionned at
+Esri's proposed Geodetic data Grid eXchange Format, GGXF, briefly mentioned at
page 86 of
https://iag.dgfi.tum.de/fileadmin/IAG-docs/Travaux2015/01_Travaux_Template_Comm_1_tvd.pdf
and page 66 of ftp://ftp.iaspei.org/pub/meetings/2010-2019/2015-Prague/IAG-Geodesy.pdf
The current trend of those works would be to use a netCDF / HDF5 container.
-So, for the sake of completness, we list hereafter a few potential candidate
+So, for the sake of completeness, we list hereafter a few potential candidate
formats and their pros and cons.
TIFF/GeoTIFF
@@ -610,7 +610,7 @@ Weak points:
metadata <http://cfconventions.org/>`_
but there is nothing really handy in them for simple georeferencing with
the coordinate of the upper-left pixel and the resolution. The practice is
- to write explict lon and lat variables with all values taken by the grid.
+ to write explicit lon and lat variables with all values taken by the grid.
GDAL has for many years supported a simpler syntax, using a GeoTransform
attribute.
@@ -652,7 +652,7 @@ Weak points:
is more complex than netCDF v3, and likely more than TIFF. We do not have
in-depth expertise of it to assess its cloud-friendliness.
-* The ones mentionned for netCDF v3 regarding georeferencing and security apply.
+* The ones mentioned for netCDF v3 regarding georeferencing and security apply.
GeoPackage
@@ -719,7 +719,7 @@ Build requirements
The minimum libtiff version will be 4.0 (RHEL 7 ships with libtiff 4.0.3).
To be able to read grids stored on the CDN, libtiff will need to build against
-zlib to have DEFLATE and LZW suport, which is met by all known binary distributions
+zlib to have DEFLATE and LZW support, which is met by all known binary distributions
of libtiff.
The libtiff dependency can be disabled at build time, but this must be
diff --git a/docs/source/community/rfc/rfc-5.rst b/docs/source/community/rfc/rfc-5.rst
index 99fec5f7..6e9bf5b7 100644
--- a/docs/source/community/rfc/rfc-5.rst
+++ b/docs/source/community/rfc/rfc-5.rst
@@ -96,7 +96,7 @@ Backward compatibility
This change is considered to be *mostly* backward compatible. There might be
impacts for software using :cpp:func:`proj_coordoperation_get_grid_used` and
assuming that the url returned is one of the proj-datumgrid-xxx files at
-https://download.osgeo.org. As mentionned in
+https://download.osgeo.org. As mentioned in
https://lists.osgeo.org/pipermail/proj/2020-January/009274.html , this
assumption was not completely bullet-proof either.
There will be impacts on software checking the value of PROJ pipeline strings
diff --git a/docs/source/development/reference/functions.rst b/docs/source/development/reference/functions.rst
index 960ed57e..d5611c9a 100644
--- a/docs/source/development/reference/functions.rst
+++ b/docs/source/development/reference/functions.rst
@@ -276,7 +276,7 @@ Coordinate transformation
.. note:: Even though he coordinate components are named :c:data:`x`, :c:data:`y`,
:c:data:`z` and :c:data:`t`, axis ordering of the to and from CRS
- is respected. Transformations exhibit the same behaviour
+ is respected. Transformations exhibit the same behavior
as if they were gathered in a :c:type:`PJ_COORD` struct.
diff --git a/docs/source/faq.rst b/docs/source/faq.rst
index cd545036..5a6763bc 100644
--- a/docs/source/faq.rst
+++ b/docs/source/faq.rst
@@ -188,6 +188,6 @@ was no longer aligned with the version number. As a consequence, it was
decided to decouple the name from the version number and once again simply
call the software PROJ.
-Use of name PROJ.4 is now strictly reserved for describing legacy behaviour
+Use of name PROJ.4 is now strictly reserved for describing legacy behavior
of the software, e.g. "PROJ.4 strings" as seen in :program:`projinfo`
output.
diff --git a/docs/source/install.rst b/docs/source/install.rst
index f6ca0d5d..dd57d22c 100644
--- a/docs/source/install.rst
+++ b/docs/source/install.rst
@@ -212,7 +212,7 @@ Autotools configure options
Most POSIX systems may not require any options to ``./configure`` if all
PROJ requirements are met, installed into common directories, and a
-"default" behaviour is desired.
+"default" behavior is desired.
Some influential environment variables are used by ``./configure``,
with no expected defaults:
diff --git a/docs/source/news.rst b/docs/source/news.rst
index c54232ef..3e135cba 100644
--- a/docs/source/news.rst
+++ b/docs/source/news.rst
@@ -167,7 +167,7 @@ Bug fixes
The major feature in PROJ 7 is significantly improved handling of gridded
models. This was implemented in :ref:`RFC4`.
The main features of the RFC4 work is that PROJ now implements a new grid format,
-Geodetic TIFF grids, for exchaning gridded transformation models. In addition
+Geodetic TIFF grids, for exchanging gridded transformation models. In addition
to the new grid format, PROJ can now also access grids online using a data
store in the cloud.
@@ -530,7 +530,7 @@ Bug Fixes
* Do not include :envvar:`PROJ_LIB` in ``proj_info().searchpath`` when context
search path is set (`#1498 <https://github.com/OSGeo/PROJ/issues/1498>`_)
-* Use correct delimeter for the current platform when parsing
+* Use correct delimiter for the current platform when parsing
PROJ_LIB (`#1497 <https://github.com/OSGeo/PROJ/issues/1497>`_)
* Do not confuse 'ID74' CRS with WKT2 ID[] node (`#1506 <https://github.com/OSGeo/PROJ/issues/1506>`_)
@@ -691,15 +691,15 @@ UPDATES
* Removed :c:func:`proj_geocentric_latitude` from `proj.h` API
(`#1170 <https://github.com/OSGeo/proj.4/issues/1170>`_)
-* Changed behaviour of :program:`proj`: Now only allow initialization of
+* Changed behavior of :program:`proj`: Now only allow initialization of
projections (`#1162 <https://github.com/OSGeo/proj.4/issues/1162>`_)
-* Changed behaviour of :ref:`tmerc <tmerc>`: Now defaults to the Extended
+* Changed behavior of :ref:`tmerc <tmerc>`: Now defaults to the Extended
Transverse Mercator algorithm (``etmerc``). Old implementation available
by adding ``+approx``
(`#404 <https://github.com/OSGeo/proj.4/issues/404>`_)
-* Chaged behaviour: Default ellipsoid now set to GRS80 (was WGS84) (`#1210 <https://github.com/OSGeo/proj.4/issues/1210>`_)
+* Chaged behavior: Default ellipsoid now set to GRS80 (was WGS84) (`#1210 <https://github.com/OSGeo/proj.4/issues/1210>`_)
* Allow multiple directories in :envvar:`PROJ_LIB` environment variable (`#1281 <https://github.com/OSGeo/proj.4/issues/1281>`_)
diff --git a/docs/source/operations/operations_computation.rst b/docs/source/operations/operations_computation.rst
index 2836b75c..f1453257 100644
--- a/docs/source/operations/operations_computation.rst
+++ b/docs/source/operations/operations_computation.rst
@@ -135,7 +135,7 @@ performed in the order they are listed below:
ballpark vertical transformation (occurs when there is a geoid model).
3. if both operations evaluate identically with respect to the above criterion,
consider as more relevant an operation that does not include a synthetic
- ballpark horizontal tranformation.
+ ballpark horizontal transformation.
4. consider as more relevant an operation that refers to shift grids that are locally available.
5. consider as more relevant an operation that refers to grids that are available
in one of the proj-datumgrid packages, but not necessarily locally available
@@ -149,7 +149,7 @@ performed in the order they are listed below:
10. in case of same accuracy, consider as more relevant an operation that does
not use grids (operations that use only parameters will be faster)
11. consider as more relevant an operation that involves less transformation steps
-12. and for completness, if two operations are comparable given all the above criteria,
+12. and for completeness, if two operations are comparable given all the above criteria,
consider as more relevant the one which has the shorter name, and if they
have the same length, consider as more relevant the one whose name comes first in
lexicographic order (obviously completely arbitrary, but a sorting
@@ -403,7 +403,7 @@ following results for the AGD84 to GDA2020 coordinate operations lookup:
+step +proj=axisswap +order=2,1
One can see that the selected intermediate CRS that has been used is GDA94.
-This is a completely novel behaviour of PROJ 6 as opposed to the logic of PROJ.4
+This is a completely novel behavior of PROJ 6 as opposed to the logic of PROJ.4
where datum transformations implied using EPSG:4326 / WGS 84 has the mandatory
datum hub. PROJ 6 no longer hardcodes it as the mandatory datum hub, and relies
on the database to find the appropriate hub(s).
@@ -468,7 +468,7 @@ techniques, we would only find one non-ballpark operation taking the route:
This is not bad, but the global validity area of use is "Australia - onshore and EEZ",
whereas GDA94 has a larger area of use.
-There is another road that can be taken by going throug GDA2020 instead of ITRF2008.
+There is another road that can be taken by going through GDA2020 instead of ITRF2008.
The GDA94 to GDA2020 transformations operate on the respective geographic CRS,
whereas GDA2020 to WGS 84 (G1762) operate on the geocentric CRS. Consequently,
GDA2020 cannot be identifier as a hub by a "simple" self-join SQL request on
@@ -508,7 +508,7 @@ particular case. Such transformations are done in 2 steps:
This is implemented by the ``createOperationsDerivedTo`` method
-For the symetric case, source CRS to a derived CRS, the above algorithm is applied
+For the symmetric case, source CRS to a derived CRS, the above algorithm is applied
by switching the source and target CRS, and then inverting the resulting operation(s).
This is mostly a matter of avoiding to write very similar code twice. This logic
is also applied to all below cases when considering the transformation between 2 different
@@ -659,7 +659,7 @@ CompoundCRS to CompoundCRS
There is some similarity with the previous paragraph. We first research the
vertical transformations between the two vertical CRS.
-1. If there is such a tranformation, be it direct, or if both vertical CRS
+1. If there is such a transformation, be it direct, or if both vertical CRS
relate to a common intermediate CRS.
If it has a registered interpolation geographic CRS, then it is used.
Otherwise we fallback to the geographic CRS of the source CRS.
diff --git a/docs/source/operations/transformations/helmert.rst b/docs/source/operations/transformations/helmert.rst
index 51784bdb..34acc332 100644
--- a/docs/source/operations/transformations/helmert.rst
+++ b/docs/source/operations/transformations/helmert.rst
@@ -33,7 +33,7 @@ kinematic transformations from global reference frames to local static frames.
All of the parameters described in the table above are marked as optional. This is true
as long as at least one parameter is defined in the setup of the transformation.
-The behaviour of the transformation depends on which parameters are used in the setup.
+The behavior of the transformation depends on which parameters are used in the setup.
For instance, if a rate of change parameter is specified a kinematic version of the
transformation is used.
diff --git a/docs/source/operations/transformations/molodensky.rst b/docs/source/operations/transformations/molodensky.rst
index 347165c8..df0b00a2 100644
--- a/docs/source/operations/transformations/molodensky.rst
+++ b/docs/source/operations/transformations/molodensky.rst
@@ -10,7 +10,7 @@ The Molodensky transformation resembles a :ref:`Helmert` with zero
rotations and a scale of unity, but converts directly from geodetic coordinates to
geodetic coordinates, without the intermediate shifts to and from cartesian
geocentric coordinates, associated with the Helmert transformation.
-The Molodensky transformation is simple to implement and to parameterize, requiring
+The Molodensky transformation is simple to implement and to parametrize, requiring
only the 3 shifts between the input and output frame, and the corresponding
differences between the semimajor axes and flattening parameters of the reference
ellipsoids. Due to its algorithmic simplicity, it was popular prior to the
diff --git a/docs/source/resource_files.rst b/docs/source/resource_files.rst
index cef22f84..ae3a808d 100644
--- a/docs/source/resource_files.rst
+++ b/docs/source/resource_files.rst
@@ -23,7 +23,7 @@ PROJ will attempt to locate its resource files - database, transformation grids
or init-files - from several directories.
The following paths are checked in order:
-- For resource files that have an explict relative or absolute path,
+- For resource files that have an explicit relative or absolute path,
the directory specified in the filename.
- Path resolved by the callback function set with
@@ -298,7 +298,7 @@ Getting crs2crs2grid.py
................................................................................
The `crs2crs2grid.py` script can be found at
-https://github.com/OSGeo/gdal/tree/trunk/gdal/swig/python/samples/crs2crs2grid.py
+https://github.com/OSGeo/gdal/tree/master/gdal/swig/python/samples/crs2crs2grid.py
The script depends on having the GDAL Python bindings operational; if they are not you
will get an error such as:
diff --git a/docs/source/specifications/geodetictiffgrids.rst b/docs/source/specifications/geodetictiffgrids.rst
index 307d8657..7c05867b 100644
--- a/docs/source/specifications/geodetictiffgrids.rst
+++ b/docs/source/specifications/geodetictiffgrids.rst
@@ -90,7 +90,7 @@ is an easy way to inspect such grid files:
half-pixel shift regarding to the coordinates stored in the original grid file. On
the reading side, PROJ will accept both conventions (for equivalent georeferencing,
the value of the origin in a PixelIsArea convention is shifted by a half-pixel
- towards the upper-left direction). Unspecified behaviour if this GeoKey is absent.
+ towards the upper-left direction). Unspecified behavior if this GeoKey is absent.
- Files hosted on the CDN will be tiled, presumably with 256x256 tiles (small
grids that are smaller than 256x256 will use a single strip). On the reading
@@ -620,9 +620,9 @@ will be followed by:
.. note::
TIFF has another mechanism to link IFDs, the SubIFD tag. This potentially
- enables to define a hiearchy of IFDs (similar to HDF5 groups). There is no
+ enables to define a hierarchy of IFDs (similar to HDF5 groups). There is no
support for that in most TIFF-using software, notably GDAL, and no compelling
- need to have a nested hiearchy, so "flat" organization with the standard IFD chaining
+ need to have a nested hierarchy, so "flat" organization with the standard IFD chaining
mechanism is adopted.
Examples of multi-grid dataset
diff --git a/docs/source/specifications/projjson.rst b/docs/source/specifications/projjson.rst
index 6bcce585..3484fce9 100644
--- a/docs/source/specifications/projjson.rst
+++ b/docs/source/specifications/projjson.rst
@@ -75,7 +75,7 @@ Examples
GeographicCRS
+++++++++++++
-The following invokation
+The following invocation
::
@@ -132,7 +132,7 @@ will output:
ProjectedCRS
++++++++++++
-The following invokation
+The following invocation
::
diff --git a/docs/source/usage/differences.rst b/docs/source/usage/differences.rst
index 2a262232..11bab0b5 100644
--- a/docs/source/usage/differences.rst
+++ b/docs/source/usage/differences.rst
@@ -4,10 +4,10 @@
Known differences between versions
================================================================================
-Once in a while, a new version of PROJ causes changes in the existing behaviour.
+Once in a while, a new version of PROJ causes changes in the existing behavior.
In this section we track deliberate changes to PROJ that break from previous
-behaviour. Most times that will be caused by a bug fix. Unfortunately, some bugs
-have existed for so long that their faulty behaviour is relied upon by software
+behavior. Most times that will be caused by a bug fix. Unfortunately, some bugs
+have existed for so long that their faulty behavior is relied upon by software
that uses PROJ.
Behavioural changes caused by new bugs are not tracked here, as they should be
@@ -57,7 +57,7 @@ rotation of the circle larger than :math:`\lambda=-70^{\circ}` and hence
should return the same output for both.
Adding the ``+over`` flag to the projection definition provides
-the old behaviour.
+the old behavior.
Version 6.0.0
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
diff --git a/docs/source/usage/environmentvars.rst b/docs/source/usage/environmentvars.rst
index 31a74e9a..e519a80b 100644
--- a/docs/source/usage/environmentvars.rst
+++ b/docs/source/usage/environmentvars.rst
@@ -48,7 +48,7 @@ done by setting the variable with no content::
Starting with PROJ version 6.1.0, the paths set by
:func:`proj_context_set_search_paths` will have priority over the
- :envvar:`PROJ_LIB` to allow for mutliple versions of PROJ
+ :envvar:`PROJ_LIB` to allow for multiple versions of PROJ
resource files on your system without conflicting.
.. envvar:: PROJ_DEBUG
diff --git a/docs/source/usage/network.rst b/docs/source/usage/network.rst
index 1b42d0aa..98461c69 100644
--- a/docs/source/usage/network.rst
+++ b/docs/source/usage/network.rst
@@ -43,7 +43,7 @@ Authorizing network access can be done in multiple ways:
Instead of using the `libcurl` implementation, an application using the PROJ
API can supply its own network implementation through C function callbacks
with :cpp:func:`proj_context_set_network_callbacks`. Enabling network use
- must still be done with one of the above mentionned method.
+ must still be done with one of the above mentioned method.
Setting endpoint
----------------
diff --git a/docs/source/usage/projections.rst b/docs/source/usage/projections.rst
index 8f1ccd50..464451c9 100644
--- a/docs/source/usage/projections.rst
+++ b/docs/source/usage/projections.rst
@@ -46,7 +46,7 @@ name for a unit (ie. ``us-ft``). Alternatively the translation to meters can be
specified with the ``+to_meter`` keyword (ie. 0.304800609601219 for US feet). The
``-lu`` argument to ``cs2cs`` or ``proj`` can be used to list symbolic unit names.
The default unit for projected coordinates is the meter.
-A few special projections deviate from this behaviour, most notably the
+A few special projections deviate from this behavior, most notably the
latlong pseudo-projection that returns degrees.
Vertical (Z) units can be specified using the ``+vunits`` keyword with a
diff --git a/docs/source/usage/transformation.rst b/docs/source/usage/transformation.rst
index a11992cf..1db63a21 100644
--- a/docs/source/usage/transformation.rst
+++ b/docs/source/usage/transformation.rst
@@ -150,7 +150,7 @@ PROJ 4.x/5.x paradigm
============ ==============================================================
.. warning::
- This section documents the behaviour of PROJ 4.x and 5.x. In PROJ 6.x,
+ This section documents the behavior of PROJ 4.x and 5.x. In PROJ 6.x,
:program:`cs2cs` has been reworked to use :c:func:`proj_create_crs_to_crs` internally,
with *late binding* capabilities, and thus is no longer constrained to using
WGS84 as a pivot (also called as *early binding* method).
diff --git a/include/proj/coordinateoperation.hpp b/include/proj/coordinateoperation.hpp
index 686a0c73..7868de4d 100644
--- a/include/proj/coordinateoperation.hpp
+++ b/include/proj/coordinateoperation.hpp
@@ -1593,6 +1593,14 @@ class PROJ_GCC_DLL Transformation : public SingleOperation {
PROJ_INTERNAL TransformationNNPtr shallowClone() const;
+ PROJ_INTERNAL TransformationNNPtr
+ promoteTo3D(const std::string &newName,
+ const io::DatabaseContextPtr &dbContext) const;
+
+ PROJ_INTERNAL TransformationNNPtr
+ demoteTo2D(const std::string &newName,
+ const io::DatabaseContextPtr &dbContext) const;
+
//! @endcond
protected:
diff --git a/include/proj/crs.hpp b/include/proj/crs.hpp
index 61844f79..7aa74c41 100644
--- a/include/proj/crs.hpp
+++ b/include/proj/crs.hpp
@@ -140,6 +140,9 @@ class PROJ_GCC_DLL CRS : public common::ObjectUsage,
PROJ_INTERNAL CRSNNPtr allowNonConformantWKT1Export() const;
+ PROJ_INTERNAL CRSNNPtr
+ attachOriginalVertCRS(const VerticalCRSNNPtr &vertCRS) const;
+
//! @endcond
protected:
diff --git a/include/proj/datum.hpp b/include/proj/datum.hpp
index 6a0db1dc..3fc5f5d8 100644
--- a/include/proj/datum.hpp
+++ b/include/proj/datum.hpp
@@ -590,6 +590,8 @@ class PROJ_GCC_DLL VerticalReferenceFrame : public Datum {
PROJ_INTERNAL void _exportToJSON(io::JSONFormatter *formatter)
const override; // throw(FormattingException)
+ PROJ_INTERNAL const std::string &getWKT1DatumType() const;
+
//! @endcond
protected:
diff --git a/include/proj/internal/lru_cache.hpp b/include/proj/internal/lru_cache.hpp
index 2f2c8bd9..b7aff6b9 100644
--- a/include/proj/internal/lru_cache.hpp
+++ b/include/proj/internal/lru_cache.hpp
@@ -206,7 +206,7 @@ class Cache {
}
private:
- // Dissallow copying.
+ // Disallow copying.
Cache(const Cache&) = delete;
Cache& operator=(const Cache&) = delete;
diff --git a/include/proj/util.hpp b/include/proj/util.hpp
index 5c6eb329..84c93366 100644
--- a/include/proj/util.hpp
+++ b/include/proj/util.hpp
@@ -306,7 +306,7 @@ struct BaseObjectNNPtr : public util::nn<BaseObjectPtr> {
using BaseObjectNNPtr = util::nn<BaseObjectPtr>;
#endif
-/** \brief Class that can be derived from, to emulate Java's Object behaviour.
+/** \brief Class that can be derived from, to emulate Java's Object behavior.
*/
class PROJ_GCC_DLL BaseObject {
public:
diff --git a/scripts/fix_typos.sh b/scripts/fix_typos.sh
index d094f0f3..152ba550 100755
--- a/scripts/fix_typos.sh
+++ b/scripts/fix_typos.sh
@@ -32,22 +32,22 @@
if ! test -d fix_typos; then
# Get our fork of codespell that adds --words-white-list and full filename support for -S option
mkdir fix_typos
- cd fix_typos
- git clone https://github.com/rouault/codespell
- cd codespell
- git checkout gdal_improvements
- cd ..
- # Aggregate base dictionary + QGIS one + Debian Lintian one
- curl https://raw.githubusercontent.com/qgis/QGIS/master/scripts/spelling.dat | sed "s/:/->/" | grep -v "colour->" | grep -v "colours->" > qgis.txt
- curl https://anonscm.debian.org/cgit/lintian/lintian.git/plain/data/spelling/corrections| grep "||" | grep -v "#" | sed "s/||/->/" > debian.txt
- cat codespell/data/dictionary.txt qgis.txt debian.txt | awk 'NF' > gdal_dict.txt
- echo "difered->deferred" >> gdal_dict.txt
- echo "differed->deferred" >> gdal_dict.txt
- cd ..
+ (cd fix_typos
+ git clone https://github.com/rouault/codespell
+ (cd codespell && git checkout gdal_improvements)
+ # Aggregate base dictionary + QGIS one + Debian Lintian one
+ curl https://raw.githubusercontent.com/qgis/QGIS/master/scripts/spell_check/spelling.dat | sed "s/:/->/" | grep -v "colour->" | grep -v "colours->" > qgis.txt
+ curl https://salsa.debian.org/lintian/lintian/-/raw/master/data/spelling/corrections | grep "||" | grep -v "#" | sed "s/||/->/" > debian.txt
+ cat codespell/data/dictionary.txt qgis.txt debian.txt | awk 'NF' > gdal_dict.txt
+ echo "difered->deferred" >> gdal_dict.txt
+ echo "differed->deferred" >> gdal_dict.txt
+ grep -v 404 < gdal_dict.txt > gdal_dict.txt.tmp
+ mv gdal_dict.txt.tmp gdal_dict.txt
+ )
fi
EXCLUDED_FILES="*configure,config.status,config.sub,*/autom4te.cache/*,libtool,aclocal.m4,depcomp,ltmain.sh,*.pdf,./m4/*,./fix_typos/*,./docs/build/*,./src/*generated*,./test/googletest/*,./include/proj/internal/nlohmann/json.hpp,*.before_reformat"
-WORDS_WHITE_LIST="metres,als,lsat,twon,ang,PJD_ERR_LSAT_NOT_IN_RANGE,COLOR_GRAT,interm"
+WORDS_WHITE_LIST="metres,als,lsat,twon,ang,PJD_ERR_LSAT_NOT_IN_RANGE,COLOR_GRAT,interm,Interm,Cartesian,cartesian,CARTESIAN,kilometre,centimetre,millimetre,millimetres,Australia"
python3 fix_typos/codespell/codespell.py -w -i 3 -q 2 -S $EXCLUDED_FILES \
-x scripts/typos_whitelist.txt --words-white-list=$WORDS_WHITE_LIST \
diff --git a/scripts/typos_whitelist.txt b/scripts/typos_whitelist.txt
index 1dcd03dd..462d22da 100644
--- a/scripts/typos_whitelist.txt
+++ b/scripts/typos_whitelist.txt
@@ -1,2 +1,3 @@
$EXE +units=us-ft +init=${INIT_FILE}:404 -E -f '%.3f' >>${OUT} <<EOF
(`#404 <https://github.com/OSGeo/proj.4/issues/404>`_)
+{"CPM", "a=6375738.7", "rf=334.29", "Comm. des Poids et Mesures 1799"},
diff --git a/src/4D_api.cpp b/src/4D_api.cpp
index ca2b1d89..8f427412 100644
--- a/src/4D_api.cpp
+++ b/src/4D_api.cpp
@@ -605,7 +605,7 @@ static int cs2cs_emulation_setup (PJ *P) {
/**************************************************************************************
If any cs2cs style modifiers are given (axis=..., towgs84=..., ) create the 4D API
equivalent operations, so the preparation and finalization steps in the pj_inv/pj_fwd
-invocators can emulate the behaviour of pj_transform and the cs2cs app.
+invocators can emulate the behavior of pj_transform and the cs2cs app.
Returns 1 on success, 0 on failure
**************************************************************************************/
diff --git a/src/apps/cs2cs.cpp b/src/apps/cs2cs.cpp
index c99e16e0..58c164c9 100644
--- a/src/apps/cs2cs.cpp
+++ b/src/apps/cs2cs.cpp
@@ -117,10 +117,10 @@ static void process(FILE *fid)
/* To avoid breaking existing tests, we read what is a possible t */
/* component of the input and rewind the s-pointer so that the final */
- /* output has consistent behaviour, with or without t values. */
+ /* output has consistent behavior, with or without t values. */
/* This is a bit of a hack, in most cases 4D coordinates will be */
/* written to STDOUT (except when using -E) but the output format */
- /* speficied with -f is not respected for the t component, rather it */
+ /* specified with -f is not respected for the t component, rather it */
/* is forward verbatim from the input. */
char *before_time = s;
double t = strtod(s, &s);
diff --git a/src/ell_set.cpp b/src/ell_set.cpp
index 434ae76d..ddd507ac 100644
--- a/src/ell_set.cpp
+++ b/src/ell_set.cpp
@@ -70,7 +70,7 @@ int pj_ellipsoid (PJ *P) {
are are taken into account as modifiers for the built in ellipsoid definition.
While this may seem strange, it is in accordance with historical PROJ
- behaviour. It can e.g. be used to define coordinates on the ellipsoid
+ behavior. It can e.g. be used to define coordinates on the ellipsoid
scaled to unit semimajor axis by specifying "+ellps=xxx +a=1"
****************************************************************************************/
diff --git a/src/geodesic.h b/src/geodesic.h
index e2265c89..b21e6c8c 100644
--- a/src/geodesic.h
+++ b/src/geodesic.h
@@ -476,7 +476,7 @@ extern "C" {
/**
* Initialize a geod_geodesicline object in terms of the direct geodesic
- * problem spacified in terms of either distance or arc length.
+ * problem specified in terms of either distance or arc length.
*
* @param[out] l a pointer to the object to be initialized.
* @param[in] g a pointer to the geod_geodesic object specifying the
diff --git a/src/grids.cpp b/src/grids.cpp
index 3284bf02..8065813a 100644
--- a/src/grids.cpp
+++ b/src/grids.cpp
@@ -1127,7 +1127,7 @@ insertIntoHierarchy(PJ_CONTEXT *ctx, std::unique_ptr<GridType> &&grid,
const auto &extent = grid->extentAndRes();
// If we have one or both of grid_name and parent_grid_name, try to use
- // the names to recreate the hiearchy
+ // the names to recreate the hierarchy
if (!gridName.empty()) {
if (mapGrids.find(gridName) != mapGrids.end()) {
pj_log(ctx, PJ_LOG_DEBUG_MAJOR, "Several grids called %s found!",
diff --git a/src/internal.cpp b/src/internal.cpp
index b4b7a683..91994077 100644
--- a/src/internal.cpp
+++ b/src/internal.cpp
@@ -475,7 +475,7 @@ void proj_context_log_debug (PJ_CONTEXT *ctx, const char *fmt, ...) {
/*****************************************************************************/
void proj_log_trace (PJ *P, const char *fmt, ...) {
/******************************************************************************
- For reporting embarrasingly detailed debugging information.
+ For reporting embarrassingly detailed debugging information.
******************************************************************************/
va_list args;
va_start( args, fmt );
diff --git a/src/iso19111/c_api.cpp b/src/iso19111/c_api.cpp
index 6b726f34..340d9fb9 100644
--- a/src/iso19111/c_api.cpp
+++ b/src/iso19111/c_api.cpp
@@ -1,7 +1,7 @@
/******************************************************************************
*
* Project: PROJ
- * Purpose: C API wraper of C++ API
+ * Purpose: C API wrapper of C++ API
* Author: Even Rouault <even dot rouault at spatialys dot com>
*
******************************************************************************
@@ -1354,7 +1354,7 @@ const char *proj_get_id_code(const PJ *obj, int index) {
* supported options are:
* <ul>
* <li>MULTILINE=YES/NO. Defaults to YES, except for WKT1_ESRI</li>
- * <li>INDENTATION_WIDTH=number. Defauls to 4 (when multiline output is
+ * <li>INDENTATION_WIDTH=number. Defaults to 4 (when multiline output is
* on).</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
@@ -1524,7 +1524,7 @@ const char *proj_as_proj_string(PJ_CONTEXT *ctx, const PJ *obj,
* supported options are:
* <ul>
* <li>MULTILINE=YES/NO. Defaults to YES</li>
- * <li>INDENTATION_WIDTH=number. Defauls to 2 (when multiline output is
+ * <li>INDENTATION_WIDTH=number. Defaults to 2 (when multiline output is
* on).</li>
* <li>SCHEMA=string. URL to PROJJSON schema. Can be set to empty string to
* disable it.</li>
@@ -2236,8 +2236,8 @@ PJ *proj_get_target_crs(PJ_CONTEXT *ctx, const PJ *obj) {
* </li>
* <li>90% means that CRS are equivalent, but the names are not exactly the
* same.</li>
- * <li>70% means that CRS are equivalent), but the names do not match at
- * all.</li>
+ * <li>70% means that CRS are equivalent, but the names are not equivalent.
+ * </li>
* <li>25% means that the CRS are not equivalent, but there is some similarity
* in
* the names.</li>
@@ -3198,7 +3198,7 @@ PJ *proj_create_vertical_crs(PJ_CONTEXT *ctx, const char *crs_name,
* use.
* It should be used by at most one thread at a time.
*
- * This is an extented (_ex) version of proj_create_vertical_crs() that adds
+ * This is an extended (_ex) version of proj_create_vertical_crs() that adds
* the capability of defining a geoid model.
*
* @param ctx PROJ context, or NULL for default context
@@ -7434,7 +7434,7 @@ void proj_operation_factory_context_set_use_proj_alternative_grid_names(
* step.
*
* By default, with the IF_NO_DIRECT_TRANSFORMATION stratgey, all potential
- * C candidates will be used if there is no direct tranformation.
+ * C candidates will be used if there is no direct transformation.
*
* @param ctx PROJ context, or NULL for default context
* @param factory_ctx Operation factory context. must not be NULL
diff --git a/src/iso19111/common.cpp b/src/iso19111/common.cpp
index b5e393b3..0eb40f12 100644
--- a/src/iso19111/common.cpp
+++ b/src/iso19111/common.cpp
@@ -422,7 +422,7 @@ bool Measure::operator==(const Measure &other) PROJ_PURE_DEFN {
/** \brief Returns whether an object is equivalent to another one.
* @param other other object to compare to
- * @param criterion comparaison criterion.
+ * @param criterion comparison criterion.
* @param maxRelativeError Maximum relative error allowed.
* @return true if objects are equivalent.
*/
diff --git a/src/iso19111/coordinateoperation.cpp b/src/iso19111/coordinateoperation.cpp
index ec515cd7..35be415f 100644
--- a/src/iso19111/coordinateoperation.cpp
+++ b/src/iso19111/coordinateoperation.cpp
@@ -119,8 +119,6 @@ static const char *BALLPARK_GEOCENTRIC_TRANSLATION =
static const char *NULL_GEOGRAPHIC_OFFSET = "Null geographic offset";
static const char *NULL_GEOCENTRIC_TRANSLATION = "Null geocentric translation";
static const char *BALLPARK_GEOGRAPHIC_OFFSET = "Ballpark geographic offset";
-static const char *BALLPARK_VERTICAL_TRANSFORMATION_PREFIX =
- " (ballpark vertical transformation";
static const char *BALLPARK_VERTICAL_TRANSFORMATION =
" (ballpark vertical transformation)";
static const char *BALLPARK_VERTICAL_TRANSFORMATION_NO_ELLIPSOID_VERT_HEIGHT =
@@ -128,6 +126,8 @@ static const char *BALLPARK_VERTICAL_TRANSFORMATION_NO_ELLIPSOID_VERT_HEIGHT =
"height correction)";
static const std::string AXIS_ORDER_CHANGE_2D_NAME = "axis order change (2D)";
+static const std::string AXIS_ORDER_CHANGE_3D_NAME =
+ "axis order change (geographic3D horizontal)";
//! @endcond
//! @cond Doxygen_Suppress
@@ -2043,15 +2043,19 @@ SingleOperation::gridsNeeded(const io::DatabaseContextPtr &databaseContext,
if (opParamvalue) {
const auto &value = opParamvalue->parameterValue();
if (value->type() == ParameterValue::Type::FILENAME) {
- GridDescription desc;
- desc.shortName = value->valueFile();
- if (databaseContext) {
- databaseContext->lookForGridInfo(
- desc.shortName, considerKnownGridsAsAvailable,
- desc.fullName, desc.packageName, desc.url,
- desc.directDownload, desc.openLicense, desc.available);
+ const auto gridNames = split(value->valueFile(), ",");
+ for (const auto &gridName : gridNames) {
+ GridDescription desc;
+ desc.shortName = gridName;
+ if (databaseContext) {
+ databaseContext->lookForGridInfo(
+ desc.shortName, considerKnownGridsAsAvailable,
+ desc.fullName, desc.packageName, desc.url,
+ desc.directDownload, desc.openLicense,
+ desc.available);
+ }
+ res.insert(desc);
}
- res.insert(desc);
}
}
}
@@ -4684,7 +4688,7 @@ ConversionNNPtr Conversion::createEqualEarth(
* 0.</li>
* </ul>
*
- * For completness, PROJ adds the falseEasting and falseNorthing parameter,
+ * For completeness, PROJ adds the falseEasting and falseNorthing parameter,
* which are not described in EPSG. They should usually be set to 0.
*
* @param properties See \ref general_properties of the conversion. If the name
@@ -4717,7 +4721,7 @@ ConversionNNPtr Conversion::createVerticalPerspective(
/** \brief Instantiate a conversion based on the Pole Rotation method, using
* the conventions of the GRIB 1 and GRIB 2 data formats.
*
- * Those are mentionned in the Note 2 of
+ * Those are mentioned in the Note 2 of
* https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/grib2_temp3-1.shtml
*
* Several conventions for the pole rotation method exists.
@@ -4838,8 +4842,7 @@ Conversion::createHeightDepthReversal(const util::PropertyMap &properties) {
*/
ConversionNNPtr Conversion::createAxisOrderReversal(bool is3D) {
if (is3D) {
- return create(createMapNameEPSGCode(
- "axis order change (geographic3D horizontal)", 15499),
+ return create(createMapNameEPSGCode(AXIS_ORDER_CHANGE_3D_NAME, 15499),
createMethodMapNameEPSGCode(
EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_3D),
{}, {});
@@ -6527,7 +6530,7 @@ struct Transformation::Private {
TransformationPtr forwardOperation_{};
- TransformationNNPtr registerInv(util::BaseObjectNNPtr thisIn,
+ TransformationNNPtr registerInv(const Transformation *thisIn,
TransformationNNPtr invTransform);
};
//! @endcond
@@ -6590,6 +6593,31 @@ TransformationNNPtr Transformation::shallowClone() const {
CoordinateOperationNNPtr Transformation::_shallowClone() const {
return util::nn_static_pointer_cast<CoordinateOperation>(shallowClone());
}
+
+// ---------------------------------------------------------------------------
+
+TransformationNNPtr
+Transformation::promoteTo3D(const std::string &,
+ const io::DatabaseContextPtr &dbContext) const {
+ auto transf = shallowClone();
+ transf->setCRSs(sourceCRS()->promoteTo3D(std::string(), dbContext),
+ targetCRS()->promoteTo3D(std::string(), dbContext),
+ interpolationCRS());
+ return transf;
+}
+
+// ---------------------------------------------------------------------------
+
+TransformationNNPtr
+Transformation::demoteTo2D(const std::string &,
+ const io::DatabaseContextPtr &dbContext) const {
+ auto transf = shallowClone();
+ transf->setCRSs(sourceCRS()->demoteTo2D(std::string(), dbContext),
+ targetCRS()->demoteTo2D(std::string(), dbContext),
+ interpolationCRS());
+ return transf;
+}
+
//! @endcond
// ---------------------------------------------------------------------------
@@ -7800,7 +7828,8 @@ createPropertiesForInverse(const CoordinateOperation *op, bool derivedFrom,
}
if (starts_with(tokens[i], INVERSE_OF)) {
name += tokens[i].substr(INVERSE_OF.size());
- } else if (tokens[i] == AXIS_ORDER_CHANGE_2D_NAME) {
+ } else if (tokens[i] == AXIS_ORDER_CHANGE_2D_NAME ||
+ tokens[i] == AXIS_ORDER_CHANGE_3D_NAME) {
name += tokens[i];
} else {
name += INVERSE_OF + tokens[i];
@@ -7996,12 +8025,11 @@ createApproximateInverseIfPossible(const Transformation *op) {
//! @cond Doxygen_Suppress
TransformationNNPtr
-Transformation::Private::registerInv(util::BaseObjectNNPtr thisIn,
+Transformation::Private::registerInv(const Transformation *thisIn,
TransformationNNPtr invTransform) {
- invTransform->d->forwardOperation_ =
- util::nn_dynamic_pointer_cast<Transformation>(thisIn);
+ invTransform->d->forwardOperation_ = thisIn->shallowClone().as_nullable();
invTransform->setHasBallparkTransformation(
- invTransform->d->forwardOperation_->hasBallparkTransformation());
+ thisIn->hasBallparkTransformation());
return invTransform;
}
//! @endcond
@@ -8039,12 +8067,24 @@ TransformationNNPtr Transformation::inverseAsTransformation() const {
parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
double z =
parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
+ auto properties = createPropertiesForInverse(this, false, false);
return d->registerInv(
- shared_from_this(),
- createGeocentricTranslations(
- createPropertiesForInverse(this, false, false), l_targetCRS,
- l_sourceCRS, negate(x), negate(y), negate(z),
- coordinateOperationAccuracies()));
+ this, create(properties, l_targetCRS, l_sourceCRS, nullptr,
+ createMethodMapNameEPSGCode(
+ useOperationMethodEPSGCodeIfPresent(
+ properties, methodEPSGCode)),
+ VectorOfParameters{
+ createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION),
+ createOpParamNameEPSGCode(
+ EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION),
+ },
+ createParams(common::Length(negate(x)),
+ common::Length(negate(y)),
+ common::Length(negate(z))),
+ coordinateOperationAccuracies()));
}
if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY ||
@@ -8062,14 +8102,14 @@ TransformationNNPtr Transformation::inverseAsTransformation() const {
if (methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) {
return d->registerInv(
- shared_from_this(),
+ this,
createAbridgedMolodensky(
createPropertiesForInverse(this, false, false), l_targetCRS,
l_sourceCRS, negate(x), negate(y), negate(z), negate(da),
negate(df), coordinateOperationAccuracies()));
} else {
return d->registerInv(
- shared_from_this(),
+ this,
createMolodensky(createPropertiesForInverse(this, false, false),
l_targetCRS, l_sourceCRS, negate(x), negate(y),
negate(z), negate(da), negate(df),
@@ -8082,10 +8122,9 @@ TransformationNNPtr Transformation::inverseAsTransformation() const {
parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
const common::Angle newOffset(negate(offset.value()), offset.unit());
return d->registerInv(
- shared_from_this(),
- createLongitudeRotation(
- createPropertiesForInverse(this, false, false), l_targetCRS,
- l_sourceCRS, newOffset));
+ this, createLongitudeRotation(
+ createPropertiesForInverse(this, false, false),
+ l_targetCRS, l_sourceCRS, newOffset));
}
if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS) {
@@ -8100,11 +8139,10 @@ TransformationNNPtr Transformation::inverseAsTransformation() const {
offsetLong.unit());
return d->registerInv(
- shared_from_this(),
- createGeographic2DOffsets(
- createPropertiesForInverse(this, false, false), l_targetCRS,
- l_sourceCRS, newOffsetLat, newOffsetLong,
- coordinateOperationAccuracies()));
+ this, createGeographic2DOffsets(
+ createPropertiesForInverse(this, false, false),
+ l_targetCRS, l_sourceCRS, newOffsetLat, newOffsetLong,
+ coordinateOperationAccuracies()));
}
if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) {
@@ -8124,11 +8162,10 @@ TransformationNNPtr Transformation::inverseAsTransformation() const {
offsetHeight.unit());
return d->registerInv(
- shared_from_this(),
- createGeographic3DOffsets(
- createPropertiesForInverse(this, false, false), l_targetCRS,
- l_sourceCRS, newOffsetLat, newOffsetLong, newOffsetHeight,
- coordinateOperationAccuracies()));
+ this, createGeographic3DOffsets(
+ createPropertiesForInverse(this, false, false),
+ l_targetCRS, l_sourceCRS, newOffsetLat, newOffsetLong,
+ newOffsetHeight, coordinateOperationAccuracies()));
}
if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS) {
@@ -8148,11 +8185,10 @@ TransformationNNPtr Transformation::inverseAsTransformation() const {
offsetHeight.unit());
return d->registerInv(
- shared_from_this(),
- createGeographic2DWithHeightOffsets(
- createPropertiesForInverse(this, false, false), l_targetCRS,
- l_sourceCRS, newOffsetLat, newOffsetLong, newOffsetHeight,
- coordinateOperationAccuracies()));
+ this, createGeographic2DWithHeightOffsets(
+ createPropertiesForInverse(this, false, false),
+ l_targetCRS, l_sourceCRS, newOffsetLat, newOffsetLong,
+ newOffsetHeight, coordinateOperationAccuracies()));
}
if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET) {
@@ -8163,7 +8199,7 @@ TransformationNNPtr Transformation::inverseAsTransformation() const {
offsetHeight.unit());
return d->registerInv(
- shared_from_this(),
+ this,
createVerticalOffset(createPropertiesForInverse(this, false, false),
l_targetCRS, l_sourceCRS, newOffsetHeight,
coordinateOperationAccuracies()));
@@ -8173,18 +8209,17 @@ TransformationNNPtr Transformation::inverseAsTransformation() const {
const double convFactor = parameterValueNumericAsSI(
EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR);
return d->registerInv(
- shared_from_this(),
- createChangeVerticalUnit(
- createPropertiesForInverse(this, false, false), l_targetCRS,
- l_sourceCRS, common::Scale(1.0 / convFactor),
- coordinateOperationAccuracies()));
+ this, createChangeVerticalUnit(
+ createPropertiesForInverse(this, false, false),
+ l_targetCRS, l_sourceCRS, common::Scale(1.0 / convFactor),
+ coordinateOperationAccuracies()));
}
#ifdef notdef
// We don't need that currently, but we might...
if (methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) {
return d->registerInv(
- shared_from_this(),
+ this,
createHeightDepthReversal(
createPropertiesForInverse(this, false, false), l_targetCRS,
l_sourceCRS, coordinateOperationAccuracies()));
@@ -8989,20 +9024,41 @@ static void ThrowExceptionNotGeodeticGeographic(const char *trfrm_name) {
// ---------------------------------------------------------------------------
-static void setupPROJGeodeticSourceCRS(io::PROJStringFormatter *formatter,
- const crs::CRSNNPtr &crs, bool addPushV3,
- const char *trfrm_name) {
- auto sourceCRSGeog = dynamic_cast<const crs::GeographicCRS *>(crs.get());
- if (!sourceCRSGeog) {
- auto compoundCRS = dynamic_cast<const crs::CompoundCRS *>(crs.get());
+// If crs is a geographic CRS, or a compound CRS of a geographic CRS,
+// or a compoundCRS of a bound CRS of a geographic CRS, return that
+// geographic CRS
+static crs::GeographicCRSPtr
+extractGeographicCRSIfGeographicCRSOrEquivalent(const crs::CRSNNPtr &crs) {
+ auto geogCRS = util::nn_dynamic_pointer_cast<crs::GeographicCRS>(crs);
+ if (!geogCRS) {
+ auto compoundCRS = util::nn_dynamic_pointer_cast<crs::CompoundCRS>(crs);
if (compoundCRS) {
const auto &components = compoundCRS->componentReferenceSystems();
if (!components.empty()) {
- sourceCRSGeog = dynamic_cast<const crs::GeographicCRS *>(
- components[0].get());
+ geogCRS = util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
+ components[0]);
+ if (!geogCRS) {
+ auto boundCRS =
+ util::nn_dynamic_pointer_cast<crs::BoundCRS>(
+ components[0]);
+ if (boundCRS) {
+ geogCRS =
+ util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
+ boundCRS->baseCRS());
+ }
+ }
}
}
}
+ return geogCRS;
+}
+
+// ---------------------------------------------------------------------------
+
+static void setupPROJGeodeticSourceCRS(io::PROJStringFormatter *formatter,
+ const crs::CRSNNPtr &crs, bool addPushV3,
+ const char *trfrm_name) {
+ auto sourceCRSGeog = extractGeographicCRSIfGeographicCRSOrEquivalent(crs);
if (sourceCRSGeog) {
formatter->startInversion();
sourceCRSGeog->_exportToPROJString(formatter);
@@ -9030,17 +9086,7 @@ static void setupPROJGeodeticSourceCRS(io::PROJStringFormatter *formatter,
static void setupPROJGeodeticTargetCRS(io::PROJStringFormatter *formatter,
const crs::CRSNNPtr &crs, bool addPopV3,
const char *trfrm_name) {
- auto targetCRSGeog = dynamic_cast<const crs::GeographicCRS *>(crs.get());
- if (!targetCRSGeog) {
- auto compoundCRS = dynamic_cast<const crs::CompoundCRS *>(crs.get());
- if (compoundCRS) {
- const auto &components = compoundCRS->componentReferenceSystems();
- if (!components.empty()) {
- targetCRSGeog = dynamic_cast<const crs::GeographicCRS *>(
- components[0].get());
- }
- }
- }
+ auto targetCRSGeog = extractGeographicCRSIfGeographicCRSOrEquivalent(crs);
if (targetCRSGeog) {
formatter->addStep("cart");
formatter->setCurrentStepInverted(true);
@@ -9540,14 +9586,14 @@ void Transformation::_exportToPROJString(
: CTABLE2Filename;
if (!hGridShiftFilename.empty()) {
auto sourceCRSGeog =
- dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
+ extractGeographicCRSIfGeographicCRSOrEquivalent(sourceCRS());
if (!sourceCRSGeog) {
throw io::FormattingException(
concat("Can apply ", methodName, " only to GeographicCRS"));
}
auto targetCRSGeog =
- dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
+ extractGeographicCRSIfGeographicCRSOrEquivalent(targetCRS());
if (!targetCRSGeog) {
throw io::FormattingException(
concat("Can apply ", methodName, " only to GeographicCRS"));
@@ -10739,7 +10785,7 @@ CoordinateOperationContext::getGridAvailabilityUse() const {
* step.
*
* By default, with the IF_NO_DIRECT_TRANSFORMATION stratgey, all potential
- * C candidates will be used if there is no direct tranformation.
+ * C candidates will be used if there is no direct transformation.
*/
void CoordinateOperationContext::setAllowUseIntermediateCRS(
IntermediateCRSUse use) {
@@ -11077,8 +11123,8 @@ struct SortFunction {
// Sorting function
// Return true if a < b
- bool operator()(const CoordinateOperationNNPtr &a,
- const CoordinateOperationNNPtr &b) const {
+ bool compare(const CoordinateOperationNNPtr &a,
+ const CoordinateOperationNNPtr &b) const {
auto iterA = map.find(a.get());
assert(iterA != map.end());
auto iterB = map.find(b.get());
@@ -11203,6 +11249,15 @@ struct SortFunction {
// Arbitrary final criterion
return a_name < b_name;
}
+
+ bool operator()(const CoordinateOperationNNPtr &a,
+ const CoordinateOperationNNPtr &b) const {
+ const bool ret = compare(a, b);
+#if 0
+ std::cerr << a->nameStr() << " < " << b->nameStr() << " : " << ret << std::endl;
+#endif
+ return ret;
+ }
};
// ---------------------------------------------------------------------------
@@ -11218,6 +11273,17 @@ static size_t getStepCount(const CoordinateOperationNNPtr &op) {
// ---------------------------------------------------------------------------
+static bool isNullTransformation(const std::string &name) {
+ if (name.find(" + ") != std::string::npos)
+ return false;
+ return starts_with(name, BALLPARK_GEOCENTRIC_TRANSLATION) ||
+ starts_with(name, BALLPARK_GEOGRAPHIC_OFFSET) ||
+ starts_with(name, NULL_GEOGRAPHIC_OFFSET) ||
+ starts_with(name, NULL_GEOCENTRIC_TRANSLATION);
+}
+
+// ---------------------------------------------------------------------------
+
struct FilterResults {
FilterResults(const std::vector<CoordinateOperationNNPtr> &sourceListIn,
@@ -11468,19 +11534,6 @@ struct FilterResults {
const auto stepCount = getStepCount(op);
- const bool isApprox =
- op->nameStr().find(BALLPARK_VERTICAL_TRANSFORMATION_PREFIX) !=
- std::string::npos;
- const bool isNullTransformation =
- op->nameStr().find(BALLPARK_GEOGRAPHIC_OFFSET) !=
- std::string::npos ||
- op->nameStr().find(NULL_GEOGRAPHIC_OFFSET) !=
- std::string::npos ||
- op->nameStr().find(NULL_GEOCENTRIC_TRANSLATION) !=
- std::string::npos ||
- op->nameStr().find(BALLPARK_GEOCENTRIC_TRANSLATION) !=
- std::string::npos;
-
bool isPROJExportable = false;
auto formatter = io::PROJStringFormatter::create();
try {
@@ -11491,10 +11544,24 @@ struct FilterResults {
} catch (const std::exception &) {
}
+#if 0
+ std::cerr << op->nameStr() << " ";
+ std::cerr << area << " ";
+ std::cerr << getAccuracy(op) << " ";
+ std::cerr << isPROJExportable << " ";
+ std::cerr << hasGrids << " ";
+ std::cerr << gridsAvailable << " ";
+ std::cerr << gridsKnown << " ";
+ std::cerr << stepCount << " ";
+ std::cerr << op->hasBallparkTransformation() << " ";
+ std::cerr << isNullTransformation(op->nameStr()) << " ";
+ std::cerr << std::endl;
+#endif
map[op.get()] = PrecomputedOpCharacteristics(
area, getAccuracy(op), isPROJExportable, hasGrids,
- gridsAvailable, gridsKnown, stepCount, isApprox,
- isNullTransformation);
+ gridsAvailable, gridsKnown, stepCount,
+ op->hasBallparkTransformation(),
+ isNullTransformation(op->nameStr()));
}
// Sort !
@@ -11541,12 +11608,8 @@ struct FilterResults {
// better
if (hasOpThatContainsAreaOfInterestAndNoGrid && res.size() > 1) {
const auto &opLast = res.back();
- const std::string &name = opLast->nameStr();
- if (name.find(BALLPARK_GEOGRAPHIC_OFFSET) != std::string::npos ||
- name.find(NULL_GEOGRAPHIC_OFFSET) != std::string::npos ||
- name.find(NULL_GEOCENTRIC_TRANSLATION) != std::string::npos ||
- name.find(BALLPARK_GEOCENTRIC_TRANSLATION) !=
- std::string::npos) {
+ if (opLast->hasBallparkTransformation() ||
+ isNullTransformation(opLast->nameStr())) {
std::vector<CoordinateOperationNNPtr> resTemp;
for (size_t i = 0; i < res.size() - 1; i++) {
resTemp.emplace_back(res[i]);
@@ -12110,7 +12173,7 @@ CoordinateOperationFactory::Private::findsOpsInRegistryWithIntermediate(
// If doing GeogCRS --> GeogCRS, only use GeogCRS as
// intermediate CRS
- // Avoid weird behaviour when doing NAD83 -> NAD83(2011)
+ // Avoid weird behavior when doing NAD83 -> NAD83(2011)
// that would go through NAVD88 otherwise.
if (context.context->getIntermediateCRS().empty() &&
dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get()) &&
@@ -12392,7 +12455,8 @@ static CoordinateOperationNNPtr createHorizVerticalPROJBased(
horizTransform, verticalTransform, geogDst);
const bool horizTransformIsNoOp =
- starts_with(horizTransform->nameStr(), NULL_GEOGRAPHIC_OFFSET);
+ starts_with(horizTransform->nameStr(), NULL_GEOGRAPHIC_OFFSET) &&
+ horizTransform->nameStr().find(" + ") == std::string::npos;
if (horizTransformIsNoOp) {
auto properties = util::PropertyMap();
properties.set(common::IdentifiedObject::NAME_KEY,
@@ -12450,11 +12514,13 @@ static CoordinateOperationNNPtr createHorizVerticalHorizPROJBased(
interpolationGeogCRS);
std::vector<CoordinateOperationNNPtr> ops;
- if (!starts_with(opSrcCRSToGeogCRS->nameStr(), NULL_GEOGRAPHIC_OFFSET)) {
+ if (!(starts_with(opSrcCRSToGeogCRS->nameStr(), NULL_GEOGRAPHIC_OFFSET) &&
+ opSrcCRSToGeogCRS->nameStr().find(" + ") == std::string::npos)) {
ops.emplace_back(opSrcCRSToGeogCRS);
}
ops.emplace_back(verticalTransform);
- if (!starts_with(opGeogCRStoDstCRS->nameStr(), NULL_GEOGRAPHIC_OFFSET)) {
+ if (!(starts_with(opGeogCRStoDstCRS->nameStr(), NULL_GEOGRAPHIC_OFFSET) &&
+ opGeogCRStoDstCRS->nameStr().find(" + ") == std::string::npos)) {
ops.emplace_back(opGeogCRStoDstCRS);
}
@@ -12746,16 +12812,6 @@ findCandidateGeodCRSForDatum(const io::AuthorityFactoryPtr &authFactory,
// ---------------------------------------------------------------------------
-static bool isNullTransformation(const std::string &name) {
-
- return starts_with(name, BALLPARK_GEOCENTRIC_TRANSLATION) ||
- starts_with(name, BALLPARK_GEOGRAPHIC_OFFSET) ||
- starts_with(name, NULL_GEOGRAPHIC_OFFSET) ||
- starts_with(name, NULL_GEOCENTRIC_TRANSLATION);
-}
-
-// ---------------------------------------------------------------------------
-
void CoordinateOperationFactory::Private::setCRSs(
CoordinateOperation *co, const crs::CRSNNPtr &sourceCRS,
const crs::CRSNNPtr &targetCRS) {
@@ -13191,7 +13247,7 @@ CoordinateOperationFactory::Private::createOperations(
return applyInverse(createOperations(targetCRS, sourceCRS, context));
}
- // boundCRS to boundCRS using the same geographic hubCRS
+ // boundCRS to boundCRS
if (boundSrc && boundDst) {
createOperationsBoundToBound(sourceCRS, targetCRS, context, boundSrc,
boundDst, res);
@@ -14327,6 +14383,7 @@ void CoordinateOperationFactory::Private::createOperationsBoundToBound(
ENTER_FUNCTION();
+ // BoundCRS to BoundCRS of horizontal CRS using the same (geographic) hub
const auto &hubSrc = boundSrc->hubCRS();
auto hubSrcGeog = dynamic_cast<const crs::GeographicCRS *>(hubSrc.get());
const auto &hubDst = boundDst->hubCRS();
@@ -14375,6 +14432,28 @@ void CoordinateOperationFactory::Private::createOperationsBoundToBound(
}
}
+ // BoundCRS to BoundCRS of vertical CRS using the same vertical datum
+ // ==> ignore the bound transformation
+ auto baseOfBoundSrcAsVertCRS =
+ dynamic_cast<crs::VerticalCRS *>(boundSrc->baseCRS().get());
+ auto baseOfBoundDstAsVertCRS =
+ dynamic_cast<crs::VerticalCRS *>(boundDst->baseCRS().get());
+ if (baseOfBoundSrcAsVertCRS && baseOfBoundDstAsVertCRS) {
+ const auto datumSrc = baseOfBoundSrcAsVertCRS->datum();
+ const auto datumDst = baseOfBoundDstAsVertCRS->datum();
+ if (datumSrc && datumDst &&
+ datumSrc->nameStr() == datumDst->nameStr() &&
+ (datumSrc->nameStr() != "unknown" ||
+ boundSrc->transformation()->_isEquivalentTo(
+ boundDst->transformation().get(),
+ util::IComparable::Criterion::EQUIVALENT))) {
+ res = createOperations(boundSrc->baseCRS(), boundDst->baseCRS(),
+ context);
+ return;
+ }
+ }
+
+ // BoundCRS to BoundCRS of vertical CRS
auto vertCRSOfBaseOfBoundSrc = boundSrc->baseCRS()->extractVerticalCRS();
auto vertCRSOfBaseOfBoundDst = boundDst->baseCRS()->extractVerticalCRS();
if (hubSrcGeog && hubDstGeog &&
@@ -14720,10 +14799,16 @@ void CoordinateOperationFactory::Private::createOperationsCompoundToGeog(
if (!componentsSrc[0]->isEquivalentTo(
target2D.get(),
util::IComparable::Criterion::EQUIVALENT)) {
- interpToTargetOps = createOperations(
- NN_NO_CHECK(interpolationGeogCRS),
- targetCRS->demoteTo2D(std::string(), dbContext),
- context);
+ // We do the transformation from the
+ // interpolationCRS
+ // to the target one in 3D (see #2225)
+ // But we don't do that between sourceCRS and
+ // interpolationCRS, as this would mess with an
+ // orthometric elevation.
+ auto interp3D = interpolationGeogCRS->promoteTo3D(
+ std::string(), dbContext);
+ interpToTargetOps =
+ createOperations(interp3D, targetCRS, context);
}
};
diff --git a/src/iso19111/crs.cpp b/src/iso19111/crs.cpp
index de02eb35..bad7deea 100644
--- a/src/iso19111/crs.cpp
+++ b/src/iso19111/crs.cpp
@@ -94,7 +94,11 @@ struct CRS::Private {
BoundCRSPtr canonicalBoundCRS_{};
std::string extensionProj4_{};
bool implicitCS_ = false;
+
bool allowNonConformantWKT1Export_ = false;
+ // for what was initially a COMPD_CS with a VERT_CS with a datum type ==
+ // ellipsoidal height / 2002
+ VerticalCRSPtr originalVertCRS_{};
void setImplicitCS(const util::PropertyMap &properties) {
const auto pVal = properties.get("IMPLICIT_CS");
@@ -562,7 +566,13 @@ CRSNNPtr CRS::shallowClone() const { return _shallowClone(); }
//! @cond Doxygen_Suppress
CRSNNPtr CRS::allowNonConformantWKT1Export() const {
- auto crs = shallowClone();
+ const auto boundCRS = dynamic_cast<const BoundCRS *>(this);
+ if (boundCRS) {
+ return BoundCRS::create(
+ boundCRS->baseCRS()->allowNonConformantWKT1Export(),
+ boundCRS->hubCRS(), boundCRS->transformation());
+ }
+ auto crs(shallowClone());
crs->d->allowNonConformantWKT1Export_ = true;
return crs;
}
@@ -573,6 +583,26 @@ CRSNNPtr CRS::allowNonConformantWKT1Export() const {
//! @cond Doxygen_Suppress
+CRSNNPtr CRS::attachOriginalVertCRS(const VerticalCRSNNPtr &vertCRS) const {
+
+ const auto boundCRS = dynamic_cast<const BoundCRS *>(this);
+ if (boundCRS) {
+ return BoundCRS::create(
+ boundCRS->baseCRS()->attachOriginalVertCRS(vertCRS),
+ boundCRS->hubCRS(), boundCRS->transformation());
+ }
+
+ auto crs(shallowClone());
+ crs->d->originalVertCRS_ = vertCRS.as_nullable();
+ return crs;
+}
+
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
+
CRSNNPtr CRS::alterName(const std::string &newName) const {
auto crs = shallowClone();
auto newNameMod(newName);
@@ -902,9 +932,17 @@ CRSNNPtr CRS::promoteTo3D(const std::string &newName,
const auto boundCRS = dynamic_cast<const BoundCRS *>(this);
if (boundCRS) {
- return BoundCRS::create(
- boundCRS->baseCRS()->promoteTo3D(newName, dbContext),
- boundCRS->hubCRS(), boundCRS->transformation());
+ auto base3DCRS = boundCRS->baseCRS()->promoteTo3D(newName, dbContext);
+ auto transf = boundCRS->transformation();
+ try {
+ transf->getTOWGS84Parameters();
+ return BoundCRS::create(
+ base3DCRS,
+ boundCRS->hubCRS()->promoteTo3D(std::string(), dbContext),
+ transf->promoteTo3D(std::string(), dbContext));
+ } catch (const io::FormattingException &) {
+ return BoundCRS::create(base3DCRS, boundCRS->hubCRS(), transf);
+ }
}
return NN_NO_CHECK(
@@ -937,9 +975,17 @@ CRSNNPtr CRS::demoteTo2D(const std::string &newName,
const auto boundCRS = dynamic_cast<const BoundCRS *>(this);
if (boundCRS) {
- return BoundCRS::create(
- boundCRS->baseCRS()->demoteTo2D(newName, dbContext),
- boundCRS->hubCRS(), boundCRS->transformation());
+ auto base2DCRS = boundCRS->baseCRS()->demoteTo2D(newName, dbContext);
+ auto transf = boundCRS->transformation();
+ try {
+ transf->getTOWGS84Parameters();
+ return BoundCRS::create(
+ base2DCRS,
+ boundCRS->hubCRS()->demoteTo2D(std::string(), dbContext),
+ transf->demoteTo2D(std::string(), dbContext));
+ } catch (const io::FormattingException &) {
+ return BoundCRS::create(base2DCRS, boundCRS->hubCRS(), transf);
+ }
}
const auto compoundCRS = dynamic_cast<const CompoundCRS *>(this);
@@ -1365,15 +1411,42 @@ void GeodeticCRS::_exportToWKT(io::WKTFormatter *formatter) const {
if (!isWKT2 && formatter->isStrict() && isGeographic &&
axisList.size() != 2 &&
oldAxisOutputRule != io::WKTFormatter::OutputAxisRule::NO) {
+
+ auto geogCRS2D = demoteTo2D(std::string(), dbContext);
+ if (dbContext) {
+ const auto res = geogCRS2D->identify(
+ io::AuthorityFactory::create(NN_NO_CHECK(dbContext), "EPSG"));
+ if (res.size() == 1) {
+ const auto &front = res.front();
+ if (front.second == 100) {
+ geogCRS2D = front.first;
+ }
+ }
+ }
+
if (CRS::getPrivate()->allowNonConformantWKT1Export_) {
formatter->startNode(io::WKTConstants::COMPD_CS, false);
formatter->addQuotedString(l_name + " + " + l_name);
- auto geogCRS = demoteTo2D(std::string(), dbContext);
- geogCRS->_exportToWKT(formatter);
- geogCRS->_exportToWKT(formatter);
+ geogCRS2D->_exportToWKT(formatter);
+ const auto oldTOWGSParameters = formatter->getTOWGS84Parameters();
+ formatter->setTOWGS84Parameters({});
+ geogCRS2D->_exportToWKT(formatter);
+ formatter->setTOWGS84Parameters(oldTOWGSParameters);
formatter->endNode();
return;
}
+
+ auto &originalVertCRS = CRS::getPrivate()->originalVertCRS_;
+ if (originalVertCRS) {
+ formatter->startNode(io::WKTConstants::COMPD_CS, false);
+ formatter->addQuotedString(l_name + " + " +
+ originalVertCRS->nameStr());
+ geogCRS2D->_exportToWKT(formatter);
+ originalVertCRS->_exportToWKT(formatter);
+ formatter->endNode();
+ return;
+ }
+
io::FormattingException::Throw(
"WKT1 does not support Geographic 3D CRS.");
}
@@ -1703,7 +1776,7 @@ static bool hasCodeCompatibleOfAuthorityFactory(
* same.
* <li>70% means that CRS are equivalent (equivalent datum and coordinate
* system),
- * but the names do not match at all.</li>
+ * but the names are not equivalent.</li>
* <li>60% means that ellipsoid, prime meridian and coordinate systems are
* equivalent, but the CRS and datum names do not match.</li>
* <li>25% means that the CRS are not equivalent, but there is some similarity
@@ -2031,6 +2104,7 @@ GeodeticCRS::_identify(const io::AuthorityFactoryPtr &authorityFactory) const {
//! @cond Doxygen_Suppress
struct GeographicCRS::Private {
cs::EllipsoidalCSNNPtr coordinateSystem_;
+
explicit Private(const cs::EllipsoidalCSNNPtr &csIn)
: coordinateSystem_(csIn) {}
};
@@ -2808,7 +2882,7 @@ bool VerticalCRS::_isEquivalentTo(
* single result is returned.
* 90% means that CRS are equivalent, but the names are not exactly the same.
* 70% means that CRS are equivalent (equivalent datum and coordinate system),
- * but the names do not match at all.
+ * but the names are not equivalent.
* 25% means that the CRS are not equivalent, but there is some similarity in
* the names.
*
@@ -3194,22 +3268,22 @@ void ProjectedCRS::_exportToWKT(io::WKTFormatter *formatter) const {
const auto &l_coordinateSystem = d->coordinateSystem();
const auto &axisList = l_coordinateSystem->axisList();
if (axisList.size() == 3 && !(isWKT2 && formatter->use2019Keywords())) {
+ auto projCRS2D = demoteTo2D(std::string(), dbContext);
+ if (dbContext) {
+ const auto res = projCRS2D->identify(
+ io::AuthorityFactory::create(NN_NO_CHECK(dbContext), "EPSG"));
+ if (res.size() == 1) {
+ const auto &front = res.front();
+ if (front.second == 100) {
+ projCRS2D = front.first;
+ }
+ }
+ }
+
if (!formatter->useESRIDialect() &&
CRS::getPrivate()->allowNonConformantWKT1Export_) {
formatter->startNode(io::WKTConstants::COMPD_CS, false);
formatter->addQuotedString(l_name + " + " + baseCRS()->nameStr());
- auto projCRS2D = demoteTo2D(std::string(), dbContext);
- if (dbContext) {
- const auto res =
- projCRS2D->identify(io::AuthorityFactory::create(
- NN_NO_CHECK(dbContext), "EPSG"));
- if (res.size() == 1) {
- const auto &front = res.front();
- if (front.second == 100) {
- projCRS2D = front.first;
- }
- }
- }
projCRS2D->_exportToWKT(formatter);
baseCRS()
->demoteTo2D(std::string(), dbContext)
@@ -3217,6 +3291,18 @@ void ProjectedCRS::_exportToWKT(io::WKTFormatter *formatter) const {
formatter->endNode();
return;
}
+
+ auto &originalVertCRS = CRS::getPrivate()->originalVertCRS_;
+ if (!formatter->useESRIDialect() && originalVertCRS) {
+ formatter->startNode(io::WKTConstants::COMPD_CS, false);
+ formatter->addQuotedString(l_name + " + " +
+ originalVertCRS->nameStr());
+ projCRS2D->_exportToWKT(formatter);
+ originalVertCRS->_exportToWKT(formatter);
+ formatter->endNode();
+ return;
+ }
+
io::FormattingException::Throw(
"Projected 3D CRS can only be exported since WKT2:2019");
}
@@ -3630,8 +3716,11 @@ void ProjectedCRS::addUnitConvertAndAxisSwap(io::PROJStringFormatter *formatter,
* single result is returned.
* 90% means that CRS are equivalent, but the names are not exactly the same.
* 70% means that CRS are equivalent (equivalent base CRS, conversion and
- * coordinate system), but the names do not match at all.
- * 50% means that CRS have similarity (equivalent base CRS and conversion),
+ * coordinate system), but the names are not equivalent.
+ * 60% means that CRS have strong similarity (equivalent base datum, conversion
+ * and coordinate system), but the names are not equivalent.
+ * 50% means that CRS have similarity (equivalent base ellipsoid and
+ * conversion),
* but the coordinate system do not match (e.g. different axis ordering or
* axis unit).
* 25% means that the CRS are not equivalent, but there is some similarity in
@@ -3655,6 +3744,11 @@ ProjectedCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const {
std::list<std::pair<GeodeticCRSNNPtr, int>> baseRes;
const auto &l_baseCRS(baseCRS());
+ const auto l_datum = l_baseCRS->datum();
+ const bool significantNameForDatum =
+ l_datum != nullptr && !ci_starts_with(l_datum->nameStr(), "unknown") &&
+ l_datum->nameStr() != "unnamed";
+ const auto &ellipsoid = l_baseCRS->ellipsoid();
auto geogCRS = dynamic_cast<const GeographicCRS *>(l_baseCRS.get());
if (geogCRS &&
geogCRS->coordinateSystem()->axisOrder() ==
@@ -3742,6 +3836,59 @@ ProjectedCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const {
}
}
+ const bool l_implicitCS = CRS::getPrivate()->implicitCS_;
+ const auto addCRS = [&](const ProjectedCRSNNPtr &crs, const bool eqName) {
+ const auto &l_unit = cs->axisList()[0]->unit();
+ if (_isEquivalentTo(crs.get(), util::IComparable::Criterion::
+ EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS,
+ dbContext) ||
+ (l_implicitCS &&
+ l_unit._isEquivalentTo(
+ crs->coordinateSystem()->axisList()[0]->unit(),
+ util::IComparable::Criterion::EQUIVALENT) &&
+ l_baseCRS->_isEquivalentTo(
+ crs->baseCRS().get(), util::IComparable::Criterion::
+ EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS,
+ dbContext) &&
+ derivingConversionRef()->_isEquivalentTo(
+ crs->derivingConversionRef().get(),
+ util::IComparable::Criterion::EQUIVALENT, dbContext))) {
+ if (crs->nameStr() == thisName) {
+ res.clear();
+ res.emplace_back(crs, 100);
+ } else {
+ res.emplace_back(crs, eqName ? 90 : 70);
+ }
+ } else if (ellipsoid->_isEquivalentTo(
+ crs->baseCRS()->ellipsoid().get(),
+ util::IComparable::Criterion::EQUIVALENT, dbContext) &&
+ derivingConversionRef()->_isEquivalentTo(
+ crs->derivingConversionRef().get(),
+ util::IComparable::Criterion::EQUIVALENT, dbContext)) {
+ if ((l_implicitCS &&
+ l_unit._isEquivalentTo(
+ crs->coordinateSystem()->axisList()[0]->unit(),
+ util::IComparable::Criterion::EQUIVALENT)) ||
+ cs->_isEquivalentTo(crs->coordinateSystem().get(),
+ util::IComparable::Criterion::EQUIVALENT,
+ dbContext)) {
+ if (!significantNameForDatum ||
+ (crs->baseCRS()->datum() &&
+ l_datum->_isEquivalentTo(
+ crs->baseCRS()->datum().get(),
+ util::IComparable::Criterion::EQUIVALENT))) {
+ res.emplace_back(crs, 70);
+ } else {
+ res.emplace_back(crs, 60);
+ }
+ } else {
+ res.emplace_back(crs, 50);
+ }
+ } else {
+ res.emplace_back(crs, 25);
+ }
+ };
+
if (authorityFactory) {
const bool unsignificantName = thisName.empty() ||
@@ -3782,43 +3929,10 @@ ProjectedCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const {
const bool eqName = metadata::Identifier::isEquivalentName(
thisName.c_str(), crs->nameStr().c_str());
foundEquivalentName |= eqName;
- if (_isEquivalentTo(
- crs.get(), util::IComparable::Criterion::
- EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS,
- dbContext)) {
- if (crs->nameStr() == thisName) {
- res.clear();
- res.emplace_back(crsNN, 100);
- return res;
- }
- res.emplace_back(crsNN, eqName ? 90 : 70);
- } else if (objects.size() == 1 &&
- CRS::getPrivate()->implicitCS_ &&
- coordinateSystem()
- ->axisList()[0]
- ->unit()
- ._isEquivalentTo(
- crs->coordinateSystem()
- ->axisList()[0]
- ->unit(),
- util::IComparable::Criterion::
- EQUIVALENT) &&
- l_baseCRS->_isEquivalentTo(
- crs->baseCRS().get(),
- util::IComparable::Criterion::
- EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS,
- dbContext) &&
- derivingConversionRef()->_isEquivalentTo(
- crs->derivingConversionRef().get(),
- util::IComparable::Criterion::EQUIVALENT,
- dbContext)) {
- res.clear();
- res.emplace_back(crsNN, crs->nameStr() == thisName
- ? 100
- : eqName ? 90 : 70);
+
+ addCRS(crsNN, eqName);
+ if (res.back().second == 100) {
return res;
- } else {
- res.emplace_back(crsNN, 25);
}
}
if (!res.empty()) {
@@ -3867,7 +3981,6 @@ ProjectedCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const {
shared_from_this().as_nullable()));
auto candidates =
authorityFactory->createProjectedCRSFromExisting(self);
- const auto &ellipsoid = l_baseCRS->ellipsoid();
for (const auto &crs : candidates) {
const auto &ids = crs->identifiers();
assert(!ids.empty());
@@ -3877,30 +3990,7 @@ ProjectedCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const {
continue;
}
- if (_isEquivalentTo(crs.get(),
- util::IComparable::Criterion::
- EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS,
- dbContext)) {
- res.emplace_back(crs, unsignificantName ? 90 : 70);
- } else if (ellipsoid->_isEquivalentTo(
- crs->baseCRS()->ellipsoid().get(),
- util::IComparable::Criterion::EQUIVALENT,
- dbContext) &&
- derivingConversionRef()->_isEquivalentTo(
- crs->derivingConversionRef().get(),
- util::IComparable::Criterion::EQUIVALENT,
- dbContext)) {
- if (coordinateSystem()->_isEquivalentTo(
- crs->coordinateSystem().get(),
- util::IComparable::Criterion::EQUIVALENT,
- dbContext)) {
- res.emplace_back(crs, 70);
- } else {
- res.emplace_back(crs, 50);
- }
- } else {
- res.emplace_back(crs, 25);
- }
+ addCRS(crs, unsignificantName);
}
res.sort(lambdaSort);
@@ -4164,6 +4254,14 @@ CRSNNPtr CompoundCRS::createLax(const util::PropertyMap &properties,
auto comp1 = components[1].get();
auto comp0Geog = dynamic_cast<const GeographicCRS *>(comp0);
auto comp0Proj = dynamic_cast<const ProjectedCRS *>(comp0);
+ if (comp0Geog == nullptr && comp0Proj == nullptr) {
+ auto comp0Bound = dynamic_cast<const BoundCRS *>(comp0);
+ if (comp0Bound) {
+ const auto *baseCRS = comp0Bound->baseCRS().get();
+ comp0Geog = dynamic_cast<const GeographicCRS *>(baseCRS);
+ comp0Proj = dynamic_cast<const ProjectedCRS *>(baseCRS);
+ }
+ }
auto comp1Geog = dynamic_cast<const GeographicCRS *>(comp1);
if ((comp0Geog != nullptr || comp0Proj != nullptr) &&
comp1Geog != nullptr) {
@@ -4181,6 +4279,22 @@ CRSNNPtr CompoundCRS::createLax(const util::PropertyMap &properties,
"The 'vertical' geographic CRS is not equivalent to the "
"geographic CRS of the horizontal part");
}
+
+ // Detect a COMPD_CS whose VERT_CS is for ellipoidal heights
+ auto comp1Vert =
+ util::nn_dynamic_pointer_cast<VerticalCRS>(components[1]);
+ if (comp1Vert != nullptr && comp1Vert->datum() &&
+ comp1Vert->datum()->getWKT1DatumType() == "2002") {
+ const auto &axis = comp1Vert->coordinateSystem()->axisList()[0];
+ if (axis->unit()._isEquivalentTo(
+ common::UnitOfMeasure::METRE,
+ util::IComparable::Criterion::EQUIVALENT) &&
+ &(axis->direction()) == &(cs::AxisDirection::UP)) {
+ return components[0]
+ ->promoteTo3D(std::string(), dbContext)
+ ->attachOriginalVertCRS(NN_NO_CHECK(comp1Vert));
+ }
+ }
}
return create(properties, components);
@@ -4293,7 +4407,7 @@ bool CompoundCRS::_isEquivalentTo(
* single result is returned.
* 90% means that CRS are equivalent, but the names are not exactly the same.
* 70% means that CRS are equivalent (equivalent horizontal and vertical CRS),
- * but the names do not match at all.
+ * but the names are not equivalent.
* 25% means that the CRS are not equivalent, but there is some similarity in
* the names.
*
@@ -4616,18 +4730,9 @@ 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, targetCRS,
- operation::Transformation::createTOWGS84(baseCRSIn, TOWGS84Parameters));
+ auto transf =
+ operation::Transformation::createTOWGS84(baseCRSIn, TOWGS84Parameters);
+ return create(baseCRSIn, transf->targetCRS(), transf);
}
// ---------------------------------------------------------------------------
diff --git a/src/iso19111/datum.cpp b/src/iso19111/datum.cpp
index 03abb0d7..33eb81fa 100644
--- a/src/iso19111/datum.cpp
+++ b/src/iso19111/datum.cpp
@@ -1788,6 +1788,9 @@ operator=(const RealizationMethod &other) {
//! @cond Doxygen_Suppress
struct VerticalReferenceFrame::Private {
util::optional<RealizationMethod> realizationMethod_{};
+
+ // 2005 = CS_VD_GeoidModelDerived from OGC 01-009
+ std::string wkt1DatumType_{"2005"};
};
//! @endcond
@@ -1837,12 +1840,21 @@ VerticalReferenceFrameNNPtr VerticalReferenceFrame::create(
realizationMethodIn));
rf->setAnchor(anchor);
rf->setProperties(properties);
+ properties.getStringValue("VERT_DATUM_TYPE", rf->d->wkt1DatumType_);
return rf;
}
// ---------------------------------------------------------------------------
//! @cond Doxygen_Suppress
+const std::string &VerticalReferenceFrame::getWKT1DatumType() const {
+ return d->wkt1DatumType_;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
void VerticalReferenceFrame::_exportToWKT(
io::WKTFormatter *formatter) const // throw(FormattingException)
{
@@ -1861,7 +1873,7 @@ void VerticalReferenceFrame::_exportToWKT(
if (isWKT2) {
Datum::getPrivate()->exportAnchorDefinition(formatter);
} else if (!formatter->useESRIDialect()) {
- formatter->add(2005); // CS_VD_GeoidModelDerived from OGC 01-009
+ formatter->add(d->wkt1DatumType_);
const auto &extension = formatter->getVDatumExtension();
if (!extension.empty()) {
formatter->startNode(io::WKTConstants::EXTENSION, false);
diff --git a/src/iso19111/factory.cpp b/src/iso19111/factory.cpp
index c938ff8b..16554b2a 100644
--- a/src/iso19111/factory.cpp
+++ b/src/iso19111/factory.cpp
@@ -5434,25 +5434,6 @@ std::string AuthorityFactory::getOfficialNameFromAlias(
// ---------------------------------------------------------------------------
-//! @cond Doxygen_Suppress
-
-static void addToListString(std::string &out, const char *in) {
- if (!out.empty()) {
- out += ',';
- }
- out += in;
-}
-
-static void addToListStringWithOR(std::string &out, const std::string &in) {
- if (!out.empty()) {
- out += " OR ";
- }
- out += in;
-}
-//! @endcond
-
-// ---------------------------------------------------------------------------
-
/** \brief Return a list of objects by their name
*
* @param searchedName Searched name. Must be at least 2 character long.
@@ -5485,155 +5466,169 @@ AuthorityFactory::createObjectsFromName(
}
std::string sql(
- "SELECT table_name, auth_name, code, name, deprecated, is_alias FROM ("
- "SELECT table_name, auth_name, code, name, deprecated, 0 as is_alias "
- "FROM object_view WHERE ");
- if (deprecated) {
- sql += "deprecated = 1 AND ";
- }
- ListOfParams params;
- if (!approximateMatch) {
- sql += "name LIKE ? AND ";
- params.push_back(searchedNameWithoutDeprecated);
- }
- if (d->hasAuthorityRestriction()) {
- sql += "auth_name = ? AND ";
- params.emplace_back(d->authority());
- }
+ "SELECT table_name, auth_name, code, name, deprecated, is_alias "
+ "FROM (");
- const auto getTableNameConstraint = [&allowedObjectTypes](
- const std::string &colName) {
+ const auto getTableAndTypeConstraints = [&allowedObjectTypes]() {
+ typedef std::pair<std::string, std::string> TableType;
+ std::list<TableType> res;
if (allowedObjectTypes.empty()) {
- return colName + " IN ("
- "'prime_meridian','ellipsoid','geodetic_datum',"
- "'vertical_datum','geodetic_crs','projected_crs',"
- "'vertical_crs','compound_crs','conversion',"
- "'helmert_transformation','grid_transformation',"
- "'other_transformation','concatenated_operation'"
- ")";
+ for (const auto &tableName :
+ {"prime_meridian", "ellipsoid", "geodetic_datum",
+ "vertical_datum", "geodetic_crs", "projected_crs",
+ "vertical_crs", "compound_crs", "conversion",
+ "helmert_transformation", "grid_transformation",
+ "other_transformation", "concatenated_operation"}) {
+ res.emplace_back(TableType(tableName, std::string()));
+ }
} else {
- std::string tableNameList;
- std::string otherConditions;
for (const auto type : allowedObjectTypes) {
switch (type) {
case ObjectType::PRIME_MERIDIAN:
- addToListString(tableNameList, "'prime_meridian'");
+ res.emplace_back(
+ TableType("prime_meridian", std::string()));
break;
case ObjectType::ELLIPSOID:
- addToListString(tableNameList, "'ellipsoid'");
+ res.emplace_back(TableType("ellipsoid", std::string()));
break;
case ObjectType::DATUM:
- addToListString(tableNameList,
- "'geodetic_datum','vertical_datum'");
+ res.emplace_back(
+ TableType("geodetic_datum", std::string()));
+ res.emplace_back(
+ TableType("vertical_datum", std::string()));
break;
case ObjectType::GEODETIC_REFERENCE_FRAME:
- addToListString(tableNameList, "'geodetic_datum'");
+ res.emplace_back(
+ TableType("geodetic_datum", std::string()));
break;
case ObjectType::VERTICAL_REFERENCE_FRAME:
- addToListString(tableNameList, "'vertical_datum'");
+ res.emplace_back(
+ TableType("vertical_datum", std::string()));
break;
case ObjectType::CRS:
- addToListString(tableNameList,
- "'geodetic_crs','projected_crs',"
- "'vertical_crs','compound_crs'");
+ res.emplace_back(TableType("geodetic_crs", std::string()));
+ res.emplace_back(TableType("projected_crs", std::string()));
+ res.emplace_back(TableType("vertical_crs", std::string()));
+ res.emplace_back(TableType("compound_crs", std::string()));
break;
case ObjectType::GEODETIC_CRS:
- addToListString(tableNameList, "'geodetic_crs'");
+ res.emplace_back(TableType("geodetic_crs", std::string()));
break;
case ObjectType::GEOCENTRIC_CRS:
- addToListStringWithOR(
- otherConditions,
- "(" + colName + " = " GEOCENTRIC_SINGLE_QUOTED " AND "
- "type = " GEOCENTRIC_SINGLE_QUOTED ")");
+ res.emplace_back(TableType("geodetic_crs", GEOCENTRIC));
break;
case ObjectType::GEOGRAPHIC_CRS:
- addToListStringWithOR(otherConditions,
- "(" + colName +
- " = 'geodetic_crs' AND "
- "type IN (" GEOG_2D_SINGLE_QUOTED
- "," GEOG_3D_SINGLE_QUOTED "))");
+ res.emplace_back(TableType("geodetic_crs", GEOG_2D));
+ res.emplace_back(TableType("geodetic_crs", GEOG_3D));
break;
case ObjectType::GEOGRAPHIC_2D_CRS:
- addToListStringWithOR(
- otherConditions,
- "(" + colName + " = 'geodetic_crs' AND "
- "type = " GEOG_2D_SINGLE_QUOTED ")");
+ res.emplace_back(TableType("geodetic_crs", GEOG_2D));
break;
case ObjectType::GEOGRAPHIC_3D_CRS:
- addToListStringWithOR(
- otherConditions,
- "(" + colName + " = 'geodetic_crs' AND "
- "type = " GEOG_3D_SINGLE_QUOTED ")");
+ res.emplace_back(TableType("geodetic_crs", GEOG_3D));
break;
case ObjectType::PROJECTED_CRS:
- addToListString(tableNameList, "'projected_crs'");
+ res.emplace_back(TableType("projected_crs", std::string()));
break;
case ObjectType::VERTICAL_CRS:
- addToListString(tableNameList, "'vertical_crs'");
+ res.emplace_back(TableType("vertical_crs", std::string()));
break;
case ObjectType::COMPOUND_CRS:
- addToListString(tableNameList, "'compound_crs'");
+ res.emplace_back(TableType("compound_crs", std::string()));
break;
case ObjectType::COORDINATE_OPERATION:
- addToListString(
- tableNameList,
- "'conversion','helmert_transformation',"
- "'grid_transformation','other_transformation',"
- "'concatenated_operation'");
+ res.emplace_back(TableType("conversion", std::string()));
+ res.emplace_back(
+ TableType("helmert_transformation", std::string()));
+ res.emplace_back(
+ TableType("grid_transformation", std::string()));
+ res.emplace_back(
+ TableType("other_transformation", std::string()));
+ res.emplace_back(
+ TableType("concatenated_operation", std::string()));
break;
case ObjectType::CONVERSION:
- addToListString(tableNameList, "'conversion'");
+ res.emplace_back(TableType("conversion", std::string()));
break;
case ObjectType::TRANSFORMATION:
- addToListString(
- tableNameList,
- "'helmert_transformation',"
- "'grid_transformation','other_transformation'");
+ res.emplace_back(
+ TableType("helmert_transformation", std::string()));
+ res.emplace_back(
+ TableType("grid_transformation", std::string()));
+ res.emplace_back(
+ TableType("other_transformation", std::string()));
break;
case ObjectType::CONCATENATED_OPERATION:
- addToListString(tableNameList, "'concatenated_operation'");
+ res.emplace_back(
+ TableType("concatenated_operation", std::string()));
break;
}
}
- std::string l_sql;
- if (!tableNameList.empty()) {
- l_sql = "((" + colName + " IN (";
- l_sql += tableNameList;
- l_sql += "))";
- if (!otherConditions.empty()) {
- l_sql += " OR ";
- l_sql += otherConditions;
- }
- l_sql += ')';
- } else if (!otherConditions.empty()) {
- l_sql = "(";
- l_sql += otherConditions;
- l_sql += ')';
- }
- return l_sql;
}
+ return res;
};
- sql += getTableNameConstraint("table_name");
-
- sql += " UNION SELECT ov.table_name AS table_name, "
- "ov.auth_name AS auth_name, "
- "ov.code AS code, a.alt_name AS name, "
- "ov.deprecated AS deprecated, 1 as is_alias FROM object_view ov "
- "JOIN alias_name a ON ov.table_name = a.table_name AND "
- "ov.auth_name = a.auth_name AND ov.code = a.code WHERE ";
- if (deprecated) {
- sql += "ov.deprecated = 1 AND ";
- }
- if (!approximateMatch) {
- sql += "a.alt_name LIKE ? AND ";
- params.push_back(searchedNameWithoutDeprecated);
- }
- if (d->hasAuthorityRestriction()) {
- sql += "ov.auth_name = ? AND ";
- params.emplace_back(d->authority());
+ const auto listTableNameType = getTableAndTypeConstraints();
+ bool first = true;
+ ListOfParams params;
+ for (const auto &tableNameTypePair : listTableNameType) {
+ if (!first) {
+ sql += " UNION ";
+ }
+ first = false;
+ sql += "SELECT '";
+ sql += tableNameTypePair.first;
+ sql += "' AS table_name, auth_name, code, name, deprecated, "
+ "0 AS is_alias FROM ";
+ sql += tableNameTypePair.first;
+ sql += " WHERE 1 = 1 ";
+ if (!tableNameTypePair.second.empty()) {
+ sql += "AND type = '";
+ sql += tableNameTypePair.second;
+ sql += "' ";
+ }
+ if (deprecated) {
+ sql += "AND deprecated = 1 ";
+ }
+ if (!approximateMatch) {
+ sql += "AND name LIKE ? ";
+ params.push_back(searchedNameWithoutDeprecated);
+ }
+ if (d->hasAuthorityRestriction()) {
+ sql += "AND auth_name = ? ";
+ params.emplace_back(d->authority());
+ }
+
+ sql += " UNION SELECT '";
+ sql += tableNameTypePair.first;
+ sql += "' AS table_name, "
+ "ov.auth_name AS auth_name, "
+ "ov.code AS code, a.alt_name AS name, "
+ "ov.deprecated AS deprecated, 1 as is_alias FROM ";
+ sql += tableNameTypePair.first;
+ sql += " ov "
+ "JOIN alias_name a ON "
+ "ov.auth_name = a.auth_name AND ov.code = a.code WHERE "
+ "a.table_name = '";
+ sql += tableNameTypePair.first;
+ sql += "' ";
+ if (!tableNameTypePair.second.empty()) {
+ sql += "AND ov.type = '";
+ sql += tableNameTypePair.second;
+ sql += "' ";
+ }
+ if (deprecated) {
+ sql += "AND ov.deprecated = 1 ";
+ }
+ if (!approximateMatch) {
+ sql += "AND a.alt_name LIKE ? ";
+ params.push_back(searchedNameWithoutDeprecated);
+ }
+ if (d->hasAuthorityRestriction()) {
+ sql += "AND ov.auth_name = ? ";
+ params.emplace_back(d->authority());
+ }
}
- sql += getTableNameConstraint("ov.table_name");
sql += ") ORDER BY deprecated, is_alias, length(name), name";
if (limitResultCount > 0 &&
diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp
index a255b959..ebec053a 100644
--- a/src/iso19111/io.cpp
+++ b/src/iso19111/io.cpp
@@ -179,7 +179,7 @@ struct WKTFormatter::Private {
*
* A formatter can be used only once (its internal state is mutated)
*
- * Its default behaviour can be adjusted with the different setters.
+ * Its default behavior can be adjusted with the different setters.
*
* @param convention WKT flavor. Defaults to Convention::WKT2
* @param dbContext Database context, to allow queries in it if needed.
@@ -201,7 +201,7 @@ WKTFormatterNNPtr WKTFormatter::create(Convention convention,
*
* A formatter can be used only once (its internal state is mutated)
*
- * Its default behaviour can be adjusted with the different setters.
+ * Its default behavior can be adjusted with the different setters.
*
* @param other source formatter.
* @return new formatter.
@@ -3944,9 +3944,18 @@ VerticalReferenceFrameNNPtr WKTParser::Private::buildVerticalReferenceFrame(
modelName);
}
- // WKT1 VERT_DATUM has a datum type after the datum name that we ignore.
- return VerticalReferenceFrame::create(buildProperties(node),
- getAnchor(node));
+ // WKT1 VERT_DATUM has a datum type after the datum name
+ const auto *nodeP = node->GP();
+ const std::string &name(nodeP->value());
+ auto &props = buildProperties(node);
+ if (ci_equal(name, WKTConstants::VERT_DATUM)) {
+ const auto &children = nodeP->children();
+ if (children.size() >= 2) {
+ props.set("VERT_DATUM_TYPE", children[1]->GP()->value());
+ }
+ }
+
+ return VerticalReferenceFrame::create(props, getAnchor(node));
}
// ---------------------------------------------------------------------------
@@ -3987,6 +3996,79 @@ WKTParser::Private::buildParametricDatum(const WKTNodeNNPtr &node) {
// ---------------------------------------------------------------------------
+static CRSNNPtr
+createBoundCRSSourceTransformationCRS(const crs::CRSPtr &sourceCRS,
+ const crs::CRSPtr &targetCRS) {
+ CRSPtr sourceTransformationCRS;
+ if (dynamic_cast<GeographicCRS *>(targetCRS.get())) {
+ GeographicCRSPtr sourceGeographicCRS =
+ sourceCRS->extractGeographicCRS();
+ sourceTransformationCRS = sourceGeographicCRS;
+ if (sourceGeographicCRS) {
+ if (sourceGeographicCRS->datum() != nullptr &&
+ sourceGeographicCRS->primeMeridian()
+ ->longitude()
+ .getSIValue() != 0.0) {
+ sourceTransformationCRS =
+ GeographicCRS::create(
+ util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ sourceGeographicCRS->nameStr() +
+ " (with Greenwich prime meridian)"),
+ datum::GeodeticReferenceFrame::create(
+ util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ sourceGeographicCRS->datum()->nameStr() +
+ " (with Greenwich prime meridian)"),
+ sourceGeographicCRS->datum()->ellipsoid(),
+ util::optional<std::string>(),
+ datum::PrimeMeridian::GREENWICH),
+ sourceGeographicCRS->coordinateSystem())
+ .as_nullable();
+ }
+ } else {
+ auto vertSourceCRS =
+ std::dynamic_pointer_cast<VerticalCRS>(sourceCRS);
+ if (!vertSourceCRS) {
+ throw ParsingException(
+ "Cannot find GeographicCRS or VerticalCRS in sourceCRS");
+ }
+ const auto &axis = vertSourceCRS->coordinateSystem()->axisList()[0];
+ if (axis->unit() == common::UnitOfMeasure::METRE &&
+ &(axis->direction()) == &AxisDirection::UP) {
+ sourceTransformationCRS = sourceCRS;
+ } else {
+ std::string sourceTransformationCRSName(
+ vertSourceCRS->nameStr());
+ if (ends_with(sourceTransformationCRSName, " (ftUS)")) {
+ sourceTransformationCRSName.resize(
+ sourceTransformationCRSName.size() - strlen(" (ftUS)"));
+ }
+ if (ends_with(sourceTransformationCRSName, " depth")) {
+ sourceTransformationCRSName.resize(
+ sourceTransformationCRSName.size() - strlen(" depth"));
+ }
+ if (!ends_with(sourceTransformationCRSName, " height")) {
+ sourceTransformationCRSName += " height";
+ }
+ sourceTransformationCRS =
+ VerticalCRS::create(
+ PropertyMap().set(IdentifiedObject::NAME_KEY,
+ sourceTransformationCRSName),
+ vertSourceCRS->datum(), vertSourceCRS->datumEnsemble(),
+ VerticalCS::createGravityRelatedHeight(
+ common::UnitOfMeasure::METRE))
+ .as_nullable();
+ }
+ }
+ } else {
+ sourceTransformationCRS = sourceCRS;
+ }
+ return NN_NO_CHECK(sourceTransformationCRS);
+}
+
+// ---------------------------------------------------------------------------
+
CRSNNPtr WKTParser::Private::buildVerticalCRS(const WKTNodeNNPtr &node) {
const auto *nodeP = node->GP();
auto &datumNode =
@@ -4111,16 +4193,18 @@ CRSNNPtr WKTParser::Private::buildVerticalCRS(const WKTNodeNNPtr &node) {
gridName != "g2012a_conus.gtx,g2012a_alaska.gtx,"
"g2012a_guam.gtx,g2012a_hawaii.gtx,"
"g2012a_puertorico.gtx,g2012a_samoa.gtx") {
- std::string transformationName(crs->nameStr());
- if (!ends_with(transformationName, " height")) {
- transformationName += " height";
- }
- transformationName += " to WGS84 ellipsoidal height";
+ auto sourceTransformationCRS =
+ createBoundCRSSourceTransformationCRS(
+ crs.as_nullable(),
+ GeographicCRS::EPSG_4979.as_nullable());
auto transformation = Transformation::
createGravityRelatedHeightToGeographic3D(
- PropertyMap().set(IdentifiedObject::NAME_KEY,
- transformationName),
- crs, GeographicCRS::EPSG_4979, nullptr, gridName,
+ PropertyMap().set(
+ IdentifiedObject::NAME_KEY,
+ sourceTransformationCRS->nameStr() +
+ " to WGS84 ellipsoidal height"),
+ sourceTransformationCRS, GeographicCRS::EPSG_4979,
+ nullptr, gridName,
std::vector<PositionalAccuracyNNPtr>());
return nn_static_pointer_cast<CRS>(BoundCRS::create(
crs, GeographicCRS::EPSG_4979, transformation));
@@ -4190,52 +4274,6 @@ CRSNNPtr WKTParser::Private::buildCompoundCRS(const WKTNodeNNPtr &node) {
// ---------------------------------------------------------------------------
-static CRSNNPtr
-createBoundCRSSourceTransformationCRS(const crs::CRSPtr &sourceCRS,
- const crs::CRSPtr &targetCRS) {
- CRSPtr sourceTransformationCRS;
- if (dynamic_cast<GeographicCRS *>(targetCRS.get())) {
- GeographicCRSPtr sourceGeographicCRS =
- sourceCRS->extractGeographicCRS();
- sourceTransformationCRS = sourceGeographicCRS;
- if (sourceGeographicCRS) {
- if (sourceGeographicCRS->datum() != nullptr &&
- sourceGeographicCRS->primeMeridian()
- ->longitude()
- .getSIValue() != 0.0) {
- sourceTransformationCRS =
- GeographicCRS::create(
- util::PropertyMap().set(
- common::IdentifiedObject::NAME_KEY,
- sourceGeographicCRS->nameStr() +
- " (with Greenwich prime meridian)"),
- datum::GeodeticReferenceFrame::create(
- util::PropertyMap().set(
- common::IdentifiedObject::NAME_KEY,
- sourceGeographicCRS->datum()->nameStr() +
- " (with Greenwich prime meridian)"),
- sourceGeographicCRS->datum()->ellipsoid(),
- util::optional<std::string>(),
- datum::PrimeMeridian::GREENWICH),
- sourceGeographicCRS->coordinateSystem())
- .as_nullable();
- }
- } else {
- sourceTransformationCRS =
- std::dynamic_pointer_cast<VerticalCRS>(sourceCRS);
- if (!sourceTransformationCRS) {
- throw ParsingException(
- "Cannot find GeographicCRS or VerticalCRS in sourceCRS");
- }
- }
- } else {
- sourceTransformationCRS = sourceCRS;
- }
- return NN_NO_CHECK(sourceTransformationCRS);
-}
-
-// ---------------------------------------------------------------------------
-
BoundCRSNNPtr WKTParser::Private::buildBoundCRS(const WKTNodeNNPtr &node) {
const auto *nodeP = node->GP();
auto &abridgedNode =
@@ -4515,12 +4553,27 @@ CRSPtr WKTParser::Private::buildCRS(const WKTNodeNNPtr &node) {
const auto *nodeP = node->GP();
const std::string &name(nodeP->value());
+ const auto applyHorizontalBoundCRSParams = [&](const CRSNNPtr &crs) {
+ if (!toWGS84Parameters_.empty()) {
+ auto ret = BoundCRS::createFromTOWGS84(crs, toWGS84Parameters_);
+ toWGS84Parameters_.clear();
+ return util::nn_static_pointer_cast<CRS>(ret);
+ } else if (!datumPROJ4Grids_.empty()) {
+ auto ret = BoundCRS::createFromNadgrids(crs, datumPROJ4Grids_);
+ datumPROJ4Grids_.clear();
+ return util::nn_static_pointer_cast<CRS>(ret);
+ }
+ return crs;
+ };
+
if (isGeodeticCRS(name)) {
if (!isNull(nodeP->lookForChild(WKTConstants::BASEGEOGCRS,
WKTConstants::BASEGEODCRS))) {
- return buildDerivedGeodeticCRS(node);
+ return util::nn_static_pointer_cast<CRS>(
+ applyHorizontalBoundCRSParams(buildDerivedGeodeticCRS(node)));
} else {
- return util::nn_static_pointer_cast<CRS>(buildGeodeticCRS(node));
+ return util::nn_static_pointer_cast<CRS>(
+ applyHorizontalBoundCRSParams(buildGeodeticCRS(node)));
}
}
@@ -4545,12 +4598,14 @@ CRSPtr WKTParser::Private::buildCRS(const WKTNodeNNPtr &node) {
PROJStringParser().createFromPROJString(projString);
auto crs = nn_dynamic_pointer_cast<CRS>(projObj);
if (crs) {
- return crs;
+ return util::nn_static_pointer_cast<CRS>(
+ applyHorizontalBoundCRSParams(NN_NO_CHECK(crs)));
}
} catch (const io::ParsingException &) {
}
}
- return util::nn_static_pointer_cast<CRS>(buildProjectedCRS(node));
+ return util::nn_static_pointer_cast<CRS>(
+ applyHorizontalBoundCRSParams(buildProjectedCRS(node)));
}
if (ci_equal(name, WKTConstants::VERT_CS) ||
@@ -4623,16 +4678,6 @@ BaseObjectNNPtr WKTParser::Private::build(const WKTNodeNNPtr &node) {
auto crs = buildCRS(node);
if (crs) {
- if (!toWGS84Parameters_.empty()) {
- return util::nn_static_pointer_cast<BaseObject>(
- BoundCRS::createFromTOWGS84(NN_NO_CHECK(crs),
- toWGS84Parameters_));
- }
- if (!datumPROJ4Grids_.empty()) {
- return util::nn_static_pointer_cast<BaseObject>(
- BoundCRS::createFromNadgrids(NN_NO_CHECK(crs),
- datumPROJ4Grids_));
- }
return util::nn_static_pointer_cast<BaseObject>(NN_NO_CHECK(crs));
}
@@ -6631,7 +6676,7 @@ PROJStringFormatter::~PROJStringFormatter() = default;
*
* A formatter can be used only once (its internal state is mutated)
*
- * Its default behaviour can be adjusted with the different setters.
+ * Its default behavior can be adjusted with the different setters.
*
* @param conventionIn PROJ string flavor. Defaults to Convention::PROJ_5
* @param dbContext Database context (can help to find alternative grid names).
@@ -7594,7 +7639,10 @@ std::set<std::string> PROJStringFormatter::getUsedGridNames() const {
for (const auto &step : d->steps_) {
for (const auto &param : step.paramValues) {
if (param.keyEquals("grids")) {
- res.insert(param.value);
+ const auto gridNames = split(param.value, ",");
+ for (const auto &gridName : gridNames) {
+ res.insert(gridName);
+ }
}
}
}
diff --git a/src/iso19111/metadata.cpp b/src/iso19111/metadata.cpp
index 8f2601e0..fc788a30 100644
--- a/src/iso19111/metadata.cpp
+++ b/src/iso19111/metadata.cpp
@@ -777,7 +777,7 @@ bool Extent::_isEquivalentTo(const util::IComparable *other,
/** \brief Returns whether this extent contains the other one.
*
- * Behaviour only well specified if each sub-extent category as at most
+ * Behavior only well specified if each sub-extent category as at most
* one element.
*/
bool Extent::contains(const ExtentNNPtr &other) const {
@@ -802,7 +802,7 @@ bool Extent::contains(const ExtentNNPtr &other) const {
/** \brief Returns whether this extent intersects the other one.
*
- * Behaviour only well specified if each sub-extent category as at most
+ * Behavior only well specified if each sub-extent category as at most
* one element.
*/
bool Extent::intersects(const ExtentNNPtr &other) const {
@@ -829,7 +829,7 @@ bool Extent::intersects(const ExtentNNPtr &other) const {
/** \brief Returns the intersection of this extent with another one.
*
- * Behaviour only well specified if there is one single GeographicExtent
+ * Behavior only well specified if there is one single GeographicExtent
* in each object.
* Returns nullptr otherwise.
*/
diff --git a/src/iso19111/util.cpp b/src/iso19111/util.cpp
index 2a6178e2..21d45e44 100644
--- a/src/iso19111/util.cpp
+++ b/src/iso19111/util.cpp
@@ -686,7 +686,7 @@ IComparable::~IComparable() = default;
/** \brief Returns whether an object is equivalent to another one.
* @param other other object to compare to
- * @param criterion comparaison criterion.
+ * @param criterion comparison criterion.
* @param dbContext Database context, or nullptr.
* @return true if objects are equivalent.
*/
diff --git a/src/malloc.cpp b/src/malloc.cpp
index 10ab2fad..c8de6630 100644
--- a/src/malloc.cpp
+++ b/src/malloc.cpp
@@ -216,7 +216,7 @@ PJ *pj_default_destructor (PJ *P, int errlev) { /* Destructor */
/* Even if P==0, we set the errlev on pj_error and the default context */
/* Note that both, in the multithreaded case, may then contain undefined */
- /* values. This is expected behaviour. For MT have one ctx per thread */
+ /* values. This is expected behavior. For MT have one ctx per thread */
if (0!=errlev)
pj_ctx_set_errno (pj_get_ctx(P), errlev);
diff --git a/src/networkfilemanager.cpp b/src/networkfilemanager.cpp
index 30229fea..4d6f425c 100644
--- a/src/networkfilemanager.cpp
+++ b/src/networkfilemanager.cpp
@@ -2180,7 +2180,7 @@ void proj_grid_cache_clear(PJ_CONTEXT *ctx) {
* use the "downloaded_file_properties" table of its grid cache database.
* Consequently files manually placed in the user-writable
* directory without using this function would be considered as
- * non-existing/obsolete and would be unconditionnaly downloaded again.
+ * non-existing/obsolete and would be unconditionally downloaded again.
*
* This function can only be used if networking is enabled, and either
* the default curl network API or a custom one have been installed.
@@ -2309,7 +2309,7 @@ int proj_is_download_needed(PJ_CONTEXT *ctx, const char *url_or_filename,
* use the "downloaded_file_properties" table of its grid cache database.
* Consequently files manually placed in the user-writable
* directory without using this function would be considered as
- * non-existing/obsolete and would be unconditionnaly downloaded again.
+ * non-existing/obsolete and would be unconditionally downloaded again.
*
* This function can only be used if networking is enabled, and either
* the default curl network API or a custom one have been installed.
diff --git a/src/projections/isea.cpp b/src/projections/isea.cpp
index 1ebbeebb..dd1d48ff 100644
--- a/src/projections/isea.cpp
+++ b/src/projections/isea.cpp
@@ -322,7 +322,7 @@ static int isea_snyder_forward(struct isea_geo * ll, struct isea_pt * out)
/*
* spherical distance from center of polygon face to any of its
- * vertexes on the globe
+ * vertices on the globe
*/
double g;
diff --git a/src/projections/ocea.cpp b/src/projections/ocea.cpp
index 4fc5391d..de9838cb 100644
--- a/src/projections/ocea.cpp
+++ b/src/projections/ocea.cpp
@@ -65,12 +65,12 @@ PJ *PROJECTION(ocea) {
/*Define Pole of oblique transformation from 1 point & 1 azimuth*/
// ERO: I've added M_PI so that the alpha is the angle from point 1 to point 2
// from the North in a clockwise direction
- // (to be consistent with omerc behaviour)
+ // (to be consistent with omerc behavior)
alpha = M_PI + pj_param(P->ctx, P->params, "ralpha").f;
lonz = pj_param(P->ctx, P->params, "rlonc").f;
/*Equation 9-8 page 80 (http://pubs.usgs.gov/pp/1395/report.pdf)*/
// Actually slightliy modified to use atan2(), as it is suggested by
- // Snyder for equation 9-1, but this is not mentionned here
+ // Snyder for equation 9-1, but this is not mentioned here
lam_p = atan2(-cos(alpha) , -sin(P->phi0) * sin(alpha)) + lonz;
/*Equation 9-7 page 80 (http://pubs.usgs.gov/pp/1395/report.pdf)*/
phi_p = asin(cos(P->phi0) * sin(alpha));
diff --git a/src/projections/tmerc.cpp b/src/projections/tmerc.cpp
index 1e5fc1a8..3a58fc02 100644
--- a/src/projections/tmerc.cpp
+++ b/src/projections/tmerc.cpp
@@ -286,7 +286,7 @@ static PJ *setup_approx(PJ *P) {
//
/*****************************************************************************/
-/* Helper functios for "exact" transverse mercator */
+/* Helper functions for "exact" transverse mercator */
inline
static double gatg(const double *p1, int len_p1, double B, double cos_2B, double sin_2B) {
double h = 0, h1, h2 = 0;
@@ -750,7 +750,7 @@ PJ *PROJECTION(etmerc) {
/* UTM uses the Poder/Engsager implementation for the underlying projection */
-/* UNLESS +approx is set in which case the Evenden/Snyder implemenation is used. */
+/* UNLESS +approx is set in which case the Evenden/Snyder implementation is used. */
PJ *PROJECTION(utm) {
long zone;
if (P->es == 0.0) {
diff --git a/src/sqlite3_utils.cpp b/src/sqlite3_utils.cpp
index 7006674f..84bbb2ce 100644
--- a/src/sqlite3_utils.cpp
+++ b/src/sqlite3_utils.cpp
@@ -133,8 +133,25 @@ static int VFSCustomAccess(sqlite3_vfs *vfs, const char *zName, int flags,
// ---------------------------------------------------------------------------
+// SQLite3 logging infrastructure
+static void projSqlite3LogCallback(void *, int iErrCode, const char *zMsg) {
+ fprintf(stderr, "SQLite3 message: (code %d) %s\n", iErrCode, zMsg);
+}
+
std::unique_ptr<SQLite3VFS> SQLite3VFS::create(bool fakeSync, bool fakeLock,
bool skipStatJournalAndWAL) {
+
+ // Install SQLite3 logger if PROJ_LOG_SQLITE3 env var is defined
+ struct InstallSqliteLogger {
+ InstallSqliteLogger() {
+ if (getenv("PROJ_LOG_SQLITE3") != nullptr) {
+ sqlite3_config(SQLITE_CONFIG_LOG, projSqlite3LogCallback,
+ nullptr);
+ }
+ }
+ };
+ static InstallSqliteLogger installSqliteLogger;
+
// Call to sqlite3_initialize() is normally not needed, except for
// people building SQLite3 with -DSQLITE_OMIT_AUTOINIT
sqlite3_initialize();
diff --git a/src/strtod.cpp b/src/strtod.cpp
index d45e62db..4311caea 100644
--- a/src/strtod.cpp
+++ b/src/strtod.cpp
@@ -46,7 +46,7 @@
* Converts ASCII string to floating point number.
*
* This function converts the initial portion of the string pointed to
- * by nptr to double floating point representation. The behaviour is the
+ * by nptr to double floating point representation. The behavior is the
* same as
*
* pj_strtod(nptr, (char **)NULL);
diff --git a/src/transformations/vgridshift.cpp b/src/transformations/vgridshift.cpp
index 6ee8a6b0..3d9f046a 100644
--- a/src/transformations/vgridshift.cpp
+++ b/src/transformations/vgridshift.cpp
@@ -46,7 +46,7 @@ struct vgridshiftData {
static void deal_with_vertcon_gtx_hack(PJ *P)
{
struct vgridshiftData *Q = (struct vgridshiftData *) P->opaque;
- // The .gtx VERTCON files stored millimetres, but the .tif files
+ // The .gtx VERTCON files stored millimeters, but the .tif files
// are in metres.
if( Q->forward_multiplier != 0.001 ) {
return;
diff --git a/test/cli/testvarious b/test/cli/testvarious
index 6d773c4c..6a77729e 100755
--- a/test/cli/testvarious
+++ b/test/cli/testvarious
@@ -959,7 +959,7 @@ echo "Test EPSG:4896 to EPSG:7930" >> ${OUT}
# compatibility, the t-component is not written to STDOUT as part of the
# coordinate data, but rather as part of the string that follows the xyz
# components. This is only seen by users when the -E option is used. Which
-# means that this test also experience that behaviour.
+# means that this test also experience that behavior.
$EXE -f %.4f EPSG:4896 EPSG:7930 -E >> ${OUT} <<EOF
3496737.2679 743254.4507 5264462.9620 2019.0
3496737.2679 743254.4507 5264462.9620 2029.0
diff --git a/test/fuzzers/README.TXT b/test/fuzzers/README.TXT
index a43e089f..3482e5cd 100644
--- a/test/fuzzers/README.TXT
+++ b/test/fuzzers/README.TXT
@@ -1,4 +1,4 @@
-This directory contain fuzzer main funtions and scripts for the
+This directory contain fuzzer main functions and scripts for the
Google OSS Fuzz project: https://github.com/google/oss-fuzz/
The main build scripts are in:
diff --git a/test/gie/more_builtins.gie b/test/gie/more_builtins.gie
index 3847c87e..610c6c3f 100644
--- a/test/gie/more_builtins.gie
+++ b/test/gie/more_builtins.gie
@@ -429,7 +429,7 @@ expect failure errno invalid_arg
operation proj=helmert rx=1 convention=1
expect failure errno invalid_arg
-# towgs84 in helmert context should alwas be position_vector
+# towgs84 in helmert context should always be position_vector
operation proj=helmert towgs84=1,2,3,4,5,6,7 convention=coordinate_frame
expect failure errno invalid_arg
diff --git a/test/unit/test_c_api.cpp b/test/unit/test_c_api.cpp
index e56fb39c..e0473d4f 100644
--- a/test/unit/test_c_api.cpp
+++ b/test/unit/test_c_api.cpp
@@ -1497,7 +1497,7 @@ TEST_F(CApi, proj_create_operations_with_pivot) {
// There is no direct transformations between both
- // Default behaviour: allow any pivot
+ // Default behavior: allow any pivot
{
auto ctxt = proj_create_operation_factory_context(m_ctxt, nullptr);
ASSERT_NE(ctxt, nullptr);
@@ -1621,7 +1621,7 @@ TEST_F(CApi, proj_create_operations_allow_ballpark_transformations) {
proj_operation_factory_context_set_grid_availability_use(
m_ctxt, ctxt, PROJ_GRID_AVAILABILITY_IGNORED);
- // Default: allowed implictly
+ // Default: allowed implicitly
{
auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt);
ASSERT_NE(res, nullptr);
@@ -4350,7 +4350,7 @@ TEST_F(
// In this particular case, PROJ computes a transformation from NAD83(2011)
// (EPSG:6318) to WGS84 (EPSG:4979) for the initial horizontal adjustment
// before the geoidgrids application. There are 6 candidate transformations
- // for that in subzones of the US and one last no-op tranformation flagged
+ // for that in subzones of the US and one last no-op transformation flagged
// as ballpark. That one used to be eliminated because by
// proj_create_crs_to_crs() because there were non Ballpark transformations
// available. This resulted thus in an error when transforming outside of
diff --git a/test/unit/test_crs.cpp b/test/unit/test_crs.cpp
index 270e7f95..cf285b0a 100644
--- a/test/unit/test_crs.cpp
+++ b/test/unit/test_crs.cpp
@@ -2303,7 +2303,7 @@ TEST(crs, projectedCRS_identify_db) {
auto res = crs->identify(factoryEPSG);
ASSERT_EQ(res.size(), 1U);
EXPECT_EQ(res.front().first->getEPSGCode(), 3044);
- EXPECT_EQ(res.front().second, 25);
+ EXPECT_EQ(res.front().second, 50);
}
{
// Identify from a WKT1 string, without explicit axis
@@ -2397,7 +2397,7 @@ TEST(crs, projectedCRS_identify_db) {
auto res = crs->identify(factoryEPSG);
EXPECT_EQ(res.size(), 1U);
EXPECT_EQ(res.front().first->getEPSGCode(), 2327);
- EXPECT_EQ(res.front().second, 50);
+ EXPECT_EQ(res.front().second, 70);
}
{
// EPSG:6646 as PROJ.4 string, using clrk80 which is pretty generic
@@ -2439,7 +2439,7 @@ TEST(crs, projectedCRS_identify_db) {
for (const auto &pair : res) {
if (pair.first->identifiers()[0]->code() == "102039") {
found = true;
- EXPECT_EQ(pair.second, 25);
+ EXPECT_EQ(pair.second, 50);
break;
}
}
@@ -2574,6 +2574,48 @@ TEST(crs, projectedCRS_identify_db) {
EXPECT_EQ(res.front().first->getEPSGCode(), 2154);
EXPECT_EQ(res.front().second, 70);
}
+ {
+ // Test identification of LKS92_Latvia_TM (#2214)
+ auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(
+ "PROJCS[\"LKS92_Latvia_TM\",GEOGCS[\"GCS_LKS92\","
+ "DATUM[\"D_Latvia_1992\","
+ "SPHEROID[\"GRS_1980\",6378137,298.257222101]],"
+ "PRIMEM[\"Greenwich\",0],UNIT[\"Degree\",0.017453292519943295]],"
+ "PROJECTION[\"Transverse_Mercator\"],"
+ "PARAMETER[\"latitude_of_origin\",0],"
+ "PARAMETER[\"central_meridian\",24],"
+ "PARAMETER[\"scale_factor\",0.9996],"
+ "PARAMETER[\"false_easting\",500000],"
+ "PARAMETER[\"false_northing\",-6000000],"
+ "UNIT[\"Meter\",1]]");
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto res = crs->identify(factoryEPSG);
+ ASSERT_EQ(res.size(), 1U);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 3059);
+ EXPECT_EQ(res.front().second, 90);
+ }
+ {
+ // Test identification of CRS where everything but datum names matches
+ auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(
+ "PROJCS[\"WGS_1984_UTM_Zone_31N\",GEOGCS[\"GCS_WGS_1984\","
+ "DATUM[\"wrong_datum_name\","
+ "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 crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto res = crs->identify(factoryEPSG);
+ ASSERT_EQ(res.size(), 1U);
+ EXPECT_EQ(res.front().first->getEPSGCode(), 32631);
+ EXPECT_EQ(res.front().second, 60);
+ }
}
// ---------------------------------------------------------------------------
@@ -4121,7 +4163,7 @@ TEST(crs, boundCRS_identify_db) {
EXPECT_EQ(boundCRS->baseCRS()->getEPSGCode(), 2106);
EXPECT_EQ(boundCRS->transformation()->nameStr(),
"NZGD2000 to WGS 84 (1)");
- EXPECT_EQ(res.front().second, 50);
+ EXPECT_EQ(res.front().second, 70);
}
{
@@ -5752,6 +5794,21 @@ TEST(crs, promoteTo3D_and_demoteTo2D) {
nn_dynamic_pointer_cast<ProjectedCRS>(crs3DAsBound->baseCRS());
ASSERT_TRUE(baseCRS != nullptr);
EXPECT_EQ(baseCRS->coordinateSystem()->axisList().size(), 3U);
+
+ auto hubCRS =
+ nn_dynamic_pointer_cast<GeographicCRS>(crs3DAsBound->hubCRS());
+ ASSERT_TRUE(hubCRS != nullptr);
+ EXPECT_EQ(hubCRS->coordinateSystem()->axisList().size(), 3U);
+
+ auto transfSrcCRS = nn_dynamic_pointer_cast<GeographicCRS>(
+ crs3DAsBound->transformation()->sourceCRS());
+ ASSERT_TRUE(transfSrcCRS != nullptr);
+ EXPECT_EQ(transfSrcCRS->coordinateSystem()->axisList().size(), 3U);
+
+ auto transfDstCRS = nn_dynamic_pointer_cast<GeographicCRS>(
+ crs3DAsBound->transformation()->targetCRS());
+ ASSERT_TRUE(transfDstCRS != nullptr);
+ EXPECT_EQ(transfDstCRS->coordinateSystem()->axisList().size(), 3U);
}
auto demoted = crs3DAsBound->demoteTo2D(std::string(), nullptr);
@@ -5762,6 +5819,21 @@ TEST(crs, promoteTo3D_and_demoteTo2D) {
nn_dynamic_pointer_cast<ProjectedCRS>(crs2DAsBound->baseCRS());
ASSERT_TRUE(baseCRS != nullptr);
EXPECT_EQ(baseCRS->coordinateSystem()->axisList().size(), 2U);
+
+ auto hubCRS =
+ nn_dynamic_pointer_cast<GeographicCRS>(crs2DAsBound->hubCRS());
+ ASSERT_TRUE(hubCRS != nullptr);
+ EXPECT_EQ(hubCRS->coordinateSystem()->axisList().size(), 2U);
+
+ auto transfSrcCRS = nn_dynamic_pointer_cast<GeographicCRS>(
+ crs2DAsBound->transformation()->sourceCRS());
+ ASSERT_TRUE(transfSrcCRS != nullptr);
+ EXPECT_EQ(transfSrcCRS->coordinateSystem()->axisList().size(), 2U);
+
+ auto transfDstCRS = nn_dynamic_pointer_cast<GeographicCRS>(
+ crs2DAsBound->transformation()->targetCRS());
+ ASSERT_TRUE(transfDstCRS != nullptr);
+ EXPECT_EQ(transfDstCRS->coordinateSystem()->axisList().size(), 2U);
}
}
diff --git a/test/unit/test_factory.cpp b/test/unit/test_factory.cpp
index aee2f572..e6cfa72b 100644
--- a/test/unit/test_factory.cpp
+++ b/test/unit/test_factory.cpp
@@ -2763,6 +2763,13 @@ TEST(factory, createObjectsFromName) {
.size(),
3U);
+ EXPECT_EQ(
+ factory
+ ->createObjectsFromName(
+ "WGS 84", {AuthorityFactory::ObjectType::GEOCENTRIC_CRS}, false)
+ .size(),
+ 1U);
+
{
auto res = factoryEPSG->createObjectsFromName(
"WGS84", {AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS}, true);
diff --git a/test/unit/test_io.cpp b/test/unit/test_io.cpp
index 0570bb7e..d62e5c59 100644
--- a/test/unit/test_io.cpp
+++ b/test/unit/test_io.cpp
@@ -2484,6 +2484,136 @@ TEST(wkt_parse, COMPD_CS_non_conformant_horizontal_plus_horizontal_as_in_LAS) {
// ---------------------------------------------------------------------------
+TEST(wkt_parse,
+ COMPD_CS_non_conformant_horizontal_TOWGS84_plus_horizontal_as_in_LAS) {
+
+ const auto wkt = "COMPD_CS[\"WGS 84 + WGS 84\",\n"
+ " GEOGCS[\"WGS 84\",\n"
+ " DATUM[\"WGS_1984\",\n"
+ " SPHEROID[\"WGS 84\",6378137,298.257223563,\n"
+ " AUTHORITY[\"EPSG\",\"7030\"]],\n"
+ " TOWGS84[0,0,0,0,0,0,0],\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"
+ " 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\"]]]";
+ auto dbContext = DatabaseContext::create();
+ auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto baseCRS = nn_dynamic_pointer_cast<GeographicCRS>(crs->baseCRS());
+ ASSERT_TRUE(baseCRS != nullptr);
+ EXPECT_EQ(baseCRS->nameStr(), "WGS 84");
+ EXPECT_EQ(baseCRS->coordinateSystem()->axisList().size(), 3U);
+
+ EXPECT_EQ(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL, dbContext)
+ .get()),
+ wkt);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse,
+ COMPD_CS_horizontal_bound_geog_plus_vertical_ellipsoidal_height) {
+ // See https://github.com/OSGeo/PROJ/issues/2228
+ const char *wkt =
+ "COMPD_CS[\"NAD83 + Ellipsoid (Meters)\",\n"
+ " GEOGCS[\"NAD83\",\n"
+ " DATUM[\"North_American_Datum_1983\",\n"
+ " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n"
+ " AUTHORITY[\"EPSG\",\"7019\"]],\n"
+ " TOWGS84[0,0,0,0,0,0,0],\n"
+ " AUTHORITY[\"EPSG\",\"6269\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]],\n"
+ " AUTHORITY[\"EPSG\",\"4269\"]],\n"
+ " VERT_CS[\"Ellipsoid (Meters)\",\n"
+ " VERT_DATUM[\"Ellipsoid\",2002],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]],\n"
+ " AXIS[\"Up\",UP]]]";
+ auto dbContext = DatabaseContext::create();
+ auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto baseCRS = nn_dynamic_pointer_cast<GeographicCRS>(crs->baseCRS());
+ ASSERT_TRUE(baseCRS != nullptr);
+ EXPECT_EQ(baseCRS->nameStr(), "NAD83");
+ EXPECT_EQ(baseCRS->coordinateSystem()->axisList().size(), 3U);
+
+ EXPECT_EQ(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL, dbContext)
+ .get()),
+ wkt);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse,
+ COMPD_CS_horizontal_projected_plus_vertical_ellipsoidal_height) {
+ // Variant of above
+ const char *wkt =
+ "COMPD_CS[\"WGS 84 / UTM zone 31N + Ellipsoid (Meters)\",\n"
+ " PROJCS[\"WGS 84 / UTM zone 31N\",\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[\"Transverse_Mercator\"],\n"
+ " PARAMETER[\"latitude_of_origin\",0],\n"
+ " PARAMETER[\"central_meridian\",3],\n"
+ " PARAMETER[\"scale_factor\",0.9996],\n"
+ " PARAMETER[\"false_easting\",500000],\n"
+ " PARAMETER[\"false_northing\",0],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]],\n"
+ " AXIS[\"Easting\",EAST],\n"
+ " AXIS[\"Northing\",NORTH],\n"
+ " AUTHORITY[\"EPSG\",\"32631\"]],\n"
+ " VERT_CS[\"Ellipsoid (Meters)\",\n"
+ " VERT_DATUM[\"Ellipsoid\",2002],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]],\n"
+ " AXIS[\"Up\",UP]]]";
+ auto dbContext = DatabaseContext::create();
+ auto obj = WKTParser().attachDatabaseContext(dbContext).createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(crs->nameStr(), "WGS 84 / UTM zone 31N");
+ EXPECT_EQ(crs->coordinateSystem()->axisList().size(), 3U);
+
+ EXPECT_EQ(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL, dbContext)
+ .get()),
+ wkt);
+}
+
+// ---------------------------------------------------------------------------
+
TEST(wkt_parse, COORDINATEOPERATION) {
std::string src_wkt;
@@ -3442,6 +3572,32 @@ TEST(wkt_parse, WKT1_VERT_DATUM_EXTENSION) {
// ---------------------------------------------------------------------------
+TEST(wkt_parse, WKT1_VERT_DATUM_EXTENSION_units_ftUS) {
+ auto wkt = "VERT_CS[\"NAVD88 height (ftUS)\","
+ " VERT_DATUM[\"North American Vertical Datum 1988\",2005,"
+ " EXTENSION[\"PROJ4_GRIDS\",\"foo.gtx\"],"
+ " AUTHORITY[\"EPSG\",\"5103\"]],"
+ " UNIT[\"US survey foot\",0.304800609601219,"
+ " AUTHORITY[\"EPSG\",\"9003\"]],"
+ " AXIS[\"Gravity-related height\",UP],"
+ " AUTHORITY[\"EPSG\",\"6360\"]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<BoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+
+ EXPECT_EQ(crs->transformation()->nameStr(),
+ "NAVD88 height to WGS84 ellipsoidal height"); // no (ftUS)
+ auto sourceTransformationCRS = crs->transformation()->sourceCRS();
+ auto sourceTransformationVertCRS =
+ nn_dynamic_pointer_cast<VerticalCRS>(sourceTransformationCRS);
+ EXPECT_EQ(
+ sourceTransformationVertCRS->coordinateSystem()->axisList()[0]->unit(),
+ UnitOfMeasure::METRE);
+}
+
+// ---------------------------------------------------------------------------
+
TEST(wkt_parse, WKT1_DATUM_EXTENSION) {
auto wkt =
"PROJCS[\"unnamed\",\n"
@@ -9854,8 +10010,18 @@ TEST(io, createFromUserInput) {
// Search names in the database
EXPECT_THROW(createFromUserInput("foobar", dbContext), ParsingException);
- EXPECT_NO_THROW(createFromUserInput("WGS 84", dbContext));
- EXPECT_NO_THROW(createFromUserInput("WGS84", dbContext));
+ {
+ // Official name
+ auto obj = createFromUserInput("WGS 84", dbContext);
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ EXPECT_TRUE(crs != nullptr);
+ }
+ {
+ // PROJ alias
+ auto obj = createFromUserInput("WGS84", dbContext);
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ EXPECT_TRUE(crs != nullptr);
+ }
EXPECT_NO_THROW(createFromUserInput("UTM zone 31N", dbContext));
EXPECT_THROW(createFromUserInput("UTM zone 31", dbContext),
ParsingException);
diff --git a/test/unit/test_operation.cpp b/test/unit/test_operation.cpp
index a655aff0..92b5f923 100644
--- a/test/unit/test_operation.cpp
+++ b/test/unit/test_operation.cpp
@@ -2904,7 +2904,7 @@ 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
+ // Addresses https://trac.osgeo.org/gdal/ticket/3026
auto wkt = "PROJCS[\"test\",\n"
" GEOGCS[\"WGS 84\",\n"
" DATUM[\"WGS 1984\",\n"
@@ -4648,7 +4648,7 @@ TEST(operation, geogCRS_to_geogCRS_context_NAD27_to_WGS84_G1762) {
// ---------------------------------------------------------------------------
TEST(operation, geogCRS_to_geogCRS_context_WGS84_G1674_to_WGS84_G1762) {
- // Check that particular behaviour with WGS 84 (Gxxx) related to
+ // Check that particular behavior with WGS 84 (Gxxx) related to
// 'geodetic_datum_preferred_hub' table and custom no-op transformations
// between WGS 84 and WGS 84 (Gxxx) doesn't affect direct transformations
// to those realizations.
@@ -7003,13 +7003,17 @@ TEST(operation, compoundCRS_with_boundGeogCRS_to_geogCRS) {
compound, GeographicCRS::EPSG_4979);
ASSERT_TRUE(op != nullptr);
EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
- "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 "
- "+step +proj=cart +ellps=WGS84 +step +proj=helmert +x=1 +y=2 "
- "+z=3 +rx=4 +ry=5 +rz=6 +s=7 +convention=position_vector +step "
- "+inv +proj=cart +ellps=WGS84 +step +proj=pop +v_3 +step "
- "+proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap "
- "+order=2,1");
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=push +v_3 "
+ "+step +proj=cart +ellps=WGS84 "
+ "+step +proj=helmert +x=1 +y=2 +z=3 +rx=4 +ry=5 +rz=6 +s=7 "
+ "+convention=position_vector "
+ "+step +inv +proj=cart +ellps=WGS84 "
+ "+step +proj=pop +v_3 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
}
// ---------------------------------------------------------------------------
@@ -7136,6 +7140,61 @@ TEST(operation,
// ---------------------------------------------------------------------------
+TEST(operation,
+ compoundCRS_with_boundProjCRS_with_ftus_and_boundVerticalCRS_to_geogCRS) {
+
+ auto wkt =
+ "COMPD_CS[\"NAD_1983_StatePlane_Alabama_West_FIPS_0102_Feet + "
+ "NAVD88 height - Geoid12B (US Feet)\",\n"
+ " PROJCS[\"NAD_1983_StatePlane_Alabama_West_FIPS_0102_Feet\",\n"
+ " GEOGCS[\"NAD83\",\n"
+ " DATUM[\"North_American_Datum_1983\",\n"
+ " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n"
+ " AUTHORITY[\"EPSG\",\"7019\"]],\n"
+ " TOWGS84[0,0,0,0,0,0,0],\n"
+ " AUTHORITY[\"EPSG\",\"6269\"]],\n"
+ " PRIMEM[\"Greenwich\",0],\n"
+ " UNIT[\"Degree\",0.0174532925199433]],\n"
+ " PROJECTION[\"Transverse_Mercator\"],\n"
+ " PARAMETER[\"latitude_of_origin\",30],\n"
+ " PARAMETER[\"central_meridian\",-87.5],\n"
+ " PARAMETER[\"scale_factor\",0.999933333333333],\n"
+ " PARAMETER[\"false_easting\",1968500],\n"
+ " PARAMETER[\"false_northing\",0],\n"
+ " UNIT[\"US survey foot\",0.304800609601219,\n"
+ " AUTHORITY[\"EPSG\",\"9003\"]],\n"
+ " AXIS[\"Easting\",EAST],\n"
+ " AXIS[\"Northing\",NORTH],\n"
+ " AUTHORITY[\"ESRI\",\"102630\"]],\n"
+ " VERT_CS[\"NAVD88 height (ftUS)\",\n"
+ " VERT_DATUM[\"North American Vertical Datum 1988\",2005,\n"
+ " EXTENSION[\"PROJ4_GRIDS\",\"foo.gtx\"],\n"
+ " AUTHORITY[\"EPSG\",\"5103\"]],\n"
+ " UNIT[\"US survey foot\",0.304800609601219,\n"
+ " AUTHORITY[\"EPSG\",\"9003\"]],\n"
+ " AXIS[\"Gravity-related height\",UP],\n"
+ " AUTHORITY[\"EPSG\",\"6360\"]]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<CompoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_NO_CHECK(crs), GeographicCRS::EPSG_4979);
+ ASSERT_TRUE(op != nullptr);
+
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=unitconvert +xy_in=us-ft +xy_out=m "
+ "+step +inv +proj=tmerc +lat_0=30 +lon_0=-87.5 "
+ "+k=0.999933333333333 +x_0=600000 +y_0=0 +ellps=GRS80 "
+ "+step +proj=unitconvert +z_in=us-ft +z_out=m "
+ "+step +proj=vgridshift +grids=foo.gtx +multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
TEST(operation, geocent_to_compoundCRS) {
auto objSrc = PROJStringParser().createFromPROJString(
"+proj=geocent +datum=WGS84 +units=m +type=crs");
@@ -7296,6 +7355,217 @@ TEST(operation, compoundCRS_to_compoundCRS_with_bound_crs_in_horiz_and_vert) {
// ---------------------------------------------------------------------------
+TEST(
+ operation,
+ compoundCRS_to_compoundCRS_with_bound_crs_in_horiz_and_vert_same_geoidgrids) {
+ auto objSrc = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +geoidgrids=@foo.gtx "
+ "+type=crs");
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+ auto objDst = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +nadgrids=@bar.gsb +geoidgrids=@foo.gtx "
+ "+type=crs");
+ auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
+ ASSERT_TRUE(dst != nullptr);
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=hgridshift +grids=@foo.gsb "
+ "+step +inv +proj=hgridshift +grids=@bar.gsb "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(
+ operation,
+ compoundCRS_to_compoundCRS_with_bound_crs_in_horiz_and_vert_same_geoidgrids_different_vunits) {
+ auto objSrc = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +geoidgrids=@foo.gtx "
+ "+type=crs");
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+ auto objDst = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +nadgrids=@bar.gsb +geoidgrids=@foo.gtx "
+ "+vunits=us-ft +type=crs");
+ auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
+ ASSERT_TRUE(dst != nullptr);
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=hgridshift +grids=@foo.gsb "
+ "+step +proj=unitconvert +z_in=m +z_out=us-ft "
+ "+step +inv +proj=hgridshift +grids=@bar.gsb "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(
+ operation,
+ compoundCRS_to_compoundCRS_with_bound_crs_in_horiz_and_vert_same_nadgrids_same_geoidgrids) {
+ auto objSrc = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS67 +nadgrids=@foo.gsb +geoidgrids=@foo.gtx "
+ "+type=crs");
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+ auto objDst = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +nadgrids=@foo.gsb +geoidgrids=@foo.gtx "
+ "+type=crs");
+ auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
+ ASSERT_TRUE(dst != nullptr);
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=noop");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(
+ operation,
+ compoundCRS_to_compoundCRS_with_bound_crs_in_horiz_and_vert_same_towgs84_same_geoidgrids) {
+ auto objSrc = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS67 +towgs84=0,0,0 +geoidgrids=@foo.gtx "
+ "+type=crs");
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+ auto objDst = PROJStringParser().createFromPROJString(
+ "+proj=longlat +ellps=GRS80 +towgs84=0,0,0 +geoidgrids=@foo.gtx "
+ "+type=crs");
+ auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
+ ASSERT_TRUE(dst != nullptr);
+ auto op = CoordinateOperationFactory::create()->createOperation(
+ NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst));
+ ASSERT_TRUE(op != nullptr);
+ EXPECT_EQ(op->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=push +v_3 "
+ "+step +proj=cart +ellps=GRS67 "
+ "+step +inv +proj=cart +ellps=GRS80 "
+ "+step +proj=pop +v_3 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(
+ operation,
+ compoundCRS_to_compoundCRS_with_bound_crs_in_horiz_and_vert_WKT1_same_geoidgrids_context) {
+ auto objSrc = WKTParser().createFromWKT(
+ "COMPD_CS[\"NAD83 / Alabama West + NAVD88 height - Geoid12B "
+ "(Meters)\",\n"
+ " PROJCS[\"NAD83 / Alabama West\",\n"
+ " GEOGCS[\"NAD83\",\n"
+ " DATUM[\"North_American_Datum_1983\",\n"
+ " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n"
+ " AUTHORITY[\"EPSG\",\"7019\"]],\n"
+ " TOWGS84[0,0,0,0,0,0,0],\n"
+ " AUTHORITY[\"EPSG\",\"6269\"]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " AUTHORITY[\"EPSG\",\"8901\"]],\n"
+ " UNIT[\"degree\",0.0174532925199433,\n"
+ " AUTHORITY[\"EPSG\",\"9122\"]],\n"
+ " AUTHORITY[\"EPSG\",\"4269\"]],\n"
+ " PROJECTION[\"Transverse_Mercator\"],\n"
+ " PARAMETER[\"latitude_of_origin\",30],\n"
+ " PARAMETER[\"central_meridian\",-87.5],\n"
+ " PARAMETER[\"scale_factor\",0.999933333],\n"
+ " PARAMETER[\"false_easting\",600000],\n"
+ " PARAMETER[\"false_northing\",0],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]],\n"
+ " AXIS[\"X\",EAST],\n"
+ " AXIS[\"Y\",NORTH],\n"
+ " AUTHORITY[\"EPSG\",\"26930\"]],\n"
+ " VERT_CS[\"NAVD88 height\",\n"
+ " VERT_DATUM[\"North American Vertical Datum 1988\",2005,\n"
+ " "
+ "EXTENSION[\"PROJ4_GRIDS\",\"g2012a_alaska.gtx,g2012a_hawaii.gtx,"
+ "g2012a_conus.gtx\"],\n"
+ " AUTHORITY[\"EPSG\",\"5103\"]],\n"
+ " UNIT[\"metre\",1,\n"
+ " AUTHORITY[\"EPSG\",\"9001\"]],\n"
+ " AXIS[\"Gravity-related height\",UP],\n"
+ " AUTHORITY[\"EPSG\",\"5703\"]]]");
+ auto src = nn_dynamic_pointer_cast<CRS>(objSrc);
+ ASSERT_TRUE(src != nullptr);
+ auto objDst = WKTParser().createFromWKT(
+ "COMPD_CS[\"NAD_1983_StatePlane_Alabama_West_FIPS_0102_Feet + NAVD88 "
+ "height - Geoid12B (US Feet)\",\n"
+ " PROJCS[\"NAD_1983_StatePlane_Alabama_West_FIPS_0102_Feet\",\n"
+ " GEOGCS[\"NAD83\",\n"
+ " DATUM[\"North_American_Datum_1983\",\n"
+ " SPHEROID[\"GRS 1980\",6378137,298.257222101,\n"
+ " AUTHORITY[\"EPSG\",\"7019\"]],\n"
+ " TOWGS84[0,0,0,0,0,0,0],\n"
+ " AUTHORITY[\"EPSG\",\"6269\"]],\n"
+ " PRIMEM[\"Greenwich\",0],\n"
+ " UNIT[\"Degree\",0.0174532925199433]],\n"
+ " PROJECTION[\"Transverse_Mercator\"],\n"
+ " PARAMETER[\"latitude_of_origin\",30],\n"
+ " PARAMETER[\"central_meridian\",-87.5],\n"
+ " PARAMETER[\"scale_factor\",0.999933333333333],\n"
+ " PARAMETER[\"false_easting\",1968500],\n"
+ " PARAMETER[\"false_northing\",0],\n"
+ " UNIT[\"US survey foot\",0.304800609601219,\n"
+ " AUTHORITY[\"EPSG\",\"9003\"]],\n"
+ " AXIS[\"Easting\",EAST],\n"
+ " AXIS[\"Northing\",NORTH],\n"
+ " AUTHORITY[\"ESRI\",\"102630\"]],\n"
+ " VERT_CS[\"NAVD88 height (ftUS)\",\n"
+ " VERT_DATUM[\"North American Vertical Datum 1988\",2005,\n"
+ " "
+ "EXTENSION[\"PROJ4_GRIDS\",\"g2012a_alaska.gtx,g2012a_hawaii.gtx,"
+ "g2012a_conus.gtx\"],\n"
+ " AUTHORITY[\"EPSG\",\"5103\"]],\n"
+ " UNIT[\"US survey foot\",0.304800609601219,\n"
+ " AUTHORITY[\"EPSG\",\"9003\"]],\n"
+ " AXIS[\"Gravity-related height\",UP],\n"
+ " AUTHORITY[\"EPSG\",\"6360\"]]]");
+ auto dst = nn_dynamic_pointer_cast<CRS>(objDst);
+ ASSERT_TRUE(dst != nullptr);
+
+ auto dbContext = DatabaseContext::create();
+ auto authFactory = AuthorityFactory::create(dbContext, "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ NN_CHECK_ASSERT(src), NN_CHECK_ASSERT(dst), ctxt);
+ ASSERT_GE(list.size(), 1U);
+ EXPECT_EQ(list[0]->nameStr(),
+ "Inverse of unnamed + "
+ "Transformation from NAD83 to WGS84 + "
+ "NAVD88 height to NAVD88 height (ftUS) + "
+ "Inverse of Transformation from NAD83 to WGS84 + "
+ "unnamed");
+ auto grids = list[0]->gridsNeeded(dbContext, false);
+ EXPECT_TRUE(grids.empty());
+ EXPECT_EQ(list[0]->exportToPROJString(PROJStringFormatter::create().get()),
+ "+proj=pipeline "
+ "+step +inv +proj=tmerc +lat_0=30 +lon_0=-87.5 +k=0.999933333 "
+ "+x_0=600000 +y_0=0 +ellps=GRS80 "
+ "+step +proj=unitconvert +z_in=m +z_out=us-ft "
+ "+step +proj=tmerc +lat_0=30 +lon_0=-87.5 +k=0.999933333333333 "
+ "+x_0=600000 +y_0=0 +ellps=GRS80 "
+ "+step +proj=unitconvert +xy_in=m +xy_out=us-ft");
+}
+
+// ---------------------------------------------------------------------------
+
TEST(operation, compoundCRS_to_compoundCRS_context) {
auto authFactory =
AuthorityFactory::create(DatabaseContext::create(), "EPSG");
@@ -7800,6 +8070,49 @@ TEST(operation, compoundCRS_to_geogCRS_3D_context) {
// ---------------------------------------------------------------------------
+TEST(operation, compoundCRS_to_geogCRS_3D_with_3D_helmert_context) {
+ // Use case of https://github.com/OSGeo/PROJ/issues/2225
+ auto dbContext = DatabaseContext::create();
+ auto authFactory = AuthorityFactory::create(dbContext, "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ // WGS84 + EGM96 height
+ auto srcObj = createFromUserInput("EPSG:4326+5773", dbContext, false);
+ auto src = nn_dynamic_pointer_cast<CRS>(srcObj);
+ ASSERT_TRUE(src != nullptr);
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(src),
+ // CH1903+
+ authFactory->createCoordinateReferenceSystem("4150")->promoteTo3D(
+ std::string(), dbContext),
+ ctxt);
+ ASSERT_GE(list.size(), 1U);
+ EXPECT_EQ(list[0]->nameStr(), "Inverse of WGS 84 to EGM96 height (1) + "
+ "Inverse of CH1903+ to WGS 84 (1)");
+ // Check that there is no push v_3 / pop v_3
+ const char *expected_proj =
+ "+proj=pipeline "
+ "+step +proj=axisswap +order=2,1 "
+ "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
+ "+step +proj=vgridshift +grids=us_nga_egm96_15.tif +multiplier=1 "
+ "+step +proj=cart +ellps=WGS84 "
+ "+step +proj=helmert +x=-674.374 +y=-15.056 +z=-405.346 "
+ "+step +inv +proj=cart +ellps=bessel "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1";
+ EXPECT_EQ(list[0]->exportToPROJString(
+ PROJStringFormatter::create(
+ PROJStringFormatter::Convention::PROJ_5, dbContext)
+ .get()),
+ expected_proj);
+}
+
+// ---------------------------------------------------------------------------
+
TEST(operation, compoundCRS_to_geogCRS_2D_promote_to_3D_context) {
auto authFactory =
AuthorityFactory::create(DatabaseContext::create(), "EPSG");
@@ -8425,13 +8738,18 @@ TEST(operation, compoundCRS_from_WKT2_no_id_to_geogCRS_3D_context) {
" VDATUM[\"Normaal Amsterdams Peil\"],\n"
" CS[vertical,1],\n"
" AXIS[\"gravity-related height (H)\",up,\n"
- " LENGTHUNIT[\"metre\",1]]]]";
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " USAGE[\n"
+ " SCOPE[\"unknown\"],\n"
+ " AREA[\"Netherlands - onshore\"],\n"
+ " BBOX[50.75,3.2,53.7,7.22]]]";
+
auto obj = WKTParser().createFromWKT(wkt2);
auto src_from_wkt2 = nn_dynamic_pointer_cast<CRS>(obj);
ASSERT_TRUE(src_from_wkt2 != nullptr);
auto list2 = CoordinateOperationFactory::create()->createOperations(
NN_NO_CHECK(src_from_wkt2), dst, ctxt);
- ASSERT_GE(list.size(), list2.size() - 1);
+ ASSERT_EQ(list.size(), list2.size());
for (size_t i = 0; i < list.size(); i++) {
const auto &op = list[i];
const auto &op2 = list2[i];
diff --git a/travis/linux_s390x/after_success.sh b/travis/linux_s390x/after_success.sh
new file mode 100755
index 00000000..9618f673
--- /dev/null
+++ b/travis/linux_s390x/after_success.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+set -e
+
+# nothing
diff --git a/travis/linux_s390x/before_install.sh b/travis/linux_s390x/before_install.sh
new file mode 100755
index 00000000..114d8a7f
--- /dev/null
+++ b/travis/linux_s390x/before_install.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+set -e
+
+./travis/before_install_apt.sh
+./travis/before_install_pip.sh
+
+sudo apt-get install -qq sqlite3 libsqlite3-dev libtiff-dev libcurl4-openssl-dev zip
diff --git a/travis/linux_s390x/install.sh b/travis/linux_s390x/install.sh
new file mode 100755
index 00000000..f2db7e4b
--- /dev/null
+++ b/travis/linux_s390x/install.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+set -e
+
+export CCACHE_CPP2=yes
+export PROJ_DB_CACHE_DIR="$HOME/.ccache"
+
+# -fno-use-cxa-atexit is needed to build with -coverage
+CC="ccache $CC" CXX="ccache $CXX" CFLAGS="-Werror" CXXFLAGS="-Werror" ./travis/install.sh