Subskrybuj newsletter o cyfrowej humanistyce i innowacjach w sektorze kultury

Budujemy mapę cyfrową w R z wykorzystaniem pakietu Leaflet

Okładka lekcji: mapa cyfrowa z markerem w postaci kociego pyszczka / Źródło: AI

Wprowadzenie

W poprzedniej lekcji nauczyliśmy się tworzyć od podstaw mapę cyfrową, wykorzystując dane geograficzne OpenStreetMap oraz framework Leaflet. Do wyświetlenia mapy i umieszczenia na niej punktów z opisem konieczne było przygotowanie strony internetowej - struktury HTML, stylów CSS oraz kodu JavaScript. Mapy cyfrowe tworzyć można jednak zdecydowanie prościej i szybciej bezpośrednio w języku R.

Cele lekcji

Celem lekcji jest zdobycie umiejętności generowania map cyfrowych w języku R, w tym:

  • importowania danych geograficznych,
  • pracy z warstwami mapy,
  • definiowania markerów i interakcji z markerami,
  • publikowania mapy jako strony WWW.

Efekty

Efektem naszej pracy będzie przygotowanie prostej mapy prezentującej wybrane punkty (zabytki lat 90. w Warszawie), z wykorzystaniem pakietu leaflet. Przykładowe dane geograficzne pozyskamy z ARCHIwum Warszawy Lat 90..

Korzystając z R, unikniemy konieczności przygotowania struktury HTML oraz ręcznej instalacji bibliotek JavaScript i CSS. Wygenerowaną mapę będziemy mogli umieścić w internecie.

Wymagania

Do skorzystania z lekcji konieczne jest konto w serwisie Posit.cloud, udostępniającym środowisko do programowania w R. Kluczowa jest też znajomość podstaw składni języka R (warto zapoznać się z tą lekcją). Warto również zapoznać się z materiałem na temat GIS, który opisuje niezbędne części składowe każdej mapy cyfrowej.

Część merytoryczna

Będziemy pracować w środowisku R, więc od razu zalogujmy się do Posit.cloud. Zakładamy nowy projekt, a wnim nowy plik R, do którego wpisywać będziemy polecenia i wysyłać je do konsoli.

Zacznijmy od instalacji pakietu leaflet i wczytania go do środowiska:

install.packages("leaflet")
library(leaflet)

Od teraz mamy dostęp do możliwości biblioteki Leaflet.

Mapa to obiekt R

Zbudujmy podstawową infrastrukturę mapy. Pamiętamy, że każdy system GIS to zestaw wielu elementów. Zacznijmy od podstawowego obiektu mapy:

map <- leaflet()

Skorzystaliśmy z funkcji leaflet(). Efektem działania tej funkcji jest widget HTML zawierający mapę (ale pozbawioną jakiejkolwiek warstwy) - wyślijmy do konsoli obiekt map:

Pusta mapa Leaflet w środowisku R / Źródło: PositCloud

Natomiast funkcja str() wyświetli nam w konsoli zawartość tego obiektu:

str(map)

Dzięki temu możemy zobaczyć, że jego częścią są biblioteki JavaScript (np. jquery-3.6.0.min.js, leaflet.js) i CSS (np. leafletfix.css). Korzystamy z tych samych zasobów, z jakich korzystaliśmy, budując mapę cyfrową na klasycznej stronie WWW 😎. Leaflet w wersji dla R jest uzupełniony o kilka dodatkowych plików, które są niezbędne, aby odpowiednio wyświetlać mapę w środowisku R.

List of 8
 $ x            :List of 1
  ..$ options:List of 1
  .. ..$ crs:List of 5
  .. .. ..$ crsClass       : chr "L.CRS.EPSG3857"
  .. .. ..$ code           : NULL
  .. .. ..$ proj4def       : NULL
  .. .. ..$ projectedBounds: NULL
  .. .. ..$ options        : Named list()
  .. .. ..- attr(*, "class")= chr "leaflet_crs"
 $ width        : NULL
 $ height       : NULL
 $ sizingPolicy :List of 7
  ..$ defaultWidth : chr "100%"
  ..$ defaultHeight: num 400
  ..$ padding      : num 0
  ..$ fill         : NULL
  ..$ viewer       :List of 6
  .. ..$ defaultWidth : NULL
  .. ..$ defaultHeight: NULL
  .. ..$ padding      : NULL
  .. ..$ fill         : logi TRUE
  .. ..$ suppress     : logi FALSE
  .. ..$ paneHeight   : NULL
  ..$ browser      :List of 5
  .. ..$ defaultWidth : NULL
  .. ..$ defaultHeight: NULL
  .. ..$ padding      : NULL
  .. ..$ fill         : logi TRUE
  .. ..$ external     : logi FALSE
  ..$ knitr        :List of 3
  .. ..$ defaultWidth : NULL
  .. ..$ defaultHeight: NULL
  .. ..$ figure       : logi TRUE
 $ dependencies :List of 7
  ..$ :List of 10
  .. ..$ name      : chr "jquery"
  .. ..$ version   : chr "3.6.0"
  .. ..$ src       :List of 1
  .. .. ..$ file: chr "lib/3.6.0"
  .. ..$ meta      : NULL
  .. ..$ script    : chr "jquery-3.6.0.min.js"
  .. ..$ stylesheet: NULL
  .. ..$ head      : NULL
  .. ..$ attachment: NULL
  .. ..$ package   : chr "jquerylib"
  .. ..$ all_files : logi TRUE
  .. ..- attr(*, "class")= chr "html_dependency"
  ..$ :List of 10
  .. ..$ name      : chr "leaflet"
  .. ..$ version   : chr "1.3.1"
  .. ..$ src       :List of 1
  .. .. ..$ file: chr "htmlwidgets/lib/leaflet"
  .. ..$ meta      : NULL
  .. ..$ script    : chr "leaflet.js"
  .. ..$ stylesheet: chr "leaflet.css"
  .. ..$ head      : NULL
  .. ..$ attachment: NULL
  .. ..$ package   : chr "leaflet"
  .. ..$ all_files : logi TRUE
  .. ..- attr(*, "class")= chr "html_dependency"
  ..$ :List of 10
  .. ..$ name      : chr "leafletfix"
  .. ..$ version   : chr "1.0.0"
  .. ..$ src       :List of 1
  .. .. ..$ file: chr "htmlwidgets/lib/leafletfix"
  .. ..$ meta      : NULL
  .. ..$ script    : NULL
  .. ..$ stylesheet: chr "leafletfix.css"
  .. ..$ head      : NULL
  .. ..$ attachment: NULL
  .. ..$ package   : chr "leaflet"
  .. ..$ all_files : logi TRUE
  .. ..- attr(*, "class")= chr "html_dependency"
  ..$ :List of 10
  .. ..$ name      : chr "proj4"
  .. ..$ version   : chr "2.6.2"
  .. ..$ src       :List of 1
  .. .. ..$ file: chr "htmlwidgets/plugins/Proj4Leaflet"
  .. ..$ meta      : NULL
  .. ..$ script    : chr "proj4.min.js"
  .. ..$ stylesheet: NULL
  .. ..$ head      : NULL
  .. ..$ attachment: NULL
  .. ..$ package   : chr "leaflet"
  .. ..$ all_files : logi FALSE
  .. ..- attr(*, "class")= chr "html_dependency"
  ..$ :List of 10
  .. ..$ name      : chr "Proj4Leaflet"
  .. ..$ version   : chr "1.0.1"
  .. ..$ src       :List of 1
  .. .. ..$ file: chr "htmlwidgets/plugins/Proj4Leaflet"
  .. ..$ meta      : NULL
  .. ..$ script    : chr "proj4leaflet.js"
  .. ..$ stylesheet: NULL
  .. ..$ head      : NULL
  .. ..$ attachment: NULL
  .. ..$ package   : chr "leaflet"
  .. ..$ all_files : logi FALSE
  .. ..- attr(*, "class")= chr "html_dependency"
  ..$ :List of 10
  .. ..$ name      : chr "rstudio_leaflet"
  .. ..$ version   : chr "1.3.1"
  .. ..$ src       :List of 1
  .. .. ..$ file: chr "htmlwidgets/lib/rstudio_leaflet"
  .. ..$ meta      : NULL
  .. ..$ script    : NULL
  .. ..$ stylesheet: chr "rstudio_leaflet.css"
  .. ..$ head      : NULL
  .. ..$ attachment: NULL
  .. ..$ package   : chr "leaflet"
  .. ..$ all_files : logi TRUE
  .. ..- attr(*, "class")= chr "html_dependency"
  ..$ :List of 10
  .. ..$ name      : chr "leaflet-binding"
  .. ..$ version   : chr "2.2.2"
  .. ..$ src       :List of 1
  .. .. ..$ file: chr "htmlwidgets/assets"
  .. ..$ meta      : NULL
  .. ..$ script    : chr "leaflet.js"
  .. ..$ stylesheet: NULL
  .. ..$ head      : NULL
  .. ..$ attachment: NULL
  .. ..$ package   : chr "leaflet"
  .. ..$ all_files : logi FALSE
  .. ..- attr(*, "class")= chr "html_dependency"
 $ elementId    : NULL
 $ preRenderHook:function (widget)  
 $ jsHooks      : list()
 - attr(*, "class")= chr [1:2] "leaflet" "htmlwidget"
 - attr(*, "package")= chr "leaflet"

Dodajemy warstwę OSM

Kolejnym elementem naszego systemu GIS będzie warstwa kafelkowa (tiles) mapy. Skorzystajmy z tej standardowej, udostępnianej przez OpenStreetMap. Dodanie warstwy jest bardzo proste - robimy to z wykorzystaniem przetwarzania potokowego.

Przetwarzanie potokowe (piping) w R oznaczane jest symbolem %>%. To sposób programowania, który pozwala na bardziej czytelne i efektywne łączenie funkcji oraz operacji na danych. Potokowanie umożliwia przekazywanie wyniku jednej funkcji jako wejścia do kolejnej funkcji, co poprawia czytelność i skraca kod. Operacja %>% działa w taki sposób, że wynik z lewej strony operatora %>% jest automatycznie przekazywany jako pierwszy argument do funkcji po prawej stronie (więcej na ten temat w jednej z poprzednich lekcji). Pamiętajmy, że aby dodać znak %>% musimy skorzystać z kombinacji klawiszy CTRL + SHIFT + M.

Skorzystajmy z funkcji addTiles():

map %>% addTiles()

To zadanie wykonać moglibyśmy też w standardowym zapisie:

addTiles(map)

Obiekt map jest w niej po prostu argumentem funkcji addTiles(). W efekcie nasza mapa ma już warstwę:

Mapa Leaflet z podstawową warstwą OpenStreetMap w środowisku R / Źródło: PositCloud

Centrujemy mapę

Mapa przedstawiająca cały świat nie będzie użyteczna - powinniśmy móc ustawić ją tak, aby pokazywała interesujący nas obszar. Robimy to z wykorzystaniem funkcji setView():

map %>% addTiles() %>% setView(20.9706, 52.3209, zoom = 17)

Parametrami funkcji setView() są m.in. długość i szerokość geograficzna oraz skala (zoom). Wycentrowaliśmy mapę na okolicach ratusza na warszawskiej Białołęce, który jest opisany w zbiorach ARCHIwum Warszawy Lat 90.

Pracujemy, korzystając z przetwarzania potokowego, więc funkcja setView() jest wykonywana na obiekcie map, zmodyfikowanym już przez funkcję addTiles().

Mapa Leaflet wycentrowana wokół wybranego punktu / Źródło: PositCloud

Dodajemy punkty (markery)

Z dostępnej w witrynie archiwum warszawskich zabytków lat 90. listy obiektów wybieramy te znajdujące się na Białołęce:

nazwa id dlugosc_geogr szerokosc_geogr
Fort Piontek 368 20.9518 52.3448
Budynek biurowo-usługowy 380 20.9694 52.3263
Przepompownia Nowodwory 378 20.9449 52.3247
Urząd Pocztowy nr 91 337 20.9549 52.3208
Ratusz Białołęka 278 20.9707 52.3211

Markery i interakcje z nimi dodajemy za pomocą funkcji addMarkers():

map %>% addTiles() %>% setView(20.9706, 52.3209, zoom = 17) %>% addMarkers(20.9707, 52.3211, popup ='<a href="https://archiwumlat90.pl/obiekty/obiekt/?id=278&source=table">Ratusz Białołęka</a>')

Warto zauważyć, że w treści popupa możemy dodawać kod HTML - pozwala to na umieszczanie linków i obrazków.

Mapa Leaflet z punktem i popupem / Źródło: PositCloud

Chociaż korzystaliśmy z przetwarzania potokowego, nasz kod zrobił się mało czytelny. Spróbujmy go uporządkować, tym bardziej, że chcemy dodać jeszcze kolejne punkty reprezentujące zabytki lat 90.

Definiujemy markery w JSON

Tabelkę z kilkoma punktami możemy zmienić na postać JSON - to użyteczny format danych do pracy w R. Aby to zrobić, możemy skorzystać z ChatGPT. W efekcie otrzymamy dane w takiej postaci:

[
    {
        "nazwa": "Fort Piontek",
        "id": 368,
        "dlugosc_geogr": 20.9518,
        "szerokosc_geogr": 52.3448
    },
    {
        "nazwa": "Budynek biurowo-usługowy",
        "id": 380,
        "dlugosc_geogr": 20.9694,
        "szerokosc_geogr": 52.3263
    },
    {
        "nazwa": "Przepompownia Nowodwory",
        "id": 378,
        "dlugosc_geogr": 20.9449,
        "szerokosc_geogr": 52.3247
    },
    {
        "nazwa": "Urząd Pocztowy nr 91",
        "id": 337,
        "dlugosc_geogr": 20.9549,
        "szerokosc_geogr": 52.3208
    },
    {
        "nazwa": "Ratusz Białołęka",
        "id": 278,
        "dlugosc_geogr": 20.9707,
        "szerokosc_geogr": 52.3211
    }
]

Zapiszmy te dane jako plik markers.json. Następnie wczytajmy pakiet jsonlite - metoda fromJSON pozwoli nam na zaimportowanie danych do obiektu R.

library(jsonlite)
markers <- fromJSON("markers.json")

Pozostaje nam tylko użyć obiekt markers. Przywołujemy go dwukrotnie: najpierw definiując podstawowy obiekt mapy funkcją leaflet(), po raz drugi - odwołując się do niego w funkcji addMarkers():

leaflet(data = markers) %>% addTiles() %>% setView(20.9706, 52.3209, zoom = 12) %>% addMarkers(~dlugosc_geogr, ~szerokosc_geogr, popup = ~nazwa)

Znak tyldy ~ poprzedza tam nazwy kolumn - w ten sposób wskazujemy źródła danych dotyczących lokalizacji punktu i tekstu podpisu. W efekcie otrzymujemy mapę z zestawem punktów:

Mapa Leaflet z zestawem punktów / Źródło: PositCloud

Tak wygenerowaną mapę możemy umieścić online. Z pola Export wybieramy opcję Save As Web Page. System automatycznie dogra nam brakujące pakiety, które pozwolą na wygenerowanie strony z mapą.

Podsumowanie

Generowanie podstawowych map z wykorzystaniem R i Leaflet jest proste - to dosłownie kilka linijek kodu. W efekcie otrzymujemy interaktywną mapę, którą możemy umieścić na serwerze, np. jako element publikacji lub prezentacji. Leaflet to bardzo obszerna biblioteka - jej możliwości nie ograniczają się do prostego umieszczania punktów na mapie. Inne funkcje Leaflet poznamy jednak już w kolejnych lekcjach.

Wykorzystanie metod

Taki sposób pracy z mapami może być użyteczny w generowaniu cyklicznych raportów, kiedy chcemy tworzyć mapy maszynowo, na podstawie określonego schematu danych i z minimalnym zaangażowaniem programistycznym. W tym przypadku konieczne jest wykorzystanie Shiny - to pakiet R pozwalający na budowanie aplikacji webowych. Shiny może być podstawą aplikacji wykorzystującej mapę - za jej pomocą można odpytywać bazę danych albo API i prezentować odpowiednio przetwarzane dane na mapę zarządzaną przez Leaflet. Tutorial przygotowania takiej aplikacji znajdziemy w serwisie Programming Historian.

Mapa Leaflet działająca w ramach aplikacji zbudowanej w Shine / Źródło: Programming Historian

R może być nie tylko użytecznym narzędziem pracy z danymi, ale też środowiskiem, w ramach którego przygotować i opublikować możemy aplikację webową.

Pomysł na warsztat

Lekcja jest gotowym modułem do warsztatu z podstaw R - warto przy planowaniu takiego modułu skorzystać także z materiałów poświęconych podstawom GIS.