Skip to Content

Goodbye PROJ.4! How to specify a coordinate reference system in R?

Current good practice in specifying a CRS in R

Coordinate reference systems: minimal background

What?

A coordinate reference system (CRS) – also called spatial reference system (SRS) – is what you need if you want to interpret numeric coordinates as actual point locations with reference to the Earth. Two types of coordinate reference system exist: geodetic and projected CRSs. The former serve only to locate coordinates relative to a 3D model of the Earth surface, while the latter add a projection to generate coordinates on a 2D map. Coordinate operations convert or transform coordinates from one CRS to another, and you often need them because the CRS may differ between dataset 1, dataset 2 or a specific mapping technology (such as leaflet).

As you can expect, a CRS is defined by several elements. Essentially, a CRS exists of:

  • a coordinate system,
  • a ‘datum’ (s.l.): it localizes the geodetic coordinate system relative to the Earth and needs a geometric definition of the ellipsoid,
  • only for projected CRSs: coordinate conversion parameters that determine the conversion from the geodetic to the projected coordinates.

We will not go deeper into these components, because we want to focus on implementation. However it is highly recommended to read further about this, in order to better understand what a CRS means. A good contemporary resource in an R context is the section ‘Coordinate reference systems: background’ in Bivand (2019) (there is also an accompanying video of that lesson).

There are a few coordinated lists of CRSs around the globe, the most famous one being the EPSG dataset, where each CRS has a unique EPSG code. You can consult these CRSs interactively at https://epsg.org (official source) and through third-party websites such as http://epsg.io. For example, the ‘World Geodetic System 1984’ (WGS84) is a geodetic CRS with EPSG code 4326, and ‘Belge 1972 / Belgian Lambert 72’ is a projected CRS with EPSG code 31370.

How did we represent a CRS in R? Evolutions in PROJ and GDAL.

It is good to know this, but you can skip this section if you like.

The reason for writing this tutorial are the recent (and ongoing) changes in several important geospatial libraries, especially GDAL and PROJ. They are used by most geospatial tools, including the key geospatial R packages rgdal, sp, sf, stars, terra and raster.

Since long, coordinate reference systems in R (and many other tools) have been represented by so called ‘PROJ.4 strings’ (or ‘proj4strings’), referring to the long-standing version 4 of the PROJ library. But, we will not use them here! It is discouraged to use ‘PROJ strings’[1] any longer to represent a CRS; several string elements for CRSs are now deprecated or unsupported. Currently, PROJ (https://proj.org) regards PROJ strings only as a means of specifying a coordinate operation (conversions or transformations between CRSs). Performing coordinate operations is the main aim of PROJ.

Let’s just have one last nostalgic peek (and then, no more!!) to the proj4string for EPSG:31370, the Belgian Lambert 72 CRS:[2]

+proj=lcc +lat_1=51.16666723333333 +lat_2=49.8333339 +lat_0=90 +lon_0=4.367486666666666 +x_0=150000.013 +y_0=5400088.438 +ellps=intl +towgs84=-106.8686,52.2978,-103.7239,0.3366,-0.457,1.8422,-1.2747 +units=m +no_defs

Several reasons have led to recent changes in GDAL and PROJ, such as the former use of the WGS84 CRS as an intermediate ‘hub’ in coordinate transformation or in defining a CRS’s datum (introducing unneeded errors), higher accuracy requirements in transformations and the availability of better CRS specification standards than PROJ strings. The changes are included in GDAL 3 and PROJ ≥ 6, which many R packages now support and promote.

In consequence, support for PROJ strings to represent a CRS is reduced and discouraged. It can still be done, preferrably adding the +type=crs element to distinguish such a string from modern PROJ strings. The latter represent a coordinate operation, not a CRS. Currently, support for most geodetic datums is already lacking in PROJ strings[3] (unless one defines it indirectly, but likely less accurately, with the now deprecated +towgs84 key). The WGS84 ensemble datum[4] (datum:EPSG::6326) is now by default assumed for a CRS declared with a PROJ string.

If you want to read more about the changes, here are some recommended resources:

What is the new way to represent a CRS in R?

Answer: The current approach is the WKT2 string, a recent and much better standard, maintained by the Open Geospatial Consortium. WKT stands for ‘Well-known text.’ ‘WKT2’ is simply the recent version of WKT, approved in 2019, so you can also refer to it as WKT.[5]

For example, this is the WKT2 string for WGS84:

GEOGCRS["WGS 84 (with axis order normalized for visualization)",
    DATUM["World Geodetic System 1984",
        ELLIPSOID["WGS 84",6378137,298.257223563,
            LENGTHUNIT["metre",1]],
        ID["EPSG",6326]],
    PRIMEM["Greenwich",0,
        ANGLEUNIT["degree",0.0174532925199433],
        ID["EPSG",8901]],
    CS[ellipsoidal,2],
        AXIS["geodetic longitude (Lon)",east,
            ORDER[1],
            ANGLEUNIT["degree",0.0174532925199433,
                ID["EPSG",9122]]],
        AXIS["geodetic latitude (Lat)",north,
            ORDER[2],
            ANGLEUNIT["degree",0.0174532925199433,
                ID["EPSG",9122]]]]

An alternative representation of the WKT2 string - not yet official at the time of writing - is PROJJSON. It is a more convenient format to use in coding.

How to specify a CRS in R?

library(sp)
library(raster)
library(sf)

Great news!
The R packages further down, and many that depend on them, now provide a means of CRS specification irrespective of the GDAL/PROJ version, hence compliant with newer GDAL/PROJ.

  • DO:

    The general principle that we recommend is: specify the CRS by using the EPSG code, but do so without using a PROJ string.

    Note: in case you wish to define a custom CRS yourself, ideally use WKT2 or its more convenient PROJJSON counterpart.

  • DON’T:

    It’s no longer advised to use PROJ strings to specify a CRS, such as +init=epsg:????, +proj=longlat, … (even though that might still work, their usage is discouraged).

Below it is demonstrated how to specify a CRS that is defined in the EPSG database (hence, having an EPSG code), for several important geospatial R packages: sf, sp and raster. Other geospatial R packages should normally inherit their approach.

First, a practical note:

  • Some packages internally still derive a PROJ string as well. This happens even while you did not enter a PROJ string. Note that the derived PROJ string will not be used further if you’re on GDAL 3 / PROJ ≥ 6, and a WKT2 string will be generated as well for actual use. In the presence of GDAL 3 / PROJ ≥ 6 and when using sp or raster, you will (at the time of writing) get a warning from rgdal about dropped keys in the generated PROJ strings.[6] You can safely ignore this warning on condition that you didn’t define the CRS with a PROJ string. Also, you can suppress the warning with options(rgdal_show_exportToProj4_warnings = "none") in the beginning of your script (before loading rgdal or dependent packages).

As a demo data set for vector data, we use a dataset of city centers (points) included in the rgdal package.

cities <- 
  st_read(system.file("vectors/cities.shp", package = "rgdal"))

This is how it looks like:

cities
Simple feature collection with 606 features and 4 fields
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: -165.27 ymin: -53.15 xmax: 177.1302 ymax: 78.2
Geodetic CRS:  WGS 84
First 10 features:
               NAME COUNTRY POPULATION CAPITAL                   geometry
1          Murmansk  Russia     468000       N  POINT (33.08604 68.96355)
2       Arkhangelsk  Russia     416000       N  POINT (40.64616 64.52067)
3  Saint Petersburg  Russia    5825000       N  POINT (30.45333 59.95189)
4           Magadan  Russia     152000       N      POINT (150.78 59.571)
5             Perm'  Russia    1160000       N  POINT (56.23246 58.00024)
6     Yekaterinburg  Russia    1620000       N  POINT (60.61013 56.84654)
7  Nizhniy Novgorod  Russia    2025000       N  POINT (43.94067 56.28968)
8           Glasgow      UK    1800000       N POINT (-4.269948 55.86281)
9            Kazan'  Russia    1140000       N  POINT (49.14547 55.73301)
10      Chelyabinsk  Russia    1325000       N    POINT (61.39261 55.145)

We now convert it to a plain dataframe (non-spatial) with the XY coordinates as columns, for the sake of the exercise. We want to add the CRS ourselves!

cities <- 
  cbind(st_drop_geometry(cities), 
        st_coordinates(cities))
head(cities, 10) # top 10 rows
               NAME COUNTRY POPULATION CAPITAL          X        Y
1          Murmansk  Russia     468000       N  33.086040 68.96355
2       Arkhangelsk  Russia     416000       N  40.646160 64.52067
3  Saint Petersburg  Russia    5825000       N  30.453327 59.95189
4           Magadan  Russia     152000       N 150.780014 59.57100
5             Perm'  Russia    1160000       N  56.232464 58.00024
6     Yekaterinburg  Russia    1620000       N  60.610130 56.84654
7  Nizhniy Novgorod  Russia    2025000       N  43.940670 56.28968
8           Glasgow      UK    1800000       N  -4.269948 55.86281
9            Kazan'  Russia    1140000       N  49.145466 55.73301
10      Chelyabinsk  Russia    1325000       N  61.392612 55.14500

sf package

Note that also the stars package, useful to represent vector and raster data cubes, uses the below approach.

Let’s check whether sf uses (for Windows: comes with) the minimal PROJ/GDAL versions that we want!

sf::sf_extSoftVersion()
          GEOS           GDAL         proj.4 GDAL_with_GEOS     USE_PROJ_H 
       "3.9.0"        "3.2.1"        "7.2.1"         "true"         "true" 

Good to go!

Defining a CRS with sf

You can simply provide the EPSG code using st_crs():

crs_wgs84 <- st_crs(4326) # WGS84 has EPSG code 4326

It is a so-called crs object:

class(crs_wgs84)
[1] "crs"

Printing the WKT2 string with sf

You can directly acces the wkt element of the crs object:

cat(crs_wgs84$wkt)
GEOGCRS["WGS 84",
    DATUM["World Geodetic System 1984",
        ELLIPSOID["WGS 84",6378137,298.257223563,
            LENGTHUNIT["metre",1]]],
    PRIMEM["Greenwich",0,
        ANGLEUNIT["degree",0.0174532925199433]],
    CS[ellipsoidal,2],
        AXIS["geodetic latitude (Lat)",north,
            ORDER[1],
            ANGLEUNIT["degree",0.0174532925199433]],
        AXIS["geodetic longitude (Lon)",east,
            ORDER[2],
            ANGLEUNIT["degree",0.0174532925199433]],
    USAGE[
        SCOPE["Horizontal component of 3D system."],
        AREA["World."],
        BBOX[-90,-180,90,180]],
    ID["EPSG",4326]]

There are a few extras to note:

  • printing the crs object shows us both the EPSG code (more generally: the user’s CRS specification) and the WKT2 string:
crs_wgs84
Coordinate Reference System:
  User input: EPSG:4326 
  wkt:
GEOGCRS["WGS 84",
    DATUM["World Geodetic System 1984",
        ELLIPSOID["WGS 84",6378137,298.257223563,
            LENGTHUNIT["metre",1]]],
    PRIMEM["Greenwich",0,
        ANGLEUNIT["degree",0.0174532925199433]],
    CS[ellipsoidal,2],
        AXIS["geodetic latitude (Lat)",north,
            ORDER[1],
            ANGLEUNIT["degree",0.0174532925199433]],
        AXIS["geodetic longitude (Lon)",east,
            ORDER[2],
            ANGLEUNIT["degree",0.0174532925199433]],
    USAGE[
        SCOPE["Horizontal component of 3D system."],
        AREA["World."],
        BBOX[-90,-180,90,180]],
    ID["EPSG",4326]]
  • if the user inputted the CRS with an EPSG code (which we did!), the latter can be returned as a number:
crs_wgs84$epsg
[1] 4326

You can (but should you?) export a PROJ string as well, with crs_wgs84$proj4string.

Set the CRS of an sf object

First we prepare an sf object from cities but still without a CRS:

cities2 <- st_as_sf(cities, coords = c("X", "Y"))
cities2
Simple feature collection with 606 features and 4 fields
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: -165.27 ymin: -53.15 xmax: 177.1302 ymax: 78.2
CRS:           NA
First 10 features:
               NAME COUNTRY POPULATION CAPITAL                   geometry
1          Murmansk  Russia     468000       N  POINT (33.08604 68.96355)
2       Arkhangelsk  Russia     416000       N  POINT (40.64616 64.52067)
3  Saint Petersburg  Russia    5825000       N  POINT (30.45333 59.95189)
4           Magadan  Russia     152000       N      POINT (150.78 59.571)
5             Perm'  Russia    1160000       N  POINT (56.23246 58.00024)
6     Yekaterinburg  Russia    1620000       N  POINT (60.61013 56.84654)
7  Nizhniy Novgorod  Russia    2025000       N  POINT (43.94067 56.28968)
8           Glasgow      UK    1800000       N POINT (-4.269948 55.86281)
9            Kazan'  Russia    1140000       N  POINT (49.14547 55.73301)
10      Chelyabinsk  Russia    1325000       N    POINT (61.39261 55.145)

Note the missing CRS!

Let’s add the CRS by using the EPSG code (we could also assign crs_wgs84 instead):

st_crs(cities2) <- 4326

Done!

Get the CRS of an sf object

Really, all you need is st_crs(), once more!

st_crs(cities2)
Coordinate Reference System:
  User input: EPSG:4326 
  wkt:
GEOGCRS["WGS 84",
    DATUM["World Geodetic System 1984",
        ELLIPSOID["WGS 84",6378137,298.257223563,
            LENGTHUNIT["metre",1]]],
    PRIMEM["Greenwich",0,
        ANGLEUNIT["degree",0.0174532925199433]],
    CS[ellipsoidal,2],
        AXIS["geodetic latitude (Lat)",north,
            ORDER[1],
            ANGLEUNIT["degree",0.0174532925199433]],
        AXIS["geodetic longitude (Lon)",east,
            ORDER[2],
            ANGLEUNIT["degree",0.0174532925199433]],
    USAGE[
        SCOPE["Horizontal component of 3D system."],
        AREA["World."],
        BBOX[-90,-180,90,180]],
    ID["EPSG",4326]]

Great!

As this returns the crs object, you can also use st_crs(cities2)$wkt to specifically return the WKT2 string!

sp package

Note that the actively developed (and matured) sf package is now recommended over the sp package (a view also shared by sf and sp developers). The sp package, which has very long been the go-to package before sf matured, is maintained in order to support existing code, but it is not further developed as much.

The sp package relies on the rgdal R package to communicate with GDAL and PROJ, so let’s check whether rgdal uses (for Windows: comes with) the minimal PROJ/GDAL versions that we want.

rgdal::rgdal_extSoftVersion()
          GDAL GDAL_with_GEOS           PROJ             sp 
       "3.2.1"         "TRUE"        "7.2.1"        "1.4-5" 

Okido.

Defining a CRS with sp

crs_wgs84 <- CRS(SRS_string = "EPSG:4326") # WGS84 has EPSG code 4326

It is a so-called CRS object:

class(crs_wgs84)
[1] "CRS"
attr(,"package")
[1] "sp"

Printing the WKT2 string with sp

wkt_wgs84 <- wkt(crs_wgs84)
cat(wkt_wgs84)
GEOGCRS["WGS 84 (with axis order normalized for visualization)",
    DATUM["World Geodetic System 1984",
        ELLIPSOID["WGS 84",6378137,298.257223563,
            LENGTHUNIT["metre",1]],
        ID["EPSG",6326]],
    PRIMEM["Greenwich",0,
        ANGLEUNIT["degree",0.0174532925199433],
        ID["EPSG",8901]],
    CS[ellipsoidal,2],
        AXIS["geodetic longitude (Lon)",east,
            ORDER[1],
            ANGLEUNIT["degree",0.0174532925199433,
                ID["EPSG",9122]]],
        AXIS["geodetic latitude (Lat)",north,
            ORDER[2],
            ANGLEUNIT["degree",0.0174532925199433,
                ID["EPSG",9122]]]]

Also note that, when printing a CRS object of sp (e.g. by running crs_wgs84), you still just get a PROJ string! We won’t print it here! However do know that the WKT2 string is currently also contained in the CRS object, just call wkt() to see it.

Set the CRS of a Spatial* object in sp

First we prepare a SpatialPointsDataFrame from cities but still without a CRS:

cities2 <- cities
coordinates(cities2) <-  ~ X + Y

Now, we can add a CRS:

proj4string(cities2) <- crs_wgs84

Note the name of the proj4string<- replacement function in sp: it still reminds of old days, but the result is GDAL3/PROJ≥6 compliant! Maybe we’ll get another function in the future that cuts the link with the ‘proj4string’ name.

Get the CRS of a Spatial* object in sp

Again, use wkt(): it works on both CRS and Spatial* objects!

cat(wkt(cities2))
GEOGCRS["WGS 84 (with axis order normalized for visualization)",
    DATUM["World Geodetic System 1984",
        ELLIPSOID["WGS 84",6378137,298.257223563,
            LENGTHUNIT["metre",1]],
        ID["EPSG",6326]],
    PRIMEM["Greenwich",0,
        ANGLEUNIT["degree",0.0174532925199433],
        ID["EPSG",8901]],
    CS[ellipsoidal,2],
        AXIS["geodetic longitude (Lon)",east,
            ORDER[1],
            ANGLEUNIT["degree",0.0174532925199433,
                ID["EPSG",9122]]],
        AXIS["geodetic latitude (Lat)",north,
            ORDER[2],
            ANGLEUNIT["degree",0.0174532925199433,
                ID["EPSG",9122]]]]

Et voilà!

raster package

At the time of writing, you still needed the development version of raster for the below code to run. You can install the development version with: remotes::install_github("rspatial/raster"). If below code does not run with the CRAN version (usually obtained with install.packages()), use the development version. Further, be aware that a terra package is in development as a successor to raster. It is aimed at faster processing and it is only compatible with GDAL3/PROJ≥6.

As you will see, raster more or less aligns with sp, although it has a few extras. For example: raster does not require the use of the proj4string<- replacement function to set the CRS. raster provides crs<-.

Let’s make a dummy raster first, without CRS:

within_belgium <- 
  raster(extent(188500, 190350, 227550, 229550),
         res = 50)
values(within_belgium) <- 1:ncell(within_belgium)
within_belgium
class      : RasterLayer 
dimensions : 40, 37, 1480  (nrow, ncol, ncell)
resolution : 50, 50  (x, y)
extent     : 188500, 190350, 227550, 229550  (xmin, xmax, ymin, ymax)
crs        : NA 
source     : memory
names      : layer 
values     : 1, 1480  (min, max)

This raster is intended for use in the Belgian Lambert 72 CRS (EPSG 31370).

Defining and printing a CRS with raster

This is not applicable. Just use the facilities of the sp package if you want to make a separate CRS object for usage in a Raster* object (see below).

Set the CRS of a Raster* object in raster

We prepare a few copies of the data:

within_belgium1 <- within_belgium
within_belgium2 <- within_belgium
within_belgium3 <- within_belgium
within_belgium4 <- within_belgium

Setting the CRS is done with the crs<- replacement function. It can take a multitude of formats; these are all equivalent:

crs(within_belgium1) <- 31370
crs(within_belgium2) <- "EPSG:31370"
crs(within_belgium3) <- st_crs(31370)$wkt # a WKT string
crs(within_belgium4) <- CRS(SRS_string = "EPSG:31370") # an sp CRS object

Note that we could also have provided the crs argument in raster(), when creating the RasterLayer object. It can take any of the above formats.

Get the CRS of a Raster* object in raster

It goes the same as in sp:

cat(wkt(within_belgium1))
PROJCRS["Belge 1972 / Belgian Lambert 72",
    BASEGEOGCRS["Belge 1972",
        DATUM["Reseau National Belge 1972",
            ELLIPSOID["International 1924",6378388,297,
                LENGTHUNIT["metre",1]]],
        PRIMEM["Greenwich",0,
            ANGLEUNIT["degree",0.0174532925199433]],
        ID["EPSG",4313]],
    CONVERSION["Belgian Lambert 72",
        METHOD["Lambert Conic Conformal (2SP)",
            ID["EPSG",9802]],
        PARAMETER["Latitude of false origin",90,
            ANGLEUNIT["degree",0.0174532925199433],
            ID["EPSG",8821]],
        PARAMETER["Longitude of false origin",4.36748666666667,
            ANGLEUNIT["degree",0.0174532925199433],
            ID["EPSG",8822]],
        PARAMETER["Latitude of 1st standard parallel",51.1666672333333,
            ANGLEUNIT["degree",0.0174532925199433],
            ID["EPSG",8823]],
        PARAMETER["Latitude of 2nd standard parallel",49.8333339,
            ANGLEUNIT["degree",0.0174532925199433],
            ID["EPSG",8824]],
        PARAMETER["Easting at false origin",150000.013,
            LENGTHUNIT["metre",1],
            ID["EPSG",8826]],
        PARAMETER["Northing at false origin",5400088.438,
            LENGTHUNIT["metre",1],
            ID["EPSG",8827]]],
    CS[Cartesian,2],
        AXIS["easting (X)",east,
            ORDER[1],
            LENGTHUNIT["metre",1]],
        AXIS["northing (Y)",north,
            ORDER[2],
            LENGTHUNIT["metre",1]],
    USAGE[
        SCOPE["Engineering survey, topographic mapping."],
        AREA["Belgium - onshore."],
        BBOX[49.5,2.5,51.51,6.4]],
    ID["EPSG",31370]]

Let’s verify whether all objects do indeed have the same CRS:

all.equal(wkt(within_belgium1), wkt(within_belgium2))
[1] TRUE
all.equal(wkt(within_belgium1), wkt(within_belgium3))
[1] TRUE
all.equal(wkt(within_belgium1), wkt(within_belgium4))
[1] TRUE

YES!

Literature

Bivand R. (2019). ECS530: (III) Coordinate reference systems. Tuesday 3 December 2019, 09:15-11.00, aud. C. https://rsbivand.github.io/ECS530_h19/ECS530_III.html#coordinate_reference_systems:_background (accessed September 16, 2020).

Bivand R. (2020a). Migration to PROJ6/GDAL3. http://rgdal.r-forge.r-project.org/articles/PROJ6_GDAL3.html (accessed September 21, 2020).

Bivand R. (2020c). How R Helped Provide Tools for Spatial Data Analysis. https://github.com/rsbivand/celebRation20_files/raw/master/bivand_200229.pdf.

Bivand R. (2020b). Upstream software dependencies of the R-spatial ecosystem (video recording). In: How R Helped Provide Tools for Spatial Data Analysis @ CelebRation 2020. https://youtu.be/D4-roPsMz48?t=2166 (accessed September 21, 2020).

Nowosad J. & Lovelace R. (2020). Recent changes in R spatial and how to be ready for them. https://geocompr.github.io/post/2020/whyr_webinar004/ (accessed September 21, 2020).

Pebesma E. & Bivand R. (2020). R spatial follows GDAL and PROJ development. https://www.r-spatial.org/r/2020/03/17/wkt.html (accessed September 21, 2020).

[1] ‘PROJ string’ is the term used in current PROJ documentation. Here, we only use ‘PROJ.4 string’ (or ‘proj4string’) when referring specifically to the PROJ string appearance in version 4 of the PROJ software.

[2] Note that the currently returned PROJ string for EPSG:31370, if requested from PROJ ≥ 6 or GDAL 3 (not shown), lacks the datum reference which, in PROJ.4, was defined indirectly by +towgs84 with the 7-parameter (Helmert) transformation to the WGS84 datum. Hence the current PROJ string is a deficient representation of EPSG:31370.

[3] Formerly, more geodetic datums could be specified in a PROJ string with the +datum key. Currently only WGS84, NAD27 and NAD83 are still supported this way. Further, if an ellipsoid is specified with +ellps (and that includes the WGS84 ellipsoid), the datum of the resulting CRS is considered as ‘unknown.’ The usage of PROJ strings to define CRSs, including the +datum and +towgs84 elements, will remain supported for mere backward compatibility (to support existing data sets), but is regarded deprecated and is discouraged by PROJ.

[4] An ensemble datum is a collection of different but closely related datum realizations without making a distinction between them. By not specifying the applicable datum realization for a coordinate data set, some degree of inaccuracy is allowed when using an ensemble datum as part of a CRS.

[5] In order to emphasize the fact that the improvements in version 2 were instructive to the new versions of GDAL and PROJ, you will often see explicit mention of ‘WKT2.’

[6] The packages give a warning especially to make developers of other, dependent packages aware that they should also make sure their functions do not require PROJ strings or use hardcoded PROJ strings. Instead, they should defer the handling of CRS representation to the basic geospatial R packages (i.e. dependent on the version of PROJ/GDAL). So, the appearance of these warnings marks a period of transition in order to let other packages become GDAL 3 and PROJ ≥ 6 compliant. And the good news is that most popular geospatial packages have become GDAL 3 and PROJ ≥ 6 compliant!