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