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!
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.
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.
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' } }
### 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
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!
Bizi izlemeye devam edin çünkü çok daha fazlası gelecek:
Burada da yayınlandı.