paint-brush
Cách tôi sử dụng Python và Folium để hình dung các hoạt động ngoài trời của mìnhby@lukaskrimphove
7,290
7,290

Cách tôi sử dụng Python và Folium để hình dung các hoạt động ngoài trời của mình

Lukas Krimphove14m2023/09/06
Read on Terminal Reader

Bắt tay vào một cuộc thám hiểm khám phá và lập bản đồ! Tìm hiểu cách thổi sức sống vào các tệp GPX của bạn và tạo bản đồ tương tác bằng Folium.
featured image - Cách tôi sử dụng Python và Folium để hình dung các hoạt động ngoài trời của mình
Lukas Krimphove HackerNoon profile picture
0-item

Tôi mơ ước được đi bộ đường dài từ Munich đến Venice và băng qua dãy Alps xinh đẹp. Nhưng vì tôi vẫn phải sống cuộc sống hàng ngày nên cuộc hành trình của tôi phải bao gồm nhiều giai đoạn, có tuần, tháng hoặc thậm chí nhiều năm giữa mỗi cuộc phiêu lưu. Điều đó không sao cả, vì nó thiên về hành trình hơn là đích đến. Tuy nhiên, tôi luôn muốn có một cách để tìm lại những con đường này một cách trực quan, để xem tôi đã đi được bao xa và đã gần đến mục tiêu của mình đến mức nào. Tôi muốn có một cách để ăn mừng những tiến bộ mà tôi đã đạt được và động viên bản thân tiến xa hơn.


May mắn thay, nhiều ứng dụng thể thao và ngoài trời, như Adidas Running, Komoot, Strava và các ứng dụng khác, đã ân cần cho phép chúng tôi xuất các hoạt động của mình dưới dạng tệp GPX. Tuy nhiên, không có nơi nào để đi với những tệp GPX đó.


Đó là lúc Python và Folium phát huy tác dụng. Gần đây tôi tình cờ biết đến Folium, một thư viện Python mạnh mẽ để tạo bản đồ tương tác. Nó có thể dễ dàng kết hợp dữ liệu địa lý, chẳng hạn như tệp GPX và cho phép tùy chỉnh và khám phá. Dựa trên dữ liệu GPS phong phú mà tôi đã thu thập được, tôi bắt đầu thử nghiệm với Folium để tạo một bản đồ giúp những chuyến du ngoạn ngoài trời của tôi trở nên sống động.


Sau một số nghiên cứu và thử nghiệm, tôi đã nghĩ ra một bản đồ cho phép tôi xem lại các hoạt động ngoài trời trước đây của mình:

Vì vậy, nếu bạn giống tôi và có một kho tàng dữ liệu GPS mà không có mục đích gì, bạn có thể thắc mắc làm thế nào tôi đến được đó. Vì vậy, trong bài viết này, tôi sẽ giải thích cách thổi sức sống vào các tệp GPX của bạn.


Hãy bắt tay vào cuộc thám hiểm khám phá và lập bản đồ!

Bắt đầu với Notebook Jupyter

Để bắt đầu cuộc phiêu lưu này, chúng tôi sẽ sử dụng Jupyter Notebook. Tại sao máy tính xách tay Jupyter? Đó là một môi trường điện toán tương tác tuyệt vời cho phép chúng tôi kết hợp mã, hình ảnh trực quan và văn bản, khiến nó trở nên hoàn hảo để thử nghiệm dữ liệu của chúng tôi và thư viện Folium.


Nếu bạn chưa cài đặt Jupyter Notebook, hãy làm theo hướng dẫn trên trang web chính thức của họ. Sau khi cài đặt, bạn có thể tạo Notebook Jupyter mới và sẵn sàng cho hành trình của mình.

Phân tích tệp GPX cho dữ liệu đường mòn

Tiếp theo, chúng tôi cần nguyên liệu thô cho bản đồ của mình - các tệp GPX. GPX (Định dạng trao đổi GPS) là định dạng tệp được sử dụng rộng rãi để lưu trữ dữ liệu vị trí, chẳng hạn như vĩ độ, kinh độ, độ cao và thời gian, khiến định dạng này trở nên lý tưởng để theo dõi các hoạt động ngoài trời.


Nếu bạn là người đam mê đi bộ đường dài, chạy bộ, đi xe đạp hoặc trượt tuyết, rất có thể bạn đã theo dõi nhiều chuyến du ngoạn khác nhau bằng ứng dụng ngoài trời hoặc thể thao. Rất nhiều ứng dụng trong số đó cho phép bạn xuất các hoạt động của mình ở định dạng GPX. Vì vậy, hãy thu thập các tệp GPX đó và bắt đầu!


Trong mã Python của chúng tôi, chúng tôi sẽ sử dụng thư viện gpxpy để phân tích các tệp GPX và trích xuất dữ liệu đường đi cần thiết, chẳng hạn như vĩ độ, kinh độ, độ cao, tốc độ và khoảng cách. Hàm parse_gpx() sẽ thực hiện tất cả công việc nặng nhọc cho chúng ta:


 ### 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

Điều đó để lại cho chúng tôi tất cả dữ liệu cần thiết của một hoạt động: danh sách tất cả tọa độ GPS và Khung dữ liệu Pandas chứa tất cả các loại số liệu.

Vẽ các đường GPX trên Bản đồ tương tác

Với dữ liệu GPS của chúng tôi hiện được sắp xếp trong Khung dữ liệu Pandas, chúng tôi có thể hình dung các hoạt động ngoài trời của mình trên bản đồ tương tác. Folium làm cho nhiệm vụ này trở nên dễ dàng:


 ### 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' } }
  • Chúng tôi sẽ tạo bản đồ có tâm ở một vị trí cụ thể (bạn có thể chọn một vị trí hoặc để mã xác định tâm dựa trên dữ liệu của bạn).
  • Chúng tôi sẽ chỉ định màu sắc và biểu tượng riêng cho từng loại hoạt động, chẳng hạn như đi bộ đường dài, chạy, đạp xe hoặc trượt tuyết. Từ điển ACTIVITY_TYPES sẽ giúp chúng ta điều này.
 ### 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
  • Chúng tôi sẽ sử dụng các khái niệm của Foliums FeatureGroup để nhóm các đường nhỏ dựa trên tên nhóm của chúng. Điều đó sẽ cho phép chúng tôi hiển thị và ẩn các nhóm hoạt động nhất định sau này.
  • Bây giờ, chúng ta sẽ lặp lại dữ liệu đã phân tích cú pháp của mình và vẽ từng đường đi trên bản đồ bằng cách sử dụng các đối tượng PolyLine và Marker của Folium. PolyLine sẽ đại diện cho đường đi thực tế, trong khi Điểm đánh dấu sẽ đóng vai trò là điểm bắt đầu cho mỗi hoạt động. Khi bạn nhấp vào điểm đánh dấu, một cửa sổ bật lên sẽ hiển thị thông tin liên quan về đường đi tương ứng.

Phần kết luận

Trong hành trình khám phá và lập bản đồ này, chúng tôi đã học cách sử dụng Python và Folium để chuyển đổi các tệp GPX thông thường thành bản đồ động và tương tác. Giờ đây, bạn có thể hồi tưởng lại những chuyến phiêu lưu ngoài trời, ăn mừng sự tiến bộ của mình và duy trì động lực cho giai đoạn tiếp theo của hành trình.


Tuy nhiên, có nhiều cách để tùy chỉnh, mở rộng và cải thiện bản đồ của bạn. Vì vậy, hãy lấy các tệp GPX của bạn, khởi động Jupyter Notebook và để các hoạt động ngoài trời trước đây của bạn trở nên sống động trên bản đồ!

Chúc bạn lập bản đồ vui vẻ!

Cái gì tiếp theo?

Hãy theo dõi vì còn nhiều điều nữa sắp tới:


  • triển khai trang web có bản đồ của bạn bằng AWS
  • vẽ sơ đồ độ cao và tốc độ bằng Python và Plotly
  • cải thiện những con đường mòn bằng những bức ảnh được chụp một chiều
  • và nhiều hơn nữa

Người giới thiệu


Cũng được xuất bản ở đây .