1.. _rfc-73:
2
3=======================================================================================================
4RFC 73: Integration of PROJ6 for WKT2, late binding capabilities, time-support and unified CRS database
5=======================================================================================================
6
7============== ============================
8Author:        Even Rouault
9Contact:       even.rouault @ spatialys.com
10Started:       2019-Jan-08
11Last modified: 2019-May-02
12Status:        Implemented in GDAL 3.0
13============== ============================
14
15Summary
16-------
17
18The document describe work related to integration of PROJ 6 with GDAL,
19which adds different capabilities: support for CRS WKT 2 version, "late
20binding" capabilities for coordinate transformations between CRS,
21support of time-dimension for coordinate operations and the use of a
22unified CRS database.
23
24Motivation
25----------
26
27The motivations are those exposed in
28`https://gdalbarn.com/#why <https://gdalbarn.com/#why>`__ , which are
29copied here
30
31Coordinate systems in GDAL, PROJ, and libgeotiff are missing modern
32capabilities and need a thorough refactoring:
33
34-  The dreaded ad hoc CSV databases in PROJ_LIB and GDAL_DATA are
35   frustrating for users, pose challenges for developers, and impede
36   interoperability of definitions.
37-  GDAL and PROJ are missing OGC WKT2 support.
38-  PROJ 5.0+ no longer requires datum transformation pivots through
39   WGS84, which can introduce errors of up to 2m, but the rest of the
40   tools do not take advantage of it.
41
42CSV database
43~~~~~~~~~~~~
44
45The use of a SQLite-based database for EPSG and other definitions will
46allow the projects to add more capability (area-aware validation),
47transition the custom peculiar data structures of the projects to
48something more universally consumable, and promote definition
49interoperability between many coordinate system handling software tools.
50
51WKT2
52~~~~
53
54`OGC WKT2 <http://docs.opengeospatial.org/is/12-063r5/12-063r5.html>`__
55fixes longstanding interoperability coordinate system definition
56discrepancies. WKT2 contains tools for describing time-dependent
57coordinate reference systems. PROJ 5+ is now capable of time-dependent
58transformations, but GDAL and other tools do not yet support them.
59
60Several countries are updating their geodetic infrastructure to include
61time-dependent coordinate systems. For example, Australia and the United
62States are adapting time-dependent coordinate systems in 2020 and 2022,
63respectively. The familiar NAD83 and NAVD88 in North America being
64replaced by NATRF2022 and NAPGD2022, and the industry WILL have to adapt
65to these challenges sooner or later.
66
67WGS84 Pivot
68~~~~~~~~~~~
69
70PROJ previously required datum transformation that pivoted through WGS84
71via a 7-parameter transform. This pivot is a practical solution, but it
72can introduce error of about two meters, and many legacy datums cannot
73be defined in terms of WGS84. PROJ 5+ now provides the tools to support
74late-binding through its `transformation pipeline
75framework <https://proj4.org/usage/transformation.html#geodetic-transformation>`__,
76but GDAL and the rest of the tools cannot use it yet. Higher accuracy
77transformations avoid stepping through WGS84 and eliminates extra
78transformation steps with side-car data from a local geodetic authority.
79
80Related work in other libraries
81-------------------------------
82
83This RFC is the last step in the "gdalbarn" work. Previous steps have
84consisted in implementing the related changes in PROJ master per `PROJ
85RFC 2 <https://proj4.org/community/rfc/rfc-2.html>`__ and in libgeotiff
86master per `libgeotiff pull request
872 <https://github.com/OSGeo/libgeotiff/pull/2>`__.
88
89Proposal
90--------
91
92Third-party library requirements
93~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
94
95GDAL master (future 3.0) will require PROJ master (future PROJ 6.0) and
96libgeotiff master (future libgeotiff 1.5 or 2.0) for build and
97execution.
98
99Regarding PROJ, no internal copy of PROJ will be embedded in GDAL
100master. It is not doable of supporting older versions of PROJ, as the
101OGRSpatialReference class has been largely rewritten to take advantage
102of functionality that has been completely moved from GDAL to PROJ: PROJ
103string import and export, WKT string import and export, EPSG database
104exploitation. To be able to use more easily GDAL master and PROJ master
105in complex setups where some GDAL dependencies use a libproj provided by
106the system, and where mixing naively PROJ master and this older libproj
107would result in runtime crashes, PROJ master can be built with
108CFLAGS/CXXFLAGS=-DPROJ_RENAME_SYMBOLS to alias its public symbols, and
109GDAL will be able to use this custom build. Note that this is not
110intended to be used in a long term, since proper packaging solutions
111will eventually use PROJ 6 to rebuild all its reverse dependencies. It
112should be noted also that PROJ is required at configure / nmake time,
113that is the dynamic loading at runtime through dlopen() / LoadLibrary()
114is no longer available.
115
116Regarding libgeotiff, the internal copy in frmts/gtiff/libgeotiff has
117been refreshed with the content of upstream libgeotiff master.
118
119All continuous integration systems (Travis-CI and AppVeyor) have been
120updated to build PROJ master as part of the GDAL build.
121
122OGRSpatialReference rewrite
123~~~~~~~~~~~~~~~~~~~~~~~~~~~
124
125The OGRSpatialReference class is central in GDAL/OGR for all coordinate
126reference systems (CRS) manipulations. Up to GDAL 2.4, this class
127contained mostly a OGR_SRSNode root node of a WKT 1 representation, and
128all getters and setters manipulated this tree representation. As part of
129this work, the main object contained internally by OGRSpatialReference
130is now a PROJ PJ object, and methods call PROJ C API getters and setters
131on this PJ object. This enables to be, mostly (*), representation
132independent.
133
134WKT1, WKT2, ESRI WKT, PROJ strings import and export is now delegated to
135PROJ. The same holds for import of CRS from the EPSG database, that now
136relies on proj.db SQLite database. Consequently all the data/\*.csv files
137that contained CRS related information have been removed from GDAL. It
138should be noted that "morphing" from ESRI WKT is now done automatically
139when importing WKT.
140
141While general semantics of methods like IsSame() or FindMatches() remain
142the same, underneath implementations are substantially different, which
143can lead to different results than previous GDAL versions in some cases.
144In the FindMatches() case, identification of CRS to EPSG entries is
145generally improved due to enhanced query capabilities in the database.
146
147(*) The "mostly" precision is here since it was not practical to do this
148rewrite in every place. So for some methods, an internal WKT1 export is
149still done. This is the case for methods that take a path to a SRS node
150(like "GEOGCS|UNIT") as an argument, or some methods like
151SetProjection(), GetProjParm(), that expect a OGC WKT1 specific name.
152Those are thought to be used mostly be drivers. Changing them to be EPSG
153names would impact a number of drivers, some of them little tested
154regarding SRS support, and which furthermore mostly support WKT1
155representation only.
156
157OGRCoordinateTransformation changes
158~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
159
160Since GDAL 2.3 and initial PROJ 5 support, when transforming between two
161CRS we still relied on the PROJ.4 string export of the source and target
162CRS to create a coordinate operation pipeline. So this limited to
163"early-binding" operations, that is using the WGS84 pivot through
164towgs84 or nadgrids PROJ keywords. Now PROJ new capabilities to find
165appropriate coordinate operations between two CRS is used, offering
166"late-binding" capabilities to take into account other pivots than WGS84
167or area of uses.
168
169OGRCreateCoordinateOperation() now takes an extra optional arguments to
170define options.
171
172One of those options is to define an area of interest that will be taken
173into account when searching candidate operations. If several operations
174match, the "best" (according to PROJ sorting criterion) will be
175selected. Note: it will systematically be used even if later calls to
176Transform() use coordinates outside of the initial area of interest.
177
178Another option is the ability to specify the coordinate operation to
179apply, so as an override of what GDAL / PROJ would have automatically
180computed, either as a PROJ string (generally a +proj=pipeline), or a WKT
181coordinate operation/concatenated operation. Users can typically select
182a specific coordinate operation by using the new PROJ projinfo utility
183that can return the candidate operations from a source_crs / target_crs
184tuple.
185
186When no option is specified, GDAL will use PROJ to list all candidate
187coordinate operations. For each call to Transform(), it will compute the
188average coordinate of the input coordinates and use it to determine the
189best coordinate operation from the candidate ones.
190
191The Transform() method now takes an extra argument to contain the
192coordinate epoch (generally as a decimal year value) for coordinate
193operations that are time-dependent. Related, the transform options of
194the GDALTransform mechanism typically used by gdalwarp now accepts a
195COORDINATE_EPOCH for the same purpose.
196
197Use of OGRSpatialReference in GDAL
198~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
199
200Currently GDAL datasets accept and return a WKT 1 string to describe the
201SRS. To be more independent of the actual encoding, and for example
202allowing a GeoPackage raster dataset to be able to use WKT 2, it is
203desirable to be able to attach a SRS that is not dependent of the
204representation (WKT 1 or WKT 2), hence using a OGRSpatialReference
205object instead of a const char\* string.
206
207The following new methods are added in GDALDataset:
208
209-  virtual const OGRSpatialReference\* GetSpatialRef() const;
210-  virtual CPLErr SetSpatialRef(const OGRSpatialReference*);
211-  virtual const OGRSpatialReference\* GetGCPSpatialRef() const;
212-  virtual CPLErr SetGCPs(int nGCPCount, const GDAL_GCP *pasGCPList,
213   const OGRSpatialReference*);
214
215To ease the transition, the following non virtual methods are added in
216GDALDataset:
217
218-  const OGRSpatialReference\* GetSpatialRefFromOldGetProjectionRef()
219   const;
220-  CPLErr OldSetProjectionFromSetSpatialRef(const OGRSpatialReference\*
221   poSRS);
222-  const OGRSpatialReference\* GetGCPSpatialRefFromOldGetGCPProjection()
223   const;
224-  CPLErr OldSetGCPsFromNew( int nGCPCount, const GDAL_GCP \*pasGCPList,
225   const OGRSpatialReference \* poGCP_SRS );
226
227and the previous GetProjectionRef(), SetProjection(), GetGCPProjection()
228and SetGCPs() are available as projected virtual methods, prefixed by an
229underscore
230
231This way to convert an existing driver, it is a matter of renaming its
232GetProjectionRef() method as \_GetProjectionRef(), and adding:
233
234::
235
236   const OGRSpatialReference* GetSpatialRef() const override {
237       return GetSpatialRefFromOldGetProjectionRef();
238   }
239
240Default WKT version
241~~~~~~~~~~~~~~~~~~~
242
243OGRSpatialReference::exportToWkt() without options will report WKT 1
244(with explicit AXIS nodes. See below "Axis order issues" paragraph) for
245CRS compatibles of this representation, and otherwise use WKT2:2018
246(typically for Geographic 3D CRS).
247
248An enhanced version of exportToWkt() accepts options to specify the
249exact WKT version used, if multi-line or single-line output must be
250used, etc.
251
252Alternatively the OSR_WKT_FORMAT configuration option can be used to
253modify the WKT version used by exportToWk() (when no explicit version is
254passed in the options of exportToWkt())
255
256The gdalinfo, ogrinfo and gdalsrsinfo utililies will default to
257outputting WKT2:2018
258
259Axis order issues
260~~~~~~~~~~~~~~~~~
261
262This is a recurring pain point. This RFC proposes a new approach
263(without pretending to solving it completely) to what was initially done
264per `RFC 20: OGRSpatialReference Axis Support <./rfc20_srs_axes>`__. The
265issue is that CRS official definitions use axis orders that do not
266conform to the way raster or vector data is traditionally encoded in GIS
267applications. The typical example is the Geographic "WGS 84" definition
268from EPSG, EPSG:4326, which uses latitude as the first axis and
269longitude as the second axis. RFC 20 decided that by default the AXIS
270definition would be stripped off from the WKT when the axis order from
271the authority did not match the GIS friendly one (and use a custom EPSGA
272authority to have WKT with official AXIS elements)
273
274This was technically possible since the WKT 1 grammar makes the AXIS
275element definition. However removal of the AXIS definitions was a
276potential source of confusion as it was unclear which axis order was
277actually used. Furthermore, in WKT2, the AXIS element is compulsory, and
278the internal PROJ representation requires also a coordinate system to be
279defined. So there would have been two unsatisfactory options:
280
281-  return patched versions of the official definition with the GIS
282   friendly order, while still using the official authority code.
283   Practical since we keep the link with the source code, but a lie
284   since we modify it. Users would not know whether they must trust the
285   encoded order, or the official order from the authority.
286-  return patched versions of the official definition with the GIS
287   friendly order, but without the official authority code. This would
288   be compliant, but we would lose the link with the authority code.
289
290The solution put forward in this RFC is to add a "data axis to SRS axis
291mapping" concept, which is a bit similar to what is done in WCS
292DescribeCoverage response to explain how the SRS axis map to the grid
293axis of a coverage
294
295Extract from
296`https://docs.geoserver.org/stable/en/user/extensions/wcs20eo/index.html <https://docs.geoserver.org/stable/en/user/extensions/wcs20eo/index.html>`__
297for a coverage that uses EPSG:4326
298
299::
300
301         <gml:coverageFunction>
302           <gml:GridFunction>
303             <gml:sequenceRule axisOrder="+2 +1">Linear</gml:sequenceRule>
304             <gml:startPoint>0 0</gml:startPoint>
305           </gml:GridFunction>
306         </gml:coverageFunction>
307
308A similar mapping is added to define how the 'x' and 'y' components in
309the geotransform matrix or in a OGRGeometry map to the axis defined by
310the CRS definition.
311
312Such mapping is given by a new method in OGRSpatialReference
313
314::
315
316   const std::vector<int>& GetDataAxisToSRSAxisMapping() const
317
318To explain its semantics, imagine that it return 2,-1,3. That is
319interpreted as:
320
321-  2: the first axis of the CRS maps to the second axis of the data
322-  -1: the second axis of the CRS maps to the first axis of the data,
323   with values negated
324-  3: the third axis of the CRS maps to the third axis of the data
325
326This is similar to the PROJ axisswap operation:
327`https://proj4.org/operations/conversions/axisswap.html <https://proj4.org/operations/conversions/axisswap.html>`__
328
329By default, on a newly create OGRSpatialReference object,
330GetDataAxisToSRSAxisMapping() returns the identity 1,2[,3], that is,
331conform to the axis order defined by the authority.
332
333As all GDAL and a vast majority of OGR drivers depend on using the "GIS
334axis mapping", a method SetAxisMappingStrategy(
335OAMS_TRADITIONAL_GIS_ORDER or OAMS_AUTHORITY_COMPLIANT or OAMS_CUSTOM )
336is added to make their job of specifying the axis mapping easier;
337
338OAMS_TRADITIONAL_GIS_ORDER means:
339
340-  for geographic 2D CRS,
341
342   -  for Latitude NORTH, Longitude EAST (such as EPSG:4326),
343      GetDataAxisToSRSAxisMapping() returns {2,1}, meaning that the data
344      order is longitude, latitude
345   -  for Longitude EAST, Latitude NORTH (such as OGC:CRS84), returns
346      {1,2}
347
348-  for projected CRS,
349
350   -  for EAST, NORTH (ie most projected CRS), return {1,2}
351   -  for NORTH, EAST, return {2,1}
352   -  for North Pole CRS, with East/SOUTH, North/SOUTH, such as
353      EPSG:5041 ("WGS 84 / UPS North (E,N)"), would return {1,2}
354   -  for North Pole CRS, with northing/SOUTH, easting/SOUTH, such as
355      EPSG:32661 ("WGS 84 / UPS North (N,E)"), would return {2,1}
356   -  similarly for South Pole CRS
357   -  for all other cases, return {1,2}
358
359OGRCreateCoordinateTransformation() now honors the data axis to srs axis
360mapping.
361
362Note: contrary to what I indicated in a previous email, gdaltransform
363behavior is unchanged, since internally the GDALTransform mechanism
364forces the GIS friendly order.
365
366Raster datasets are modified to call
367SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER) on the
368OGRSpatialReference\* they return, and assumes it in SetSpatialRef()
369(assumed and unchecked for now)
370
371Vector layers mostly all call
372SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER) on the
373OGRSpatialReference\* returned by GetSpatialRef(). In the case of the
374GML driver, if the user defines the INVERT_AXIS_ORDER_IF_LAT_LONG open
375option, axis swapping is not done (as previously) and the
376AUTHORITY_COMPLIANT strategy is used. ICreateLayer() when receiving a
377OGRSpatialReference\* may decide (and most will do it) to change the
378axis mapping strategy. That is: if it receives a OGRSpatialReference
379with AUTHORITY_COMPLIANT order, it may decide to switch to
380TRADITIONAL_GIS_ORDER and GetSpatialRef()::GetDataAxisToSRSAxisMapping()
381will reflect that. ogr2ogr is modified to do the geometry axis swapping
382in that case.
383
384Related to that change, WKT 1 export now always return the AXIS element,
385and EPSG:xxxx thus behaves identically to EPSGA:xxxx
386
387So a summary view of this approach is that in the formal SRS definition,
388we no longer do derogations regarding axis order, but we add an
389additional interface to describe how we actually make our match match
390with the SRS definition.
391
392Driver changes
393~~~~~~~~~~~~~~
394
395Raster drivers that returned / accepted a SRS as a WKT string through
396the GetProjectionRef(), SetProjection(), GetGCPProjection() and
397SetGCPs() methods have been upgraded to use the new virtual methods, in
398most cases by using the compatibility layer.
399
400The GDALPamDataset (PAM .aux.xml files) and the GDAL VRT drivers have
401been fully upgraded to support the new interfaces, and
402serialize/deserialize the data axis to SRS axis mapping values.
403
404The GeoPackage driver now fully supports the official "gpkg_crs_wkt"
405extension used to store WKT 2 string definitions in the
406gpkg_spatial_ref_sys table. The driver attempts at not using the
407extension when SRS can be encoded as WKT1 strings, and will
408automatically add the "definition_12_063" column to an existing
409gpkg_spatial_ref_sys table if a SRS requiring WKT2 (typically a
410Geographic 3D CRS) is inserted.
411
412Changes in utilities
413~~~~~~~~~~~~~~~~~~~~
414
415-  gdalinfo and ogrinfo reports the data axis to CRS axis mapping
416   whenever a CRS is reported. They will also output WKT2_2018 by
417   default, unless "-wkt_format wkt1" is specified.
418
419::
420
421   Driver: GTiff/GeoTIFF
422   Files: out.tif
423   Size is 20, 20
424   Coordinate System is:
425   GEOGCRS["WGS 84",
426       DATUM["World Geodetic System 1984",
427           ELLIPSOID["WGS 84",6378137,298.257223563,
428               LENGTHUNIT["metre",1]]],
429       PRIMEM["Greenwich",0,
430           ANGLEUNIT["degree",0.0174532925199433]],
431       CS[ellipsoidal,2],
432           AXIS["geodetic latitude (Lat)",north,
433               ORDER[1],
434               ANGLEUNIT["degree",0.0174532925199433]],
435           AXIS["geodetic longitude (Lon)",east,
436               ORDER[2],
437               ANGLEUNIT["degree",0.0174532925199433]],
438       USAGE[
439           SCOPE["unknown"],
440           AREA["World"],
441           BBOX[-90,-180,90,180]],
442       ID["EPSG",4326]]
443   Data axis to CRS axis mapping: 2,1 <-- here
444   Origin = (2.000000000000000,49.000000000000000)
445   Pixel Size = (0.100000000000000,-0.100000000000000)
446
447-  gdalwarp, ogr2ogr and gdaltransform have gained a -ct switch that can
448   be used by advanced users to specify a coordinate operation, either
449   as a PROJ string (generally a +proj=pipeline), or a WKT coordinate
450   operation/concatenated operation, as explained in the above
451   "OGRCoordinateTransformation changes" paragraph. Note: the pipeline
452   must take into account the axis order of the CRS, even if the
453   underlying raster/vector drivers use the "GIS friendly" order. For
454   example "+proj=pipeline +step +proj=axisswap +order=2,1 +step
455   +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=utm +zone=31
456   +ellps=WGS84" when transforming from EPSG:4326 to EPSG:32631.
457
458-  gdalsrsinfo is enhanced to be able to specify the 2 new supported WKT
459   variants: WKT2_2015 and WKT2_2018. It will default to outputting
460   WKT2_2018
461
462SWIG binding changes
463~~~~~~~~~~~~~~~~~~~~
464
465The enhanced ExportToWkt() and OGRCoordinateTransformation methods are
466available through SWIG bindings. May require additional typemaps for
467non-Python languages (particularly for the support of 4D X,Y,Z,time
468coordinates)
469
470Backward compatibility
471----------------------
472
473This work is intended to be *mostly* backward compatible, yet inevitable
474differences will be found. For example the WKT 1 and PROJ string export
475has been completely rewritten in PROJ, and so while being hopefully
476equivalent to what GDAL 2.4 or earlier generated, this is not strictly
477identical: number of significant digits, order of PROJ parameters,
478rounding, etc etc...
479
480MIGRATION_GUIDE.TXT has been updated to reflect some differences:
481
482-  OSRImportFromEPSG() takes into account official axis order.
483-  removal of OPTGetProjectionMethods(), OPTGetParameterList() and
484   OPTGetParameterInfo() No equivalent.
485-  removal of OSRFixup() and OSRFixupOrdering(): no longer needed since
486   objects constructed are always valid
487-  removal of OSRStripCTParms(). Use OSRExportToWktEx() instead with the
488   FORMAT=SQSQL option
489-  exportToWkt() outputs AXIS nodes
490-  OSRIsSame(): now takes into account data axis to CRS axis mapping,
491   unless IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES is set as an option
492   to OSRIsSameEx()
493-  ogr_srs_api.h: SRS_WKT_WGS84 macro is no longer declared by default
494   since WKT without AXIS is too ambiguous. Preferred remediation: use
495   SRS_WKT_WGS84_LAT_LONG. Or #define USE_DEPRECATED_SRS_WKT_WGS84
496   before including ogr_srs_api.h
497
498Out-of-tree raster drivers will be impacted by the introduction of the
499new virtual methods GetSpatialRef(), SetSpatialRef(), GetGCPSpatialRef()
500and SetGCPs(..., const OGRSpatialReference\* poSRS), and the removal of
501their older equivalents using WKT strings instead of a
502OGRSpatialReference\* instance.
503
504Documentation
505-------------
506
507New methods have been documented, and documentation of existing methods
508has been changed when appropriate during the development. That said, a
509more thorough pass will be needed. The tutorials will also have to be
510updated.
511
512Testing
513-------
514
515The autotest suite has been adapted in a number of places since the
516expected results have changed for a number of reasons (AXIS node
517exported in WKT, differences in WKT and PROJ string generation). New
518tests have been added for the new capabilities.
519
520It should be noted that autotest not necessarily checks everything, and
521issues have been discovered and fixed through manual testing. The
522introduction of the "data axis to CRS axis mapping" concept is also
523quite error prone, as it requires setting the OAMS_TRADITIONAL_GIS_ORDER
524strategy in a lot of different places.
525
526So users and developers are kindly invited to thoroughly test GDAL once
527this work has landed in master.
528
529Implementation
530--------------
531
532Done by Even Rouault, `Spatialys <http://www.spatialys.com>`__.
533Available per `PR 1185 <https://github.com/OSGeo/gdal/pull/1185>`__
534Funded through `gdalbarn <https://gdalbarn.com/>`__ sponsoring.
535
536While it is provided as a multiple commit for """easier""" review, it
537will be probably squashed in a single commit for inclusion in master, as
538intermediate steps are not all buildable, due to PROJ symbol renames
539having occurred during the development, which would break bisectability.
540
541Voting history
542--------------
543
544Adopted with +1 from PSC members HowardB, JukkaR, DanielM and EvenR
545
546Modifications
547-------------
548
5492019-May-02: change mentions of GDAL 2.5 to GDAL 3.0
550