SatRday Paris: Créer des images de gaufres interactives

gaufre_chocolat_rasters_header

Les outils cartographiques peuvent-ils être détournés vers d’autres usages ? Bien sûr ! Voyez comment nous jouons avec leaflet et leafgl pour rendre rapidement une énorme gaufre faîtes de millions de polygones.

Les outils de cartographie ne sont pas seulement faits pour les données géographiques

À SatRday Paris, j’ai fait une présentation intitulée “Tout sauf les cartes avec des outils de cartographie”.
L’objectif est de montrer qu’il existe des outils puissants dans certains domaines qui peuvent être appliqués à d’autres domaines. Il faut juste ouvrir un peu son esprit. Personnellement, j’aime jouer avec les cartes et les données spatiales. De fait, je vois des raster partout…

Dans cet article de blog, je montre une petite partie de ma présentation. Il semble que ce qui a laissé sa marque dans l’esprit des gens, c’est l’histoire des gaufres…. Alors, c’est la première histoire que je vais partager avec vous !

Les gaufres sont des composants électroniques

Chez ThinkR, nous développons et déployons des applications Shiny. Ici c’est pour un client qui travaille avec des composants électroniques. Je ne peux pas en dire plus sur ce travail. C’est pour ça que j’ai utilisé une analogie avec les gaufres.

Ouvre ton esprit, j’ai dit ! Faites preuve d’imagination.

Imaginez la plus grande gaufre du monde. Une gaufre avec des millions de cases.

Brooklyn, Martha Friedman Waffle
Brooklyn, Martha Friedman Waffle

Nous allons ajouter du chocolat dessus. Mais nous ne sommes pas très doués et il n’est pas possible de mettre la même quantité de chocolat dans chaque case.

Pour chaque case, nous connaissons les coordonnées de son centre. Nous connaissons la quantité de chocolat. Dans ce travail, nous voulons pouvoir :

  • Explorer chaque case si nécessaire
  • Zoom avant/zoom arrière sur ces millions de cases
  • Montrer cette gaufre dans une application Shiny
  • Passez de l’affichage d’une gaufre à l’autre de la manière la plus fluide et rapide possible.

Un raster est une structure multipolygonale

Un package qui est apparu récemment parmi les outils de cartographie dans R c’est {leafgl}. Il s’agit d’un package R uniquement disponible sur Github : https://github.com/r-spatial/leafgl. Il permet un rendu rapide de millions de points avec leaflet.
Les gaufres étaient l’occasion parfaite de tester ce package !

Une gaufre est une grille régulière. Elle peut être vue comme un raster. Cependant, {leafgl} ne fonctionne qu’avec des objets vectoriels. Bien sûr, {leaflet} peut imprimer des rasters, mais ce n’était pas assez rapide pour nous. Ce n’est pas toujours le cas, mais parfois la vitesse compte….
Donc, encore une fois, ouvrez votre esprit et essayez de voir un objet cartographique vectoriel lorsque vous regardez un raster.

library(raster)
library(rasterVis)
library(waffler)
library(cowplot)
# Build a raster
mat <- matrix(c(1, 0, 0, 1), ncol = 2)
r <- raster(mat)
plot(r)

# Get centers
r_df <- data.frame(coordinates(r), values = values(r))
# Transform as polygons
r_waffle <- wafflerize(r_df)
g1 <- gplot(r) +
  geom_raster(aes(fill = value)) +
  guides(fill = FALSE) +
  ggtitle("Original raster")
g2 <- ggplot(r_waffle) +
  geom_sf(aes(colour = LETTERS[1:nrow(r_waffle)]), size = 2) +
  guides(colour = FALSE) +
  ggtitle("Raster as polygon")
plot_grid(plotlist = list(g1, g2))

Vous voyez les rasters comme des polygones maintenant ?
Alors on va jouer avec une gaufre plus grosse ! On peut, alors pourquoi s’en priver ?

Tracez une gaufre en forme de cœur

Un rectangle c’est, disons, ennuyeux, on va donc construire une gaufre en forme de cœur avec {sf}. Le cœur est un polygone à partir duquel on peut extraire un ensemble régulier de points en utilisant st_sample . Cet ensemble de points symbolise le centre de chaque alvéole de la gaufre. Ensuite, on peut introduire plus ou moins de chocolat dans chaque case.
L’astuce consiste à utiliser des outils spatiaux sur des données non spatiales. Le cœur que nous dessinons ne doit pas être placé sur une position géographique réelle sur Terre.

En effet, j’écris actuellement ce billet de blog dans le train (#OnTheTrainAgain comme dit Colin), donc le cœur dessiné n’a pas de position fixe. Même si je suis sur Terre, mon train ne suit pas un chemin en forme de cœur. Enfin bon, vous comprenez l’idée…

De fait, pour pouvoir jouer avec ce polygone, il vaut mieux lui assigner une projection. Ajoutons une projection Lambert93 (EPSG : 2154), car on est en France !

library(sf)
# Construct heart (code from @dmarcelinobr)
xhrt <- function(t) 16 * sin(t)^3
yhrt <- function(t) 13 * cos(t) - 5 * cos(2 * t) - 2 * cos(3 * t) - cos(4 * t)
# create heart as polygon
heart_sf <- tibble(t = seq(0, 2 * pi, by = .1)) %>%
  mutate(y = yhrt(t),
         x = xhrt(t)) %>% 
  bind_rows(., head(., 1)) %>% 
  dplyr::select(x, y) %>% 
  as.matrix() %>% 
  list() %>% st_polygon() %>% 
  st_sfc(crs = 2154)
g1 <- ggplot(heart_sf) +
  geom_sf(fill = "#cb181d") +
  coord_sf(crs = 2154, datum = 2154) +
  ggtitle("Heart sf polygon")
# create grid
heart_grid <- st_sample(heart_sf, size = 500, type = "regular") %>% 
  cbind(as.data.frame(st_coordinates(.))) %>% 
  rename(x = X, y = Y) %>% 
  st_sf() %>% 
  mutate(z = cos(2*x) - cos(x) + sin(y),
         z_text = paste("Info: ", round(z)))
g2 <- ggplot(heart_grid) +
  geom_sf(colour = "#cb181d") +
  coord_sf(crs = 2154, datum = 2154) +
  ggtitle("Heart as regular point grid")
g3 <- ggplot(heart_grid) +
  geom_sf(aes(colour = z), size = 2) +
  scale_colour_distiller(palette = "YlOrBr", type = "seq", direction = 1) +
  theme(panel.background = element_rect(fill = "#000000")) +
  coord_sf(crs = 2154, datum = 2154) +
  guides(colour = FALSE) +
  ggtitle("Chocolate quantity for each point")
cowplot::plot_grid(g1, g2, g3, ncol = 3)

La projection choisie est cependant problématique. {leaflet} nécessite des objets spatiaux dont les coordonnées géographiques ne sont pas projetées (EPSG : 4326). Alors pourquoi n’ai-je pas assigné directement les coordonnées en degrés ? Parce que {leaflet} produit une carte en projection Mercator (EPSG : 3857), et je ne veux pas que mon cœur soit déformé par le changement de projection. Pourquoi n’ai-je pas assigné une projection Mercator alors ? Parce que j’aime les cartes et je n’aime pas Mercator parce qu’il donne une très mauvaise représentation des distances et des surfaces de notre Monde. Mais c’est un autre problème… Dans notre cas, je vais en effet devoir créer ma grille de polygones avec une projection Mercator, la transformer en coordonnées géographiques, pour pas que mon cœur ne soit tout aplati sur la carte finale.
Pour transformer un ensemble régulier de points en une grille de polygones prête pour {leaflet}, en prenant en compte les déformations, j’ai créé le package {waffler}, disponible sur Github uniquement (https://github.com/ThinkR-open/waffler). Utilisez devtools::install_github("ThinkR-open/waffler") pour l’essayer.
Nous allons utiliser la fonction wafflerize. Le paramètre fact est pour agrandir la taille du cœur. Si nous conservons une résolution de 1 pour une gaufre carrée de 500x500, elle sera trop petite pour apparaître avec des coordonnées géographiques suffisamment distinctes.

# Generate a grid polygon from points
heart_polygon <- wafflerize(heart_grid, fact = 1000000)
g1 <- ggplot(heart_polygon) +
  geom_sf(aes(fill = z), colour = "blue", size = 0.5) +
  scale_fill_distiller(palette = "YlOrBr", type = "seq", direction = 1) +
  theme(panel.background = element_rect(fill = "#000000")) +
  coord_sf(crs = 4326, datum = 4326) +
  guides(fill = FALSE) +
  ggtitle("Chocolate quantity (geographical coordinates = data)")
g2 <- ggplot(heart_polygon) +
  geom_sf(aes(fill = z), colour = "blue", size = 0.5) +
  scale_fill_distiller(palette = "YlOrBr", type = "seq", direction = 1) +
  theme(panel.background = element_rect(fill = "#000000")) +
  coord_sf(crs = 3857, datum = 3857) +
  guides(fill = FALSE) +
  ggtitle("Chocolate quantity (Mercator projection = leaflet)")
plot_grid(plotlist = list(g1, g2), ncol = 2)

Dessiner une grande gaufre interactive

On y est presque ! Rappelez-vous que notre objectif était de construire une gaufre interactive.
Pour la démonstration, nous construisons un cœur plus grand avec 50000 points. Dessinons-le avec {leafgl}.

library(leaflet)
library(leafgl)
# Bigger heart
heart_grid <- st_sample(heart_sf, size = 50000, type = "regular") %>% 
  cbind(as.data.frame(st_coordinates(.))) %>% 
  rename(x = X, y = Y) %>% 
  st_sf() %>% 
  mutate(z = cos(2*x) - cos(x) + sin(y),
         z_text = as.character(round(z, digits = 1)))
# Generate a grid polygon from points
heart_polygon2 <- wafflerize(heart_grid, fact = 100)
# Define colors for `addGlPolygons`
cols <- brewer_pal(palette = "YlOrBr", type = "seq")(7)
colours <- gradient_n_pal(cols)(rescale(heart_polygon2$z))
colours_rgb <- (t(col2rgb(colours, alpha = FALSE))/255) %>% as.data.frame()
# Render as leaflet
m <- leaflet() %>%
  # The popup is currently not working anymore with {leafgl}. 
  # I cheat a little and take the previous version waiting for a patch...
    leaflet.glify::addGlifyPolygons(data = heart_polygon2, # addGlPolygons(
                color = colours_rgb,
                popup = "z_text",
                opacity = 1) %>% 
  setView(lng = mean(st_bbox(heart_polygon2)[c(1,3)]),
          lat = mean(st_bbox(heart_polygon2)[c(2,4)]), zoom = 15)

Vous pouvez maintenant naviguer à l’intérieur de cet objet polygon-raster. Cliquez sur les cases pour connaître la quantité de chocolat…

La prochaine fois que tu verras des gaufres dans la nature, tu penseras peut-être à moi…

Auteur

Sébastien Rochette
Sébastien RochetteModeller, R-trainer, Playing with maps
Modeller, R-trainer, Playing with maps