<- rast("data/MENA_noaa_projected.tif") MENA_lights
Lab 1
We will start by exploring raster data processing and choropleths we can make from it. Raster data, consisting of gridded cells, allows us to represent continuous geographic phenomena such as temperature, elevation, or satellite imagery. Choropleths, on the other hand, are an effective way to visualize spatial patterns through the use of color-coded regions, making them invaluable for displaying discrete data like population density or election results. By combining these techniques, we will gain a comprehensive toolkit for conveying complex geographical information in a visually compelling manner.
NOAA Night Lights data
Import raster data
Plot it.
plot(MENA_lights)
Have a look at the CRS.
crs(MENA_lights)
[1] "PROJCRS[\"Africa_Albers_Equal_Area_Conic\",\n BASEGEOGCRS[\"WGS 84\",\n DATUM[\"World Geodetic System 1984\",\n ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n LENGTHUNIT[\"metre\",1]]],\n PRIMEM[\"Greenwich\",0,\n ANGLEUNIT[\"degree\",0.0174532925199433]],\n ID[\"EPSG\",4326]],\n CONVERSION[\"Albers Equal Area\",\n METHOD[\"Albers Equal Area\",\n ID[\"EPSG\",9822]],\n PARAMETER[\"Latitude of false origin\",0,\n ANGLEUNIT[\"degree\",0.0174532925199433],\n ID[\"EPSG\",8821]],\n PARAMETER[\"Longitude of false origin\",25,\n ANGLEUNIT[\"degree\",0.0174532925199433],\n ID[\"EPSG\",8822]],\n PARAMETER[\"Latitude of 1st standard parallel\",20,\n ANGLEUNIT[\"degree\",0.0174532925199433],\n ID[\"EPSG\",8823]],\n PARAMETER[\"Latitude of 2nd standard parallel\",-23,\n ANGLEUNIT[\"degree\",0.0174532925199433],\n ID[\"EPSG\",8824]],\n PARAMETER[\"Easting at false origin\",0,\n LENGTHUNIT[\"metre\",1],\n ID[\"EPSG\",8826]],\n PARAMETER[\"Northing at false origin\",0,\n LENGTHUNIT[\"metre\",1],\n ID[\"EPSG\",8827]]],\n CS[Cartesian,2],\n AXIS[\"easting\",east,\n ORDER[1],\n LENGTHUNIT[\"metre\",1,\n ID[\"EPSG\",9001]]],\n AXIS[\"northing\",north,\n ORDER[2],\n LENGTHUNIT[\"metre\",1,\n ID[\"EPSG\",9001]]]]"
Import the MENA shapefile
Import the Middle East and North Africa (MENA) shapefile, plot it, and verify its Coordinate Reference System (CRS). Is it the same as the raster’s CRS?
<- read_sf("data/MENA_projected.shp")
MENA_adm1 plot(MENA_adm1$geometry)
crs(MENA_adm1)
[1] "PROJCRS[\"Africa_Albers_Equal_Area_Conic\",\n BASEGEOGCRS[\"WGS 84\",\n ENSEMBLE[\"World Geodetic System 1984 ensemble\",\n MEMBER[\"World Geodetic System 1984 (Transit)\"],\n MEMBER[\"World Geodetic System 1984 (G730)\"],\n MEMBER[\"World Geodetic System 1984 (G873)\"],\n MEMBER[\"World Geodetic System 1984 (G1150)\"],\n MEMBER[\"World Geodetic System 1984 (G1674)\"],\n MEMBER[\"World Geodetic System 1984 (G1762)\"],\n MEMBER[\"World Geodetic System 1984 (G2139)\"],\n ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n LENGTHUNIT[\"metre\",1]],\n ENSEMBLEACCURACY[2.0]],\n PRIMEM[\"Greenwich\",0,\n ANGLEUNIT[\"degree\",0.0174532925199433]],\n ID[\"EPSG\",4326]],\n CONVERSION[\"Africa_Albers_Equal_Area_Conic\",\n METHOD[\"Albers Equal Area\",\n ID[\"EPSG\",9822]],\n PARAMETER[\"Latitude of false origin\",0,\n ANGLEUNIT[\"degree\",0.0174532925199433],\n ID[\"EPSG\",8821]],\n PARAMETER[\"Longitude of false origin\",25,\n ANGLEUNIT[\"degree\",0.0174532925199433],\n ID[\"EPSG\",8822]],\n PARAMETER[\"Latitude of 1st standard parallel\",20,\n ANGLEUNIT[\"degree\",0.0174532925199433],\n ID[\"EPSG\",8823]],\n PARAMETER[\"Latitude of 2nd standard parallel\",-23,\n ANGLEUNIT[\"degree\",0.0174532925199433],\n ID[\"EPSG\",8824]],\n PARAMETER[\"Easting at false origin\",0,\n LENGTHUNIT[\"metre\",1],\n ID[\"EPSG\",8826]],\n PARAMETER[\"Northing at false origin\",0,\n LENGTHUNIT[\"metre\",1],\n ID[\"EPSG\",8827]]],\n CS[Cartesian,2],\n AXIS[\"(E)\",east,\n ORDER[1],\n LENGTHUNIT[\"metre\",1]],\n AXIS[\"(N)\",north,\n ORDER[2],\n LENGTHUNIT[\"metre\",1]],\n USAGE[\n SCOPE[\"Not known.\"],\n AREA[\"Africa\"],\n BBOX[-35,-25,39,55]],\n ID[\"ESRI\",102022]]"
Reproject the Raster
As we are using both the raster
and terra
packages to handle the raster data it is useful to write terra::
or raster::
in front of the function we are using.
We use the terra project()
function, we need to define two things:
- The object we want to reproject and
- The CRS that we want to reproject it to.
<- terra::project(MENA_lights, crs(MENA_adm1)) # reporjectig the raster data to the crs of the MENA shapefile
MENA_lights crs(MENA_lights)
[1] "PROJCRS[\"Africa_Albers_Equal_Area_Conic\",\n BASEGEOGCRS[\"WGS 84\",\n ENSEMBLE[\"World Geodetic System 1984 ensemble\",\n MEMBER[\"World Geodetic System 1984 (Transit)\"],\n MEMBER[\"World Geodetic System 1984 (G730)\"],\n MEMBER[\"World Geodetic System 1984 (G873)\"],\n MEMBER[\"World Geodetic System 1984 (G1150)\"],\n MEMBER[\"World Geodetic System 1984 (G1674)\"],\n MEMBER[\"World Geodetic System 1984 (G1762)\"],\n MEMBER[\"World Geodetic System 1984 (G2139)\"],\n ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n LENGTHUNIT[\"metre\",1]],\n ENSEMBLEACCURACY[2.0]],\n PRIMEM[\"Greenwich\",0,\n ANGLEUNIT[\"degree\",0.0174532925199433]],\n ID[\"EPSG\",4326]],\n CONVERSION[\"Africa_Albers_Equal_Area_Conic\",\n METHOD[\"Albers Equal Area\",\n ID[\"EPSG\",9822]],\n PARAMETER[\"Latitude of false origin\",0,\n ANGLEUNIT[\"degree\",0.0174532925199433],\n ID[\"EPSG\",8821]],\n PARAMETER[\"Longitude of false origin\",25,\n ANGLEUNIT[\"degree\",0.0174532925199433],\n ID[\"EPSG\",8822]],\n PARAMETER[\"Latitude of 1st standard parallel\",20,\n ANGLEUNIT[\"degree\",0.0174532925199433],\n ID[\"EPSG\",8823]],\n PARAMETER[\"Latitude of 2nd standard parallel\",-23,\n ANGLEUNIT[\"degree\",0.0174532925199433],\n ID[\"EPSG\",8824]],\n PARAMETER[\"Easting at false origin\",0,\n LENGTHUNIT[\"metre\",1],\n ID[\"EPSG\",8826]],\n PARAMETER[\"Northing at false origin\",0,\n LENGTHUNIT[\"metre\",1],\n ID[\"EPSG\",8827]]],\n CS[Cartesian,2],\n AXIS[\"(E)\",east,\n ORDER[1],\n LENGTHUNIT[\"metre\",1]],\n AXIS[\"(N)\",north,\n ORDER[2],\n LENGTHUNIT[\"metre\",1]],\n USAGE[\n SCOPE[\"Not known.\"],\n AREA[\"Africa\"],\n BBOX[-35,-25,39,55]],\n ID[\"ESRI\",102022]]"
Cropping and Masking
Cropping and masking are both spatial operations used in raster data analysis.
Cropping:
Purpose: Cropping a raster involves changing the extent of the raster dataset by specifying a new bounding box or geographic area of interest. The result is a new raster that covers only the specified region.
Typical Use: Cropping is commonly used when you want to reduce the size of a raster dataset to focus on a smaller geographic area of interest while retaining all the original data values within that area.
Masking:
Purpose: Applying a binary mask to the dataset. The mask is typically a separate raster or polygon layer where certain areas are designated as “masked” (1) or “unmasked” (0).
Typical Use: Masking is used when you want to extract or isolate specific areas or features within a raster dataset. For example, you might use a mask to extract land cover information within the boundaries of a protected national park.
In many cases, these cropping and masking are executed one after the other because it is computationally easier to crop when dealing with large datasets, and then masking.
<- crop(MENA_lights, extent(MENA_adm1))
MENA_lights_crop <- mask(MENA_lights_crop, MENA_adm1) MENA_lights_mask
Simple plot
plot(MENA_lights_mask)
plot(MENA_adm1$geometry, col= NA, add=T)
Let’s improve this a bit. Remember that there is a lot we can do with ColorBrewer.
= rev(brewer.pal(6,"YlGnBu"))
pal plot(MENA_lights_mask, breaks=c(0,10,20,30,40,Inf), col=pal)
plot(MENA_adm1$geometry, col= NA, add=T)
# Define the palette
= rev(brewer.pal(6,"YlGnBu"))
pal
# Create the base map
tm_shape(MENA_lights_mask) +
tm_raster(breaks = c(0,10,20,30,40,Inf),
palette = pal) + # Plot the raster with breaks and palette
tm_shape(MENA_adm1) +
tm_borders(lwd = 1, col = "darkgrey") + # Add borders to the administrative boundaries
tm_layout(frame = FALSE, legend.outside = TRUE, legend.outside.position = "right",
title = "Night light intensity") # Remove frame
Questions to ask yourself about how you can improve these maps, going back to geo-visualisation and choropleths.
What are the logical breaks for night lights data?
What should the colours be?
Have a look at some of the
tmap
documentation to improve your map further .
Spatial join with vector data
Keep Egypt from all Middle East and North Africa Countries.
# Subset MENA_adm1 to select only Egypt
<- MENA_adm1 %>%
Egypt filter(name == "Egypt")
# Crop MENA_adm1 raster to Egypt
<- crop(MENA_lights_mask, extent(Egypt)) Egypt_lights_mask
Using osmdata
package to query amenity data. We only query hospitals to keep it brief.
# creating bounding box for Cairo
<- getbb("Cairo Egypt")
egypt_bb <- opq(bbox = egypt_bb)
cairo
# Getting hospitals in Cairo
<- cairo %>%
amenities add_osm_feature(key = "amenity", value = c("hospital")) %>%
osmdata_sf ()
# checking object and types of data
amenities
Object of class 'osmdata' with:
$bbox : 29.7483062,31.2200331,30.3209168,31.9090054
$overpass_call : The call submitted to the overpass API
$meta : metadata including timestamp and version numbers
$osm_points : 'sf' Simple Features Collection with 1380 points
$osm_lines : NULL
$osm_polygons : 'sf' Simple Features Collection with 92 polygons
$osm_multilines : NULL
$osm_multipolygons : 'sf' Simple Features Collection with 2 multipolygons
Keeping just the OSM points from the OSM data.
<- amenities$osm_points amenities_points
Plotting
An initial plot.
# Plotting
ggplot() +
geom_sf(data = amenities$osm_points, fill = 'orange') +
geom_spatraster(data=Egypt_lights_mask, alpha = 0.5) +
coord_sf(xlim = c(30.852356, 31.717529), ylim = c(29.699982, 30.473532)) + theme_minimal()
<SpatRaster> resampled to 501014 cells for plotting
Starting to improve the plot.
# Plotting
ggplot() +
geom_sf(data = amenities$osm_points, aes(color = "Hospitals"), fill = 'red', size = 1, stroke = 1, color = "darkred") +
geom_spatraster(data = Egypt_lights_mask, aes(fill = ..value..), alpha = 0.5) +
scale_fill_gradient(low = "black", high = "yellow", name = "Night Lights") +
coord_sf(xlim = c(30.852356, 31.717529), ylim = c(29.699982, 30.473532)) +
labs(title = "Distribution of Hospitals and Night Lights in Egypt",
x = "Longitude", y = "Latitude") +
theme_minimal() +
theme(legend.position = "bottom")
More on geom_spatraster
(here)[https://dieghernan.github.io/tidyterra/reference/geom_spatraster.html].
Extracting
You might want to extract values from a raster data set, and then map them in a vector sf
framework or extract them to analyse them statistically. If it therefore very useful to know how to extract:
# Using the 'raster::extract' function, it calculates the illumination values at the coordinates of the points.
<- raster::extract(Egypt_lights_mask,
lights_hospitals
amenities_points)
# Attach nightlight data at each point to the amenities dataframe
<- cbind(amenities_points, lights_hospitals)
lights_hospitals
# Keep only specified columns
<- lights_hospitals %>%
lights_hospitals select(MENA_noaa_projected, ID, geometry, osm_id)
# Check out the data
head(lights_hospitals)
Simple feature collection with 6 features and 3 fields
Geometry type: POINT
Dimension: XY
Bounding box: xmin: 31.2256 ymin: 30.06897 xmax: 31.29417 ymax: 30.08557
Geodetic CRS: WGS 84
MENA_noaa_projected ID osm_id geometry
32538148 63 1 32538148 POINT (31.29417 30.06897)
272580023 63 2 272580023 POINT (31.22562 30.08249)
272580024 63 3 272580024 POINT (31.2256 30.08322)
272580025 63 4 272580025 POINT (31.22574 30.08381)
272580026 63 5 272580026 POINT (31.22597 30.08485)
272580027 63 6 272580027 POINT (31.22584 30.08557)
Make sure all your data is in the same CRS, otherwise the raster::extract
function will not work properly.
You should not be seeing NAs, if you do you should use the terra project()
function.
There is also the exactextractor
package which we will use in the next section.