NOTE: This page has been revised for Winter 2021, but may undergo further edits.
One extremely useful feature of R for analyzing geographical data is its ability to provide maps of data in the same computing environment that the data analysis is being performed in, and moreover, to read, manipulate and analyze data with explicitly spatial coordinate information. This facility for handling spatial data was built into S relatively early on, and is now implemented three ways, through the following packages:
maps
, which contains a database of world, country (and for the U.S.) state outlines, and includes functions (in the mapproj
package) for doing projections (or “spatial transforms”);sp
(“spatial”), which provides a format for spatial data in R, and a number of functions for its input (through the rgdal
package), output and display (together constituting a set of “classes and methods”), the rgeos
package, which implements many of the spatial topology functions that formerly required a full suite of GIS software;sf
which implements the “simple features” approach of representing spatial objects and their geometries [https://r-spatial.github.io/sf/index.html], and uses the “built-in” data.frame
(or tibble
) structure in R to store and manipulate data, while linking to the GEOS, GDAL, and PROJ libraries directly. The ’sf` package now seems to be the main focus for development of geospatial analyses and mapping in R.Two other projects/packages are aimed at handling spatial data. These include:
raster
(and the related rasterVis
package, which implements lattice
-type graphics) for handing raster data [https://rspatial.org/raster/index.html] (and which is accompanied by the terra
package, which is “conceived as a replacement” for raster [https://rspatial.org/terra/index.html] )stars
, which considers all data to be both spatial and temporal, with those that aren’t thought of as special cases, as opposed to typical [https://r-spatial.github.io/stars/] which is designed to handle spatiotemporal arrays or datacubes.The main place to go to get an overview of the kinds and capabilities of the spatial packages in R is the Spatial Task Views on CRAN
The examples here use several libraries, datasets, and shapefiles that should be downloaded and/or installed, and read in before proceeding.
The easiest way to get most of these data files and shape-file components, is to download a copy of the workspace geog495.RData
and “load” it, or read it in. (Some of the larger files are not included.) Otherwise, all of them are available to download from the Datasets on the Other tab of the course web page.
Right-click on the above link, and save it to your working folder. (If you’ve forgotten where that is, type getwd()
on the command line). Then read it in to R
The difference in the internal (to R) representation of spatial data (as exemplified by a shapefile) by the sp
and sf
packages can be seen by reading a typical shapefile (polygon outlines of western U.S. states) by both
Load some libraries:
library(sp)
library(rgeos)
## rgeos version: 0.5-5, (SVN revision 640)
## GEOS runtime version: 3.8.1-CAPI-1.13.3
## Linking to sp version: 1.4-2
## Polygon checking: TRUE
library(rgdal)
## rgdal: version: 1.5-21, (SVN revision 1105)
## Geospatial Data Abstraction Library extensions to R successfully loaded
## Loaded GDAL runtime: GDAL 3.1.4, released 2020/10/20
## Path to GDAL shared files: /Library/Frameworks/R.framework/Versions/4.0/Resources/library/rgdal/gdal
## GDAL binary built with GEOS: TRUE
## Loaded PROJ runtime: Rel. 6.3.1, February 10th, 2020, [PJ_VERSION: 631]
## Path to PROJ shared files: /Library/Frameworks/R.framework/Versions/4.0/Resources/library/rgdal/proj
## Linking to sp version:1.4-5
## To mute warnings of possible GDAL/OSR exportToProj4() degradation,
## use options("rgdal_show_exportToProj4_warnings"="none") before loading rgdal.
library(sf)
## Linking to GEOS 3.8.1, GDAL 3.1.4, PROJ 6.3.1
Note that the sp
requires the rgeos
and rgdal
packages for manipulating and reading spatial data, while the sf
package links to compiled versions of the GEOS
, GDAL
, and PROJ5
libraries.
library(RColorBrewer)
library(ggplot2)
library(classInt)
Read the shapefile as an sp
object. (The class()
function tells us what kind of object it is, and head()
lists the first few lines as well as some information about the object.)
Load a couple of other libraries that will be used here:
# read a shapefile
<- "/Users/bartlein/Documents/geog495/data/shp/wus.shp"
shapefile <- readOGR(shapefile) wus_sp
## OGR data source with driver: ESRI Shapefile
## Source: "/Users/bartlein/Documents/geog495/data/shp/wus.shp", layer: "wus"
## with 17 features
## It has 1 fields
class(wus_sp)
## [1] "SpatialPolygonsDataFrame"
## attr(,"package")
## [1] "sp"
And now using the st_read()
function in the sf()
package:
<- st_read(shapefile) wus_sf
## Reading layer `wus' from data source `/Users/bartlein/Documents/geog495/data/shp/wus.shp' using driver `ESRI Shapefile'
## Simple feature collection with 17 features and 1 field
## geometry type: MULTIPOLYGON
## dimension: XY
## bbox: xmin: -124.6813 ymin: 25.9378 xmax: -93.53536 ymax: 49.00508
## geographic CRS: WGS 84
class(wus_sf)
## [1] "sf" "data.frame"
head(wus_sf)
## Simple feature collection with 6 features and 1 field
## geometry type: MULTIPOLYGON
## dimension: XY
## bbox: xmin: -124.3834 ymin: 31.34652 xmax: -94.58387 ymax: 49.00508
## geographic CRS: WGS 84
## ID geometry
## 1 arizona MULTIPOLYGON (((-114.6374 3...
## 2 california MULTIPOLYGON (((-120.006 42...
## 3 colorado MULTIPOLYGON (((-102.0552 4...
## 4 idaho MULTIPOLYGON (((-117.0266 4...
## 5 kansas MULTIPOLYGON (((-94.63544 3...
## 6 montana MULTIPOLYGON (((-104.0491 4...
It’s easy to see that sf
objects “store” data as regular R dataframes, while sp
objects could be thought of as being “like” dataframes. This difference has implications for working with the objects using other R functions.
The following examples demonstrates the ability of R to make some simple maps.
orcounty.shp
shape fileRead an Oregon county data set as a shape file, with county outlines and some attribute data from the old 1990 U.S. Census.
<- "/Users/bartlein/Documents/geog495/data/shp/orcounty.shp"
shapefile <- st_read(shapefile) orcounty_sf
## Reading layer `orcounty' from data source `/Users/bartlein/Documents/geog495/data/shp/orcounty.shp' using driver `ESRI Shapefile'
## Simple feature collection with 36 features and 52 fields
## geometry type: POLYGON
## dimension: XY
## bbox: xmin: -124.5584 ymin: 41.98779 xmax: -116.4694 ymax: 46.23626
## geographic CRS: WGS 84
names(orcounty_sf)
## [1] "NAME" "STATE_NAME" "STATE_FIPS" "CNTY_FIPS" "FIPS" "AREA" "POP1990"
## [8] "POP1997" "POP90_SQMI" "HOUSEHOLDS" "MALES" "FEMALES" "WHITE" "BLACK"
## [15] "AMERI_ES" "ASIAN_PI" "OTHER" "HISPANIC" "AGE_UNDER5" "AGE_5_17" "AGE_18_29"
## [22] "AGE_30_49" "AGE_50_64" "AGE_65_UP" "NEVERMARRY" "MARRIED" "SEPARATED" "WIDOWED"
## [29] "DIVORCED" "HSEHLD_1_M" "HSEHLD_1_F" "MARHH_CHD" "MARHH_NO_C" "MHH_CHILD" "FHH_CHILD"
## [36] "HSE_UNITS" "VACANT" "OWNER_OCC" "RENTER_OCC" "MEDIAN_VAL" "MEDIANRENT" "UNITS_1DET"
## [43] "UNITS_1ATT" "UNITS2" "UNITS3_9" "UNITS10_49" "UNITS50_UP" "MOBILEHOME" "NO_FARMS87"
## [50] "AVG_SIZE87" "CROP_ACR87" "AVG_SALE87" "geometry"
Plot the data, first including the attributes, and then just the outline or “geometry” of the shapefile:
plot(orcounty_sf, max.plot=52) # plot the attributes
plot(st_geometry(orcounty_sf), axes = TRUE)
The first plot is not very useful, but illustrates the contents of the file.
Now make a map of the population in 1990 using equal-frequency class intervals.
# equal-frequency class intervals -- chunk 1
= "POP1990"
plotvar <- orcounty_sf$POP1990
plotvals <- "Population 1990"
title <- "Quantile (Equal-Frequency) Class Intervals"
subtitle <- 8
nclr <- brewer.pal(nclr,"BuPu")
plotclr <- classIntervals(plotvals, nclr, style="quantile")
class <- findColours(class, plotclr) colcode
This first chunk of code (above) just does some setting up, while this next chunk actually produces the map:
# chunk 2
plot(orcounty_sf[plotvar], col=colcode, xlim=c(-124.5, -115), ylim=c(42,47))
title(main=title, sub=subtitle)
legend("bottomright", legend=names(attr(colcode, "table")),
fill=attr(colcode, "palette"), cex=1.0, bty="n")
Here’s what’s going on. First, a temporary variable plotvar
is created by copying the values of orcounty.sh@data$POP1990
(from “attribute” data in the shape file). Next the number of colors is set nclr <- 8
and a set of appropriate colors is generated using the brewer.pal()
function. The classIntervals()
function assigns the individual observations of plotvar
to one of nclr
equal-frequency class intervals, and then the findColours()
function (note the Canadian/U.K. spelling) determines the color for each observation (each county).
In the second block, the first use of the plot()
function plots the shapefile within an appropriate range of latitudes and longitudes, while the second use plots colored polygons on top. The rest of block 2 adds a title and legend. It’s a little clunky, and the placement of labels are not great, but it’s quick.
Here’s an equal-interval (size) class interval map:
# equal-interval class intervals -- chunk 1
= "POP1990"
plotvar <- orcounty_sf$POP1990
plotvals <- "Population 1990"
title <- "Equal-size Class Intervals"
subtitle <- 8
nclr <- brewer.pal(nclr,"BuPu")
plotclr <- classIntervals(plotvals, nclr, style="equal")
class <- findColours(class, plotclr)
colcode
# chunk 2
plot(orcounty_sf[plotvar], col=colcode, xlim=c(-124.5, -115), ylim=c(42,47))
title(main=title, sub=subtitle)
legend("bottomright", legend=names(attr(colcode, "table")),
fill=attr(colcode, "palette"), cex=1.0, bty="n")
A ggplot2
version of the above, can be constructed as follows:
<- brewer.pal(9, "Greens") #[3:6]
pal ggplot() +
geom_sf(data = orcounty_sf, aes(fill = POP1990)) +
scale_fill_gradientn(colors = pal) +
coord_sf() + theme_bw()
Here’s a map of the Oregon climate station data with the data coming from the orstationc.csv
file, and the basemap from orotl.shp
# symbol plot -- equal-interval class intervals
<- orstationc$tann
plotvals <- "Oregon Climate Station Data -- Annual Temperature"
title <- "Equal-Interval Class Intervals"
subtitle <- 8
nclr <- brewer.pal(nclr,"PuOr")
plotclr <- plotclr[nclr:1] # reorder colors
plotclr <- classIntervals(plotvals, nclr, style="equal")
class <- findColours(class, plotclr)
colcode
plot(st_geometry(orcounty_sf), xlim=c(-124.5, -115), ylim=c(42,47))
points(orstationc$lon, orstationc$lat, pch=16, col=colcode, cex=2)
points(orstationc$lon, orstationc$lat, cex=2)
title(main=title, sub=subtitle)
legend(-117, 44, legend=names(attr(colcode, "table")),
fill=attr(colcode, "palette"), cex=1.0, bty="n")
Here’s a third map with the Oregon climate station data locations and data coming from the shape file:
# symbol plot -- equal-interval class intervals
<- "/Users/bartlein/Documents/geog495/data/shp/orstations.shp"
shapefile <- st_read(shapefile) orstations_sf
## Reading layer `orstations' from data source `/Users/bartlein/Documents/geog495/data/shp/orstations.shp' using driver `ESRI Shapefile'
## Simple feature collection with 92 features and 12 fields
## geometry type: POINT
## dimension: XY
## bbox: xmin: -124.567 ymin: 42.05 xmax: -116.967 ymax: 46.15
## geographic CRS: WGS 84
plot(orstations_sf, max.plot=52) # plot the stations
plot(st_geometry(orstations_sf), axes = TRUE)
# symbol plot -- fixed-interval class intervals
<- orstations_sf$pann
plotvals <- "Oregon Climate Station Data -- Annual Precipitation"
title <- "Fixed-Interval Class Intervals"
subtitle <- 5
nclr <- brewer.pal(nclr+1,"BuPu")[2:(nclr+1)]
plotclr <- classIntervals(plotvals, nclr, style="fixed",
class fixedBreaks=c(0,200,500,1000,2000,9999))
<- findColours(class, plotclr)
colcode
# block 2
plot(st_geometry(orcounty_sf), xlim=c(-124.5, -115), ylim=c(42,47))
points(orstations_sf$lon, orstations_sf$lat, pch=16, col=colcode, cex=2)
points(orstations_sf$lon, orstations_sf$lat, cex=2)
title(main=title, sub=subtitle)
legend(-117, 44, legend=names(attr(colcode, "table")),
fill=attr(colcode, "palette"), cex=1.0, bty="n")
Notice how the expression orstations_sf$pann
refers to a specific variable (pann
), contained in the data attribute of the shape file. Some other things: This map uses fixed (ahead of making the map) class intervals (fixedBreaks
) and the two points()
function “calls”: the first plots a colored dot (col=colcode
), and the second then just plots a unfilled dot (in black) to put a nice line around each point to make the symbol more obvious.
Here’s a ggplot2
version of a fixed class-interval map:
<- c(0,200,500,1000,2000,9999)
cutpts <- factor(findInterval(orstations_sf$pann, cutpts))
plot_factor <- 5
nclr <- brewer.pal(nclr+1,"BuPu")[2:(nclr+1)]
plotclr ggplot() +
geom_sf(data = orcounty_sf, fill=NA) +
geom_point(aes(orstations_sf$lon, orstations_sf$lat, color = plot_factor), size = 5.0, pch=16) +
scale_colour_manual(values=plotclr, aesthetics = "colour",
labels = c("0 - 200", "200 - 500", "500 - 1000", "1000 - 2000", "> 2000"),
name = "Ann. Precip.", drop=TRUE) +
labs(x = "Longitude", y = "Latitude") +
theme_bw()
We tend to be influenced more by the simple area of an object on the page or screen than by whatever is being plotted. For example, for the 1990 county population, plotted as polygons with equal-width class intervals Multnomah county, the most populated one, despite being plotted in a dark color, gets kind of dominated by the larger, less populated counties that surround it. The eastern Oregon counties, the least populated, occupy the greatest area on the map. The solution is the bubble plot:
# bubble plot equal-frequency class intervals
<- (st_centroid(orcounty_sf)) orcounty_cntr
## Warning in st_centroid.sf(orcounty_sf): st_centroid assumes attributes are constant over geometries of x
## Warning in st_centroid.sfc(st_geometry(x), of_largest_polygon = of_largest_polygon): st_centroid does not
## give correct centroids for longitude/latitude data
ggplot() +
geom_sf(data = orcounty_sf) +
geom_sf(data = orcounty_cntr, aes(size = AREA), color="purple") +
scale_size_area(max_size = 15) +
labs(x = "Longitude", y = "Latitude", title = "Area") +
theme_bw()
Here the size of each symbol is calculated using the county population using the code at the end of the first block, and then the symbol is plotted at the centroid of each county, which is located using the st_centroid()
function. Note that the symbol sizes could be made vary continuously.
The following example illustrates a fairly realistic task, producing a projected map of nicely scaled precipitaion-ratio (July vs. January) values for weather stations across the West. Such precipitation ratios illustrate the seasonality (e.g. summer vs. winter) of precipitation, and cancel out the strong effect of elevation on precipitation amounts. (High-elevation stations in general have higher precipitation than low-elevations stations, and that effect would otherwise overprint the large-scale pattern of precipitation seasonality.) The precipitation data are stored in a .csv
file, and the example illustrates the creation of suitable base map, and the projection of both the base map and the precipitation data.
Here the base map will be extracted from the maps
package data base. (An alternative source might be the “Natural Earth” shapefiles, accessible through the rnaturalearth
package.) Begin by loading the maps
package:
library(maps)
The map()
function extracts the outlines for, in this case the western U.S. states listed in the regions
argument of the function.
<- map("state", regions=c("washington", "oregon", "california", "idaho", "nevada",
wus_map "montana", "utah", "arizona", "wyoming", "colorado", "new mexico", "north dakota", "south dakota",
"nebraska", "kansas", "oklahoma", "texas"), plot = FALSE, fill = FALSE)
The class()
function indicates the class of the data set, in this case “map” and the contents of the object can be listed using the str()
function:
class(wus_map)
## [1] "map"
str(wus_map)
## List of 4
## $ x : num [1:3323] -115 -115 -115 -115 -115 ...
## $ y : num [1:3323] 35 35.1 35.1 35.2 35.2 ...
## $ range: num [1:4] -124.7 -93.5 25.9 49
## $ names: chr [1:21] "arizona" "california" "colorado" "idaho" ...
## - attr(*, "class")= chr "map"
The “geometry” of the data can be seen by plotting it, as a scatter plot:
plot(wus_map, pch=16, cex=0.5)
What we want, however is a base map of class sf
(i.e. a simple-features object). That can be done by wrapping the map()
by the st_as_sf()
function:
<- st_as_sf(map("state", regions=c("washington", "oregon", "california", "idaho", "nevada",
wus_sf "montana", "utah", "arizona", "wyoming", "colorado", "new mexico", "north dakota", "south dakota",
"nebraska", "kansas", "oklahoma", "texas"), plot = FALSE, fill = TRUE))
It can be verified that the class of wus_sf
is indeed a simple feature sf
as well as a standard dataframe, data.frame
:
class(wus_sf)
## [1] "sf" "data.frame"
Plot wus_sf
plot(wus_sf)
and just the geometry (i.e. outlines)
plot(st_geometry(wus_sf))
The contents of the object can be listed using head()
, which describes the object, and as in its usual application, lists the first few lines:
head(wus_sf)
## Simple feature collection with 6 features and 1 field
## geometry type: MULTIPOLYGON
## dimension: XY
## bbox: xmin: -124.3834 ymin: 31.34652 xmax: -94.58387 ymax: 49.00508
## geographic CRS: WGS 84
## ID geom
## 1 arizona MULTIPOLYGON (((-114.6374 3...
## 2 california MULTIPOLYGON (((-120.006 42...
## 3 colorado MULTIPOLYGON (((-102.0552 4...
## 4 idaho MULTIPOLYGON (((-117.0266 4...
## 5 kansas MULTIPOLYGON (((-94.63544 3...
## 6 montana MULTIPOLYGON (((-104.0491 4...
Here’s a ggplot2
map:
ggplot() + geom_sf(data = wus_sf) + theme_bw()
Similar basemaps with the outlines of the U.S., Canada, and Mexico, and of the coterminous U.S. can also be extracted.
<- st_as_sf(map("world", regions=c("usa", "canada", "mexico"), plot = FALSE, fill = TRUE))
na_sf <- st_as_sf(map("state", plot = FALSE, fill = TRUE)) conus_sf
Because na_sf
and conus_sf
are data.frames, they can be combined in the usual way, using rbind()
:
<- rbind(na_sf, conus_sf) na2_sf
Verify that the objects are indeed combined:
head(na2_sf)
## Simple feature collection with 6 features and 1 field
## geometry type: MULTIPOLYGON
## dimension: XY
## bbox: xmin: -178.1945 ymin: 14.54541 xmax: 179.78 ymax: 83.11611
## geographic CRS: WGS 84
## ID geom
## 1 Canada MULTIPOLYGON (((-59.7876 43...
## 2 Mexico MULTIPOLYGON (((-91.68369 1...
## 3 USA MULTIPOLYGON (((-155.5813 1...
## 4 alabama MULTIPOLYGON (((-87.46201 3...
## 5 arizona MULTIPOLYGON (((-114.6374 3...
## 6 arkansas MULTIPOLYGON (((-94.05103 3...
Plot the combined object:
ggplot() + geom_sf(data = na2_sf) + theme_bw()
The precipitation-ratio data can be read in the usual way:
<- "/Users/bartlein/Documents/geog495/data/csv/wus_pratio.csv"
csvfile <- read.csv(csvfile)
wus_pratio names(wus_pratio)
## [1] "lat" "lon" "elev" "pjanpann" "pfebpann" "pmarpann" "paprpann" "pmaypann" "pjunpann"
## [10] "pjulpann" "paugpann" "pseppann" "poctpann" "pnovpann" "pdecpann"
Get the July-to-January precipitation ratio. (The input values are all ratios themselves of the value for a particular month to the annual value. Dividing the July ratio by the January ratio cancles out annual precipitation, and gives us the ratio we want.)
# get July to annual precipitation ratios
$pjulpjan <- wus_pratio$pjulpann/wus_pratio$pjanpann # pann values cancel out
wus_pratiohead(wus_pratio)
## lat lon elev pjanpann pfebpann pmarpann paprpann pmaypann pjunpann pjulpann paugpann
## 1 42.1833 -119.9000 1397 0.0987 0.1073 0.1030 0.1030 0.0901 0.0730 0.0429 0.0429
## 2 42.9667 -120.0000 1320 0.0610 0.0563 0.0939 0.1080 0.1362 0.1033 0.0704 0.0798
## 3 44.4000 -123.7500 70 0.1533 0.1348 0.1121 0.0756 0.0468 0.0266 0.0086 0.0129
## 4 44.9167 -120.7333 866 0.1186 0.0904 0.0904 0.0791 0.0989 0.0734 0.0311 0.0424
## 5 42.2500 -123.1667 391 0.1547 0.1406 0.1141 0.0781 0.0469 0.0250 0.0078 0.0172
## 6 45.7167 -120.2000 84 0.1522 0.1087 0.0826 0.0739 0.0739 0.0391 0.0217 0.0304
## pseppann poctpann pnovpann pdecpann pjulpjan
## 1 0.0515 0.0687 0.1245 0.1030 0.43465046
## 2 0.0610 0.0845 0.0798 0.0704 1.15409836
## 3 0.0322 0.0657 0.1584 0.1730 0.05609915
## 4 0.0537 0.0678 0.1356 0.1158 0.26222597
## 5 0.0234 0.0703 0.1641 0.1562 0.05042017
## 6 0.0478 0.0739 0.1348 0.1609 0.14257556
To produce a map with “nice” class-interval coloring of the symbols, we’ll want to create a factor using the findInterval()
function from the classInt
package. This is done by first specifiying the cutpoints (here a quasi-logrithmic scale appropriate for ratios), then appying the findInterval()
function and the factor()
function.
# convert the (continous) preciptation ratio to a factor
<- c(0.0, .100, .200, .500, .800, 1.0, 1.25, 2.0, 5.0, 10.0, 9999.0)
cutpts <- factor(findInterval(wus_pratio$pjulpjan, cutpts))
pjulpjan_factor head(cbind(wus_pratio$pjulpjan, pjulpjan_factor, cutpts[pjulpjan_factor]))
## pjulpjan_factor
## [1,] 0.43465046 3 0.2
## [2,] 1.15409836 6 1.0
## [3,] 0.05609915 1 0.0
## [4,] 0.26222597 3 0.2
## [5,] 0.05042017 1 0.0
## [6,] 0.14257556 2 0.1
Map the outlines, and overlay the point data:
## ggplot2 map of pjulpjan
ggplot() +
geom_sf(data = na2_sf, fill=NA, color="gray") +
geom_sf(data = wus_sf, fill=NA, color="black") +
geom_point(aes(wus_pratio$lon, wus_pratio$lat, color = pjulpjan_factor), size = 1.0 ) +
scale_color_brewer(type = "div", palette = "PRGn", aesthetics = "color", direction = 1,
labels = c("0.0 - 0.1", "0.1 - 0.2", "0.2 - 0.5", "0.5 - 0.8", "0.8 - 1.0",
"1.0 - 1.25", "1.25 - 2.0", "2.0 - 5.0", "5.0 - 10.0", "> 10.0"),
name = "Jul:Jan Ppt. Ratio") +
labs(x = "Longitude", y = "Latitude") +
coord_sf(crs = st_crs(wus_sf), xlim = c(-125, -90), ylim = c(25, 50)) +
theme_bw()
Note the build-up of the differnt layers on the map: First, the combined (na2_sf
) outlines of the U.S. Canada and Mexico and the coterminous U.S. states are plotted in gray, then the specific outlines of the western U.S. states are added in black (to emphasize the focus on the western U.S.), and finally the precipitation ratios are plotted as symbols on top. The map clearly shows the spatial variations in precipitation seasonality: winer wet along the west coast and intermountain West, and summer wet over the Great Plains, and eastern Rocky Mtns. The interesting regions on the map are those where there is mixture or sharp contrast in precipitation seasonality.
The above map is ok, but by not being equal-area, it would not support well the task of estimating the relative areas of summer-wet and winter-wet precipitation regimes. Projection, or spatial transformation is easily done with spatial features. First, the st_crs()
function is used to specify the coordinate reference system (CRS) for the desired projection (in this case a Labert equal-area projection, centered on 30 N and 110 W), then the st_transform()
funtion does the projection.
# projection of wus_sf
= st_crs("+proj=laea +lat_0=30 +lon_0=-110") # Lambert equal area
laea = st_transform(wus_sf, laea) wus_sf_proj
Here is the projected wus_sf
object, with a graticule overlaid:
plot(st_geometry(wus_sf_proj), axes = TRUE)
plot(st_geometry(st_graticule(wus_sf_proj)), axes = TRUE, add = TRUE)
Note that the coorinates of the map are now in metres.
Here’s a nicer map, with the graticule labelled:
ggplot() + geom_sf(data = wus_sf_proj) + theme_bw()
Project the other outlines.
# project the other outlines
= st_transform(na_sf, laea)
na_sf_proj = st_transform(conus_sf, laea)
conus_sf_proj = st_transform(na2_sf, laea) na2_sf_proj
Now project the precipitation ratio data. First, verify that the data currently are just a simple dataframe:
class(wus_pratio)
## [1] "data.frame"
We can convert it to an sf
object using the st_as_sf()
function, specifying the names of the variables that provide the coordinates of the points.
<- st_as_sf(wus_pratio, coords = c("lon", "lat")) wus_pratio_sf
Verify that wus_pratio_sf
is an sf
object now.
class(wus_pratio_sf)
## [1] "sf" "data.frame"
Get as summary of the object:
wus_pratio_sf
## Simple feature collection with 3728 features and 14 fields
## geometry type: POINT
## dimension: XY
## bbox: xmin: -124.5667 ymin: 25.9 xmax: -93.5667 ymax: 49
## CRS: NA
## First 10 features:
## elev pjanpann pfebpann pmarpann paprpann pmaypann pjunpann pjulpann paugpann pseppann poctpann
## 1 1397 0.0987 0.1073 0.1030 0.1030 0.0901 0.0730 0.0429 0.0429 0.0515 0.0687
## 2 1320 0.0610 0.0563 0.0939 0.1080 0.1362 0.1033 0.0704 0.0798 0.0610 0.0845
## 3 70 0.1533 0.1348 0.1121 0.0756 0.0468 0.0266 0.0086 0.0129 0.0322 0.0657
## 4 866 0.1186 0.0904 0.0904 0.0791 0.0989 0.0734 0.0311 0.0424 0.0537 0.0678
## 5 391 0.1547 0.1406 0.1141 0.0781 0.0469 0.0250 0.0078 0.0172 0.0234 0.0703
## 6 84 0.1522 0.1087 0.0826 0.0739 0.0739 0.0391 0.0217 0.0304 0.0478 0.0739
## 7 525 0.1255 0.0976 0.1056 0.0857 0.0777 0.0458 0.0259 0.0299 0.0438 0.0737
## 8 859 0.1197 0.0940 0.0883 0.0883 0.1054 0.0684 0.0342 0.0399 0.0456 0.0741
## 9 3 0.1431 0.1173 0.1097 0.0733 0.0487 0.0381 0.0170 0.0182 0.0387 0.0833
## 10 1284 0.1287 0.0951 0.0951 0.0690 0.0784 0.0709 0.0429 0.0448 0.0466 0.0522
## pnovpann pdecpann pjulpjan geometry
## 1 0.1245 0.1030 0.43465046 POINT (-119.9 42.1833)
## 2 0.0798 0.0704 1.15409836 POINT (-120 42.9667)
## 3 0.1584 0.1730 0.05609915 POINT (-123.75 44.4)
## 4 0.1356 0.1158 0.26222597 POINT (-120.7333 44.9167)
## 5 0.1641 0.1562 0.05042017 POINT (-123.1667 42.25)
## 6 0.1348 0.1609 0.14257556 POINT (-120.2 45.7167)
## 7 0.1434 0.1414 0.20637450 POINT (-122.7167 42.2167)
## 8 0.1254 0.1168 0.28571429 POINT (-120.7167 44.75)
## 9 0.1566 0.1548 0.11879804 POINT (-123.8833 46.15)
## 10 0.1325 0.1418 0.33333333 POINT (-118.4833 44.5667)
Note that the object has a new column, geometry
. Note also that the object does not have a CRS. (The attributes epsg
and proj4string
are NA
.) Set the CRS (knowing that the data are currently unprojected, i.e. the CRS is longtude and latitude.
# set crs
st_crs(wus_pratio_sf) <- st_crs("+proj=longlat")
Verify that the CRS has been set:
st_crs(wus_pratio_sf)
## Coordinate Reference System:
## User input: +proj=longlat
## wkt:
## 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]]]]
Now project the point data:
# projection of wus_pratio_sf
= st_crs("+proj=laea +lat_0=30 +lon_0=-110") # Lambert equal area
laea = st_transform(wus_pratio_sf, laea) wus_pratio_sf_proj
# plot the projected points
plot(st_geometry(wus_pratio_sf_proj), pch=16, cex=0.5, axes = TRUE)
plot(st_geometry(st_graticule(wus_pratio_sf_proj)), axes = TRUE, add = TRUE)
Map the projected data:
# ggplot2 map of pjulpman projected
ggplot() +
geom_sf(data = na2_sf_proj, fill=NA, color="gray") +
geom_sf(data = wus_sf_proj, fill=NA) +
geom_sf(data = wus_pratio_sf_proj, aes(color = pjulpjan_factor), size = 1.0 ) +
scale_color_brewer(type = "div", palette = "PRGn", aesthetics = "color", direction = 1,
labels = c("0.0 - 0.1", "0.1 - 0.2", "0.2 - 0.5", "0.5 - 0.8", "0.8 - 1.0",
"1.0 - 1.25", "1.25 - 2.0", "2.0 - 5.0", "5.0 - 10.0", "> 10.0"),
name = "Jul:Jan Ppt. Ratio") +
labs(x = "Longitude", y = "Latitude") +
coord_sf(crs = st_crs(wus_sf_proj), xlim = c(-1400000, 1500000), ylim = c(-400000, 2150000)) +
theme_bw()
In addition to the web pages listed above, simple features and mapping are discussed in the following Bookdown eBooks:
Lovelace, R., J. Nowosad, and J. Muenchow, 2019, Geocomputation with R [https://bookdown.org/robinlovelace/geocompr/]
Pebesma, E. and R. Bivand, 2020, Spatial Data Science [https://keen-swartz-3146c4.netlify.com]
Also worth looking at is chapter 3 in Bivand, R., et al. (2013) Applied Spatial Data Analysis with R, 2nd edition, which describes the sp
package approach to mapping. (Search for the eBook version on the UO Library page [http://library.uoregon.edu]) (The whole book is worth looking at, but Ch. 3 is the key reference for the display of spatial data (i.e. mapping) using the older sp
package.)