A fundamental aspect of spatial data is defining its Coordinate Reference System (CRS). Briefly, a CRS defines how to represent the Earth’s surface on the plane. Until we define a projection, coordinates are just numbers.
A CRS is a combination of an ellipsoid, a datum, and a projection.
In reality, Earth has a irregular shape. An ellipsoid is a 3D simplified model that approximates the shape of the Earth.
Some ellipsoids are global, i.e. they try to model the entire globe. Among these, WGS84 is the most recent and more widely used global reference system. GRS80 is also widely used.
But global ellipsoids have larger errors than ellipsoids fitted locally. Local ellipsoids are used when higher precision is needed. Examples: ED50 (Europe), OSGB36 (United Kingdom).
The datum provides the information needed to “anchor” coordinates to the surface of the Earth. Defines the origin point and the direction of the coordinate axes.
The datum always specifies the ellipsoid, and sometimes have the same name.
Finally, the projection indicates how we represent locations on the surface of the earth. Basically we have two options:
Different combinations of ellipsoid, datum and transformation result in many different spatial projections. They can be classified as planar, conical or cylindrical.
All map projections
introduce distortions in area, shape or distance. These properties are
mutually exclusive: a map projection that preserves one will distort the
other two.
For example, the Mercator projection is well-know for preserving shapes and exaggerating the area of objects near the poles. South America in, in reality, much larger than Greenland. In the Mercator projection, the loxodromes (or rhumb lines) are straight lines, so it is good for navigation.
On the other hands, the Azimuthal Equidistant projection does not preserve shapes (can you see Australia?). But it preserves distances to and from the center of the projection (it is an equidistant projection).
The Cylindrical Equal Area is an example of a projection that does not distort the areas (i.e is an equal area projection). Here the proportion between the areas of Greenland and South America is right… although the shapes are distorted near the poles.
Conformal projections preserve local shapes.
… or select one based on your personality: https://imgs.xkcd.com/comics/map_projections.png
The sf package links directly to the external library PROJ, a very complete cartographic projection library (http://trac.osgeo.org/proj/). In the PROJ library, projections are defined using projstring, a text string defining the projection, datum and associated parameters. Here are some examples:
For unprojected (geographical) data: “+proj=longlat +datum=WGS84”
For data in the UTM projection, zone 28: “+proj=utm +zone=28 +datum=WGS84”
For a Lamberts Equal Area projection centred in the Nordic Seas: “+proj=laea +lat_0=69 +lon_0=-4 +datum=WGS84”
Many common projections are included in the European Petroleum Survey Group (EPSG) database (http://www.epsg.org/), and can be specified only by using a reference number. For example, the EPSG code 4326 indicates urprojected data, so it is equivalent to the projstring “+proj=longlat +datum=WGS84”.
When we read a spatial object into, if the projection is included in the metadata then the projection will be included in the object. We can use the st_crs() function to extract it.
library(rnaturalearth)
# need to install below if not already on your computer
# install.packages("rnaturalearthhires", repos = "http://packages.ropensci.org", type = "source")
library(tidyverse)
library(sf)
<- ne_countries(returnclass = "sf", country = "Iceland", scale = 10) %>%
iceland st_geometry()
st_crs(iceland) # Find out the CRS
## Coordinate Reference System:
## User input: +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0
## wkt:
## BOUNDCRS[
## SOURCECRS[
## GEOGCRS["unknown",
## 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["longitude",east,
## ORDER[1],
## ANGLEUNIT["degree",0.0174532925199433,
## ID["EPSG",9122]]],
## AXIS["latitude",north,
## ORDER[2],
## ANGLEUNIT["degree",0.0174532925199433,
## ID["EPSG",9122]]]]],
## TARGETCRS[
## 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["latitude",north,
## ORDER[1],
## ANGLEUNIT["degree",0.0174532925199433]],
## AXIS["longitude",east,
## ORDER[2],
## ANGLEUNIT["degree",0.0174532925199433]],
## ID["EPSG",4326]]],
## ABRIDGEDTRANSFORMATION["Transformation from unknown to WGS84",
## METHOD["Geocentric translations (geog2D domain)",
## ID["EPSG",9603]],
## PARAMETER["X-axis translation",0,
## ID["EPSG",8605]],
## PARAMETER["Y-axis translation",0,
## ID["EPSG",8606]],
## PARAMETER["Z-axis translation",0,
## ID["EPSG",8607]]]]
If the metadata does not includes the CRS, or if we create our own sf object, the CRS will not be defined.
<- data.frame(lat = c(66,65.5), lon = c(-20, -19)) %>%
mysf st_as_sf(coords = c("lon", "lat"))
st_crs(mysf)
## Coordinate Reference System: NA
We can define it with st_crs() (or with st_set_crs() if we use it in a pipe)
<- data.frame(lat = c(66,65.5), lon = c(-20, -19)) %>%
mysf st_as_sf(coords = c("lon", "lat")) %>%
st_set_crs(4326)
st_crs(mysf)
## 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["unknown"],
## AREA["World"],
## BBOX[-90,-180,90,180]],
## ID["EPSG",4326]]
If I change the CRS of an object that already has it, I get a warning:
st_set_crs(mysf, 5325)
## Simple feature collection with 2 features and 0 fields
## Geometry type: POINT
## Dimension: XY
## Bounding box: xmin: -20 ymin: 65.5 xmax: -19 ymax: 66
## Projected CRS: ISN2004 / Lambert 2004
## geometry
## 1 POINT (-20 66)
## 2 POINT (-19 65.5)
By doing this we are changing the metadata of the object, but we are not reprojecting the data.
Vector data is comprised of points and combination of points into lines and polygons. Reprojecting vector data consists on transforming the coordinates of these points.
In most cases we use the st_transform() function to project sf objects. This function can receive the proj4 string or the EPSG code of the target projection.
<- ne_countries (returnclass = "sf", continent = "Europe") %>%
europe st_geometry()
# Transform to the Mollweide projection
<- europe %>% st_transform("+proj=moll")
europe_moll
plot(europe, axes = TRUE)
plot(europe_moll, axes = TRUE)
st_crs(europe)
## Coordinate Reference System:
## User input: +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0
## wkt:
## BOUNDCRS[
## SOURCECRS[
## GEOGCRS["unknown",
## 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["longitude",east,
## ORDER[1],
## ANGLEUNIT["degree",0.0174532925199433,
## ID["EPSG",9122]]],
## AXIS["latitude",north,
## ORDER[2],
## ANGLEUNIT["degree",0.0174532925199433,
## ID["EPSG",9122]]]]],
## TARGETCRS[
## 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["latitude",north,
## ORDER[1],
## ANGLEUNIT["degree",0.0174532925199433]],
## AXIS["longitude",east,
## ORDER[2],
## ANGLEUNIT["degree",0.0174532925199433]],
## ID["EPSG",4326]]],
## ABRIDGEDTRANSFORMATION["Transformation from unknown to WGS84",
## METHOD["Geocentric translations (geog2D domain)",
## ID["EPSG",9603]],
## PARAMETER["X-axis translation",0,
## ID["EPSG",8605]],
## PARAMETER["Y-axis translation",0,
## ID["EPSG",8606]],
## PARAMETER["Z-axis translation",0,
## ID["EPSG",8607]]]]
st_crs(europe_moll)
## Coordinate Reference System:
## User input: +proj=moll
## wkt:
## PROJCRS["unknown",
## BASEGEOGCRS["unknown",
## 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]]],
## CONVERSION["unknown",
## METHOD["Mollweide"],
## PARAMETER["Longitude of natural origin",0,
## ANGLEUNIT["degree",0.0174532925199433],
## ID["EPSG",8802]],
## PARAMETER["False easting",0,
## LENGTHUNIT["metre",1],
## ID["EPSG",8806]],
## PARAMETER["False northing",0,
## LENGTHUNIT["metre",1],
## ID["EPSG",8807]]],
## CS[Cartesian,2],
## AXIS["(E)",east,
## ORDER[1],
## LENGTHUNIT["metre",1,
## ID["EPSG",9001]]],
## AXIS["(N)",north,
## ORDER[2],
## LENGTHUNIT["metre",1,
## ID["EPSG",9001]]]]
Additional projections are available using the st_transform_proj() in the lwgeom package. The lwgeom package provides bindings to the liblwgeom library (part of the PostGis).
<- lwgeom::st_transform_proj(europe, "+proj=wintri")
europe_win
plot(europe_win, axes = TRUE)
We can modify the projections by passing the required parameters. For example:
# Centred in Paris
<- st_transform(europe,
europe_laea1 crs = "+proj=laea +lon_0=-2.35 +lat_48.8")
# Centred in Moscow
<- st_transform(europe,
europe_laea2 crs = "+proj=laea +lon_0=37.6 +lat_0=55.7")
plot(europe_laea1, axes = TRUE)
plot(europe_laea2, axes = TRUE)
One of the advantages of using the sf objects is that we can utilise the geom_sf() function when plotting with ggplot. geom_sf() uses by default the coord_sf() function to control the coordinate system of the plot. coord_sf() ensures that all layers use a common CRS (otherwise it is not possible to place them together in the same graph). You can either specify it using the CRS argument, or coord_sf() will take it from the first layer that defines a CRS.
Let’s load two sf objects with different CRS’s.:
<- read_sf("ftp://ftp.hafro.is/pub/data/shapes/iceland_coastline.gpkg")
coast st_crs(coast)
## Coordinate Reference System:
## User input: unnamed
## wkt:
## PROJCRS["unnamed",
## BASEGEOGCRS["GRS 1980(IUGG, 1980)",
## DATUM["unknown",
## ELLIPSOID["GRS80",6378137,298.257222101,
## LENGTHUNIT["metre",1,
## ID["EPSG",9001]]]],
## PRIMEM["Greenwich",0,
## ANGLEUNIT["degree",0.0174532925199433]]],
## CONVERSION["unnamed",
## METHOD["Lambert Conic Conformal (2SP)",
## ID["EPSG",9802]],
## PARAMETER["Latitude of 1st standard parallel",64.25,
## ANGLEUNIT["degree",0.0174532925199433],
## ID["EPSG",8823]],
## PARAMETER["Latitude of 2nd standard parallel",65.75,
## ANGLEUNIT["degree",0.0174532925199433],
## ID["EPSG",8824]],
## PARAMETER["Latitude of false origin",65,
## ANGLEUNIT["degree",0.0174532925199433],
## ID["EPSG",8821]],
## PARAMETER["Longitude of false origin",-19,
## ANGLEUNIT["degree",0.0174532925199433],
## ID["EPSG",8822]],
## PARAMETER["Easting at false origin",500000,
## LENGTHUNIT["Meter",1],
## ID["EPSG",8826]],
## PARAMETER["Northing at false origin",500000,
## LENGTHUNIT["Meter",1],
## ID["EPSG",8827]]],
## CS[Cartesian,2],
## AXIS["(E)",east,
## ORDER[1],
## LENGTHUNIT["Meter",1]],
## AXIS["(N)",north,
## ORDER[2],
## LENGTHUNIT["Meter",1]]]
<- read_sf("ftp://ftp.hafro.is/pub/data/shapes/iceland_contours.gpkg")
depth st_crs(depth)
## Coordinate Reference System:
## User input: WGS 84
## 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["unknown"],
## AREA["World"],
## BBOX[-90,-180,90,180]],
## ID["EPSG",4326]]
Note the differences:
ggplot() +
geom_sf(data = coast, fill = "darkgray") +
geom_sf(data = depth)
ggplot() +
geom_sf(data = depth) +
geom_sf(data = coast, fill = "darkgray")
ggplot() +
geom_sf(data = depth) +
geom_sf(data = coast, fill = "darkgray") +
coord_sf(crs = 4087) # World Equidistant Cylindrical projection
Here 1 degree north is the same as 1 degrees longitude.
The different Europe projections above via ggplot
<-
p1 ggplot() +
geom_sf(data = europe) +
ggtitle("longlat")
<-
p2 ggplot() +
geom_sf(data = europe_moll) +
ggtitle("moll")
<-
p3 ggplot() +
geom_sf(data = europe_laea1) +
ggtitle("Center Paris")
<-
p4 ggplot() +
geom_sf(data = europe_laea2) +
ggtitle("Center Moscow")