Introduction

In this practical, we will replicate a koala population sustainability study conducted in the Lower Hunter region of NSW for the Department of Sustainability, Environment, Water, Population and Communities by the consultancy Eco Logical.

This study serves as an excellent example of applying geospatial analysis to discrete entities. It involves intersecting multiple thematic data layers, each assigned weights based on attribute values, feature geometry, or proximity to other layers. To ensure the practical is manageable within the three-hour timeframe, some layers have been pre-prepared. Additionally, the study area has been reduced to three of the five Lower Hunter LGAs analysed in the original Eco Logical study.

The goal of this practical is to prepare the remaining data layers and conduct the final overlay operation to generate a koala habitat value index.

Further details can be found in the Eco Logical Final Report, which is included in this practical’s data package and available for download from the Australian Government website.


The practical is assessed and as a group you are required to submit the resulting .html file via moodle or email (instructions will be provided in class). Before submission, make sure that you have entered the names and student numbers of all group members at the top of the document.

Please note:

  • Write your R code chunks in the code blocs provided.
  • You must submit a knitted document save as an .html file. Do not submit the R markdown (.Rmd) file.

Study methodology

The methodology behind the koala habitat study is described in detail in the Eco Logical Final Report. The following section reproduces information from Table 3 of the report, providing information regarding the various data layers used in the modelling done by Eco Logical.

Data layers

The data layers that are provided as part of this practical were obtained from Eco Logical and represent the layers that were used in their Final Report. The data layers that are derived as part of the practical use raw data that is different to the data used by Eco Logical in their study (e.g., roads, rail corridors, and built areas are taken from a different source) and so the calculated final layers may be different to the ones produced by Eco Logical.

All data layers were cropped to the extent of the three Lower Hunter LGAs considered here: Lake Macquarie, Newcastle, and Port Stephens.

Data layers provided

1. Primary feed trees

  • Description:
    Selection of vegetation types known to have primary preferred koala tree species with the proportional abundance of the specific tree species within the vegetation type identified as either Nil, low, moderate, or high.

  • Details:
    An initial multiplier of \(\times 3\) was applied to these data to distinguish from secondary feed or supplementary tree species.

  • Values:
    0: Outside of any vegetation classification containing identified primary feed trees.
    60: Vegetation types with a low proportion of primary feed trees.
    150: Vegetation types with a moderate proportion of primary feed trees.
    300: Vegetation types with a high proportion of primary feed trees.

  • Weighting: \(\times 3\)


2. Secondary feed trees

  • Description:
    Selection of vegetation types known to have secondary preferred koala tree species with the proportional abundance of the specific tree species within the vegetation type identified as either Nil, low, moderate, or high.

  • Details:
    An initial multiplier of \(\times 2\) was applied to these data to distinguish from primary feed or supplementary tree species.

  • Values:
    0: Outside of any vegetation classification containing identified secondary feed trees.
    40: Vegetation types with a low proportion of secondary feed trees.
    100: Vegetation types with a moderate proportion of secondary feed trees.
    200: Vegetation types with a high proportion of secondary feed trees.

  • Weighting: \(\times 2\)


3. Supplementary trees

  • Description:
    Selection of vegetation types known to have supplementary tree species supporting shelter and roosting for koala, with the proportional abundance of the specific tree species within the vegetation type identified as either Nil, low, moderate, or high.

  • Values:
    0: Outside of any vegetation classification containing identified supplementary trees.
    20: Vegetation types with a low proportion of supplementary trees.
    50: Vegetation types with a moderate proportion of supplementary trees.
    100: Vegetation types with a high proportion of supplementary trees.

  • Weighting: \(\times 1\)


4. Vegetation proximity to water

  • Description:
    Extant vegetation within 50 m of 3rd order or higher drainage lines.

  • Values:
    0: Outside identified buffer.
    100: Within identified buffer.

  • Weighting: \(\times 2\)


5. Vegetation patch size

  • Description:
    The size of a patch of vegetation. A patch is defined as an area of consolidated vegetation that is separated from other patches by more than 100 m by mapped infrastructure to represent consolidated koala habitat.

  • Values:
    0: Smallest patch size.
    100: Patch at least 100 ha in size.

  • Weighting: \(\times 3\)


Data layers derived in practical

1. Soil fertility

  • Description:
    Identification of areas with high soil fertility.

  • Values:
    0: Outside of identified fertile soils.
    100: Within identified fertile soils.

  • Weighting: \(\times 2\)


2. Buffered koala sightings

  • Description:
    100 m buffer around known recorded sightings of koala.
    NB: This is not a comprehensive dataset; it is based only on areas where sightings have been recorded.

  • Values:
    0: Not within 100 m of a known record.
    75: Within 100 m of a known record dated 1985 or earlier.
    100: Within 100 m of a known record dated 1986 or later.

  • Weighting: \(\times 1\)


3. Distance to infrastructure

  • Description:
    300 m buffer around identified and mapped major barriers to koala movement.

  • Values:
    0: Within 300 m of identified infrastructure.
    100: 300 m or greater from identified infrastructure.

  • Weighting: \(\times3\)


Koala habitat value index

The final koala habitat suitability map is calculated by combining all the scores assigned to each data layer (see values above) with each layer further multiplied by a weight (see weighting above).

The calculation is as follows:

\[khv = [(veg_{prim} \times 3) + (veg_{sec} \times 2) + veg_{supp}] + (soil \times 2) +\] \[+ (veg_{water} \times 2) + koala + (urb \times 3) + (veg_{patch} \times 3)\] where:

  • \(veg_{prim}\) : primary feed trees
  • \(veg_{sec}\) : secondary feed trees
  • \(veg_{supp}\) : supplementary trees
  • \(soil\) : soil fertility
  • \(veg_{water}\) : vegetation proximity to water
  • \(koala\) : buffered koala sightings
  • \(urb\) : distance to infrastructure
  • \(veg_{patch}\) : vegetation patch size

The final score is then normalised to provide a habitat value index from 0 to 100:

  • Lower koala habitat value (0 – 19) – Areas that have little or no identified mapped values for koala habitat within the landscape. The majority of these areas are highly disturbed, fragmented or urbanised.

  • Moderate koala habitat value (20 – 33) – Areas that have some mapped koala habitat values within the landscape. In most cases the values within this category will provide supporting habitat for koala in the area.

  • High koala habitat value (34 – 48) – Areas with priority koala habitat values. These areas will include a large proportion of the criteria for koala habitat and provide an important resource for koalas in the area.

  • Very High koala habitat value (49 – 100) – An accumulation of priority values for koala habitat. These areas contain the majority of or even all values identified as criteria meeting priority koala habitat within the Lower Hunter area.


Derive missing data layers

Load necessary R packages

All necessary packages should already be installed. However, if any are missing, you can install them using the install.packages() function in R:

# Main spatial packages
install.packages("sf")
install.packages("terra")

# tmap dev version
install.packages("tmap", repos = c("https://r-tmap.r-universe.dev",
                                   "https://cloud.r-project.org"))
# leaflet for dynamic maps
install.packages("leaflet"

# tidyverse for additional data wrangling
install.packages("tidyverse")

Load packages:

library(sf)
library(terra)
library(tmap)
library(leaflet)
library(tidyverse)

# Set tmap to static maps
tmap_mode("plot")

Data files provided

The following data files are provided:

Vector data:

  • built_areas: a polygon ESRI shapefile representing various ‘built’ land uses in the study area. Data was obtained from OpenStreetMap.

  • koala_sightings: a point ESRI shapefile representing koala sightings in the study area. Data was obtained from the BioNet Atlas of NSW Wildlife hosted by the NSW Office of Environment and Heritage.

  • railways : a line ESRI shapefile representing railway tracks in the study area. Data was obtained from OpenStreetMap.

  • roads : a line ESRI shapefile representing roads in the study area. Data was obtained from the Geoscience Australia GEODATA TOPO 2.5M 2003 data product.

  • soil_landscapes : a polygon ESRI shapefile representing soil landscapes. The data is based on digital maps from the NSW Office of Environment and Heritage. The following map sheets were combined together: Soil Landscapes of the Gosford-Lake Macquarie (1:100,000), Soil Landscapes of the Newcastle (1:100,000), and Soil Landscapes of the Port Stephens (1:100,000).

  • study_area_bnd : a polygon ESRI shapefile representing the the study area, obtained by merging the administrative boundaries of the three LGA’s considered here: Lake Macquarie, Newcastle, and Port Stephens.

Raster data:

  • allveg_patch_size: a geoTIFF raster representing size of vegetation patch. Data obtained from Eco Logical.

  • allveg_prim, allveg_sec, and allveg_supp: geoTIFF rasters representing koala feed trees. Data obtained from Eco Logical.

  • dist_to_waters: a geoTIFF raster representing vegetation proximity to water. Data obtained from Eco Logical.

Layer 1: Soil fertility

Load the soil landscapes dataset (soil_landscapes) along with the polygon shapefile representing the study area boundary (study_area_bnd):

# Read in shapefiles
soil_landscapes <- read_sf("./data/vector/soil_landscapes.shp")
hunter_bnd <- read_sf("./data/vector/study_area_bnd.shp")

# Print the first rows of the soil landscapes layer
head(soil_landscapes)

The soil landscapes layer includes four attribute columns, the forth (PROCESS) representing the geomorphic type of landscape on which the soils have formed.

Plot the soil_landscapes layer, colouring each polygon according to the value of the PROCESS column:

# Plot data for quick visual inspection
soils_map <- tm_shape(soil_landscapes) + 
        tm_fill("PROCESS") + 
        tm_layout(frame = FALSE, legend.text.size = 0.5, legend.title.size = 0.5)

# Plot study area bnd
soils_map + tm_shape(hunter_bnd) +  tm_borders("black") 

To clean up the data, we will first crop soil_landscapes to the extent of the study area (i.e., hunter_bnd). Next, we will isolate those soil landscapes that can sustain koala feed tree species. These soils include those formed on Quaternary alluvial, colluvial, and aeolian deposits, and those formed in swampy areas.

To crop the soil_landscapes layer will use the sf function st_intersection():

# Intersect the two polygon layers
soil_landscapes_bnd <- st_intersection(soil_landscapes, hunter_bnd)

# Plot data for quick visual inspection
tm_shape(soil_landscapes_bnd) + 
  tm_fill("PROCESS") + 
  tm_layout(frame = FALSE, legend.text.size = 0.6, legend.title.size = 0.6)

Next we extract the soil landscapes of interest. We use the dplyr function filter():

# Create list of PROCESS values of interest
soils <- c("AEOLIAN", "ALLUVIAL", "COLLUVIAL", "SWAMP", "RESIDUAL", "ESTUARINE")

# Filter data and extract polygons of interest
soil_landscapes_filter <- filter(soil_landscapes_bnd, PROCESS %in% soils)

# Plot study area bnd first as it has larger extent
soils_map2 <- tm_shape(hunter_bnd) +  tm_borders("gray")

# Add soils  
soils_map2 + tm_shape(soil_landscapes_filter) + 
      tm_fill("PROCESS") + 
      tm_layout(frame = FALSE, legend.text.size = 0.6, legend.title.size = 0.6)

The next chunk of code adds a new attribute column (called SCORE) and uses the dplyr function mutate() to assign values based on another column (in this case PROCESS). We assign the value 100 to each polygon where PROCESS equals “AEOLIAN”, “ALLUVIAL”, “COLLUVIAL”, “SWAMP”, “RESIDUAL”, or “ESTUARINE”.

# Add a new numeric field 'SCORE' based on conditions applied to 'PROCESS'
# Here, we assign:
#   100 if 'PROCESS' equals "AEOLIAN", "ALLUVIAL", "COLLUVIAL", ...
#   0 for all other cases
soil_landscapes_filter <- soil_landscapes_filter %>%
  mutate(SCORE = case_when(
    PROCESS == "AEOLIAN" ~ 100,
    PROCESS == "ALLUVIAL" ~ 100,
    PROCESS == "COLLUVIAL" ~ 100,
    PROCESS == "SWAMP" ~ 50,
    PROCESS == "RESIDUAL" ~ 50,
    PROCESS == "ESTUARINE" ~ 0,
    TRUE ~ 0  # default numeric value for unmatched cases
  ))

# Plot study area bnd first as it has larger extent
soils_map3 <- tm_shape(hunter_bnd) +  tm_borders("gray")

# Add soils  
soils_map3 +  tm_shape(soil_landscapes_filter) + 
        tm_fill("SCORE") + 
        tm_layout(frame = FALSE, legend.text.size = 0.6, legend.title.size = 0.6)

The last step will convert soil_landscapes_filter to a raster. This will be our final soil fertility layer:

# Convert the sf object to a terra SpatVector
soil_landscapes_terra <- vect(soil_landscapes_filter)

# Read in the target SpatRaster that defines the extent and resolution
# Here we read in one of the Eco Logical geoTIFFs provided
target_raster <- rast("./data/raster/allveg_patch_size.tif")

# Rasterise the polygons using an attribute field
soil_landscapes_rast <- rasterize(soil_landscapes_terra, target_raster, field = "SCORE")

# Convert all NA (no data) values in the new raster to 0
soil_landscapes_rast[is.na(soil_landscapes_rast)] <- 0

# Plot data for quick visual inspection
soils_map4 <- tm_shape(soil_landscapes_rast) + 
       tm_raster(col.scale = tm_scale_categorical()) + 
       tm_layout(frame = FALSE, legend.text.size = 0.6, legend.title.size = 0.6)

# Plot study area bnd on top
soils_map4 + tm_shape(hunter_bnd) +  tm_borders("white") 

Layer 2: Koala sightings

Load the koala sightings dataset (koala_sightings):

# Read in shapefile
koala_sightings <- read_sf("./data/vector/koala_sightings.shp")

# Print the first rows of the soil landscapes layer
head(koala_sightings)

The koala sightings layer contains 20 attribute fields, with the last one (DateRank) containing two values: ‘75’ for all sightings made before 1985, and ‘100’ for all koala sightings dated 1986 and later. Data on sightings reported before 1985 are considered less reliable than those made after, explaining the lower rank.

Plot the koala_sightings layer, colouring each point according to the value of the DateRank column:

# Plot study area bnd first as it has larger extent
koala_map <- tm_shape(hunter_bnd) +  tm_borders("black")

# Add koalas
koala_map + tm_shape(koala_sightings) + 
        tm_dots(size = 0.2, fill = "DateRank", fill.scale = tm_scale_categorical()) + 
        tm_layout(frame = FALSE, legend.text.size = 0.5, legend.title.size = 0.5)

In the next step, we drop all columns except DateRank and create a 100 m buffer around each point using st_buffer(). Next, we dissolve the layer using st_union() so that all features are grouped together based on their value for DateRank. Lastly, we rename DateRank to SCORE.

# Retain only the "DateRank" attribute column (geometry is preserved automatically)
koala_sightings <- koala_sightings %>% 
  filter(DateRank == 100) %>%
  select(DateRank)

# Create a 100m buffer around each point
koala_buffers <- st_buffer(koala_sightings, dist = 100)

# Dissolve (union) buffers by the DateRank field and rename field to SCORE
koala_buffers_dissolved <- koala_buffers %>%
  group_by(DateRank) %>%
  summarise(geometry = st_union(geometry), .groups = "drop") %>%
  rename(SCORE = DateRank)

# Plot study area bnd first as it has larger extent
koala_map2 <- tm_shape(hunter_bnd) +  tm_borders("black")

# Add koalas
koala_map2 + tm_shape(koala_buffers_dissolved) + 
        tm_fill(fill = "SCORE", fill.scale = tm_scale_categorical()) + 
        tm_layout(frame = FALSE, legend.text.size = 0.5, legend.title.size = 0.5)

We are now ready to convert the koala buffer layer (koala_buffers_dissolved) to a raster with scores:

# Convert the sf object to a terra SpatVector
koala_buffers_terra <- vect(koala_buffers_dissolved)

# Read in the target SpatRaster that defines the extent and resolution
# Here we read in one of the Eco Logical geoTIFFs provided
target_raster <- rast("./data/raster/allveg_patch_size.tif")

# Rasterise the polygons using an attribute field
koala_buffers_rast <- rasterize(koala_buffers_terra, target_raster, field = "SCORE")

# Convert all NA (no data) values in the new raster to 0
koala_buffers_rast[is.na(koala_buffers_rast)] <- 0

# Plot data for quick visual inspection
koala_map3 <- tm_shape(koala_buffers_rast) + 
       tm_raster(col.scale = tm_scale_categorical()) + 
       tm_layout(frame = FALSE, legend.text.size = 0.6, legend.title.size = 0.6)

# Plot study area bnd on top
koala_map3 + tm_shape(hunter_bnd) +  tm_borders("white") 

Layer 3: Dist. to infrastructure

For our final layer, we need to calculate a 300 m buffer around infrastructure that may represent a barrier for koala movement, and assign a score of 100 to all areas that are beyond this 300 m buffer. Here, we will consider the following types of infrastructure as barriers to koala movement: roads, railway corridors, and built-up areas, including those classified as developed land even if they lack actual buildings.

Load the relevant datasets, namely, built_areas, roads, and railways:

# Read in shapefiles
built_areas <- read_sf("./data/vector/built_areas.shp")
roads <- read_sf("./data/vector/roads.shp")
railways <- read_sf("./data/vector/railways.shp")

# Plot bnd on top for larger extent
bnd_map <- tm_shape(hunter_bnd) +  tm_borders("black") + tm_layout(frame = FALSE)

# Plot the three layers on the same map

built_map <- bnd_map + 
      tm_shape(built_areas) + tm_fill("lightgray")
roads_map <- built_map + 
      tm_shape(roads) + tm_lines(col = "red", lwd = 1, lty = "solid")
roads_map + tm_shape(railways) + tm_lines(col = "black", lwd = 2, lty = "solid")

The roads layer contains all road types, including minor roads and tracks, and not all of these will constitute a barrier for koala movement. For this reason we need to clean up this layer before proceeding with the calculation of the buffers.

Print a list of the roads layer attribute table columns:

names(roads)
##  [1] "OBJECTID"   "FEATTYPE"   "NAME"       "CLASS"      "FORMATION" 
##  [6] "NRN"        "SRN"        "FEATREL"    "ATTRREL"    "PLANACC"   
## [11] "SOURCE"     "CREATED"    "RETIRED"    "PID"        "SYMBOL"    
## [16] "FEATWIDTH"  "TEXTNOTE"   "SHAPE_Leng" "geometry"

There is a column called CLASS and this will contain the various road types. Print a list with the entries in this column:

unique(roads$CLASS)
## [1] "Minor Road"       "Track"            "Secondary Road"   "Principal Road"  
## [5] "Dual Carriageway"

From the above, we can see that we have six road types, including “Minor Road” and “Track”. The next chunk of code drops all roads with these two classifications:

# Create list of CLASS values of interest
road_class <- c("Minor Road", "Secondary Road", "Principal Road", "Dual Carriageway")

# Filter data and extract roads of interest
roads_filtered <- filter(roads, CLASS %in% road_class)

# Repeat the previous map to see roads remaining
# Plot bnd on top for larger extent
bnd_map <- tm_shape(hunter_bnd) +  tm_borders("black") + tm_layout(frame = FALSE)

# Plot the three layers on the same map

built_map <- bnd_map + 
  tm_shape(built_areas) + tm_fill("lightgray")
roads_map <- built_map + 
  tm_shape(roads_filtered) + tm_lines(col = "red", lwd = 1, lty = "solid")
roads_map + tm_shape(railways) + tm_lines(col = "black", lwd = 2, lty = "solid")

Next, drop all attribute fields, create a new SCORE field for each layer, and populate with the value zero:

# Create new sf objects that retain only the geometry column
# This effectively drops all other attribute fields
built_areas_clean <- st_sf(geometry = st_geometry(built_areas))
roads_clean <- st_sf(geometry = st_geometry(roads_filtered))
railways_clean <- st_sf(geometry = st_geometry(railways))

# Add a new attribute field called SCORE and populate it with 0 for each feature
built_areas_clean$SCORE <- 0
roads_clean$SCORE <- 0
railways_clean$SCORE <- 0

# Optionally, reorder the columns so that SCORE comes before the geometry column
built_areas_clean <- built_areas_clean[, c("SCORE", "geometry")]
roads_clean <- roads_clean[, c("SCORE", "geometry")]
railways_clean <- railways_clean[, c("SCORE", "geometry")]

Create the 300 m buffer and dissolve features:

# Create a 300m buffer around each point
built_buffers <- st_buffer(built_areas_clean, dist = 300)
roads_buffers <- st_buffer(roads_clean, dist = 300)
railways_buffers <- st_buffer(railways_clean, dist = 300)

# Dissolve (union) buffers by the SCORE field
built_buffers_dissolved <- built_buffers %>%
  group_by(SCORE) %>%
  summarise(geometry = st_union(geometry))
roads_buffers_dissolved <- roads_buffers %>%
  group_by(SCORE) %>%
  summarise(geometry = st_union(geometry))
railways_buffers_dissolved <- railways_buffers %>%
  group_by(SCORE) %>%
  summarise(geometry = st_union(geometry))

# Join the three layers together and dissolve again
# Combine the sf objects using rbind 
# (works if they have identical column names)
infrastructure <- 
    rbind(built_buffers_dissolved,
          roads_buffers_dissolved,
          railways_buffers_dissolved
          )

# Dissolve (union)
infrastructure_dissolved <- st_union(infrastructure)

# Convert the unioned geometry back into an sf object
infrastructure_sf <- st_sf(geometry = infrastructure_dissolved)

# Add SCORE field and sort attribute columns
infrastructure_sf$SCORE <- 0
infrastructure_sf <- infrastructure_sf[, c("SCORE", "geometry")]

# Plot bnd
bnd_map <- tm_shape(hunter_bnd) + 
            tm_borders("black") + 
            tm_layout(frame = FALSE)

# Add infrastructure
bnd_map + tm_shape(infrastructure_sf) + 
            tm_fill("gray") + 
            tm_layout(frame = FALSE)

We are now ready to convert the infrastructure buffer layer (infrastructure_dissolved) to a raster with scores:

# Convert the sf object to a terra SpatVector
infrastructure_terra <- vect(infrastructure_sf)

# Read in the target SpatRaster that defines the extent and resolution
# Here we read in one of the Eco Logical GeoTIFFs provided
target_raster <- rast("./data/raster/allveg_patch_size.tif")

# Rasterise the polygons using an attribute field
infrastructure_rast <- rasterize(infrastructure_terra, target_raster, field = "SCORE")

# Convert all NA (no data) values in the new raster to 300
infrastructure_rast[is.na(infrastructure_rast)] <- 300

# Plot data for quick visual inspection
infra_map <- tm_shape(infrastructure_rast) + 
       tm_raster(col.scale = tm_scale_categorical()) + 
       tm_layout(frame = FALSE, legend.text.size = 0.6, legend.title.size = 0.6)

# Plot study area bnd on top
infra_map + tm_shape(hunter_bnd) +  tm_borders("white") 

We have now calculated the three missing final layers:

  • \(soil\) : soil fertility. This is layer soil_landscapes_rast
  • \(koala\) : buffered koala sightings. This is layer koala_buffers_rast
  • \(urb\) : distance to infrastructure. This is layer infrastructure_rast

Calculate koala habitat value

To repeat from above, the final koala habitat suitability map is calculated by combining all the scores assigned to each data layer, with each layer further multiplied by a weight:

The calculation is as follows:

\[khv = [(veg_{prim} \times 3) + (veg_{sec} \times 2) + veg_{supp}] + (soil \times 2) +\] \[+ (veg_{water} \times 2) + koala + (urb \times 3) + (veg_{patch} \times 3)\] where:

  • \(veg_{prim}\) : primary feed trees
  • \(veg_{sec}\) : secondary feed trees
  • \(veg_{supp}\) : supplementary trees
  • \(soil\) : soil fertility
  • \(veg_{water}\) : vegetation proximity to water
  • \(koala\) : buffered koala sightings
  • \(urb\) : distance to infrastructure
  • \(veg_{patch}\) : vegetation patch size

Calculating \(khv\) using map algebra is straight forward:

# Read in Eco Logical rasters
veg_prim <- rast("./data/raster/allveg_prim.tif")
veg_sec <- rast("./data/raster/allveg_sec.tif")
veg_supp <- rast("./data/raster/allveg_supp.tif")
veg_water <- rast("./data/raster/dist_to_water.tif")
veg_patch <- rast("./data/raster/allveg_patch_size.tif")

# Map algebra
koala_habitat <- (3 * veg_prim) + (2 * veg_sec) + veg_supp + 
                 (2 * soil_landscapes_rast) + 
                 (2 * veg_water) + 
                 koala_buffers_rast + 
                 (3 * infrastructure_rast) + 
                 (3 * veg_patch)

#Change the name of the layer to "khv"
names(koala_habitat) <- "khv"

# Plot bnd
khv_map <- tm_shape(hunter_bnd) +  tm_borders("black") 

# Add khv
khv_map + tm_shape(koala_habitat) + 
    tm_raster("khv", 
          col.scale = tm_scale_continuous_sqrt(
            values = "viridis", 
              ), 
          col.legend = tm_legend(
            title = "Habitat \nSuitability \nIndex",
            position = tm_pos_out(cell.h = "right", cell.v = "center"),
            orientation = "portrait"
              )
    ) +
    tm_layout(frame = FALSE, 
              legend.text.size = 0.6, 
              legend.title.size = 0.6) 

To calculate the normalised \(khv\), first we check the maximum value:

print(koala_habitat)
## class       : SpatRaster 
## size        : 3793, 4165, 1  (nrow, ncol, nlyr)
## resolution  : 25, 25  (x, y)
## extent      : 332811.5, 436936.5, 6312635, 6407460  (xmin, xmax, ymin, ymax)
## coord. ref. : GDA_1994_Transverse_Mercator 
## source(s)   : memory
## varname     : allveg_prim 
## name        :  khv 
## min value   :    0 
## max value   : 3000

The maximum \(khv\) value is 3000, and so to normalise the \(khv\) values, we divide all grid cells of the koala_habitat raster with 3000 and then multiply by 100. We read the maximum value directly from the raster using the global() function:

# Normalise using the new maximum
max_val <- global(koala_habitat, "max", na.rm = TRUE)[1,1]
koala_habitat_norm <- (koala_habitat / max_val) * 100

We are now ready to make our final map:

# Draw bnd
khvnorm_map <- tm_shape(hunter_bnd) +  tm_borders("black") 

# Add normalised khv  
khvnorm_map + tm_shape(koala_habitat_norm) + 
       tm_raster("khv",
          col.scale = tm_scale_intervals(                               # Custom breaks
            values = c("#584053", "#8dc6bf", "#fcbc66", "#f97b4f"),     # Define colours
            breaks = c(0, 20, 34, 49, 100),           
            labels = c("low", "moderate", "high", "very high")          # Legend labels
            ), 
          col.legend = tm_legend(
            title = "HSV Class",
            position = tm_pos_out(cell.h = "right", cell.v = "center"),
            orientation = "portrait"
            )
       )+
       tm_layout(frame = FALSE, 
                 legend.text.size = 0.6, 
                 legend.title.size = 0.6)

Exercises

Each of the following exercise is worth 20 marks (80 marks in total). You obtain 20 marks for reading the explanation above and running all provided code chunks.

Exercise 1 – Sensitivity test: Change layer weights

The koala habitat index is calculated by combining several weighted spatial layers. Test how sensitive the model is to these weights.

Task

  1. Locate the section of the code where the weights for the habitat layers are assigned before the final overlay.
  2. Modify the weights as follows:
    • Increase the weight of Primary feed trees by 50%.
    • Reduce the weight of Distance to infrastructure by half.
  3. Re-run the overlay model.
  4. Compare the new habitat index map with the original map.

Questions

  • Which areas of high habitat value changed the most? The areas that changed the most were those with a high presence of primary feed trees, particularly in more vegetated inland regions. These areas showed an increase in habitat suitability after the weight of this layer was increased. Areas close to infrastructure also showed a slight increase in suitability, as the influence of distance to infrastructure was reduced. Overall, the greatest changes occurred where food availability and infrastructure previously had competing effects.
  • Why might primary feed trees have such a strong influence on habitat suitability? Primary feed trees are the main food source for koalas and are essential for their survival. Areas with a high abundance of these trees are therefore more suitable habitats. Increasing their weight in the model places greater importance on food availability, making it a dominant factor in determining habitat suitability. This reflects ecological reality, as food availability is a key limiting factor for species distribution. Solve Exercise 1 in the code block provided below:
# Code block for Exercise 1

# Read in Eco Logical rasters
veg_prim <- rast("./data/raster/allveg_prim.tif")
veg_sec <- rast("./data/raster/allveg_sec.tif")
veg_supp <- rast("./data/raster/allveg_supp.tif")
veg_water <- rast("./data/raster/dist_to_water.tif")
veg_patch <- rast("./data/raster/allveg_patch_size.tif")

# Map algebra
koala_habitat <- (4.5 * veg_prim) + (2 * veg_sec) + veg_supp + 
                 (2 * soil_landscapes_rast) + 
                 (2 * veg_water) + 
                 koala_buffers_rast + 
                 (1.5 * infrastructure_rast) + 
                 (3 * veg_patch)

#Change the name of the layer to "khv"
names(koala_habitat) <- "khv"

# Plot bnd
khv_map <- tm_shape(hunter_bnd) +  tm_borders("black") 

# Add khv
khv_map + tm_shape(koala_habitat) + 
    tm_raster("khv", 
          col.scale = tm_scale_continuous_sqrt(
            values = "viridis", 
              ), 
          col.legend = tm_legend(
            title = "Habitat \nSuitability \nIndex",
            position = tm_pos_out(cell.h = "right", cell.v = "center"),
            orientation = "portrait"
              )
    ) +
    tm_layout(frame = FALSE, 
              legend.text.size = 0.6, 
              legend.title.size = 0.6) 

Exercise 2 – Test a stricter road-barrier assumption

In the practical, only Secondary Road, Principal Road, and Dual Carriageway are treated as barriers. Create an alternative infrastructure layer in which Minor Road is also treated as a barrier, then re-run the habitat model.

Task

  1. Include Minor Road in the road filter.
  2. Rebuild the cleaned road layer
  3. Buffer and dissolve
  4. Combine and rasterise
  5. Re-run the habitat model and compare with original

Questions

  • How much more of the study area falls within the infrastructure buffer?

Including minor roads increases the total area that falls within the infrastructure buffer. This results in a larger proportion of the study area being classified as close to infrastructure, which reduces overall habitat suitability.

  • Which parts of the habitat map lose suitability under this stricter assumption?

Areas near minor roads lose the most suitability under this stricter assumption, particularly in developed or fragmented regions. These areas were previously considered suitable but are now downgraded due to the increased extent of infrastructure influence.

Solve Exercise 2 in the code block provided below:

# Code block for Exercise 2

# Create list of CLASS values of interest
road_class <- c("Minor Road", "Secondary Road", "Principal Road", "Dual Carriageway")

# Filter data and extract roads of interest
roads_filtered <- filter(roads, CLASS %in% road_class)

# Read in Eco Logical rasters
veg_prim <- rast("./data/raster/allveg_prim.tif")
veg_sec <- rast("./data/raster/allveg_sec.tif")
veg_supp <- rast("./data/raster/allveg_supp.tif")
veg_water <- rast("./data/raster/dist_to_water.tif")
veg_patch <- rast("./data/raster/allveg_patch_size.tif")

# Map algebra
koala_habitat <- (3 * veg_prim) + (2 * veg_sec) + veg_supp + 
                 (2 * soil_landscapes_rast) + 
                 (2 * veg_water) + 
                 koala_buffers_rast + 
                 (3 * infrastructure_rast) + 
                 (3 * veg_patch)

#Change the name of the layer to "khv"
names(koala_habitat) <- "khv"

# Plot bnd
khv_map <- tm_shape(hunter_bnd) +  tm_borders("black") 

# Add khv
khv_map + tm_shape(koala_habitat) + 
    tm_raster("khv", 
          col.scale = tm_scale_continuous_sqrt(
            values = "viridis", 
              ), 
          col.legend = tm_legend(
            title = "Habitat \nSuitability \nIndex",
            position = tm_pos_out(cell.h = "right", cell.v = "center"),
            orientation = "portrait"
              )
    ) +
    tm_layout(frame = FALSE, 
              legend.text.size = 0.6, 
              legend.title.size = 0.6) 

Exercise 3 – Reclassify the soil fertility layer more selectively

The practical assigns the same score (100) to all selected soil processes. Create a more selective version of the soil fertility layer where:

  • ALLUVIAL, COLLUVIAL, AEOLIAN = 100
  • SWAMP, ESTUARINE = 50
  • RESIDUAL = 0

Then re-run the habitat model.

Task

  1. Revise scores assigned to each soil type
  2. Create new soil fertility layer
  3. Re-run the habitat model and compare with original

Questions

  • Which areas decrease most under the stricter soil scoring?

Areas associated with swamp, estuarine, and residual soil types show some decrease in habitat suitability under the stricter soil scoring. However, these changes are relatively minor and not clearly distinguishable at the map scale, suggesting that the effect is subtle and localised.

  • Does the final habitat map seem very sensitive to this change?

The final habitat map does not appear highly sensitive to the change in soil scoring. There is little visible difference between the original and modified maps, indicating that soil fertility has a relatively minor influence compared to other factors such as primary feed trees, infrastructure, and vegetation patch size. This suggests that the model is more strongly driven by these higher-weighted variables.

Solve Exercise 3 in the code block provided below:

# Code block for Exercise 3

# Add a new numeric field 'SCORE' based on conditions applied to 'PROCESS'
# Here, we assign:
#   100 if 'PROCESS' equals "AEOLIAN", "ALLUVIAL", "COLLUVIAL", ...
#   0 for all other cases
soil_landscapes_filter <- soil_landscapes_filter %>%
  mutate(SCORE = case_when(
    PROCESS == "AEOLIAN" ~ 100,
    PROCESS == "ALLUVIAL" ~ 100,
    PROCESS == "COLLUVIAL" ~ 100,
    PROCESS == "SWAMP" ~ 50,
    PROCESS == "RESIDUAL" ~ 50,
    PROCESS == "ESTUARINE" ~ 0,
    TRUE ~ 0  # default numeric value for unmatched cases
  ))

# Draw bnd
khvnorm_map <- tm_shape(hunter_bnd) +  tm_borders("black") 

# Add normalised khv  
khvnorm_map + tm_shape(koala_habitat_norm) + 
       tm_raster("khv",
          col.scale = tm_scale_intervals(                               # Custom breaks
            values = c("#584053", "#8dc6bf", "#fcbc66", "#f97b4f"),     # Define colours
            breaks = c(0, 20, 34, 49, 100),           
            labels = c("low", "moderate", "high", "very high")          # Legend labels
            ), 
          col.legend = tm_legend(
            title = "HSV Class",
            position = tm_pos_out(cell.h = "right", cell.v = "center"),
            orientation = "portrait"
            )
       )+
       tm_layout(frame = FALSE, 
                 legend.text.size = 0.6, 
                 legend.title.size = 0.6)

Exercise 4 – Use only recent koala sightings in the model

The practical combines all koala sightings, but older records are scored lower (75) than more recent records (100). Create a new habitat model that uses only recent sightings (DateRank == 100).

Task

  1. Filter using recent sightings only
  2. Create new koala sightings layer
  3. Re-run the habitat model and compare with original

Questions

  • How does the habitat map change when older sightings are removed?

The habitat map shows only minor changes when older sightings are removed. Some areas experience a slight reduction in suitability, particularly where suitability was previously supported by older records. Overall, the distribution becomes slightly more fragmented, but the general pattern of habitat suitability remains similar.

  • Which areas remain important even when only recent observations are used?

Areas with recent koala sightings remain the most important, as they continue to show high habitat suitability. These core areas are likely to represent current and reliable koala habitats, and remain largely unchanged despite the removal of older records.

Solve Exercise 4 in the code block provided below:

# Code block for Exercise 4

# Retain only the "DateRank" attribute column (geometry is preserved automatically)
koala_sightings <- koala_sightings %>% 
  filter(DateRank == 100) %>%
  select(DateRank)

# Draw bnd
khvnorm_map <- tm_shape(hunter_bnd) +  tm_borders("black") 

# Add normalised khv  
khvnorm_map + tm_shape(koala_habitat_norm) + 
       tm_raster("khv",
          col.scale = tm_scale_intervals(                               # Custom breaks
            values = c("#584053", "#8dc6bf", "#fcbc66", "#f97b4f"),     # Define colours
            breaks = c(0, 20, 34, 49, 100),           
            labels = c("low", "moderate", "high", "very high")          # Legend labels
            ), 
          col.legend = tm_legend(
            title = "HSV Class",
            position = tm_pos_out(cell.h = "right", cell.v = "center"),
            orientation = "portrait"
            )
       )+
       tm_layout(frame = FALSE, 
                 legend.text.size = 0.6, 
                 legend.title.size = 0.6)