6

Basically, I computed a global distribution probability model in the form of ASCII, say: gdpm. gdpm's values are all between 0 and 1.

Then I imported a local map from shape file:

shape <- file.choose()  
map <- readOGR(shape, basename(file_path_sans_ext(shape)))

The next step, I rasterized gdpm, and cropped using the local map:

ldpm <- mask(gdpm, map)

Then, I reclassified this continuous model into a discrete model (I divided the model into 6 levels):

recalc <- matrix(c(0, 0.05, 0, 0.05, 0.2, 1, 0.2, 0.4, 2, 0.4, 0.6, 3, 0.6, 0.8, 4, 0.8, 1, 5), ncol = 3, byrow = TRUE) 

ldpmR <- reclassify(ldpm, recalc)

enter image description here

I've got a cropped and reclassified raster, now I need to summarize land cover, that is, to each level, I want to calculate its proportion of area in each region of the local map. (I don't know how to describe it in terminology). I found and followed an example (RobertH):

ext <- raster::extract(ldpmR, map)

tab <- sapply(ext, function(x) tabulate(x, 10))
tab <- tab / colSums(tab)

But I'm not sure if it works, since the output of tab is confusing. So how to compute land cover area correctly? How can I apply the correct method within each polygon?

My original data is too large, I can only provide an alternative raster (I think this example should apply a different reclassify matrix):

Example raster

Or you can generate a test raster (RobertH):

library(raster)
s <- stack(system.file("external/rlogo.grd", package="raster")) 
writeRaster(s, file='testtif', format='GTiff', bylayer=T, overwrite=T)
f <- list.files(pattern="testtif_..tif")

I also have a question about plotting a raster:

r <- as(r, "SpatialPixelsDataFrame")
r <- as.data.frame(r)
colnames(r) <- c("value", "x", "y")

I do this conversion to make a raster plot-able with ggplot2, is there a more concise method?

Tianjian Qin
  • 525
  • 4
  • 14

2 Answers2

7

loki's answer is OK, but this can be done the raster way, which is safer. And it is important to consider whether the coordinates are angular (longitude/latitude) or planar (projected)

Example data

library(raster)
r <- raster(system.file("external/test.grd", package="raster"))
r <- r / 1000
recalc <- matrix(c(0, 0.05, 0, 0.05, 0.2, 1, 0.2, 0.4, 2, 0.4, 0.6, 3, 0.6, 0.8, 4, 0.8, 2, 5), ncol = 3, byrow = TRUE) 
r2 <- reclassify(r, recalc)

Approach 1. Only for planar data

f <- freq(r2, useNA='no')
apc <- prod(res(r))
f <- cbind(f, area=f[,2] * apc)
f

#     value count    area
#[1,]     1    78  124800
#[2,]     2  1750 2800000
#[3,]     3   819 1310400
#[4,]     4   304  486400
#[5,]     5   152  243200

Approach 2. For angular data (but also works for planar data)

a <- area(r2)
z <- zonal(a, r2, 'sum')
z
#     zone     sum
#[1,]    1  124800
#[2,]    2 2800000
#[3,]    3 1310400
#[4,]    4  486400
#[5,]    5  243200

If you want to summarize by polygons, you can do something like this:

# example polygons
a <- rasterToPolygons(aggregate(r, 25))

Approach 1

# extract values (slow)
ext <- extract(r2, a)

# tabulate values for each polygon
tab <- sapply(ext, function(x) tabulate(x, 5))
# adjust for area (planar data only)
tab <- tab * prod(res(r2))

# check the results, by summing over the regions
rowSums(tab)
#[1]  124800 2800000 1310400  486400  243200    

Approach 2

x <- rasterize(a, r2)
z <- crosstab(x, r2)
z <- cbind(z, area = z[,3] * prod(res(r2)))

Check results:

aggregate(z[, 'area', drop=F], z[,'Var2', drop=F], sum)
  Var2    area
#1    1  124800
#2    2 2800000
#3    3 1310400
#4    4  486400
#5    5  243200

Note that if you are dealing with lon/lat data you cannot use prod(res(r)) to get the cell size. In that case you will need to use the area function and loop over classes, I think.

You also asked about plotting. There are many ways to plot a Raster* object. The basic ones are:

 image(r2)
 plot(r2)
 spplot(r2)

 library(rasterVis); 
 levelplot(r2)

More tricky approaches:

 library(ggplot2) # using a rasterVis method
 theme_set(theme_bw())
 gplot(r2) + geom_tile(aes(fill = value)) +
      facet_wrap(~ variable) +
      scale_fill_gradient(low = 'white', high = 'blue') +
      coord_equal()


 library(leaflet)
 leaflet() %>% addTiles() %>%
 addRasterImage(r2, colors = "Spectral", opacity = 0.8)       
Robert Hijmans
  • 40,301
  • 4
  • 55
  • 63
  • Both approaches work fine, thanks. I added an image to make my question a bit clearer: my local map has multiple polygons, is it possible to apply this approach to all polygons respectively, in a neat way? – Tianjian Qin Dec 04 '17 at 03:27
  • 1
    I have expanded my answer – Robert Hijmans Dec 04 '17 at 04:19
  • 1
    Be warned that leaflet is [currently](https://github.com/rstudio/leaflet/pull/476#pullrequestreview-75999985) not able to correctly display categorical rasters as it only supports bilinear interpolation when projecting values internally. To get correct results you will need to make sure your raster layer is in longlat and set `project = FALSE` in `addRasterImage`. – TimSalabim Dec 04 '17 at 06:45
  • Thanks for your patience, though I encountered a minor issue...as you can see in my image, some coastal regions have more than one polygon (small islands), could you tell me how to attach a polygon's name (I suppose it has an attribute "name") in the table produced in summarize approach 2 (`z`)? – Tianjian Qin Dec 04 '17 at 09:32
  • I managed to filter my map, and created a minimal subset with 7 polygons. When it comes to `x <- rasterize(a, r2)`, I encountered error `In strsplit(s, "") : input string 1 is invalid in this locale`. However `z` seems correct. And when it comes to `aggregate(z[, 'area', drop=F], z[,'Var2', drop=F], sum)`, I encountered `Error in MaxEntAP_EXz[, "Var2", drop = F] : subscript out of bounds` – Tianjian Qin Dec 04 '17 at 13:36
4

Seems like you can get the area by the number of pixels.
Let's start with a reproducible example:

r <- raster(system.file("external/test.grd", package="raster"))
plot(r)

enter image description here

Since, the values in this raster are in another range than your data, let's adapt them to your values:

r <- r / 1000
r[r>1,] <- 1

Afterwards, we apply your reclassification:

recalc <- matrix(c(0, 0.05, 0, 0.05, 0.2, 1, 0.2, 0.4, 2, 0.4, 0.6, 3, 0.6, 0.8, 4, 0.8, 1, 5), ncol = 3, byrow = TRUE) 
r2 <- reclassify(r, recalc)
plot(r2)

enter image description here

Now, how do we get the area?
Since you are working with a projected raster, you can simply use the number of pixels and the raster resolution. Therefore, we first need to check the resolution and the map units of the projection:

res(r)
# [1] 40 40
crs(r)
# CRS arguments:
#   +init=epsg:28992
# +towgs84=565.237,50.0087,465.658,-0.406857,0.350733,-1.87035,4.0812 +proj=sterea
# +lat_0=52.15616055555555 +lon_0=5.38763888888889 +k=0.9999079 +x_0=155000
# +y_0=463000 +ellps=bessel +units=m +no_defs

Now, we know that we are dealing with pixels of 40 x40 meters, since we have a metric CRS.

Let's use this information to calculate the area of each class.

app <- res(r)[1] * res(r)[2] # area per pixel

table(r2[]) * app
#      1       2       3       4       5 
# 124800 2800000 1310400  486400  243200 

For the plotting of georeferenced rasters, I would like to refer you to an older question here on SO

Robert Hijmans
  • 40,301
  • 4
  • 55
  • 63
loki
  • 9,816
  • 7
  • 56
  • 82