<- "https://dev.nceas.ucsb.edu/knb/d1/mn/v2/object/urn%3Auuid%3Aaceaecb2-1ce0-4d41-a839-d3607d32bb58"
knb_url
download.file(url = knb_url, destfile = 'shapefile_demo_data.zip')
unzip('shapefile_demo_data.zip', exdir = 'data')
file.remove('shapefile_demo_data.zip')
Learning Objectives
- How to use the
sf
package to wrangle spatial data - Static mapping with ggplot
- Adding basemaps to static maps
- Interactive mapping with
leaflet
13.1 Brief introduction to sf
From the sf
vignette:
Simple features or simple feature access refers to a formal standard (ISO 19125-1:2004) that describes how objects in the real world can be represented in computers, with emphasis on the spatial geometry of these objects. It also describes how such objects can be stored in and retrieved from databases, and which geometrical operations should be defined for them.
The sf
package is an R implementation of Simple Features. This package incorporates:
- a new spatial data class system in R
- functions for reading and writing spatial data
- tools for spatial operations on vectors
Most of the functions in this package starts with prefix st_
which stands for spatial and temporal.
In this lesson, our goal is to use a shapefile of Alaska regions and rivers, and data on population in Alaska by community to create a map that looks like this:
13.2 About the data
All of the data used in this tutorial are simplified versions of real datasets available on the KNB Data Repository. We are using simplified datasets to ease the processing burden on all our computers since the original geospatial datasets are high-resolution. These simplified versions of the datasets may contain topological errors.
The spatial data we will be using to create the map are:
Data | Original datasets |
---|---|
Alaska regional boundaries | Jared Kibele and Jeanette Clark. 2018. State of Alaska’s Salmon and People Regional Boundaries. Knowledge Network for Biocomplexity. doi:10.5063/F1125QWP. |
Community locations and population | Jeanette Clark, Sharis Ochs, Derek Strong, and National Historic Geographic Information System. 2018. Languages used in Alaskan households, 1990-2015. Knowledge Network for Biocomplexity. doi:10.5063/F11G0JHX. |
Alaska rivers | The rivers shapefile is a simplified version of Jared Kibele and Jeanette Clark. Rivers of Alaska grouped by SASAP region, 2018. Knowledge Network for Biocomplexity. doi:10.5063/F1SJ1HVW. |
- Navigate to this dataset on KNB’s test site and download the zip folder.
- Upload the zip folder to the
data
folder in thetraining_{USERNAME}
project. You don’t need to unzip the folder ahead of time, uploading will automatically unzip the folder.- Alternatively, programmatically download and extract the demo data with:
- Create a new Quarto file.
- Title it “Working with Spatial Data in R”
- Save the file and name it “intro-to-spatial-data”.
- Load the following libraries at the top of your Quarto file.
library(readr)
library(sf)
library(ggplot2)
library(leaflet)
library(scales)
library(ggspatial)
library(dplyr)
13.3 Exploring the data using plot()
and st_crs()
First let’s read in the shapefile of regional boundaries in Alaska using read_sf()
and then create a basic plot of the data plot()
.
# read in shapefile using read_sf()
<- read_sf("data/ak_regions_simp.shp") ak_regions
# quick plot
plot(ak_regions)
We can also examine its class using class()
.
class(ak_regions)
[1] "sf" "tbl_df" "tbl" "data.frame"
sf
objects usually have two types of classes: sf
and data.frame
.
Since our shapefile object has the data.frame
class, viewing the contents of the object using the head()
function or other exploratory functions shows similar results as if we read in data using read.csv()
or read_csv()
.
But, unlike a typical data.frame
, an sf
object has spatial metadata (geometry type
, dimension
, bbox
, epsg (SRID)
, proj4string
) and an additional column typically named geometry
that contains the spatial data.
head(ak_regions)
Simple feature collection with 6 features and 3 fields
Geometry type: MULTIPOLYGON
Dimension: XY
Bounding box: xmin: -179.2296 ymin: 51.15702 xmax: 179.8567 ymax: 71.43957
Geodetic CRS: WGS 84
# A tibble: 6 × 4
region_id region mgmt_area geometry
<int> <chr> <dbl> <MULTIPOLYGON [°]>
1 1 Aleutian Islands 3 (((-171.1345 52.44974, -171.1686 52.4174…
2 2 Arctic 4 (((-139.9552 68.70597, -139.9893 68.7051…
3 3 Bristol Bay 3 (((-159.8745 58.62778, -159.8654 58.6137…
4 4 Chignik 3 (((-155.8282 55.84638, -155.8049 55.8655…
5 5 Copper River 2 (((-143.8874 59.93931, -143.9165 59.9403…
6 6 Kodiak 3 (((-151.9997 58.83077, -152.0358 58.8271…
glimpse(ak_regions)
Rows: 13
Columns: 4
$ region_id <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13
$ region <chr> "Aleutian Islands", "Arctic", "Bristol Bay", "Chignik", "Cop…
$ mgmt_area <dbl> 3, 4, 3, 3, 2, 3, 4, 4, 2, 4, 2, 1, 4
$ geometry <MULTIPOLYGON [°]> MULTIPOLYGON (((-171.1345 5..., MULTIPOLYGON (((-139.9552 6.…
13.3.1 Coordinate Reference System (CRS)
Every sf
object needs a coordinate reference system (or crs
) defined in order to work with it correctly. A coordinate reference system contains both a datum and a projection.
The datum is how you georeference your points (in 3 dimensions!) onto a spheroid, or the Earth. The Earth is not a perfect sphere and there are many ways to describe its shape. For example, is the Earth shaped like a lemon, lime, or orange? The shape, or datum, that you choose will depend on the scope of your project (for instance, local vs. global) and the specific locations.
The projection is how these points are mathematically transformed to represent the georeferenced point on a flat piece of paper. Since you will visualize a 3D object onto a 2D space, there will be some distortions depending on the projection that you choose. Analogously, how do you peel the fruit (representing the Earth) and flatten the peel?
All coordinate reference systems require a datum. However, some coordinate reference systems are “unprojected” (also called geographic coordinate systems). Coordinates in latitude/longitude use a geographic (unprojected) coordinate system. One of the most commonly used geographic coordinate systems is WGS 1984.
ESRI has a blog post that explains these concepts in more detail with very helpful diagrams and examples.
You can view what crs
is set by using the function st_crs()
.
st_crs(ak_regions)
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["latitude",north,
ORDER[1],
ANGLEUNIT["degree",0.0174532925199433]],
AXIS["longitude",east,
ORDER[2],
ANGLEUNIT["degree",0.0174532925199433]],
ID["EPSG",4326]]
This looks pretty confusing. Without getting into the details, that long string says that this data has a geographic coordinate system (WGS84) with no projection. A convenient way to reference crs
quickly is by using the EPSG code, a number that represents a standard projection and datum. You can check out a list of (lots!) of EPSG codes here.
We will use multiple EPSG codes in this lesson. Here they are, along with their more readable names:
- 3338: Alaska Albers (projected CRS)
- 4326: WGS84 (World Geodetic System 1984), used in GPS (unprojected CRS)
- 3857: Pseudo-Mercator, used in Google Maps, OpenStreetMap, Bing, ArcGIS, ESRI (projected CRS)
You will often need to transform your geospatial data from one coordinate system to another. The st_transform()
function does this quickly for us. You may have noticed the maps above looked wonky because of the dateline. We might want to set a different projection for this data so it plots nicer. A good one for Alaska is called the Alaska Albers projection, with an EPSG code of 3338.
<- ak_regions %>%
ak_regions_3338 st_transform(crs = 3338)
st_crs(ak_regions_3338)
Coordinate Reference System:
User input: EPSG:3338
wkt:
PROJCRS["NAD83 / Alaska Albers",
BASEGEOGCRS["NAD83",
DATUM["North American Datum 1983",
ELLIPSOID["GRS 1980",6378137,298.257222101,
LENGTHUNIT["metre",1]]],
PRIMEM["Greenwich",0,
ANGLEUNIT["degree",0.0174532925199433]],
ID["EPSG",4269]],
CONVERSION["Alaska Albers (meters)",
METHOD["Albers Equal Area",
ID["EPSG",9822]],
PARAMETER["Latitude of false origin",50,
ANGLEUNIT["degree",0.0174532925199433],
ID["EPSG",8821]],
PARAMETER["Longitude of false origin",-154,
ANGLEUNIT["degree",0.0174532925199433],
ID["EPSG",8822]],
PARAMETER["Latitude of 1st standard parallel",55,
ANGLEUNIT["degree",0.0174532925199433],
ID["EPSG",8823]],
PARAMETER["Latitude of 2nd standard parallel",65,
ANGLEUNIT["degree",0.0174532925199433],
ID["EPSG",8824]],
PARAMETER["Easting at false origin",0,
LENGTHUNIT["metre",1],
ID["EPSG",8826]],
PARAMETER["Northing at false origin",0,
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["Topographic mapping (small scale)."],
AREA["United States (USA) - Alaska."],
BBOX[51.3,172.42,71.4,-129.99]],
ID["EPSG",3338]]
plot(ak_regions_3338)
Much better!
13.4 sf
& the Tidyverse
sf objects can be used as a regular data.frame
object in many operations. We already saw the results of plot()
and head()
.
Since sf
objects are data.frames, they play nicely with packages in the tidyverse
. Here are a couple of simple examples:
13.4.1 select()
# returns the names of all the columns in dataset
colnames(ak_regions_3338)
[1] "region_id" "region" "mgmt_area" "geometry"
%>%
ak_regions_3338 select(region)
Simple feature collection with 13 features and 1 field
Geometry type: MULTIPOLYGON
Dimension: XY
Bounding box: xmin: -2175328 ymin: 405653 xmax: 1579226 ymax: 2383770
Projected CRS: NAD83 / Alaska Albers
# A tibble: 13 × 2
region geometry
<chr> <MULTIPOLYGON [m]>
1 Aleutian Islands (((-1156666 420855.1, -1159837 417990.3, -1161898 41694…
2 Arctic (((571289.9 2143072, 569941.5 2142691, 569158.2 2142146…
3 Bristol Bay (((-339688.6 973904.9, -339302 972297.3, -339229.2 9710…
4 Chignik (((-114381.9 649966.8, -112866.8 652065.8, -108836.8 65…
5 Copper River (((561012 1148301, 559393.7 1148169, 557797.7 1148492, …
6 Kodiak (((115112.5 983293, 113051.3 982825.9, 110801.3 983211.…
7 Kotzebue (((-678815.3 1819519, -677555.2 1820698, -675557.8 1821…
8 Kuskokwim (((-1030125 1281198, -1029858 1282333, -1028980 1284032…
9 Cook Inlet (((35214.98 1002457, 36660.3 1002038, 36953.11 1001186,…
10 Norton Sound (((-848357 1636692, -846510 1635203, -840513.7 1632225,…
11 Prince William Sound (((426007.1 1087250, 426562.5 1088591, 427711.6 1089991…
12 Southeast (((1287777 744574.1, 1290183 745970.8, 1292940 746262.7…
13 Yukon (((-375318 1473998, -373723.9 1473487, -373064.8 147393…
Note the sticky geometry column! The geometry column will stay with your sf
object even if it is not called explicitly.
13.4.2 filter()
unique(ak_regions_3338$region)
[1] "Aleutian Islands" "Arctic" "Bristol Bay"
[4] "Chignik" "Copper River" "Kodiak"
[7] "Kotzebue" "Kuskokwim" "Cook Inlet"
[10] "Norton Sound" "Prince William Sound" "Southeast"
[13] "Yukon"
%>%
ak_regions_3338 filter(region == "Southeast")
Simple feature collection with 1 feature and 3 fields
Geometry type: MULTIPOLYGON
Dimension: XY
Bounding box: xmin: 559475.7 ymin: 722450 xmax: 1579226 ymax: 1410576
Projected CRS: NAD83 / Alaska Albers
# A tibble: 1 × 4
region_id region mgmt_area geometry
* <int> <chr> <dbl> <MULTIPOLYGON [m]>
1 12 Southeast 1 (((1287777 744574.1, 1290183 745970.8, 1292940 …
13.5 Spatial Joins
You can also use the sf
package to create spatial joins, useful for when you want to utilize two datasets together.
We have some population data, but it gives the population by city, not by region. To determine the population per region we will need to:
- Read in the population data from a
csv
and turn it into ansf
object - Use a spatial join (
st_join()
) to assign each city to a region - Use
group_by()
andsummarize()
to calculate the total population by region - Save the spatial object you created using
write_sf()
1. Read in alaska_population.csv
using read.csv()
# read in population data
<- read_csv("data/alaska_population.csv") pop
Turn pop
into a spatial object
The st_join()
function is a spatial left join. The arguments for both the left and right tables are objects of class sf
which means we will first need to turn our population data.frame
with latitude and longitude coordinates into an sf
object.
We can do this easily using the st_as_sf()
function, which takes as arguments the coordinates and the crs
. The remove = F
specification here ensures that when we create our geometry
column, we retain our original lat
lng
columns, which we will need later for plotting. Although it isn’t said anywhere explicitly in the file, let’s assume that the coordinate system used to reference the latitude longitude coordinates is WGS84, which has a crs
number of 4326.
<- st_as_sf(pop,
pop_4326 coords = c('lng', 'lat'),
crs = 4326,
remove = F)
head(pop_4326)
Simple feature collection with 6 features and 5 fields
Geometry type: POINT
Dimension: XY
Bounding box: xmin: -176.6581 ymin: 51.88 xmax: -154.1703 ymax: 62.68889
Geodetic CRS: WGS 84
# A tibble: 6 × 6
year city lat lng population geometry
<dbl> <chr> <dbl> <dbl> <dbl> <POINT [°]>
1 2015 Adak 51.9 -177. 122 (-176.6581 51.88)
2 2015 Akhiok 56.9 -154. 84 (-154.1703 56.94556)
3 2015 Akiachak 60.9 -161. 562 (-161.4314 60.90944)
4 2015 Akiak 60.9 -161. 399 (-161.2139 60.91222)
5 2015 Akutan 54.1 -166. 899 (-165.7731 54.13556)
6 2015 Alakanuk 62.7 -165. 777 (-164.6153 62.68889)
2. Join population data with Alaska regions data using st_join()
Now we can do our spatial join! You can specify what geometry function the join uses (st_intersects
, st_within
, st_crosses
, st_is_within_distance
…) in the join
argument. The geometry function you use will depend on what kind of operation you want to do, and the geometries of your shapefiles.
In this case, we want to find what region each city falls within, so we will use st_within
.
<- st_join(pop_4326,
pop_joined
ak_regions_3338, join = st_within)
This gives an error!
Error: st_crs(x) == st_crs(y) is not TRUE
Turns out, this won’t work right now because our coordinate reference systems are not the same. Luckily, this is easily resolved using st_transform()
, and projecting our population object into Alaska Albers.
<- st_transform(pop_4326,
pop_3338 crs = 3338)
<- st_join(pop_3338,
pop_joined
ak_regions_3338, join = st_within)
head(pop_joined)
Simple feature collection with 6 features and 8 fields
Geometry type: POINT
Dimension: XY
Bounding box: xmin: -1537925 ymin: 472626.9 xmax: -10340.71 ymax: 1456223
Projected CRS: NAD83 / Alaska Albers
# A tibble: 6 × 9
year city lat lng population geometry region_id region
<dbl> <chr> <dbl> <dbl> <dbl> <POINT [m]> <int> <chr>
1 2015 Adak 51.9 -177. 122 (-1537925 472626.9) 1 Aleutian I…
2 2015 Akhiok 56.9 -154. 84 (-10340.71 770998.4) 6 Kodiak
3 2015 Akiac… 60.9 -161. 562 (-400885.5 1236460) 8 Kuskokwim
4 2015 Akiak 60.9 -161. 399 (-389165.7 1235475) 8 Kuskokwim
5 2015 Akutan 54.1 -166. 899 (-766425.7 526057.8) 1 Aleutian I…
6 2015 Alaka… 62.7 -165. 777 (-539724.9 1456223) 13 Yukon
# ℹ 1 more variable: mgmt_area <dbl>
3. Calculate the total population by region using group_by()
and summarize()
Next we compute the total population for each region. In this case, we want to do a group_by()
and summarize()
as this were a regular data.frame
. Otherwise all of our point geometries would be included in the aggregation, which is not what we want. Our goal is just to get the total population by region. We remove the sticky geometry using as.data.frame()
, on the advice of the sf::tidyverse
help page.
<- pop_joined %>%
pop_region as.data.frame() %>%
group_by(region) %>%
summarise(total_pop = sum(population))
head(pop_region)
# A tibble: 6 × 2
region total_pop
<chr> <dbl>
1 Aleutian Islands 8840
2 Arctic 8419
3 Bristol Bay 6947
4 Chignik 311
5 Cook Inlet 408254
6 Copper River 2294
And use a regular left_join()
to get the information back to the Alaska region shapefile. Note that we need this step in order to regain our region geometries so that we can make some maps.
<- left_join(ak_regions_3338,
pop_region_3338
pop_region, by = "region")
# plot to check
plot(pop_region_3338["total_pop"])
So far, we have learned how to use sf
and dplyr
to use a spatial join on two datasets and calculate a summary metric from the result of that join.
Say we want to calculate the population by Alaska management area, as opposed to region.
<- pop_region_3338 %>%
pop_mgmt_3338 group_by(mgmt_area) %>%
summarize(total_pop = sum(total_pop))
plot(pop_mgmt_3338["total_pop"])
Notice that the region geometries were combined into a single polygon for each management area.
If we don’t want to combine geometries, we can specify do_union = F
as an argument.
<- pop_region_3338 %>%
pop_mgmt_3338 group_by(mgmt_area) %>%
summarize(total_pop = sum(total_pop), do_union = F)
plot(pop_mgmt_3338["total_pop"])
4. Save the spatial object to a new file using write_sf()
Save the spatial object to disk using write_sf()
and specifying the filename. Writing your file with the extension .shp
will assume an ESRI driver driver, but there are many other format options available.
write_sf(pop_region_3338, "data/ak_regions_population.shp")
13.6 Visualize with ggplot
ggplot2
now has integrated functionality to plot sf objects using geom_sf()
.
We can plot sf
objects just like regular data.frames using geom_sf
.
ggplot(pop_region_3338) +
geom_sf(aes(fill = total_pop)) +
labs(fill = "Total Population") +
scale_fill_continuous(low = "khaki",
high = "firebrick",
labels = comma) +
theme_bw()
We can also plot multiple shapefiles in the same plot. Say if we want to visualize rivers in Alaska, in addition to the location of communities, since many communities in Alaska are on rivers. We can read in a rivers shapefile, double-check the crs
to make sure it is what we need, and then plot all three shapefiles
- the regional population (polygons),
- the locations of cities (points), and
- the rivers (linestrings).
<- read_sf("/data/shapefiles/ak_rivers_simp.shp") rivers_3338
st_crs(rivers_3338)
Coordinate Reference System:
User input: Albers
wkt:
PROJCRS["Albers",
BASEGEOGCRS["GCS_GRS 1980(IUGG, 1980)",
DATUM["D_unknown",
ELLIPSOID["GRS80",6378137,298.257222101,
LENGTHUNIT["metre",1,
ID["EPSG",9001]]]],
PRIMEM["Greenwich",0,
ANGLEUNIT["Degree",0.0174532925199433]]],
CONVERSION["unnamed",
METHOD["Albers Equal Area",
ID["EPSG",9822]],
PARAMETER["Latitude of false origin",50,
ANGLEUNIT["Degree",0.0174532925199433],
ID["EPSG",8821]],
PARAMETER["Longitude of false origin",-154,
ANGLEUNIT["Degree",0.0174532925199433],
ID["EPSG",8822]],
PARAMETER["Latitude of 1st standard parallel",55,
ANGLEUNIT["Degree",0.0174532925199433],
ID["EPSG",8823]],
PARAMETER["Latitude of 2nd standard parallel",65,
ANGLEUNIT["Degree",0.0174532925199433],
ID["EPSG",8824]],
PARAMETER["Easting at false origin",0,
LENGTHUNIT["metre",1],
ID["EPSG",8826]],
PARAMETER["Northing at false origin",0,
LENGTHUNIT["metre",1],
ID["EPSG",8827]]],
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]]]]
Note that although no EPSG code is set explicitly, with some sleuthing we can determine that this is EPSG:3338
. This site is helpful for looking up EPSG codes.
ggplot() +
geom_sf(data = pop_region_3338,
aes(fill = total_pop)) +
geom_sf(data = pop_3338,
size = 0.5) +
geom_sf(data = rivers_3338,
aes(linewidth = StrOrder)) +
scale_linewidth(range = c(0.05, 0.5),
guide = "none") +
labs(title = "Total Population by Alaska Region",
fill = "Total Population") +
scale_fill_continuous(low = "khaki",
high = "firebrick",
labels = comma) +
theme_bw()
13.6.1 Incorporate base maps into static maps using ggspatial
The ggspatial
package has a function that can add tile layers from a few predefined tile sources like OpenStreetMap. Making sure that the tiles will plot correctly can be a finicky, so we will reproject our population data into the OpenStreetMap projection, Pseudo-Mercator (EPSG 3857), first.
Then we will add ggspatial::annotation_map_tile()
function into ggplot
to add a base map to our map. This can take a couple of minutes to load.
<- st_transform(pop_3338,
pop_3857 crs = 3857)
ggplot(data = pop_3857) +
::annotation_map_tile(type = "osm", zoom = 4) + # higher zoom values are more detailed
ggspatialgeom_sf(aes(color = population),
fill = NA) +
scale_color_continuous(low = "darkkhaki",
high = "firebrick",
labels = comma)
|
| | 0%
|
|======== | 11%
|
|================ | 22%
|
|======================= | 33%
|
|=============================== | 44%
|
|======================================= | 56%
|
|=============================================== | 67%
|
|====================================================== | 78%
|
|============================================================== | 89%
|
|======================================================================| 100%
13.7 Visualize sf
objects with leaflet
We can also make an interactive map from our data above using leaflet
.
leaflet
(unlike ggplot
) will project data for you. The catch is that you have to give it both a projection (like Alaska Albers), and that your shapefile must use a geographic coordinate system. This means that we need to use our shapefile with the 4326 EPSG code. Remember you can always check what crs
you have set using st_crs
.
Here we define a leaflet projection for Alaska Albers, and save it as a variable to use later.
<- leaflet::leafletCRS(
epsg3338 crsClass = "L.Proj.CRS",
code = "EPSG:3338",
proj4def = "+proj=aea +lat_1=55 +lat_2=65 +lat_0=50 +lon_0=-154 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs",
resolutions = 2 ^ (16:7)
)
You might notice that this looks familiar! The syntax is a bit different, but most of this information is also contained within the crs
of our shapefile:
st_crs(pop_region_3338)
Coordinate Reference System:
User input: EPSG:3338
wkt:
PROJCRS["NAD83 / Alaska Albers",
BASEGEOGCRS["NAD83",
DATUM["North American Datum 1983",
ELLIPSOID["GRS 1980",6378137,298.257222101,
LENGTHUNIT["metre",1]]],
PRIMEM["Greenwich",0,
ANGLEUNIT["degree",0.0174532925199433]],
ID["EPSG",4269]],
CONVERSION["Alaska Albers (meters)",
METHOD["Albers Equal Area",
ID["EPSG",9822]],
PARAMETER["Latitude of false origin",50,
ANGLEUNIT["degree",0.0174532925199433],
ID["EPSG",8821]],
PARAMETER["Longitude of false origin",-154,
ANGLEUNIT["degree",0.0174532925199433],
ID["EPSG",8822]],
PARAMETER["Latitude of 1st standard parallel",55,
ANGLEUNIT["degree",0.0174532925199433],
ID["EPSG",8823]],
PARAMETER["Latitude of 2nd standard parallel",65,
ANGLEUNIT["degree",0.0174532925199433],
ID["EPSG",8824]],
PARAMETER["Easting at false origin",0,
LENGTHUNIT["metre",1],
ID["EPSG",8826]],
PARAMETER["Northing at false origin",0,
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["Topographic mapping (small scale)."],
AREA["United States (USA) - Alaska."],
BBOX[51.3,172.42,71.4,-129.99]],
ID["EPSG",3338]]
Since leaflet
requires that we use an unprojected coordinate system, let’s use st_transform()
yet again to get back to WGS84.
<- pop_region_3338 %>%
pop_region_4326 st_transform(crs = 4326)
<- leaflet(options = leafletOptions(crs = epsg3338)) %>%
m addPolygons(data = pop_region_4326,
fillColor = "gray",
weight = 1)
m
We can add labels, legends, and a color scale.
<- colorNumeric(palette = "Reds", domain = pop_region_4326$total_pop)
pal
<- leaflet(options = leafletOptions(crs = epsg3338)) %>%
m addPolygons(
data = pop_region_4326,
fillColor = ~ pal(total_pop),
weight = 1,
color = "black",
fillOpacity = 1,
label = ~ region
%>%
) addLegend(
position = "bottomleft",
pal = pal,
values = range(pop_region_4326$total_pop),
title = "Total Population"
)
m
We can also add the individual communities, with popup labels showing their population, on top of that!
<- colorNumeric(palette = "Reds", domain = pop_region_4326$total_pop)
pal
<- leaflet(options = leafletOptions(crs = epsg3338)) %>%
m addPolygons(
data = pop_region_4326,
fillColor = ~ pal(total_pop),
weight = 1,
color = "black",
fillOpacity = 1
%>%
) addCircleMarkers(
data = pop_4326,
lat = ~ lat,
lng = ~ lng,
radius = ~ log(population / 500),
# arbitrary scaling
fillColor = "gray",
fillOpacity = 1,
weight = 0.25,
color = "black",
label = ~ paste0(pop_4326$city, ", population ", comma(pop_4326$population))
%>%
) addLegend(
position = "bottomleft",
pal = pal,
values = range(pop_region_4326$total_pop),
title = "Total Population"
)
m
13.8 More Spatial Resources
There is a lot more functionality to sf
including the ability to intersect
polygons, calculate distance
, create a buffer
, and more. Here are some more great resources and tutorials for a deeper dive into this great package:
13.9 Basemaps Resources
- Documentation for the
ggmap
package