The quickest way to get a plot of your results is by using
sf
and its in-built plotting
functionality. There are many packages within R
that can be used to generate
visualizations of geographic data, including
ggplot2
and
tmap
. The package
maptiles
lets you add basemaps
to your plots, giving your data real world context.
The OS Data Hub APIs return GeoJSON, which can be used to make a simple features
data frame. This type of data frame works in a very similar way to a regular
data.frame
in R
, with the addition of a list-like column of geometry and
attributes for geospatial data. The R
package sf
can be used to create these
objects. For more information on sf
see the technical
documentation.
sf
allows you to quickly visualize the data returned by your API query, using
the coordinate reference system of the data to accurately plot the spatial
relationships. Plotting this way can be useful to quickly check your API query
results are looking like you were expecting, as well as giving you options to
produce highly polished plots.
This example requests water features along a section of the Glastonbury Canal in Somerset from the 'wtr-fts-water-1' collection via the NGD Features API and then makes a simple plot to show what was returned.
library(osdatahub) library(sf)
# Choose data collection <- 'wtr-fts-water-1' # Define query extent W <- 342730 S <- 137700 E <- 347700 N <- 141642 crs <- 'EPSG:27700' extent <- extent_from_bbox(c(W, S, E, N), crs = crs) # Query API results <- query_ngd(extent, collection = collection, crs = crs, max_results = 100000)
These results are in GeoJSON format. The sf
package can convert this format
into a data.frame
with the appropriate geometry information. Alternatively,
the osdatahub
package for R
provides the option to return the query results
as a data.frame
object. Specify the returnType
argument as 'sf' in
query_ngd()
.
results_df <- st_read(results, crs = st_crs(crs), quiet = TRUE) #> Warning: st_crs<- : replacing crs does not reproject data; use st_transform for #> that
We can ignore the warning about setting the CRS in this case because we specified the API should return the features in EPSG:27700.
Now it is as simple as calling plot()
on the data frame, including some
optional styling parameters, to visualize the results. The in-built plotting
commands of sf
are detailed in this
vignette.
# Plot the query extent plot(st_geometry(extent$polygon), lty = 'dashed', main = 'Water features', axes = TRUE, xlab = 'Eastings', ylab = 'Northings') # Plot the query results plot(st_geometry(results_df), col = 'purple', add = TRUE) mtext('Contains OS data © Crown copyright and database rights, 2025.', side = 1, line = 4)
This plot shows us the features returned form the API and plots them correctly in geogrpahic space. Once we've checked the results look sensible, it would be nice to see how the feaures relate to the rest of the geography of the area. To do this we might want to add a basemap, which we can do using maptiles and tmap.
maptiles
downloads and composes images
from web map tile services. You can use the OS Maps API with maptiles to get a
variety of different basemaps. Find out more about the OS Maps API
here.
tmap
is one of the more sophisticated
mapping and visualisation packages in R
that is designed to work with sf
objects and can display basemaps.
This first example demonstrates how to create a plot similar to the basic sf
plot using tmap
.
library(maptiles) library(tmap) # Make the same plot as above, but using tmap query_plot <- tm_shape(results_df) + tm_fill(col = 'purple') + tm_borders() + tm_shape(extent$polygon) + tm_borders(lty = 'dashed') + tm_grid(labels.format = list(big.mark = ""), lines = FALSE) + tm_credits('Contains OS data © Crown copyright and database rights, 2025.', position = c("LEFT", "BOTTOM")) query_plot
To add a basemap we will retrieve the OS Maps tiles using a custom source in
maptiles
.
# Define the tile server parameters osmaps <- list(src = 'OS Maps', q = 'https://api.os.uk/maps/raster/v1/zxy/Light_3857/{z}/{x}/{y}.png?key={apikey}', sub = '', cit = 'Contains OS data © Crown copyright and database rights, 2025.') # Download tiles and compose basemap raster tile_maps <- get_tiles(x = extent$polygon, provider = osmaps, crop = FALSE, cachedir = tempdir(), apikey = get_os_key(), verbose = FALSE) # Add basemap to the tmap final_plot <- tm_shape(tile_maps, bbox = st_bbox(results_df)) + tm_rgb() + query_plot final_plot
Note that maptiles
currently only supports tiles in EPSG:3857 projection. The
package attempts to re-project the basemap to match the CRS of the query
features. This can cause some distortion in the basemap image. If this warping
is not acceptable, osdatahub
provides an alternative interface to query and
download the tiles in either ESPG:27700 or EPSG:3857. However, it requires more
advanced processing and some additional packages, such as terra
.
# Download tiles res <- query_maps(extent_from_bbox(st_bbox(results_df), crs = 27700), layer = 'Light_27700', output_dir = tempdir()) #> Light_27700_6_161_345.png already exists. Set `overwrite` to TRUE. #> Light_27700_6_162_345.png already exists. Set `overwrite` to TRUE. #> Light_27700_6_163_345.png already exists. Set `overwrite` to TRUE. #> Light_27700_6_161_344.png already exists. Set `overwrite` to TRUE. #> Light_27700_6_162_344.png already exists. Set `overwrite` to TRUE. #> Light_27700_6_163_344.png already exists. Set `overwrite` to TRUE. # Convert tiles into georeferenced rasters png2rast <- function(path, bbox, crs){ img <- png::readPNG(path) * 255 img <- terra::rast(img) terra::RGB(img) <- c(1,2,3) terra::ext(img) <- bbox[c(1,3,2,4)] terra::crs(img) <- crs return(img) } imgList <- lapply(res, function(t){ png2rast(t$file_path, t$bbox, t$crs) }) # Combine tiles into basemap basemap <- do.call(terra::mosaic, imgList) # Add basemap to the tmap base_plot <- tm_shape(basemap, bbox = results_df) + tm_rgb() + query_plot base_plot
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.