
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:
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ę:
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().
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.
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:
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.
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.