Acceso a la base de datos de OpenStreetMaps desde R
La base de datos de Open Street Maps
Recientemente creé un mapa de la distribución de gasolineras y estaciones de carga eléctrica en Europa.
Population density through the number of gas stations in Europe. #dataviz @AGE_Oficial @mipazos @simongerman600 @openstreetmap pic.twitter.com/eIUx2yn7ej
— Dr. Dominic Royé (@dr_xeo) February 25, 2018
¿Cómo se puede obtener estos datos?
Pues, en este caso usé puntos de interés (PDIs) de la base de datos de Open Street Maps (OSM). Obviamente OSM no sólo contiene las carreteras, sino también información que nos puede ser útil a la hora de usar un mapa, como por ejemplo las ubicaciones de hospitales o gasolineras. Para evitar la descarga de todo el OSM y extraer la información requerida, se puede hacer uso de una overpass API, que nos permite hacer consultas a la base de datos de OSM con nuestros propios criterios.
Una forma fácil de acceder a una overpass API es a través de overpass-turbo.eu, que incluso incluye un asistente para construir una consulta y muestra los resultados sobre un mapa interactivo. Una explicación detallada de la página anterior la podemos encontrar aquí. Sin embargo, tenemos a nuestra disposicón el paquete osmdata que nos permite crear y hacer las consultas directamente desde el entorno de R. Aún así, el uso de la overpass-turbo.eu puede ser útil cuando no estamos seguros de lo que buscamos o tenemos alguna dificultad en construir la consulta.
Acceso a la overpass API desde R
El primer paso, que debemos seguir, es instalar varios paquetes, en el caso de que no estén instaldos. En casi todos mis scripts hago uso de tidyverse que es una colección fundamental de distintos paquetes, incluyendo dplyr (manipulación de datos), ggplot2 (visualización), etc. El paquete sf es el nuevo estándar para trabajar con datos espaciales y es compatible con ggplot2 y dplyr. Por último, ggmap nos facilita el trabajo para crear mapas.
#instalamos los paquetes osmdata, sf, tidyverse y ggmap
if(!require("osmdata")) install.packages("osmdata")
if(!require("tidyverse")) install.packages("tidyverse")
if(!require("sf")) install.packages("sf")
if(!require("ggmap")) install.packages("ggmap")
#cargamos las librerías
library(tidyverse)
library(osmdata)
library(sf)
library(ggmap)
Construir una consulta
Antes de crear una consulta, debemos conocer qué podemos filtrar. La función available_features( )
nos devuelve un listado amplio de las características disponibles en OSM que a su vez tienen diferentes categorías (tags). Están disponibles más detalles en la wiki de OSM aquí.
Por ejemplo, la característica shop contiene como categoría entre otros supermarket, fishing, books, etc.
#las primeras cinco características
head(available_features())
## [1] "4wd_only" "abandoned" "abutters" "access" "addr" "addr:city"
#instalaciones y establecimientos públicos
head(available_tags("amenity"))
## [1] "animal_boarding" "animal_breeding" "animal_shelter" "arts_centre"
## [5] "atm" "baby_hatch"
#tiendas
head(available_tags("shop"))
## [1] "agrarian" "alcohol" "anime" "antiques" "appliance" "art"
La primera consulta: ¿Dónde podemos encontrar cines en Madrid?
Para construir la consulta se hace uso del pipe operator %>%
que ayuda a encadenar varias funciones sin asignar el resultado a un nuevo objeto. Su uso es muy extendido especialmente con el paquete tidyverse. Si quieres saber más de su uso, aquí tienes un tutorial.
En la primera parte de la consulta debemos indicar el lugar donde queremos extraer la información. La función getbb( )
crea un rectángulo de selección para un lugar dado, buscando el nombre. La función principal es opq( )
que construye la consulta final. Añadimos con la función add_osm_feature( )
nuestros criterios de filtro. En esta primera consulta buscaremos cines en Madrid. Por eso, usamos como clave amenity y como categoría cinema. Existen varios formatos para obtener el resultado de la consulta. La función osmdata_*( )
envía la consulta al servidor y en función del sufijo * sf/sp/xml nos devuelve el formato simple feature, spatial o XML.
#construcción de la consulta
q <- getbb("Madrid") %>%
opq() %>%
add_osm_feature("amenity", "cinema")
str(q) #la estructura de la consulta
## List of 4
## $ bbox : chr "40.3119774,-3.8889539,40.6437293,-3.5179163"
## $ prefix : chr "[out:xml][timeout:25];\n(\n"
## $ suffix : chr ");\n(._;>;);\nout body;"
## $ features: chr " [\"amenity\"=\"cinema\"]"
## - attr(*, "class")= chr [1:2] "list" "overpass_query"
## - attr(*, "nodes_only")= logi FALSE
cinema <- osmdata_sf(q)
cinema
## Object of class 'osmdata' with:
## $bbox : 40.3119774,-3.8889539,40.6437293,-3.5179163
## $overpass_call : The call submitted to the overpass API
## $meta : metadata including timestamp and version numbers
## $osm_points : 'sf' Simple Features Collection with 220 points
## $osm_lines : NULL
## $osm_polygons : 'sf' Simple Features Collection with 12 polygons
## $osm_multilines : NULL
## $osm_multipolygons : NULL
Vemos que el resultado es una lista de distintos objetos espaciales. En nuestro caso únicamente nos interesaría osm_points.
¿Cómo podemos visulizar estos puntos?
La ventaja de objetos sf es que para ggplot2 existe una geometría propia geom_sf( )
. Además, haciendo uso de ggmap podemos incluir un mapa de fondo. La función get_map( )
descarga el mapa para un lugar dado. En lugar puede ser una dirección, latitud/longitud o un rectángulo de selección. El argumento maptype nos permite indicar el estilo o tipo de mapa. Podemos consultar más detalles en la ayuda de la función ?get_map
.
Cuando construimos un gráfico con ggplot habitualmente empezamos con ggplot( )
. En este caso, se empieza por ggmap( )
que incluye el objeto con nuestro mapa de fondo. Después añadimos con geom_sf( )
los puntos de los cines en Madrid. Es importante indicar con el argumento inherit.aes=FALSE que debe usar aesthetic mappings del objeto espacial osm_points. Además, indicamos el color (colour, fill), transparencia (alpha), tipo y tamaño (size) del círculo.
#nuestro mapa de fondo
mad_map <- get_map(getbb("Madrid"), maptype = "toner-background")
#mapa final
ggmap(mad_map)+
geom_sf(data = cinema$osm_points,
inherit.aes = FALSE,
colour = "#238443",
fill = "#004529",
alpha = .5,
size = 4,
shape = 21)+
labs(x = "", y = "")
¿Dónde están los supermercados de Mercadona?
En lugar de obtener un rectángulo de selección con la función getbb( )
podemos construir nuestro propio. Para ello, creamos un vector de cuatro elementos, siendo aquí el orden Oeste/Sur/Este/Norte. En la consulta usamos dos características: name y shop para poder filtrar supermercados que sean de esta marca en concreto. En función del area o bien del volumen que tenga la consulta, es necesario ampliar el tiempo de espera. Por defecto, son 25 segundo (timeout).
El mapa que creamos en este caso se basa únicamente en los puntos de supermercados. Por eso, usamos la gramática habitual añadiendo la geometría geom_sf( )
. La función theme_void( )
elimina todo con excepción de los puntos.
#rectángulo de selección para la Península Ibérica
m <- c(-10, 30, 5, 46)
#construcción de la consulta
q <- m %>%
opq (timeout = 25*100) %>%
add_osm_feature("name", "Mercadona") %>%
add_osm_feature("shop", "supermarket")
str(q) #estructura de la consulta
## List of 4
## $ bbox : chr "30,-10,46,5"
## $ prefix : chr "[out:xml][timeout:2500];\n(\n"
## $ suffix : chr ");\n(._;>;);\nout body;"
## $ features: chr " [\"name\"=\"Mercadona\"] [\"shop\"=\"supermarket\"]"
## - attr(*, "class")= chr [1:2] "list" "overpass_query"
## - attr(*, "nodes_only")= logi FALSE
#consulta
mercadona <- osmdata_sf(q)
#mapa final del resultado
ggplot(mercadona$osm_points)+
geom_sf(colour = "#08519c",
fill = "#08306b",
alpha = .5,
size = 1,
shape = 21)+
theme_void()