4

I have a polygon (zones) and a set of coordinates (points). I'd like to create a spatial kernal density raster for the entire polygon and extract the sum of the density by zone. Points outside of the polygon should be discarded.

library(raster)
library(tidyverse)
library(sf)
library(spatstat)
library(maptools)

load(url("https://www.dropbox.com/s/iv1s5butsx2v01r/example.RData?dl=1"))
# alternatively, links to gists for each object
# https://gist.github.com/ericpgreen/d80665d22dfa1c05607e75b8d2163b84
# https://gist.github.com/ericpgreen/7f4d3cee3eb5efed5486f7f713306e96

ggplot() +
  geom_sf(data = zones) +
  geom_sf(data = points) +
  theme_minimal()

enter image description here

I tried converting to ppp with {spatstat} and then using density(), but I'm confused by the units in the result. I believe the problem is related to the units of the map, but I'm not sure how to proceed.

Update

Here's the code to reproduce the density map I created:

zones_owin <- as.owin(as_Spatial(zones))
pts <- st_coordinates(points)
p <- ppp(pts[,1], pts[,2], window=zones_owin, unitname=c("metre","metres"))
ds <- density(p) 
r <- raster(ds)
plot(r)

enter image description here

Eric Green
  • 7,385
  • 11
  • 56
  • 102
  • Can you include all the relevant code? How did you convert to raster? And it's better if you add a sample of the data to the post instead of via a third party – camille Jan 22 '20 at 15:34
  • I added links to gists of each object if folks prefer to get the data that way. My raster code follows a convoluted process of shapefile conversions, so I stripped down the example to `sf` objects to start over in this post. But my earlier attempt was basically `raster(density(ppp()))`. – Eric Green Jan 22 '20 at 15:59
  • @camille I played a bit and got the code to reproduce the density map I created. I'm not sure what to make of the units. Maybe I need to take a different approach altogether. – Eric Green Jan 22 '20 at 17:35

2 Answers2

1

I'm not sure if this answers all of your question, but should be a good start. Clarify in a comment or in your question should you need a different type of output.

It removes all points that are not inside one of the 'zone' polygons, counts them by zone and plots the zones colored by the number of points that fall within.

library(raster)
library(tidyverse)
library(sf)
#> Linking to GEOS 3.6.2, GDAL 2.2.3, PROJ 4.9.3
library(spatstat)

library(maptools)
#> Checking rgeos availability: TRUE

load(url("https://www.dropbox.com/s/iv1s5butsx2v01r/example.RData?dl=1"))
# alternatively, links to gists for each object
# https://gist.github.com/ericpgreen/d80665d22dfa1c05607e75b8d2163b84
# https://gist.github.com/ericpgreen/7f4d3cee3eb5efed5486f7f713306e96

p1 <- ggplot() +
  geom_sf(data = zones) +
  geom_sf(data = points) +
  theme_minimal()

#Remove points outside of zones
points_inside <- st_intersection(points, zones)
#> although coordinates are longitude/latitude, st_intersection assumes that they are planar
#> Warning: attribute variables are assumed to be spatially constant throughout all
#> geometries
nrow(points)
#> [1] 308
nrow(points_inside)
#> [1] 201

p2 <- ggplot() + 
  geom_sf(data = zones) + 
  geom_sf(data = points_inside)

points_per_zone <- st_join(zones, points_inside) %>%
  count(LocationID.x)
#> although coordinates are longitude/latitude, st_intersects assumes that they are planar

p3 <- ggplot() + 
  geom_sf(data = points_per_zone, 
          aes(fill = n)) + 
  scale_fill_viridis_c(option = 'C')

points_per_zone
#> Simple feature collection with 4 features and 2 fields
#> geometry type:  POLYGON
#> dimension:      XY
#> bbox:           xmin: 34.0401 ymin: -1.076718 xmax: 34.17818 ymax: -0.9755066
#> epsg (SRID):    4326
#> proj4string:    +proj=longlat +ellps=WGS84 +no_defs
#> # A tibble: 4 x 3
#>   LocationID.x     n                                                    geometry
#> *        <dbl> <int>                                               <POLYGON [°]>
#> 1           10   129 ((34.08018 -0.9755066, 34.0803 -0.9757393, 34.08046 -0.975…
#> 2           20    19 ((34.05622 -0.9959458, 34.05642 -0.9960835, 34.05665 -0.99…
#> 3           30    29 ((34.12994 -1.026372, 34.12994 -1.026512, 34.12988 -1.0266…
#> 4           40    24 ((34.11962 -1.001829, 34.11956 -1.002018, 34.11966 -1.0020…

cowplot::plot_grid(p1, p2, p3, nrow = 2, ncol = 2)

It seems I underestimated the difficulty of your problem. Is something like the plot below (& underlying data) what you're looking for?

enter image description here

It uses raster with ~50x50 grid, raster::focal with a window of 9x9 using the mean to interpolate the data.

mrhellmann
  • 5,069
  • 11
  • 38
  • Thanks @mrhellmann. I think this does a nice job counting points per feature. What I’m looking for is some interpolation over the space. Kernel density, IDW, etc. I tried density but I’m struggling to make sense of the units. – Eric Green Jan 23 '20 at 01:51
  • I'll see if I can come up with something closer to what you're looking for. – mrhellmann Jan 23 '20 at 02:02
  • The updated plot is interesting. Very much what I was thinking. Can you share how to manipulate the parameters? – Eric Green Jan 23 '20 at 12:44
  • @EricGreen I'll clean it up and edit the post this evening. The code to prouduce this is currently a minefield of missteps in rstudio's history log. – mrhellmann Jan 23 '20 at 12:57
1

Units are difficult when you work directly with geographic coordinates (lon, lat). If possible you should convert to planar coordinates (which is a requirement for spatstat) and proceed from there. The planar coordinates would typically be in units of meters, but I guess it depends on the specific projection and underlying ellipsoid etc. You can see this answer for how to project to planar coordinates with sf and export to spatstat format using maptools. Note: You have to manually choose a sensible projection (you can use http://epsg.io to find one) and you have to project both the polygon and the points.

Once everything is in spatstat format you can use density.ppp to do kernel smoothing. The resulting grid values (object of class im) are intensities of points, i.e., number of points per square unit (e.g. square meter). If you want to aggregate over some region you can use integral.im(..., domain = ...) to get the expected number of points in this region for a point process model with the given intensity.

Ege Rubak
  • 4,347
  • 1
  • 10
  • 18
  • Very helpful! I was missing the `st_transform()` step to planar. Can you advise on how to change the units? `zones` is now `+units=m`. What if I wanted to set the units to km? – Eric Green Jan 23 '20 at 12:40
  • Those units are particular to the projection. You can either find a projection that uses the units you're looking for, or instead do your calculations and convert the units at the end (which I prefer). – Eugene Chong Jan 23 '20 at 13:44
  • Thanks. The conversion is more complicated than a simple multiplication or division, correct? – Eric Green Jan 23 '20 at 14:12
  • 1
    Once you have planar coordinates and units of e.g. m the transformation to km is just a simple division by 1000 -- nothing fancy there. You can do it in spatstat with the `rescale` function where you can also give names to units, but it doesn't know anything about units per se, so you can't just ask spatstat to convert m to km. You have to tell that the scaling factor is 1000 and that the new name is km. Good luck! – Ege Rubak Jan 24 '20 at 10:46