Converting UK grid references to points for mapping

Mon, Jan 3, 2022 8-minute read

Converting UK grid references for mapping

The UK has a grid reference system for its spatial references (among others). This is a standard reference used by Ordnance Survey maps and other products. A beginners guide to grid references is provided by Ordnance survey here. In the world of biological recording grid references are commonly used as the spatial reference for the WHAT part of the key info makign up a record (WHO, WHAT, WHERE, WHEN). However, most GIS systems and other mapping systems cannot map grid references directly. For QGIS there is the FSC plugin for working with grid references. For R, you have to do some of the work yourself.

Here I set out how to convert UK grid references into a simple features point data set and then map it using the {leaflet} package. You can download the code and the sample dataset from Github.

Loading and cleaning the sampel data

As ever first, we need to load some libraries, load the data and clean it up a bit. The data are a download of my iRecord records from 2020 and 2021, mostly recorded during covid lockdowns! I like to use the {janitor} package to clean up the column names.

library(sf)
## Linking to GEOS 3.9.0, GDAL 3.2.1, PROJ 7.2.1
library(rnrfa)
library(readr)
library(janitor)
## 
## Attaching package: 'janitor'
## The following objects are masked from 'package:stats':
## 
##     chisq.test, fisher.test
library(leaflet)

r <- read_csv("data/myrecords.csv")
## Warning: Missing column names filled in: 'X1' [1], 'X13' [13]
## Parsed with column specification:
## cols(
##   X1 = col_character(),
##   ID = col_double(),
##   Src = col_character(),
##   Species = col_character(),
##   `Common name` = col_character(),
##   `Species group` = col_character(),
##   Location = col_character(),
##   `Map ref.` = col_character(),
##   `Vice county` = col_character(),
##   Date = col_character(),
##   Recorder = col_character(),
##   Determiner = col_character(),
##   X13 = col_character()
## )
r <- janitor::clean_names(r)

A UK grid reference uses a two letter reference for the 100km grid square in which it is is, plus 2-10 numbers to describe the remaining eastings and northings (x and y) of the location. It looks something like this SU1234 5678. To map this, we need to convert it into eastings and northing, which essentially encode the distance in metres from the grid zero point, somewhere out in the Atlantic Ocean. For the grid reference I used as an example that would be 41234 15678.

The {rnrfa} package provides a handy function to convert grid refs into their eastings and northings, as follows:

# convert grid refs to E,N

x <- osg_parse(r$map_ref)

# write to data

r$east <- x$easting
r$north <- x$northing

The osg_parse() function creates a list of two vectors, one for eastings and one for northings, which we can then add back to our dataframe. We could use this directly in QGIS now, as it will recognise those references from a plain text file and create the points for you. To do this in R I used the Well Known Text format to describe the points. Others can explain this better than me, but suffice it to say, it is a way of representing vector data in plain text. That makes it really useful for converting those eastings and northings into point data. We can do this very simply using the paste function:

r$wkt <- paste0("POINT(", r$east, " ", r$north, ")")

Now we have a wkt representation of out points, we can convert this into a simple features dataset. The {sf} package has a function to convert data into simple features and we can use the wkt to create the points, as follows:

rs <- st_as_sf(r, wkt="wkt")

# check tha this has worked!
class(rs)
## [1] "sf"          "spec_tbl_df" "tbl_df"      "tbl"         "data.frame"

So we now have a simple features dataset of points for my records. There is no CRS assigned to this data, and we need to do that so we can transform it for later use. UK grid references are in EPSG:27700:

st_crs(rs) <- 27700

Now we have a dataset, we can map it using the {leaflet} library. We need to transform our data in WGS84 for mapping in leaflet, but once done we can go right ahead an map it.

# transform data for leaflet
rs <- st_transform(rs, 4326)

# create leaflet map

m <- leaflet(rs) %>% 
  addTiles() %>% 
  addMarkers(popup = paste0("Species: ", rs$common_name,"<br>",
                           "(", rs$species, ")"))
m

And there we have it - UK grid references converted into a webmap.

sessionInfo()
## R version 3.6.3 (2020-02-29)
## Platform: x86_64-pc-linux-gnu (64-bit)
## Running under: Ubuntu 20.04.3 LTS
## 
## Matrix products: default
## BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.9.0
## LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.9.0
## 
## locale:
##  [1] LC_CTYPE=en_GB.UTF-8       LC_NUMERIC=C              
##  [3] LC_TIME=en_GB.UTF-8        LC_COLLATE=en_GB.UTF-8    
##  [5] LC_MONETARY=en_GB.UTF-8    LC_MESSAGES=en_GB.UTF-8   
##  [7] LC_PAPER=en_GB.UTF-8       LC_NAME=C                 
##  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
## [11] LC_MEASUREMENT=en_GB.UTF-8 LC_IDENTIFICATION=C       
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] leaflet_2.0.4.1 janitor_2.0.1   readr_1.3.1     rnrfa_2.0.4    
## [5] sf_1.0-4       
## 
## loaded via a namespace (and not attached):
##  [1] Rcpp_1.0.7           lubridate_1.7.9      lattice_0.20-40     
##  [4] tidyr_1.1.0          png_0.1-7            class_7.3-15        
##  [7] zoo_1.8-9            assertthat_0.2.1     digest_0.6.28       
## [10] utf8_1.2.2           R6_2.5.1             plyr_1.8.6          
## [13] evaluate_0.14        e1071_1.7-9          httr_1.4.2          
## [16] ggplot2_3.3.3        blogdown_0.20        pillar_1.6.4        
## [19] RgoogleMaps_1.4.5.3  rlang_0.4.12         curl_4.3.2          
## [22] jquerylib_0.1.4      blob_1.2.2           rmarkdown_2.11      
## [25] rgdal_1.5-27         stringr_1.4.0        htmlwidgets_1.5.1   
## [28] munsell_0.5.0        proxy_0.4-26         compiler_3.6.3      
## [31] xfun_0.26            pkgconfig_2.0.3      htmltools_0.5.2.9000
## [34] tidyselect_1.1.1     tibble_3.1.6         bookdown_0.20       
## [37] fansi_0.5.0          crayon_1.4.2         dplyr_1.0.7         
## [40] bitops_1.0-7         grid_3.6.3           jsonlite_1.7.2      
## [43] gtable_0.3.0         lifecycle_1.0.1      DBI_1.1.1           
## [46] magrittr_2.0.1       units_0.7-2          scales_1.1.1        
## [49] KernSmooth_2.23-16   stringi_1.7.5        sp_1.4-2            
## [52] snakecase_0.11.0     bslib_0.3.1          ellipsis_0.3.2      
## [55] generics_0.1.1       vctrs_0.3.8          rjson_0.2.20        
## [58] tools_3.6.3          ggmap_3.0.0          glue_1.5.0          
## [61] purrr_0.3.4          crosstalk_1.1.0.1    hms_0.5.3           
## [64] jpeg_0.1-8.1         parallel_3.6.3       fastmap_1.1.0       
## [67] yaml_2.2.1           colorspace_1.4-1     classInt_0.4-3      
## [70] knitr_1.36           sass_0.4.0