Mejora del dataset de aparcamientos de València con información de barrios

datos_espaciales
OSM
Valencia
Autor/a
Afiliación

Pablo Senabre Catalá

Universitat de Valencia

Fecha de publicación

1 de abril de 2025

Input

El estacionamiento es un elemento clave en la planificación urbana, ya que incide directamente en la movilidad y en la calidad de vida de los ciudadanos. En esta práctica, nos centramos en analizar los aparcamientos del municipio de València, con el objetivo de estudiar su distribución a lo largo de los distintos barrios y de comprender mejor cómo se integran en el tejido urbano.

Para obtener los datos, hemos utilizado OpenStreetMap (OSM), una fuente de información geográfica abierta, gratuita y continuamente actualizada por la comunidad. La extracción se ha realizado mediante una consulta en RStudio que recoge todos los elementos con la etiqueta amenity=parking dentro del área de València. El conjunto resultante incluye, entre otros, los siguientes campos:

  • osm_id: Identificador único de cada aparcamiento en OSM.

  • name: Nombre del aparcamiento (si se ha registrado).

  • geometry: Ubicación geográfica de cada aparcamiento.

Es importante destacar que la etiqueta amenity=parking abarca una amplia variedad de instalaciones, lo que incluye:

  • Aparcamientos a nivel de superficie: Zonas abiertas o áreas en la vía pública destinadas al estacionamiento.

  • Garajes o estacionamientos de varios pisos (multi-storey): Estructuras destinadas a estacionar vehículos en múltiples niveles.

  • Estacionamientos subterráneos (underground): Instalaciones ubicadas por debajo del nivel del suelo.

De esta forma, se incluyen todos los tipos de aparcamientos registrados en OSM, permitiendo así un análisis global de la infraestructura de estacionamiento en la ciudad.


Sin embargo, los datos originales de OSM no indican a qué barrio pertenece cada aparcamiento. Para enriquecer el dataset, se ha incorporado un segundo conjunto de datos que contiene los límites oficiales de los barrios de València, obtenido desde el portal de datos abiertos del Ayuntamiento o de la Generalitat Valenciana. Esta integración posibilita asignar a cada aparcamiento su correspondiente barrio, lo que facilita un análisis espacial más detallado y útil para la toma de decisiones urbanísticas.

Descripción

A continuación, se detallan los pasos seguidos para obtener y preparar los datos de aparcamientos en València, combinando información de OSM con los límites oficiales de los barrios:

Primero, cargamos las librerías necesarias:

Data (c) OpenStreetMap contributors, ODbL 1.0. https://www.openstreetmap.org/copyright
Linking to GEOS 3.12.2, GDAL 3.9.3, PROJ 9.4.1; sf_use_s2() is TRUE
Warning: package 'purrr' was built under R version 4.4.3
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.1     ✔ tibble    3.2.1
✔ lubridate 1.9.4     ✔ tidyr     1.3.1
✔ purrr     1.0.4     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors

Definimos el área geográfica de interés, utilizando la función getbb para obtener el bounding box de València, y realizamos la consulta a OSM para extraer los aparcamientos (elementos etiquetados como amenity=parking):

# Definición del bounding box y consulta a OSM
valencia_bb <- getbb("Valencia, Spain")
query_parking <- opq(bbox = valencia_bb) %>% 
  add_osm_feature(key = "amenity", value = "parking")
osm_parking <- osmdata_sf(query_parking)

# Extraemos aparcamientos como puntos y polígonos
parking_points <- osm_parking$osm_points %>% 
  select(any_of(c("osm_id", "name", "geometry")))
parking_polygons <- osm_parking$osm_polygons %>% 
  select(any_of(c("osm_id", "name", "geometry")))

Para delimitar el área de estudio, obtenemos el límite administrativo de València desde OSM, filtrando por boundary=administrative, admin_level=8 y name=València.

# Límite administrativo de València
valencia_admin <- opq(bbox = getbb("Valencia, Spain")) %>% 
  add_osm_feature(key = "boundary", value = "administrative") %>% 
  add_osm_feature(key = "admin_level", value = "8") %>% 
  add_osm_feature(key = "name", value = "València") %>% 
  osmdata_sf()

if (!is.null(valencia_admin$osm_multipolygons) && nrow(valencia_admin$osm_multipolygons) > 0) {
  valencia_boundary <- valencia_admin$osm_multipolygons
} else if (!is.null(valencia_admin$osm_polygons) && nrow(valencia_admin$osm_polygons) > 0) {
  valencia_boundary <- valencia_admin$osm_polygons
} else {
  stop("No se encontró el límite administrativo de València.")
}

Realizamos una intersección espacial para conservar solo aquellos aparcamientos que se encuentran dentro del límite administrativo y combinamos ambos conjuntos (puntos y polígonos):

# Filtrar aparcamientos dentro del límite de València
parking_points_valencia <- st_intersection(parking_points, valencia_boundary)
Warning: attribute variables are assumed to be spatially constant throughout
all geometries
parking_polygons_valencia <- st_intersection(parking_polygons, valencia_boundary)
Warning: attribute variables are assumed to be spatially constant throughout
all geometries
# Combinar y eliminar duplicados
all_parkings <- rbind(parking_points_valencia, parking_polygons_valencia) %>% 
  distinct(osm_id, .keep_all = TRUE)

Antes de incorporar la información de los barrios, es interesante visualizar la distribución de los aparcamientos tal como se obtendrían de OSM:

# Definir la extensión geográfica de Valencia
valencia_bb <- getbb("Valencia, Spain")

# Consulta OSM para obtener aparcamientos (amenity=parking)
query_parkings <- opq(bbox = valencia_bb) %>%
  add_osm_feature(key = "amenity", value = "parking")

# Extraer los datos en formato sf (se extraen solo los puntos)
osm_parkings <- osmdata_sf(query_parkings)
parkings_points <- osm_parkings$osm_points

# Visualizar los aparcamientos en un mapa interactivo
leaflet(parkings_points) %>%
  addProviderTiles("CartoDB.Positron") %>%
  addCircleMarkers(
    radius = 4, 
    color = "blue",
    popup = ~paste("OSM ID:", osm_id)
  ) %>%
  setView(lng = -0.3763, lat = 39.4699, zoom = 12)

Tratamiento

Posteriormente, cargamos el dataset de barrios (en formato GeoJSON o Shapefile) descargado de la fuente pública y aseguramos que ambos datasets tengan el mismo sistema de coordenadas (CRS):

# Cargar el dataset de barrios

barrios <- st_read("data/000136/districtes-distritos.geojson", quiet = TRUE)

# Asegurar el mismo CRS
all_parkings <- st_transform(all_parkings, st_crs(barrios))

Realizamos la unión espacial para asignar a cada aparcamiento el barrio correspondiente:

# Unión espacial: asignar barrio a cada parking
parkings_with_barrios <- st_join(all_parkings, barrios["nombre"])
parkings_barrios <- parkings_with_barrios %>% 
  filter(!is.na(nombre))

A continuación, se cuenta el número de aparcamientos por barrio y se unen estos datos al dataset de barrios para la construcción del mapa coroplético:

# Contar aparcamientos por barrio
parking_counts <- parkings_barrios %>% 
  st_set_geometry(NULL) %>% 
  group_by(nombre) %>% 
  summarize(n_parkings = n())

# Unir la cuenta al dataset de barrios
barrios_parking <- left_join(barrios, parking_counts, by = "nombre")

# Agrupar los barrios con aparcamientos para el coroplético
barrios_union <- barrios_parking %>% 
  filter(!is.na(n_parkings)) %>% 
  group_by(nombre) %>% 
  summarise(geometry = st_union(geometry),
            n_parkings = first(n_parkings))

Además, se crea una paleta “YlOrRd” para el coroplético y se convierten las geometrías de los aparcamientos a puntos (centroides) para la capa de marcadores:

# Paleta "YlOrRd" para el choropletico
pal <- colorNumeric(palette = "YlOrRd",
                    domain = barrios_union$n_parkings,
                    na.color = "transparent")

# Convertir a puntos (centroides) para la capa de marcadores
parkings_barrios_points <- st_centroid(parkings_barrios)
Warning: st_centroid assumes attributes are constant over geometries

También se genera un gráfico de barras con ggplot2 para visualizar la distribución de aparcamientos por barrio:

# Gráfico de barras
plot_parking <- parking_counts %>% 
  ggplot(aes(x = reorder(nombre, -n_parkings), y = n_parkings, fill = n_parkings)) +
  geom_bar(stat = "identity") +
  coord_flip() +
  labs(title = "Número de Aparcamientos por Barrio",
       x = "Barrio",
       y = "Número de Parkings") +
  scale_fill_gradient(low = "yellow", high = "red") +
  theme_minimal()

Output

El resultado final es un conjunto de datos enriquecido y visualizaciones interactivas que permiten analizar la distribución de los aparcamientos en València por barrio. Se obtienen las siguientes mejoras:

  • Mapa coroplético: Los barrios se colorean con la paleta “YlOrRd” según el número de aparcamientos.

  • Resaltado y etiquetas: Al pasar el ratón sobre un barrio, se resalta su contorno y aparece el nombre.

  • Marcadores con clúster: Los aparcamientos se muestran como marcadores agrupados (convertidos a centroides) con popups enriquecidos.

  • Herramientas interactivas: Se incluyen un buscador por nombre de barrio, un minimapa y una herramienta de medición.

  • Gráfico de barras: Se representa la distribución de aparcamientos por barrios.

El siguiente código genera el mapa interactivo final.

mapa <- leaflet() %>%
  addProviderTiles("CartoDB.Positron") %>%
  # Capa coroplética con resaltado y etiquetas
  addPolygons(data = barrios_union,
              fillColor = ~pal(n_parkings),
              fillOpacity = 0.7,
              color = "black",
              weight = 1,
              highlightOptions = highlightOptions(
                color = "blue",
                weight = 2,
                bringToFront = TRUE
              ),
              label = ~nombre,
              popup = ~paste("<strong>Barrio:</strong>", nombre,
                             "<br><strong>Parkings:</strong>", n_parkings),
              group = "Barrios (Choropleth)") %>%
  # Capa de marcadores con clúster
  addMarkers(data = parkings_barrios_points,
             clusterOptions = markerClusterOptions(),
             popup = ~paste("<strong>Parking OSM ID:</strong>", osm_id,
                            "<br><strong>Barrio:</strong>", nombre,
                            ifelse(is.na(name), "", paste("<br><strong>Nombre:</strong>", name))),
             group = "Parkings (Markers)") %>%
  # Buscador por nombre de barrio
  addSearchFeatures(
    targetGroups = "Barrios (Choropleth)",
    options = searchFeaturesOptions(propertyName = "nombre", zoom = 14, hideMarkerOnCollapse = TRUE)
  ) %>%
  # Minimapa y herramienta de medición
  addMiniMap(tiles = providers$CartoDB.Positron, toggleDisplay = TRUE) %>%
  addMeasure(position = "topleft",
             primaryLengthUnit = "meters",
             primaryAreaUnit = "sqmeters",
             activeColor = "#3D535D",
             completedColor = "#7D4479") %>%
  # Leyenda, barra de escala y control de capas
  addLegend("bottomright",
            pal = pal,
            values = barrios_union$n_parkings,
            title = "Nº de Parkings",
            opacity = 0.7) %>%
  addScaleBar(position = "bottomleft") %>%
  addLayersControl(overlayGroups = c("Barrios (Choropleth)", "Parkings (Markers)"),
                   options = layersControlOptions(collapsed = FALSE)) %>%
  setView(lng = -0.3763, lat = 39.4699, zoom = 12)

# Mostrar el mapa interactivo y el gráfico de barras
mapa
plot_parking


El fichero generado con este procedimiento puede descargarse desde aquí.



Proyectos de Innovación Educativa Emergente PIEE-2737007 y PIEE-3325394