<- c("tidyverse", "sf", "mapSpain", "osmdata", "ggspatial")
libs <- libs %in% rownames(installed.packages())
installed_libs if (any(installed_libs == F)) {install.packages(libs[!installed_libs])}
invisible(lapply(libs, library, character.only = T))
rm(libs, installed_libs)
9 OpenStreetMap (OSM)
9.1 Introducción
OpenStreetMap (OSM) es un proyecto colaborativo para crear un mapa libre y editable del mundo, el cual permite a los usuarios visualizar, editar y utilizar datos geográficos de cualquier parte del globo. Los datos de OpenStreetMap son creados por personas voluntarias que contribuyen en el proyecto aportando información geoespacial, por ejemplo, mediante el uso de SIG, recopilando datos desde el terreno con dispositivos GPS, dibujando mapas a partir de imágenes satelitales, o agregando información de fuentes públicas.
Los datos de OpenStreetMap cubren una amplia gama de tipos de información geográfica, incluyendo calles, edificios, negocios, bosques, ríos, montañas y mucho más, haciéndolo útil para una variedad de aplicaciones. Los datos del mapa fuente son creados y mantenidos por miles de voluntarios de todo el mundo, utilizando procesos similares al mantenimiento de la enciclopedia Wikipedia.
El proyecto fue iniciado en 2004 por Steve Coast en el Reino Unido, con el objetivo de crear un mapa libre que cualquiera pudiera usar y editar. Desde entonces, ha crecido significativamente con la ayuda de miles de voluntarios de todo el mundo. A diferencia de los mapas comerciales, que pueden tener restricciones legales o técnicas de uso, los datos de OpenStreetMap se distribuyen bajo la licencia Open Data Commons Open Database License (ODbL), lo que permite su uso y distribución libremente, siempre que se proporcione el crédito adecuado y se comparta cualquier trabajo derivado bajo la misma licencia.
El aspecto más visible de OSM es su interfaz gráfica, pero los mapas también se pueden ver, importar o editar en muchas aplicaciones como QGIS, OpenLayers, ArcGIS y otras aplicaciones dedicadas de OSM.
No obstante lo anterior, el núcleo del proyecto OSM es la información subyacente, que está disponible de forma gratuita (open access) para que todos la puedan (podamos) ver o editar, facilitando así la creación de mapas personalizados. Por ello, la relevancia de OSM está en los datos, y no en los mapas que se generan a partir de ellos.
9.2 Librerías
Para esta práctica, usaremos algunos paquetes utilizados anteriormente, como dplyr
(Wickham et al., 2023) y ggplot2
(Wickham et al., 2022) (ambos incluidos en tidyverse
), sf
(Pebesma, 2022) (para trabajar con datos espaciales) y mapSpain
(Hernangómez, 2025) (para obtener cartografía de España). Además de estas librerías, en esta sesión usaremos osmdata
(Padgham et al., 2023) (para importar datos de OpenStreetMaps) y ggspatial
(Dunnington, 2022) (una extensión de ggplot que facilita la representación de datos geoespaciales).
Con el siguiente código, instalamos y cargamos las librerías de forma “oculta” a medida que las vayamos necesitando.
Algunas funcionalidades de osmdata
están limitadas si usamos la conexión a internet de la UV (eduroam)…
¿Qué devuelve esta función?
::has_internet() curl
Para resolver esto (si estamos usando la red eduroam), debemos “engañar” a R:
assign("has_internet_via_proxy", TRUE, environment(curl::has_internet))
9.3 Construcción de una consulta (query)
Antes de crear una consulta, necesitamos saber qué podemos filtrar. La función available_features()
devuelve una lista de características (features) OSM disponibles que tienen diferentes etiquetas (tags). Para más detalles puedes acceder a la wiki de OSM. Por ejemplo, la característica tienda (shop) contiene varias etiquetas, entre otras, supermercado, pesca, libros, etc.
<- osmdata::available_features()
osm_features head(osm_features,30)
[1] "4wd_only" "abandoned"
[3] "abutters" "access"
[5] "addr" "addr:city"
[7] "addr:conscriptionnumber" "addr:country"
[9] "addr:county" "addr:district"
[11] "addr:flats" "addr:full"
[13] "addr:hamlet" "addr:housename"
[15] "addr:housenumber" "addr:inclusion"
[17] "addr:interpolation" "addr:place"
[19] "addr:postbox" "addr:postcode"
[21] "addr:province" "addr:state"
[23] "addr:street" "addr:subdistrict"
[25] "addr:suburb" "addr:unit"
[27] "admin_level" "aeroway"
[29] "agricultural" "alcohol"
<- osmdata::available_tags("amenity")) (osm_amenity
<- osmdata::available_tags("shop")) (osm_shop
Para construir la consulta (query), usaremos el operador tubería (pipe) %>%
del paquete dplyr
. En la primera parte de la consulta necesitamos indicar el lugar (bounding box) de donde queremos extraer la información. La función getbb()
crea un cuadro delimitador para un lugar determinado, a partir del nombre indicado. La función principal es opq()
(Overpass Query) que construye la consulta final. A continuación, agregamos los criterios de filtro con la función add_osm_feature()
.
9.3.1 Cines en Valencia (ciudad)
En esta primera consulta, nos centraremos en los cines que hay en la ciudad de Valencia. Para ello, usaremos amenity como clave (key
) y cinema como valor (value
). Existen varios formatos para obtener los datos espaciales resultantes de la consulta. La función osmdata_*()
envía la consulta al servidor y, dependiendo del sufijo *
sf/sp/xml, devuelve una simple feature, un formato espacial (spatial) o un XML.
<- osmdata::getbb("Valencia")
bbox_valencia class(bbox_valencia)
[1] "matrix" "array"
bbox_valencia
min max
x -0.4325512 -0.2725205
y 39.2784496 39.5666090
<- bbox_valencia %>%
q ::opq() %>%
osmdata::add_osm_feature(key = "amenity", value = "cinema")
osmdata
str(q) # vemos la estructura de la consulta
List of 5
$ bbox : chr "39.2784496,-0.4325512,39.566609,-0.2725205"
$ prefix : chr "[out:xml][timeout:25];\n(\n"
$ suffix : chr ");\n(._;>;);\nout body;"
$ features : chr "[\"amenity\"=\"cinema\"]"
$ osm_types: chr [1:3] "node" "way" "relation"
- attr(*, "class")= chr [1:2] "list" "overpass_query"
- attr(*, "nodes_only")= logi FALSE
<- osmdata::osmdata_sf(q)
cines_valencia class(cines_valencia)
[1] "list" "osmdata" "osmdata_sf"
cines_valencia
Object of class 'osmdata' with:
$bbox : 39.2784496,-0.4325512,39.566609,-0.2725205
$overpass_call : The call submitted to the overpass API
$meta : metadata including timestamp and version numbers
$osm_points : 'sf' Simple Features Collection with 290 points
$osm_lines : NULL
$osm_polygons : 'sf' Simple Features Collection with 12 polygons
$osm_multilines : NULL
$osm_multipolygons : NULL
Observamos que el resultado es una lista que contiene, entre otros, y además del bbox
, diferentes objetos espaciales: points, lines, polygons, multilines y multipolygons. En este caso, únicamente hay datos en osm_points
y osm_polygons
. Veamos el contenido de los osm_points
:
<- cines_valencia$osm_points
cines_valencia_points class(cines_valencia_points)
[1] "sf" "data.frame"
st_crs(cines_valencia_points)
Coordinate Reference System:
User input: EPSG:4326
wkt:
GEOGCRS["WGS 84",
ENSEMBLE["World Geodetic System 1984 ensemble",
MEMBER["World Geodetic System 1984 (Transit)"],
MEMBER["World Geodetic System 1984 (G730)"],
MEMBER["World Geodetic System 1984 (G873)"],
MEMBER["World Geodetic System 1984 (G1150)"],
MEMBER["World Geodetic System 1984 (G1674)"],
MEMBER["World Geodetic System 1984 (G1762)"],
ELLIPSOID["WGS 84",6378137,298.257223563,
LENGTHUNIT["metre",1]],
ENSEMBLEACCURACY[2.0]],
PRIMEM["Greenwich",0,
ANGLEUNIT["degree",0.0174532925199433]],
CS[ellipsoidal,2],
AXIS["geodetic latitude (Lat)",north,
ORDER[1],
ANGLEUNIT["degree",0.0174532925199433]],
AXIS["geodetic longitude (Lon)",east,
ORDER[2],
ANGLEUNIT["degree",0.0174532925199433]],
USAGE[
SCOPE["Horizontal component of 3D system."],
AREA["World."],
BBOX[-90,-180,90,180]],
ID["EPSG",4326]]
Representamos gráficamente el resultado obtenido:
ggplot() +
geom_sf(data = cines_valencia_points)
Como hemos comentado en otras ocasiones, si no tenemos referencias, es difícil ubicar las geometrías simples (aunque sepamos las coordenadas). En esta ocasión, usaremos la función annotation_map_tile()
del paquete ggspatial
(Dunnington, 2022) para agregar un mapa de teselas (imágenes cuadradas que configuran el mapa completo) como una capa más del objeto generado con ggplot()
:
ggplot() +
::annotation_map_tile(type="cartolight", zoom = 12) +
ggspatialgeom_sf(data = cines_valencia_points, color = "#238443", size = 2.5, alpha=0.5) +
theme_void()
Zoom: 12
El estilo de mapa usado por defecto es el habitual de OSM (type=osm
), aunque se pueden usar otros:
::osm.types() rosm
[1] "osm" "opencycle"
[3] "hotstyle" "loviniahike"
[5] "loviniacycle" "stamenbw"
[7] "stamenwatercolor" "osmtransport"
[9] "thunderforestlandscape" "thunderforestoutdoors"
[11] "cartodark" "cartolight"
9.3.2 Cines en Valencia (provincia)
Otra forma de obtener el área de búsqueda es a partir del bounding box de otro objeto espacial ya existente. Para ello, usaremos la función st_bbox()
del paquete sf
.
<- mapSpain::esp_get_prov() %>% filter(cpro == "46")
val <- sf::st_bbox(val)
bbox_val class(bbox_val)
[1] "bbox"
bbox_val
xmin ymin xmax ymax
-1.528067 38.686653 -0.025874 40.211700
¿Hemos tenido en cuenta las consideraciones vistas en sesiones anteriores? ¿Qué faltaría por hacer?
El procedimiento es análogo al caso anterior.
Dependiendo de la zona o volumen de la consulta, es necesario ampliar el tiempo de espera. De forma predeterminada, el límite (timeout
) se establece en 25 segundos.
<- bbox_val %>%
q ::opq(timeout = 1000) %>%
osmdata::add_osm_feature(key = "amenity", value = "cinema")
osmdata
<- osmdata::osmdata_sf(q)
cines_val <- cines_val$osm_points
cines_val_points
ggplot() +
::annotation_map_tile(type="cartolight", zoom = 9) +
ggspatialgeom_sf(data = cines_val_points, color = "#238443", size = 2.5, alpha = .5) +
theme_void()
Zoom: 9
¿Todas las ubicaciones obtenidas están en la provincia de Valencia?
9.3.3 Supermercados Mercadona
En lugar de obtener un cuadro delimitador (bounding box) con la función getbb()
, podemos construir nuestro propio cuadro. Para hacer esto creamos un vector de cuatro elementos. El orden tiene que ser left/bottom/right/top (Oeste/Sur/Este/Norte). En la consulta utilizaremos dos características: nombre (name) y tienda (shop) para filtrar los supermercados de la cadena Mercadona.
<- mapSpain::esp_get_country(moveCAN = F)
spain ggplot() +
geom_sf(data = spain)
<- c(-10, 35, 5, 45)
bbox_spain
<- bbox_spain %>%
q1 ::opq(timeout = 3600) %>%
osmdata::add_osm_feature("name", "Mercadona") %>%
osmdata::add_osm_feature("shop", "supermarket")
osmdata
<- osmdata_sf(q1)
mercadona class(mercadona)
[1] "list" "osmdata" "osmdata_sf"
<- mercadona$osm_points
mercadona_points class(mercadona_points)
[1] "sf" "data.frame"
ggplot() +
geom_sf(data = mercadona_points, color = "#229E6B", alpha = .5) +
theme_void()
ggplot() +
::annotation_map_tile(type = "cartolight", zoom = 8) +
ggspatialgeom_sf(data = mercadona_points, color = "#229E6B", alpha = .5) +
theme_minimal()
Zoom: 8
9.3.4 Supermercados Consum
Ahora haremos lo mismo que en el epígrafe anterior, pero para los supermercados Consum:
<- bbox_spain %>%
q2 ::opq(timeout = 1000) %>%
osmdata::add_osm_feature("name", "Consum") %>%
osmdata::add_osm_feature("shop", "supermarket")
osmdata
<- osmdata_sf(q2)
consum <- consum$osm_points consum_points
Pintamos ambos conjuntos de datos en el mismo gráfico:
ggplot() +
::annotation_map_tile(type = "cartolight", zoom = 8) +
ggspatialgeom_sf(data = mercadona_points, color = "#229E6B", alpha = .5) +
geom_sf(data = consum_points, color = "#F8951D", alpha = .5) +
theme_minimal()
Zoom: 8
9.4 Supermercados España
Para finalizar, uniremos ambos datasets y crearemos un gráfico más adecuado:
<- mercadona_points %>%
sf1 mutate(nombre = "Mercadona") %>%
select(nombre)
<- consum_points %>%
sf2 mutate(nombre = "Consum") %>%
select(nombre)
<- rbind(sf1,sf2)
sf
ggplot() +
::annotation_map_tile(type="cartolight", zoom=8) +
ggspatialgeom_sf(data = sf, aes(color=nombre), alpha = .5) +
guides(color = guide_legend(override.aes = list(shape=15, size=5))) +
scale_color_manual(values = c("Mercadona"="#229E6B", "Consum" = "#F8951D")) +
theme_minimal() +
theme(legend.position = "bottom",
legend.direction = "horizontal",
legend.title = element_blank())
Zoom: 8