Przejdź do treści

Gdzie się bujasz na rowerze?

Mam znajomego, który lubi jeździć na rowerze za każdym razem inną trasą. Skąd ma wiedzieć czy daną ulicą już jechał czy nie? Pomożemy mu robiąc heatmapę.

Idea

Przede wszystkim – trzeba mieć dane. Dzisiaj będziemy operować na danych zebranych w aplikacji Strava, zatem ruszamy w teren i kręcimy kilometry!

Osobiście trochę jeżdżę, ale nie na tyle żeby inwestować w pełne konto na Stravie. Bo pełne konto ma opcję rysowania heatmapy…

Swoją drogą co miesiąc Strava przygotowuje mapki i publikuje je na swoich stronach i tak dla przykładu jeździ się rowerem po Polsce:

a tak po Warszawie i okolicach:

Każdy chciałby mieć taką swoją mapkę, nie każdy chce tylko za to płacić. Pomożemy!

Eksport danych ze Stravy

Po zalogowaniu się w przeglądarce (w aplikacji na telefonie to nie działa), przechodzimy do swojego profilu

Tam w sekcji “My Account” na dole znajdujemy na dole “Download or Delete Your Account”

Przechodzimy do mechanizmu eksportu danych klikając w “Get Started”

W punkcie drugim – “Download Request” – wciskamy przycisk. Po jakimś czasie dostajemy maila z linkiem do archiwum

Pobieramy je, rozpakowujemy. Interesują nas tylko dwa elementy:

  • folder activities
  • plik activities.csv

Oba wypakowujemy do katalogu z naszym skryptem.

Ważne: w katalogu “activities” niektóre pliki są spakowane GZipem (mają rozszerzenie .gz) – warto je wypakować. W Linuxie wystarczy gunzip *.gz w konsoli. W Windows pewnie potrzeba jakiegoś dodatkowego programu (7-Zip dobry na wszystko).

Oczywiście można te pliki wypakować już w Pythonie, ale pominiemy ten fragment.

Przygotowanie danych

Kiedy już zgromadziliśmy nasze dane w odpowiednich strukturach możemy przystąpić do ich przetworzenia.

Zadanie będzie polegało na wyciągnięciu z każdego z plików czasu pomiaru i współrzędnych.

Struktura danych

Wszystkie trasy Strava eksportuje do plików XML w formacie GPX. Z grubsza taki plik wygląda tak:

Mamy sekcję trkseg w której znajdujemy kolekcję (listę) poszczególnych punktów pomiarowych trkpt. Każdy z tych punktów ma swoje współrzędne GPS (i to do heatmapy nam wystarczy), a także inne parametry – tutaj wysokość nad poziomem morza (ele) oraz czas pomiaru (time).

My te dane przetworzymy w zwykłego pandasowego data frame’a. Zatem potrzebujemy przejść po drzewie pliku XML i wyłowić odpowiednie elementy z poszczególnych gałęzi,

Drugą stroną jest plik activities.csv który ma całą masę metadanych o każdej z przebytych tras. Nas będą interesować tylko wybrane informacje:

  • Activity ID – unikalny ID przebytej trasy
  • Activity Name – nazwa aktywności
  • Activity Type – typ aktywności – pozwoli nam na wybranie jedynie tras przebytych na rowerze
  • Filename – ścieżka do pliku GPX z zapisem trasy

Prawdę mówiąc nie przydadzą nam się do niczego “Activity ID” oraz “Activity Name”, ale zawsze warto je zostawić gdyby np. trzeba było coś zweryfikować. Gotowe (przetworzone ze wszystkich XMLi) dane zapiszemy też do jednego zbiorczego pliku CSV – więc może do dalszej analizy ID i nazwa aktywności się przyda?

Czas na kod

Kod będzie krótki, bo algorytm jest prosty:

  • każdy z plików XML z zapisem trasy przerabiamy na data frame
  • dodajemy informacje o typie przejazdu
  • całość sklejamy w jeden wielki data frame (i sobie go zapisujemy, bo może zechcemy wykorzystać jakieś narzędzie BI do analizy tych danych w innych przekrojach?)
  • korzystając z NumPy przygotujemy sobie heatmapę do statycznej grafiki
  • korzystając z Folium przygotujemy interaktywną mapę (i zapiszemy do HTMLa)

Na początek importy:

Teraz pomocnicza funkcja czytająca jeden plik GPX (XML) i tłumacząca go na pandasowy data frame:

Do zmiennych przypiszmy ścieżki do poszczególnych zbiorów danych wejściowych i wyjściowych:

A teraz w jednej pętli możemy przetworzyć wszystkie pliki z trasami:

Wczytajmy metadane tras i połączmy je z już otrzymanymi punktami z poszczególnych przejazdów:

Statyczna heatmapa

W zasadzie każdy z punktów na trasie ma swoje współrzędne i są to współrzędne w dwuwymiarowej przestrzeni. A tak się składa, że biblioteka NumPy ma taką funkcję jak histogram2d(). Dzięki temu możemy w prosty sposób przygotować dane do heatmapy, a później – przy pomocy zwykłego matplotlib – je pokazać:

Moje przejazdy po Warszawie wyglądają mniej więcej tak:

Taki obrazek ma kilka wad. Przede wszystkim nie da się go bezpośrednio nałożyć na mapę – odwzorowanie nie jest takie do jakiego jesteśmy przyzwyczajeni, trzeba by coś porozciągać. Po drugie – bez nałożenia na mapę trudno rozpoznać konkretne miejsca. Ale tak przygotowany obrazek świetnie się sprawdzi jako prerekwizyt do przygotowania infografiki.

Heatmapa interaktywna

Dlaczego jednak nie wykorzystać gotowych rozwiązań i nie przygotować heatmapy nałożonej od razu na mapę? Użyjemy Folium:

Na początek agregujemy dane, bo na tym polega heatmapa. Agregujemy w Pandasie:

Jedna uwaga odnośnie ostatniej linii powyższego kodu: zliczamy liczbę odczytów położenia w danym punkcie, a nie liczbę tras które przez dany punkt prowadziły. To oznacza, że chodząc w kółko dostaniemy wiele odczytów z grubsza jednego punktu. Lepszym sposobem byłoby policzenie unikalnych Activity ID w danym punkcie – to dałoby unikalną liczbę tras. Jeśli chcemy w ten sposób to robimy nieco inaczej:

Mając przygotowane agregaty wyznaczamy środek mapy (tak, żeby nie trzeba było jej jakoś przesuwać za każdym razem), samą mapę (podkład mapowy), a następnie po prostu nakładamy wyliczoną heatmapę:

Na koniec wygenerowaną mapę zapisujemy do pliku HTML:

Teraz możemy otworzyć wygenerowany plik na przykład w przeglądarce i cieszyć się wynikiem naszej pracy.

Nie jest tajemnicą gdzie mieszkam, więc powyższa heatmapa to rzeczywiste dane.

Podsumowanie

Co tu się dzisiaj wydarzyło? Nauczyliśmy się aż czterech rzeczy!

  • jak przeczytać plik XML
  • jak agregować dwuwymiarowe dane w NumPy
  • jak agregować je w Pandas
  • jak przygotować mapkę w Folium

Co można było jeszcze zrobić?

Można policzyć prędkość chwilową pomiędzy kolejnymi punktami trasy i na przykład z tych wartości (po uśrednieniu) wyrysować heatmapę.
Mając czas pomiaru w danym punkcie możemy też pokusić się o sprawdzenie czy po południu osiągamy większą prędkość jazdy niż rano. Dodając dane o kierunku wiatru (to będzie trudne – skąd wziąć tak szczegółowe w lokalizacji i czasie dane?) oraz wyliczając azymut ruchu z dwóch sąsiednich punktów pomiarowych możemy określić czy prędkość jazdy po dojęciu/dodaniu siły wiatru jest stała albo zależna od pory dnia.
W danych mamy również wartość ele czyli wysokość. Można zatem policzyć gradient wysokości w każdym z punktów i znowu zestawić to z prędkością.

W większości przypadków te pomysły są nieco absurdalne, ale zobaczcie co można uzyskać mają serię danych pomiarowych z czterema tylko cechami: czasem pomiaru, długością i szerokością geograficzną oraz wysokością nad poziomem morza. Z tych czterech parametrów wyprowadzamy kolejne: prędkość chwilowa, zmiana wysokości, azymut ruchu, cechy związane z czasem (godzina w ciągu dnia, dzień tygodnia, miesiąc, pora roku, rok, czas jaki upłynął od rozpoczęcia trasy) które możemy ze sobą dowolnie zestawiać.

Nie wspominam tutaj o samych geograficznych cechach i na przykład zastosowaniu algorytmów uczenia maszynowego, chociażby algorytmu DBSCAN który pozwoliłby na znalezienie grup blisko sąsiadujących punktów. Mogłyby to być jakieś ulubione miejsca albo coś tego typu.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *