Catégories
R

The giant French Olympic-size swimming pool

Day 8 of 30DayMapChallenge : « Openstreetmap » (previously).

What if all private swimming pools could be merged into one 25 m width pool? OSM is not just a map, it’s a database, so ask OSM… I know that not all swimming pools are present in OSM, but it’s just an exercise 🙂 and it can give us an order of magnitude or at least a minimum.

We will use a simplified map of France in the background and ask to the Overpass API (with {osmdata}) all « leisure=swimming_pool » and « access=private ». It takes 6 hours and 15 Go of RAM…

library(dplyr)
library(readr)
library(ggplot2)
library(sf)
library(osmdata)
library(ggspatial)
library(glue)
library(httr)
library(units)

fr_bbox <- getbb("France métropolitaine", featuretype = "area")

GET("http://r.iresmi.net/wp-content/uploads/2021/12/adminexpress_simpl.gpkg_.zip",
    write_disk("~/adminexpress_simpl.gpkg_.zip"))
dir.create("~/adminexpress")
unzip("~/adminexpress_simpl.gpkg_.zip", exdir = "~/adminexpress")

fr <- read_sf("~/adminexpress/adminexpress_simpl.gpkg", layer = "region") %>% 
  filter(insee_reg > "06")

# 6 hours
pool <- opq(fr_bbox, timeout = 600) %>%
  add_osm_feature(key = "leisure", value = "swimming_pool") %>% 
  add_osm_feature(key = "access", value = "private") %>% 
  osmdata_sf()

pool_p <- pool$osm_polygons %>% 
  select(osm_id)
pool_mp <- pool$osm_multipolygons %>% 
  select(osm_id)

rm(pool) ; gc()

# keep only pools in France (bbox was wider)
pool_fr <- pool_p %>% 	
  bind_rows(pool_mp) %>% 
  st_filter(fr) %>% 
  mutate(area = st_area(geometry)) 

# what resulting dimension ?
olympic_pool_dim <- pool_fr %>% 
  st_drop_geometry() %>% 
  summarise(total_area = drop_units(sum(area, na.rm = TRUE))) %>% 
  mutate(width = 25,
         length = total_area / width)

# rotate sf object
rot <- function(a) matrix(c(cos(a), sin(a), -sin(a), cos(a)), 2, 2)

# create the pool, translate it in France and rotate
olympic_pool <- c(0, 0,
                  0, olympic_pool_dim$length, 
                  olympic_pool_dim$width, olympic_pool_dim$length,
                  olympic_pool_dim$width, 0,
                  0, 0) %>% 
  matrix(ncol = 2, byrow = TRUE) %>% 
  list() %>% 
  st_polygon() %>% 
  st_sfc(crs = "EPSG:2154") *
  rot(pi / 4) + 
  c(400000, 6300000)

# map
fr %>% 
  st_transform("EPSG:2154") %>% 
  ggplot() +
  geom_sf() +
  geom_sf(data = st_sf(olympic_pool, crs = "EPSG:2154"), color = "blue") +
  labs(title = "The giant French Olympic-size swimming pool",
  	   subtitle = glue("What if all private swimming pools could be merged
                        into one 25 m width pool?"),
       caption = glue("pool data from © OpenStreetMap contributors
                      map IGN Adminexpress
                      r.iresmi.net - {Sys.Date()}")) +
  annotation_scale(height = unit(0.1, "cm")) +
  theme_minimal() +
  theme(panel.grid = element_blank(),
        axis.text = element_blank())

ggsave("pool.png", width = 20, height = 12.36, units = "cm", scale = 1.1)

We get an Olympic swimming pool (25 m width) measuring at least 755 km in length !

The giant French Olympic-size swimming pool
Catégories
R

Use data from Openstreetmap

Day 5 of 30DayMapChallenge : « Ukraine » (previously).

Using {osmdata} to extract streets :

library(dplyr)
library(ggplot2)
library(sf)
library(osmdata)
library(ggspatial)
library(glue)

ua_bbox <- getbb("kiev, ukraine", featuretype = "city")

ua <- opq(ua_bbox) %>%
  add_osm_features(features = c('"highway"="primary"',
                                '"highway"="secondary"',
                                '"highway"="tertiary"',
                                '"highway"="unclassified"',
                                '"highway"="residential"')) %>% 
  osmdata_sf()

ua$osm_lines %>% 
  bind_rows(ua$osm_multilines) %>% 
  st_set_crs("EPSG:4326") %>% 
  ggplot() +
  geom_sf(color = "#FFD700", size = 0.2) +
  annotation_scale(bar_cols =  c("#FFD700", "#0057B7"),
                   line_col = "#FFD700",
                   text_col = "#FFD700",
                   height = unit(0.1, "cm")) +
  coord_sf(xlim = ua_bbox[c(1, 3)],
           ylim = ua_bbox[c(2, 4)]) +
  labs(title = "Kyiv",
       caption = glue("© OpenStreetMap contributors
                      r.iresmi.net - {Sys.Date()}")) +
  theme_void() +
  theme(plot.background = element_rect(color = NA, 
                                       fill = "#0057B7"),
        plot.title = element_text(color = "#FFD700"),
        plot.caption = element_text(size = 5,
                                    color = "#FFD700"))

ggsave("kyiv.png", width = 20, height = 12.36, units = "cm", scale = 1.1)

Kyiv
Catégories
R

Mapping multiple trends with confidence

A tutorial to compute trends by groups and plot/map the results

We will use dplyr::nest to create a list-column and will apply a model (with purrr::map) to each row, then we will extract each slope and its p value with map and broom::tidy.

Setup

library(tidyverse)
library(httr)
library(sf)
library(readxl)
library(janitor)
library(fs)
library(broom)
library(scales)
library(rnaturalearth)
library(rnaturalearthdata)
library(rgeos)

fk <- function(x) format(x, big.mark = " ")

Data

Map data. Départements polygons from OSM.

if(!file_exists("departements-20140528-100m.shp")) {
  f <- tempfile()
  GET("http://osm13.openstreetmap.fr/~cquest/openfla/export/departements-20140528-100m-shp.zip",
      write_disk(f))
  unzip(f)
}

dep <- st_read("departements-20140528-100m.shp")

Population data by département 1990-2008 from INSEE.

if(!file_exists("pop.xls")) {
  GET("https://www.insee.fr/fr/statistiques/fichier/2012713/TCRD_004.xls", 
      write_disk("pop.xls"))
}
  
pop <- read_xls("pop.xls", skip = 3) %>% 
  clean_names() %>% 
  head(-1) %>% 
  rename(insee_dep = x1,
         dep = x2,
         x2018 = x2018_p) %>% 
  select(-4) %>% 
  gather(annee, pop, 3:7) %>% 
  mutate(annee = as.integer(str_replace(annee, "x", "")))

Population trends for each département

pop_model <- function(df) {
  lm(pop ~ annee, data = df)
}

trends <- pop %>% 
  group_by(insee_dep, dep) %>% 
  nest() %>% 
  mutate(model = map(data, pop_model),
         glance = map(model, glance),
         coeff = map(model, tidy, conf.int = TRUE)) 

Plot

trends %>% 
  unnest(coeff) %>% 
  filter(term == "annee",
         !insee_dep %in% c("F", "M")) %>% 
  ggplot(aes(fct_reorder(insee_dep, estimate), estimate,
             color = if_else(p.value <= .05,
                             if_else(estimate >= 0, "positive", "négative"),
                             "n.s."))) +
    geom_point() +
    geom_errorbar(aes(ymin = conf.low, ymax = conf.high), width = .5) +
    scale_color_manual(name = "Tendance",
                       values = c("positive" = "red",  "n.s." = "lightgray", "négative" = "blue")) +
    scale_y_continuous(labels = fk) +
    labs(title = "Évolution des populations départementales françaises",
         subtitle = "1990-2018",
         x = "dép.",
         y = bquote(Delta[population] ~ (habitant %.% an^{-1})),
         caption = "r.iresmi.net\ndonnées INSEE") +
    guides(color = guide_legend(reverse = TRUE)) +
    theme(plot.caption = element_text(size = 7))
Only 9 départements have a clearly decreasing population

Map

pop_dep <- trends %>% 
  unnest(coeff) %>% 
  filter(term == "annee") %>% 
  right_join(dep, by = c("insee_dep" = "code_insee")) %>%
  left_join(filter(pop, annee == 2018, !insee_dep %in% c("F", "M")), by = "insee_dep") %>% 
  st_as_sf() %>% 
  st_transform(2154) 

moy_fr <- trends %>% 
  unnest(coeff) %>% 
  filter(term == "annee",  !insee_dep %in% c("F", "M")) %>% 
  summarise(mean(estimate, na.rm = TRUE)) %>% 
  pull()

world <- ne_countries(scale = "medium", returnclass = "sf") %>% 
  filter(continent == "Europe") %>% 
  st_transform(2154) 

pop_dep %>% 
  ggplot() +
    geom_sf(data = world, fill = "grey97", color = 0) +
    geom_sf(color = "lightgrey", fill = "floralwhite", size = .2) +
    stat_sf_coordinates(data = filter(pop_dep, p.value > .05),
                        aes(size = pop),
                        fill = "lightgrey", color = "lightgrey", shape = 21, alpha = 0.8) +
    stat_sf_coordinates(data = filter(pop_dep, p.value <= .05),
                        aes(size = pop, fill = estimate),
                        color = "lightgrey", shape = 21, alpha = 0.8) +
    coord_sf(xlim = c(100000, 1200000), ylim = c(6000000, 7200000)) +
    scale_fill_gradient2(name = bquote(atop(displaystyle(atop(Delta ~ population[1990-2018],
                                                               (habitant %.% an^{-1}))), 
                                             moy. == .(round(moy_fr)) ~ ", en gris : n.s.")),
                          labels = fk,
                          low = "darkblue", mid = "white", high = "darkred", midpoint = moy_fr) +
    scale_size_area(name = "habitants (2018)", labels = fk, max_size = 10) +
    labs(title = "Évolution des populations départementales françaises",
         subtitle = "Métropole, 1990-2018",
         x = "",
         y = "",
         caption = "r.iresmi.net\ndonnées INSEE 2018\nfond cartographique : contributeurs Openstreetmap 2014\nNaturalearth") +
    theme_bw() +
    theme(plot.caption = element_text(size = 7),
          legend.text.align = 1)
Population is growing stronger in Paris suburbs and in peripheral southern départements