paint-brush
Açık Hava Etkinliklerimi Görselleştirmek için Python ve Folium'u Nasıl Kullandımby@lukaskrimphove
7,297
7,297

Açık Hava Etkinliklerimi Görselleştirmek için Python ve Folium'u Nasıl Kullandım

Lukas Krimphove14m2023/09/06
Read on Terminal Reader
Read this story w/o Javascript

Bir keşif ve haritalama seferine çıkın! GPX dosyalarınıza nasıl hayat vereceğinizi ve Folium'u kullanarak etkileşimli haritalar oluşturmayı öğrenin.
featured image - Açık Hava Etkinliklerimi Görselleştirmek için Python ve Folium'u Nasıl Kullandım
Lukas Krimphove HackerNoon profile picture
0-item

Münih'ten Venedik'e yürüyüş yapmayı ve güzel Alpler'de yürüyüş yapmayı hayal ediyorum. Ancak yine de günlük hayatımı yaşamak zorunda olduğum için yolculuğum, her macera arasında haftalar, aylar ve hatta yıllar süren birden fazla aşamadan oluşmak zorunda. Bunda sorun yok, çünkü mesele varış noktasından çok yolculukla ilgili. Ancak her zaman bu yolları görsel olarak takip etmenin, ne kadar ilerlediğimi ve hedefime ne kadar yakın olduğumu görmenin bir yolunu istedim. Kaydettiğim ilerlemeyi kutlamanın ve yolculuğu daha da ileri götürmek için kendimi motive etmenin bir yolunu istedim.


Neyse ki Adidas Running, Komoot, Strava ve diğerleri gibi birçok açık hava ve spor uygulaması, aktivitelerimizi GPX dosyaları olarak dışa aktarmamıza nezaketle izin veriyor. Ancak bu GPX dosyalarıyla gidecek hiçbir yer yok.


Python ve Folium'un devreye girdiği yer burasıdır. Yakın zamanda etkileşimli haritalar oluşturmaya yönelik güçlü bir Python kütüphanesi olan Folium'a rastladım. GPX dosyaları gibi coğrafi verileri kolayca dahil edebilir ve özelleştirme ve keşfetmeye olanak tanır. Topladığım çok sayıda GPS verisinden yararlanarak, açık hava gezilerimi hayata geçirecek bir harita oluşturmak için Folium'u denemeye başladım.


Biraz araştırma ve testten sonra geçmişteki açık hava etkinliklerimi tekrar gözden geçirmeme olanak tanıyan bir harita buldum:

Yani siz de benim gibiyseniz ve hiçbir amacı olmayan bir GPS veri hazinesine sahipseniz, oraya nasıl geldiğimi merak edebilirsiniz. Bu makalede, GPX dosyalarınıza nasıl hayat vereceğinizi açıklayacağım.


Haydi bir keşif ve haritalama seferine çıkalım!

Jupyter Notebook'a Başlarken

Bu maceraya başlamak için Jupyter Notebook'u kullanacağız. Neden Jupyter Notebook? Kodu, görselleştirmeleri ve metni birleştirmemize olanak tanıyan, verilerimizle ve Folium kitaplığıyla denemeler yapmak için mükemmel hale getiren harika bir etkileşimli bilgi işlem ortamıdır.


Jupyter Notebook'u henüz yüklemediyseniz resmi web sitesindeki talimatları izleyin. Kurulduktan sonra yeni bir Jupyter Notebook oluşturabilir ve yolculuğunuza hazırlanabilirsiniz.

İz Verileri için GPX Dosyalarını Ayrıştırma

Daha sonra haritamız için hammadde olan GPX dosyalarına ihtiyacımız var. GPX (GPS Değişim Formatı), enlem, boylam, yükseklik ve zaman gibi konum verilerini saklayan ve onu dış mekan aktivitelerini izlemek için ideal kılan, yaygın olarak kullanılan bir dosya formatıdır.


Eğer hevesli bir yürüyüşçü, koşucu, bisikletçi veya kayakçıysanız, muhtemelen bir açık hava veya spor uygulamasını kullanarak çeşitli gezileri izlemişsinizdir. Bu uygulamaların birçoğu etkinliklerinizi GPX formatında dışa aktarmanıza olanak tanır. O halde GPX dosyalarını toplayın ve başlayalım!


Python kodumuzda, GPX dosyalarını ayrıştırmak ve enlem, boylam, yükseklik, hız ve mesafe gibi temel iz verilerini çıkarmak için gpxpy kitaplığını kullanacağız. parse_gpx() işlevi bizim için tüm ağır işleri yapacak:


 ### READ GPX FILES import gpxpy import pandas as pd import numpy as np import haversine as hs from pathlib import Path def parse_gpx(file_path): # parse gpx file to pandas dataframe gpx = gpxpy.parse(open(file_path), version='1.0') data = [] points = [] for track in gpx.tracks: for segment in track.segments: for point_idx, point in enumerate(segment.points): points.append(tuple([point.latitude, point.longitude])) # calculate distances between points if point_idx == 0: distance = np.nan else: distance = hs.haversine( point1=points[point_idx-1], point2=points[point_idx], unit=hs.Unit.METERS ) data.append([point.longitude, point.latitude,point.elevation, point.time, segment.get_speed(point_idx), distance]) columns = ['Longitude', 'Latitude', 'Elevation', 'Time', 'Speed', 'Distance'] gpx_df = pd.DataFrame(data, columns=columns) return points, gpx_df activities = {} frames = [] for activity_type in ACTIVITY_TYPES: activities[activity_type] = {} pathlist = Path(activity_type).glob('**/*.gpx') for path in pathlist: # exclude hidden directories and files # will lead to performance issues if there are lots of hidden files if any(part.startswith('.') for part in path.parts): continue activity_group = path.parts[1] activity_name = path.parts[2] if activity_group not in activities[activity_type]: activities[activity_type][activity_group] = [] points, gpx_df = parse_gpx(path) gpx_df['Elevation_Diff'] = np.round(gpx_df['Elevation'].diff(), 2) gpx_df['Cum_Elevation'] = np.round(gpx_df['Elevation_Diff'].cumsum(), 2) gpx_df['Cum_Distance'] = np.round(gpx_df['Distance'].cumsum(), 2) gpx_df['Gradient'] = np.round(gpx_df['Elevation_Diff'] / gpx_df['Distance'] * 100, 1) activities[activity_type][activity_group].append({ 'name': activity_name.replace('.gpx', '').replace('_', ' '), 'points': points, 'gpx_df': gpx_df }) frames.append(gpx_df) df = pd.concat(frames) df

Bu bize bir aktivitenin tüm gerekli verilerini bırakıyor: tüm GPS koordinatlarının bir listesi ve her türlü ölçümü içeren bir Pandas DataFrame.

İnteraktif Haritada GPX Yollarını Çizme

Artık Pandas DataFrame'de düzenlenen GPS verilerimiz sayesinde, açık hava aktivitelerimizi etkileşimli bir harita üzerinde görselleştirebiliyoruz. Folium bu görevi çocuk oyuncağı haline getiriyor:


 ### CONFIG # LOCATION = None LOCATION = [48.13743, 11.57549] # latitude, longitude ZOOM_START = 10 ACTIVITY_TYPES = { 'Hiking': { 'icon': 'person-hiking', 'color': 'green' }, 'Running': { 'icon': 'person-running', 'color': 'orange' }, 'Biking': { 'icon': 'person-biking', 'color': 'red' }, 'Skiing': { 'icon': 'person-skiing', 'color': 'blue' } }
  • Belirli bir konuma odaklanmış bir harita oluşturacağız (birini seçebilir veya verilerinize göre merkezi kodun belirlemesine izin verebilirsiniz).
  • Yürüyüş, koşu, bisiklete binme veya kayak gibi her aktivite türüne benzersiz renkler ve simgeler atayacağız. ACTIVITY_TYPES sözlüğü bu konuda bize yardımcı olacaktır.
 ### READ GPX FILES import gpxpy import pandas as pd import numpy as np import haversine as hs from pathlib import Path def parse_gpx(file_path): # parse gpx file to pandas dataframe gpx = gpxpy.parse(open(file_path), version='1.0') data = [] points = [] for track in gpx.tracks: for segment in track.segments: for point_idx, point in enumerate(segment.points): points.append(tuple([point.latitude, point.longitude])) # calculate distances between points if point_idx == 0: distance = np.nan else: distance = hs.haversine( point1=points[point_idx-1], point2=points[point_idx], unit=hs.Unit.METERS ) data.append([point.longitude, point.latitude,point.elevation, point.time, segment.get_speed(point_idx), distance]) columns = ['Longitude', 'Latitude', 'Elevation', 'Time', 'Speed', 'Distance'] gpx_df = pd.DataFrame(data, columns=columns) return points, gpx_df activities = {} frames = [] for activity_type in ACTIVITY_TYPES: activities[activity_type] = {} pathlist = Path(activity_type).glob('**/*.gpx') for path in pathlist: # exclude hidden directories and files # will lead to performance issues if there are lots of hidden files if any(part.startswith('.') for part in path.parts): continue activity_group = path.parts[1] activity_name = path.parts[2] if activity_group not in activities[activity_type]: activities[activity_type][activity_group] = [] points, gpx_df = parse_gpx(path) gpx_df['Elevation_Diff'] = np.round(gpx_df['Elevation'].diff(), 2) gpx_df['Cum_Elevation'] = np.round(gpx_df['Elevation_Diff'].cumsum(), 2) gpx_df['Cum_Distance'] = np.round(gpx_df['Distance'].cumsum(), 2) gpx_df['Gradient'] = np.round(gpx_df['Elevation_Diff'] / gpx_df['Distance'] * 100, 1) activities[activity_type][activity_group].append({ 'name': activity_name.replace('.gpx', '').replace('_', ' '), 'points': points, 'gpx_df': gpx_df }) frames.append(gpx_df) df = pd.concat(frames) df That leaves us with all the necessary data of an activity: a list of all the GPS coordinates and a Pandas DataFrame containing all kinds of metrics. Plotting GPX Trails on the Interactive Map With our GPS data now organized in a Pandas DataFrame, we can visualize our outdoor activities on an interactive map. Folium makes this task a breeze: ### CONFIG # LOCATION = None LOCATION = [48.13743, 11.57549] # latitude, longitude ZOOM_START = 10 ACTIVITY_TYPES = { 'Hiking': { 'icon': 'person-hiking', 'color': 'green' }, 'Running': { 'icon': 'person-running', 'color': 'orange' }, 'Biking': { 'icon': 'person-biking', 'color': 'red' }, 'Skiing': { 'icon': 'person-skiing', 'color': 'blue' } } We'll create a map centered at a specific location (you can choose one or let the code determine the center based on your data). We'll assign unique colors and icons to each activity type, such as hiking, running, biking, or skiing. The ACTIVITY_TYPES dictionary will help us with this. ### CREATE MAP import folium from folium import plugins as folium_plugins if LOCATION: location = LOCATION else: location=[df.Latitude.mean(), df.Longitude.mean()] map = folium.Map(location=location, zoom_start=ZOOM_START, tiles=None) folium.TileLayer('OpenStreetMap', name='OpenStreet Map').add_to(map) folium.TileLayer('Stamen Terrain', name='Stamen Terrain').add_to(map) ### MAP TRAILS def timedelta_formatter(td): td_sec = td.seconds hour_count, rem = divmod(td_sec, 3600) hour_count += td.days * 24 minute_count, second_count = divmod(rem, 60) return f'{hour_count}h, {minute_count}min, {second_count}s' def create_activity_popup(activity): df = activity['gpx_df'] attributes = { 'Date': { 'value': df['Time'][df.index[0]].strftime("%m/%d/%Y"), 'icon': 'calendar' }, 'Start': { 'value': df['Time'][df.index[0]].strftime("%H:%M:%S"), 'icon': 'clock' }, 'End': { 'value': df['Time'][df.index[-1]].strftime("%H:%M:%S"), 'icon': 'flag-checkered' }, 'Duration': { 'value': timedelta_formatter(df['Time'][df.index[-1]]-df['Time'][df.index[0]]), 'icon': 'stopwatch' }, 'Distance': { 'value': f"{np.round(df['Cum_Distance'][df.index[-1]] / 1000, 2)} km", 'icon': 'arrows-left-right' }, 'Average Speed': { 'value': f'{np.round(df.Speed.mean() * 3.6, 2)} km/h', 'icon': 'gauge-high' }, 'Max. Elevation': { 'value': f'{np.round(df.Elevation.max(), 2)} m', 'icon': 'mountain' }, 'Uphill': { 'value': f"{np.round(df[df['Elevation_Diff']>0]['Elevation_Diff'].sum(), 2)} m", 'icon': 'arrow-trend-up' }, 'Downhill': { 'value': f"{np.round(abs(df[df['Elevation_Diff']<0]['Elevation_Diff'].sum()), 2)} m", 'icon': 'arrow-trend-down' }, } html = f"<h4>{activity['name'].upper()}</h4>" for attribute in attributes: html += f'<i class="fa-solid fa-{attributes[attribute]["icon"]}" title="{attribute}"> {attributes[attribute]["value"]}</i></br>' return folium.Popup(html, max_width=300) feature_groups = {} for activity_type in activities: color = ACTIVITY_TYPES[activity_type]['color'] icon = ACTIVITY_TYPES[activity_type]['icon'] for activity_group in activities[activity_type]: # create and store feature groups # this allows different activity types in the same feature group if activity_group not in feature_groups: # create new feature group fg = folium.FeatureGroup(name=activity_group, show=True) feature_groups[activity_group] = fg map.add_child(fg) else: # use existing fg = feature_groups[activity_group] for activity in activities[activity_type][activity_group]: # create line on map points = activity['points'] line = folium.PolyLine(points, color=color, weight=4.5, opacity=.5) fg.add_child(line) # create marker marker = folium.Marker(points[0], popup=create_activity_popup(activity), icon=folium.Icon(color=color, icon_color='white', icon=icon, prefix='fa')) fg.add_child(marker) map.add_child(folium.LayerControl(position='bottomright')) folium_plugins.Fullscreen(position='topright').add_to(map) map
  • Yolları grup adlarına göre gruplandırmak için Foliums Özellik Grubu kavramlarını kullanacağız. Bu, daha sonra belirli etkinlik gruplarını göstermemize ve gizlememize olanak tanıyacaktır.
  • Şimdi ayrıştırılmış verilerimizi tekrarlayacağız ve Folium'un PolyLine ve Marker nesnelerini kullanarak haritadaki her izi işaretleyeceğiz. PolyLine gerçek yolu temsil edecek, İşaretçi ise her aktivite için bir başlangıç noktası görevi görecektir. Bir işaretleyiciye tıkladığınızda, açılan pencerede ilgili iz hakkında ilgili bilgiler görüntülenecektir.

Çözüm

Bu keşif ve haritalama yolculuğunda, sıradan GPX dosyalarını dinamik ve etkileşimli bir haritaya dönüştürmek için Python ve Folium'u nasıl kullanacağımızı öğrendik. Artık açık hava maceralarınızı yeniden yaşayabilir, ilerlemenizi kutlayabilir ve yolculuğunuzun bir sonraki aşaması için motive kalabilirsiniz.


Ancak haritanızı özelleştirmenin, genişletmenin ve iyileştirmenin birçok yolu vardır. GPX dosyalarınızı alın, Jupyter Notebook'u çalıştırın ve geçmiş açık hava etkinliklerinizin haritada canlanmasına izin verin!

Mutlu haritalama!

Sıradaki ne?

Bizi izlemeye devam edin çünkü çok daha fazlası gelecek:


  • AWS kullanarak haritanızla bir web sitesi dağıtma
  • Python ve Plotly kullanarak yükseklik ve hız profillerinin çizilmesi
  • tek yönde çekilen resimlerle parkurları zenginleştirmek
  • ve daha fazlası

Referanslar

  • Jupyter Notebook dahil tüm kod GitHub'umda .
  • Folium hakkında araştırma yaparken, yapmayı planladığım şeyin aynısını yapan Patrick'in harika bir makalesini buldum. Onun çalışması benim çözümümü geliştirmek için harika bir temel oluşturdu, bu yüzden lütfen kontrol edin.
  • Jüpiter Not Defteri
  • Yaprak
  • Pandalar

Burada da yayınlandı.