Bitfaragó a nyugdíj kapujában: Mire jó az AI, ha nem receptet akarsz generálni?
Ha a mainstream médiát hallgatjuk, a mesterséges intelligencia arra való, hogy tízévesek rajzfilmes avatárt csináljanak magukból, vagy irodisták kérjenek tőle udvarias elutasító levelet a főnöküknek.
Egy évtizedekig terminálokon, relációs adatbázisokon, műszerfalakon és forrasztópákán edződött műszaki agynak viszont az AI nem csodafegyver és nem játék, hanem egy végtelen türelmű junior fejlesztő, digitális laborasszisztens és fordított mérnöki segédeszköz.
Nézzük meg pontról pontra, hogyan állítható hadrendbe a technológia egy tapasztalt, kódoló és túrázó műszaki ember műhelyében.
1. Kódolás: Python és Android fejlesztés turbófokozaton
Nem kell mindent a nulláról legépelni, és a dokumentációk böngészése sem viszi el a fél napot:
Gyors refaktorálás és boilerplate: Pár másodperc alatt megírja a Room adatbázis migrációs szkriptjét Kotlinban, vagy összeállítja a Pandas/GeoPandas pipeline-t a túraadatok feldolgozásához.
Architektúra-ellenőrzés: Átnyújtható neki egy kusza Python modul azzal a felütéssel: „Keresd meg a memóriaszivárgást és tedd szálbiztossá az adatgyűjtést.”
API wrapper generálás: Pillanatok alatt készít REST végpontokhoz vagy helyi IoT hardverekhez tiszta, típusos klienskönyvtárat.
2. Térképek, túrázás és kerékpározás: Térinformatikai barkácsolás
A digitális térképészet és a terepi mozgás az adatelemzés aranybányája:
GPX adatelemzés saját kóddal: A rögzített GPX trackekből a Python szkriptekkel kinyert szintkülönbségek, pulzus- és sebességadatok elemzéséhez az AI megírja a simító algoritmusokat és a kalória- vagy wattbecslést.
OpenStreetMap automatizáció: GeoJSON adatok szűrése, Overpass API lekérdezések összeállítása (pl. „Minden ivóvízvételi pont és pihenőhely a tervezett 40 km-es bakonyi szakasz mentén”).
Időjárás és magassági profil modellezés: Olyan mikroszkriptek építése, amelyek az útvonal mentén elhelyezett magassági pontokhoz igazítják a becsült menetidőt a szintemelkedés és az uralkodó szélirány alapján.
3. Fényképezés és videókészítés: Raw adatoktól a vágóasztalig
A vizuális műfajban a neurális hálók ma már a hardver szintjén dolgoznak:
Zajos, régi felvételek restaurálása: Helyi vagy felhős modellekkel zajszűrés, élesítés, felbontásnövelés (upscaling) anélkül, hogy a részletek műanyaggá válnának.
Automatikus indexelés és metaadat-katalogizálás: Python segítségével helyi képfelismerő modellekkel kulcsszavazható több ezer archív családi és természetfotó (EXIF adatok, helyszínek, növényfajok szerint).
Videó utómunka gyorsítása: FFmpeg parancssori szörnyetegek összerakása egyetlen prompttal, hangzajok eltávolítása neurális szűrőkkel, automatikus feliratozás és jelenetvágási pontok detektálása.
4. Fizikai barkácsolás: Digitális tervasztal és hibakeresés
A fizikai világban a multimodális (látó) AI működik a leghatékonyabban:
Szenzorkódok és mikrokontrollerek: Legyen szó ESP32-ről vagy Raspberry Piről, a hardveres lábkiosztások, I2C busz konfigurációk és szenzoros adatkiolvasások percek alatt működő kódba önthetők.
Alkatrész-azonosítás fotó alapján: Ráfotózva egy lekopott feliratú IC-re vagy furcsa mechanikai alkatrészre, az AI segít kideríteni a lábkiosztást, helyettesítő típust vagy a menettípust.
3D modellezés és nyomtatás: OpenSCAD programkódok generálása egyszerűbb egyedi rögzítőkhöz, biciklis kormányadapterekhez vagy szenzordobozokhoz közvetlenül szöveges leírásból.
A lényeg: Irányító maradsz, nem felhasználó
A kulcs a perspektíva: az AI nem helyettesíti a 40 év alatt felhalmozott műszaki intuíciót és fizikai tapasztalatot. Viszont leveszi a válladról a szintaktikai bíbelődést, felgyorsítja a prototípusok építését, és közvetlen hidat képez a monitoron futó kód és a bringára szerelt hardver között.
Egy példa
A nyers GPS-adatok tipikus hibája a vertikális pontatlanság: a másodpercenként rögzített magasság ugrál, ami szűretlenül akár 30–50%-kal is túlbecsülheti a valós szintemelkedést.
Az alábbi önálló szkript gpxpy segítségével dolgozza fel a fájlt, a WGS84 koordináták távolságát haversine-képlettel számítja, a vertikális mérési zajt pedig Savitzky–Golay digitális szűrővel vagy mozgóátlaggal simítja.
Szükséges csomagok telepítése
pip install gpxpy numpy pandas scipy matplotlib
Python feldolgozó szkript (gpx_analyzer.py)
import math
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from scipy.signal import savgol_filter
import gpxpy
def haversine_distance(lat1, lon1, lat2, lon2):
"""Két WGS84 koordináta közötti távolság méterben."""
r_earth = 6371000.0 # Föld sugara méterben
phi1, phi2 = math.radians(lat1), math.radians(lat2)
delta_phi = math.radians(lat2 - lat1)
delta_lambda = math.radians(lon2 - lon1)
a = (
math.sin(delta_phi / 2.0) ** 2
+ math.cos(phi1) * math.cos(phi2) * math.sin(delta_lambda / 2.0) ** 2
)
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
return r_earth * c
def load_gpx_to_dataframe(gpx_path):
"""GPX trackpontok kinyerése DataFrame struktúrába."""
with open(gpx_path, "r", encoding="utf-8") as f:
gpx = gpxpy.parse(f)
records = []
for track in gpx.tracks:
for segment in track.segments:
for pt in segment.points:
records.append(
{
"lat": pt.latitude,
"lon": pt.longitude,
"ele": pt.elevation,
"time": pt.time,
}
)
df = pd.DataFrame(records)
if df.empty:
raise ValueError("A GPX-fájl nem tartalmaz érvényes trackpontokat.")
return df
def process_gpx(df, window_length=15, polyorder=2):
"""Kumulatív távolság számítása, magasság simítása és statisztikák."""
# 1. Távolságok számítása lépésenként
distances = [0.0]
for i in range(1, len(df)):
d = haversine_distance(
df.at[i - 1, "lat"],
df.at[i - 1, "lon"],
df.at[i, "lat"],
df.at[i, "lon"],
)
distances.append(d)
df["step_dist"] = distances
df["cum_dist_km"] = np.cumsum(distances) / 1000.0
# 2. Magassági adatok simítása (Savitzky-Golay szűrő zajszűréshez)
# Ha a mintaszám kisebb, mint az ablakméret, dinamikusan csökkentjük
actual_window = min(window_length, len(df))
if actual_window % 2 == 0:
actual_window -= 1
if actual_window > polyorder:
df["ele_smooth"] = savgol_filter(df["ele"], actual_window, polyorder)
else:
df["ele_smooth"] = df["ele"].rolling(window=3, min_periods=1).mean()
# 3. Szintkülönbségek számítása a simított görbén
ele_diffs = np.diff(df["ele_smooth"], prepend=df["ele_smooth"].iloc[0])
# Hozzáadunk egy minimális küszöböt (pl. 0.2 m), hogy az apró remegést kizárjuk
ele_diffs_thresholded = np.where(np.abs(ele_diffs) > 0.2, ele_diffs, 0.0)
total_ascent = float(np.sum(ele_diffs_thresholded[ele_diffs_thresholded > 0]))
total_descent = float(np.abs(np.sum(ele_diffs_thresholded[ele_diffs_thresholded < 0])))
return df, total_ascent, total_descent
def plot_profile(df, total_ascent, total_descent, output_png=None):
"""Magassági profil megjelenítése a nyers és a simított adatokkal."""
plt.figure(figsize=(12, 6))
# Nyers adat háttérben
plt.plot(
df["cum_dist_km"],
df["ele"],
label="Nyers GPS adat",
color="#b0bec5",
linestyle="--",
linewidth=1,
alpha=0.7,
)
# Simított profil
plt.plot(
df["cum_dist_km"],
df["ele_smooth"],
label="Simított profil (Savitzky-Golay)",
color="#0288d1",
linewidth=2,
)
plt.fill_between(
df["cum_dist_km"],
df["ele_smooth"],
df["ele_smooth"].min() - 10,
color="#b3e5fc",
alpha=0.3,
)
total_dist = df["cum_dist_km"].iloc[-1]
title_text = (
f"Túra profilja\n"
f"Össztáv: {total_dist:.2f} km | "
f"Összes szintemelkedés: +{total_ascent:.0f} m | "
f"Összes lejtés: -{total_descent:.0f} m"
)
plt.title(title_text, fontsize=13, fontweight="bold", pad=12)
plt.xlabel("Megtett távolság (km)", fontsize=11)
plt.ylabel("Tengerszint feletti magasság (m)", fontsize=11)
plt.grid(True, linestyle=":", alpha=0.6)
plt.legend(loc="upper right")
plt.tight_layout()
if output_png:
plt.savefig(output_png, dpi=200)
print(f"[OK] Diagram mentve: {output_png}")
plt.show()
if __name__ == "__main__":
GPX_FILE = "turautes.gpx" # Cseréld ki a saját GPX fájlod elérési útjára
try:
data = load_gpx_to_dataframe(GPX_FILE)
processed_data, ascent, descent = process_gpx(data, window_length=21)
print(f"--- Statisztika ---")
print(f"Trackpontok száma: {len(processed_data)}")
print(f"Teljes táv: {processed_data['cum_dist_km'].iloc[-1]:.2f} km")
print(f"Szintemelkedés: +{ascent:.1f} m")
print(f"Szintesés: -{descent:.1f} m")
print(f"Min. magasság: {processed_data['ele_smooth'].min():.1f} m")
print(f"Max. magasság: {processed_data['ele_smooth'].max():.1f} m")
plot_profile(processed_data, ascent, descent, output_png="magassagi_profil.png")
except FileNotFoundError:
print(f"[HIBA] A fájl nem található: {GPX_FILE}")
Működési elv és finomhangolás
WGS84 projekció: A
haversine_distancefüggvény gömbi trigonometriával számítja ki a szomszédos pontok távolságát, így nem szükséges külső térinformatikai vetületet transzformálni.Savitzky–Golay szűrő: Mozgó ablakban lokális másodfokú polinomot illeszt a magassági adatokra. Előnye a sima mozgóátlaggal szemben, hogy a hirtelen gerinceket, csúcsokat nem „vágja le”, de a GPS másodperces remegését kiveszi.
Zajküszöb (
0.2 m): A GPS minimális driftjét az integrálás előtt egy alsó határérték nullázza, megelőzve az álló helyzetben halmozódó szintemelkedést.
Finomítás
A GPS rögzítés során álló helyzetben a koordináták driftelése hamis mozgást és sebességet generál. A tisztított mozgási idő és a megállások szétválasztásához egy sebesség-küszöbértéket (SPEED_THRESHOLD = 0.8 m/s ≈ 2.88 km/h) és egy minimális megállási időtartamot (MIN_STOP_DURATION = 30 s) alkalmazunk.
Bővített Python szkript (gpx_time_analyzer.py)
import math
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from scipy.signal import savgol_filter
import gpxpy
def haversine_distance(lat1, lon1, lat2, lon2):
"""Két WGS84 koordináta közötti távolság méterben."""
r_earth = 6371000.0
phi1, phi2 = math.radians(lat1), math.radians(lat2)
delta_phi = math.radians(lat2 - lat1)
delta_lambda = math.radians(lon2 - lon1)
a = (
math.sin(delta_phi / 2.0) ** 2
+ math.cos(phi1) * math.cos(phi2) * math.sin(delta_lambda / 2.0) ** 2
)
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
return r_earth * c
def format_duration(seconds):
"""Másodpercek átalakítása ÓÓ:PP:MP formátumra."""
hours = int(seconds // 3600)
minutes = int((seconds % 3600) // 60)
secs = int(seconds % 60)
return f"{hours:02d}:{minutes:02d}:{secs:02d}"
def load_gpx_to_dataframe(gpx_path):
"""GPX trackpontok kinyerése időbélyegekkel."""
with open(gpx_path, "r", encoding="utf-8") as f:
gpx = gpxpy.parse(f)
records = []
for track in gpx.tracks:
for segment in track.segments:
for pt in segment.points:
records.append(
{
"lat": pt.latitude,
"lon": pt.longitude,
"ele": pt.elevation,
"time": pt.time,
}
)
df = pd.DataFrame(records)
if df.empty:
raise ValueError("A GPX-fájl nem tartalmaz trackpontokat.")
if df["time"].isnull().all():
raise ValueError("A GPX-fájl nem tartalmaz időbélyegeket.")
df["time"] = pd.to_datetime(df["time"])
return df
def analyze_gpx(
df,
window_length=21,
polyorder=2,
speed_threshold_kmh=2.5,
min_stop_seconds=45,
):
"""Teljes metrikai elemzés távolságra, szintre, időre és megállásokra."""
n_points = len(df)
speed_threshold_ms = speed_threshold_kmh / 3.6
# 1. Delta távolságok és idők kiszámítása
step_dists = [0.0]
step_times = [0.0]
for i in range(1, n_points):
d = haversine_distance(
df.at[i - 1, "lat"], df.at[i - 1, "lon"],
df.at[i, "lat"], df.at[i, "lon"]
)
dt = (df.at[i, "time"] - df.at[i - 1, "time"]).total_seconds()
step_dists.append(d)
step_times.append(max(0.0, dt))
df["step_dist"] = step_dists
df["step_time_s"] = step_times
df["cum_dist_km"] = np.cumsum(step_dists) / 1000.0
# 2. Pillanatnyi sebesség (m/s és km/h) szűréssel
speeds_ms = [0.0]
for d, t in zip(step_dists[1:], step_times[1:]):
speeds_ms.append(d / t if t > 0 else 0.0)
df["speed_ms"] = speeds_ms
df["speed_kmh"] = df["speed_ms"] * 3.6
# 3. Magasság simítása és szintkülönbségek
actual_window = min(window_length, n_points)
if actual_window % 2 == 0:
actual_window -= 1
if actual_window > polyorder:
df["ele_smooth"] = savgol_filter(df["ele"], actual_window, polyorder)
else:
df["ele_smooth"] = df["ele"].rolling(window=3, min_periods=1).mean()
ele_diffs = np.diff(df["ele_smooth"], prepend=df["ele_smooth"].iloc[0])
ele_diffs_thresh = np.where(np.abs(ele_diffs) > 0.2, ele_diffs, 0.0)
total_ascent = float(np.sum(ele_diffs_thresh[ele_diffs_thresh > 0]))
total_descent = float(np.abs(np.sum(ele_diffs_thresh[ele_diffs_thresh < 0])))
# 4. Megállások és mozgás detektálása
df["is_moving"] = df["speed_ms"] >= speed_threshold_ms
stops = []
current_stop_start = None
current_stop_duration = 0.0
current_stop_points = []
for idx, row in df.iterrows():
if not row["is_moving"]:
if current_stop_start is None:
current_stop_start = row["time"]
current_stop_duration += row["step_time_s"]
current_stop_points.append(row)
else:
if current_stop_start is not None:
if current_stop_duration >= min_stop_seconds:
stops.append({
"start_time": current_stop_start,
"duration_s": current_stop_duration,
"lat": np.mean([p["lat"] for p in current_stop_points]),
"lon": np.mean([p["lon"] for p in current_stop_points]),
"km_mark": current_stop_points[0]["cum_dist_km"],
})
current_stop_start = None
current_stop_duration = 0.0
current_stop_points = []
# Utolsó megállás lezárása
if current_stop_start and current_stop_duration >= min_stop_seconds:
stops.append({
"start_time": current_stop_start,
"duration_s": current_stop_duration,
"lat": np.mean([p["lat"] for p in current_stop_points]),
"lon": np.mean([p["lon"] for p in current_stop_points]),
"km_mark": current_stop_points[0]["cum_dist_km"],
})
# 5. Idő- és sebességstatisztikák összesítése
total_time_s = (df["time"].iloc[-1] - df["time"].iloc[0]).total_seconds()
moving_time_s = df.loc[df["is_moving"], "step_time_s"].sum()
paused_time_s = max(0.0, total_time_s - moving_time_s)
total_dist_km = df["cum_dist_km"].iloc[-1]
moving_dist_km = df.loc[df["is_moving"], "step_dist"].sum() / 1000.0
avg_speed_total_kmh = (total_dist_km / (total_time_s / 3600.0)) if total_time_s > 0 else 0.0
avg_speed_moving_kmh = (moving_dist_km / (moving_time_s / 3600.0)) if moving_time_s > 0 else 0.0
max_speed_kmh = df["speed_kmh"].quantile(0.99) # 99-es percentilis a GPS mérési tüskék ellen
summary = {
"total_distance_km": total_dist_km,
"total_ascent_m": total_ascent,
"total_descent_m": total_descent,
"total_time_s": total_time_s,
"moving_time_s": moving_time_s,
"paused_time_s": paused_time_s,
"avg_speed_total_kmh": avg_speed_total_kmh,
"avg_speed_moving_kmh": avg_speed_moving_kmh,
"max_speed_kmh": max_speed_kmh,
"stops": stops,
}
return df, summary
def print_summary(s):
"""Eredmények kiírása formázva."""
print("=" * 50)
print(" TÚRA / EDZÉS STATISZTIKA")
print("=" * 50)
print(f"Teljes távolság: {s['total_distance_km']:.2f} km")
print(f"Szintemelkedés (+): {s['total_ascent_m']:.0f} m")
print(f"Szintesés (-): {s['total_descent_m']:.0f} m")
print("-" * 50)
print(f"Teljes eltelt idő: {format_duration(s['total_time_s'])}")
print(f"Tiszta mozgási idő: {format_duration(s['moving_time_s'])}")
print(f"Pihenőidő (állás): {format_duration(s['paused_time_s'])}")
print("-" * 50)
print(f"Bruttó átlagsebesség: {s['avg_speed_total_kmh']:.2f} km/h")
print(f"Mozgási átlagsebesség: {s['avg_speed_moving_kmh']:.2f} km/h")
print(f"Max. sebesség (szűrt): {s['max_speed_kmh']:.2f} km/h")
print("-" * 50)
print(f"Detektált megállások száma: {len(s['stops'])}")
for idx, stop in enumerate(s['stops'], 1):
print(f" #{idx} | {stop['km_mark']:.2f}. km-nél | "
f"Időtartam: {format_duration(stop['duration_s'])} | "
f"Kezdés: {stop['start_time'].strftime('%H:%M:%S')}")
print("=" * 50)
if __name__ == "__main__":
GPX_FILE = "turautes.gpx"
try:
raw_df = load_gpx_to_dataframe(GPX_FILE)
df_processed, stats = analyze_gpx(
raw_df,
speed_threshold_kmh=2.8, # Gyaloglás alatti sebesség = állás
min_stop_seconds=60 # Legalább 1 perces egy helyben maradás = megállás
)
print_summary(stats)
except FileNotFoundError:
print(f"[HIBA] Fájl nem található: {GPX_FILE}")
Működési logika és szűrési mechanizmusok
Pontatlan csúcssebességek levágása: A kód
quantile(0.99)értékkel határozza meg a maximális sebességet. Ezzel kizárja azokat a hibás 1 másodperces kiugrásokat, amikor a GPS ugrása miatt a rendszer 80–100 km/h-s pillanatnyi sebességet számolna.Álló helyzeti pontszórás eliminálása: A
speed_threshold_kmhalatti elmozdulásokat a szkript nem adja hozzá a tiszta mozgási időhöz, így a padon ülve vagy kilátóponton rögzített „remegés” nem növeli a mozgási időt és nem rontja a mozgási átlagsebességet.Klaszterezett megállás-detektálás: A megállási események átlagolt GPS-koordinátát és kilométer-szelvényt kapnak, így közvetlenül ráilleszthetők térképre vagy POI adatbázisra.
Az alábbi kiegészítő modul a korábban feldolgozott DataFrame adatait használja fel. Két közös X-tengelyű (cum_dist_km) subplotot készít: a felső a magassági profilt, az alsó a pillanatnyi és átlagsebességet jeleníti meg, a detektált pihenőhelyeket pedig függőleges sávként mindkét diagramon átvezeti.
Diagramrajzoló modul (gpx_plotter.py)
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
def plot_dual_profile(df, stats, output_png=None):
"""
Két egymás alatti subplot rajzolása:
1. Magassági profil (szintemelkedés, szintcsökkenés)
2. Sebességprofil (pillanatnyi és mozgási átlagsebesség)
Közös X-tengellyel és kiemelt megállóhely-sávokkal.
"""
fig, (ax_ele, ax_spd) = plt.subplots(
nrows=2, ncols=1, figsize=(13, 8), sharex=True,
gridspec_kw={"height_ratios": [1.2, 1.0]}
)
x = df["cum_dist_km"]
# --- 1. FELSŐ SUBPLOT: Magassági profil ---
ax_ele.plot(
x, df["ele_smooth"],
color="#1565c0", linewidth=2.0, label="Tengerszint feletti magasság"
)
min_ele = df["ele_smooth"].min()
ax_ele.fill_between(
x, df["ele_smooth"], min_ele - 5,
color="#bbdefb", alpha=0.45
)
ax_ele.set_ylabel("Magasság (m)", fontsize=11, fontweight="bold")
ax_ele.set_ylim(bottom=min_ele - 10)
ax_ele.grid(True, linestyle=":", alpha=0.6)
ax_ele.legend(loc="upper right", frameon=True)
header_text = (
f"Túra összegzés: {stats['total_distance_km']:.2f} km | "
f"+{stats['total_ascent_m']:.0f} m / -{stats['total_descent_m']:.0f} m | "
f"Mozgási idő: {int(stats['moving_time_s'] // 3600):02d}:{int((stats['moving_time_s'] % 3600) // 60):02d}"
)
ax_ele.set_title(header_text, fontsize=12, fontweight="bold", pad=12)
# --- 2. ALSÓ SUBPLOT: Sebességprofil ---
# Nyers sebesség zaja helyett 5 pontos gördülő átlag a tiszta ábrázoláshoz
speed_smooth = df["speed_kmh"].rolling(window=5, min_periods=1, center=True).mean()
ax_spd.plot(
x, speed_smooth,
color="#2e7d32", linewidth=1.8, label="Pillanatnyi sebesség (simított)"
)
ax_spd.axhline(
stats["avg_speed_moving_kmh"],
color="#d32f2f", linestyle="--", linewidth=1.5,
label=f"Mozgási átlag ({stats['avg_speed_moving_kmh']:.1f} km/h)"
)
ax_spd.set_xlabel("Megtett távolság (km)", fontsize=11, fontweight="bold")
ax_spd.set_ylabel("Sebesség (km/h)", fontsize=11, fontweight="bold")
ax_spd.set_ylim(bottom=0, top=max(stats["max_speed_kmh"] * 1.15, 10))
ax_spd.grid(True, linestyle=":", alpha=0.6)
ax_spd.legend(loc="upper right", frameon=True)
# --- 3. MEGÁLLÓK JELÖLÉSE MINDKÉT SUBPLOTON ---
for i, stop in enumerate(stats.get("stops", [])):
km = stop["km_mark"]
dur_min = stop["duration_s"] / 60.0
# Függőleges sáv mindkét tengelyen
for ax in (ax_ele, ax_spd):
ax.axvline(km, color="#ff8f00", linestyle="-.", alpha=0.75, linewidth=1.3)
# Felirat a felső diagram tetején
ax_ele.text(
km, ax_ele.get_ylim()[1] * 0.98,
f" M{i+1}\n({dur_min:.0f}p)",
color="#e65100", fontsize=8.5, fontweight="bold",
ha="center", va="top"
)
plt.tight_layout()
if output_png:
plt.savefig(output_png, dpi=220)
print(f"[OK] Kétpaneles diagram mentve: {output_png}")
plt.show()
# --- Használati példa a korábbi feldolgozóval együtt ---
if __name__ == "__main__":
from gpx_time_analyzer import load_gpx_to_dataframe, analyze_gpx
GPX_PATH = "turautes.gpx"
try:
raw_df = load_gpx_to_dataframe(GPX_PATH)
df_proc, summary_stats = analyze_gpx(raw_df)
plot_dual_profile(
df_proc,
summary_stats,
output_png="magassag_es_sebesseg_profil.png"
)
except FileNotFoundError:
print(f"[HIBA] Nem található fájl: {GPX_PATH}")
Megjelenítési sajátosságok
Csatolt X-tengely (
sharex=True): Ha a Matplotlib ablakban zoomolsz vagy mozgatod a távolsági tengelyt, a két diagram szinkronban marad.Aszimmetrikus rácsarány (
height_ratios=[1.2, 1.0]): A szintrajz kapja a nagyobb vertikális hangsúlyt, mivel a terep vonalvezetése jobban igényli a felbontást.Gördülő sebességsimítás: A rajzolás pillanatában futtatott 5 pontos
rolling.mean()kisimítja a másodperces GPS ugrásokat, de nem torzítja el a hosszabb gyorsulásokat vagy lejtőzéseket.Megállási jelölők (
M1, M2...): A narancssárga pont-vonal sávok közvetlenül összekötik az álló helyzetet a szintprofil megfelelő koordinátájával (pl. hegytetőn tartott pihenő).
Az alábbi modul a korábban kinyert adatstruktúrából önálló, böngészőben megnyitható HTML fájlt generál a Plotly segítségével. A két subplot (magasság és sebesség) közös X tengelyt használ, a hovermode="x unified" beállításnak köszönhetően pedig az egér egyetlen függőleges vonalán egyszerre látható az adott kilométernél rögzített pontos időpont, magasság, sebesség és állapot.
Csomag telepítése
pip install plotly
Plotly interaktív generátor (gpx_plotly_generator.py)
import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
def generate_interactive_html(df, stats, output_html="tura_profil.html"):
"""Önálló interaktív Plotly HTML generálása szinkronizált kurzorral és megállókkal."""
# 1. Előkészítés: időbélyeg formázás és mozgóátlag sebesség
df = df.copy()
df["time_str"] = df["time"].dt.strftime("%H:%M:%S")
df["speed_smooth"] = (
df["speed_kmh"].rolling(window=5, min_periods=1, center=True).mean()
)
# 2. Két összefűzött subplot létrehozása közös X tengellyel
fig = make_subplots(
rows=2,
cols=1,
shared_xaxes=True,
vertical_spacing=0.08,
row_heights=[0.58, 0.42],
subplot_titles=(
"<b>Magassági profil</b>",
"<b>Sebesség és megállások</b>",
),
)
# --- 1. SUBPLOT: Magasság ---
fig.add_trace(
go.Scatter(
x=df["cum_dist_km"],
y=df["ele_smooth"],
mode="lines",
name="Magasság",
line=dict(color="#1976d2", width=2.5),
fill="tozeroy",
fillcolor="rgba(25, 118, 210, 0.15)",
customdata=df[["time_str", "speed_smooth", "ele"]],
hovertemplate=(
"<b>Magasság:</b> %{y:.1f} m (nyers: %{customdata[2]:.1f} m)<br>"
"<b>Időpont:</b> %{customdata[0]}<extra></extra>"
),
),
row=1,
col=1,
)
# --- 2. SUBPLOT: Sebesség ---
fig.add_trace(
go.Scatter(
x=df["cum_dist_km"],
y=df["speed_smooth"],
mode="lines",
name="Pillanatnyi sebesség",
line=dict(color="#2e7d32", width=2),
hovertemplate="<b>Sebesség:</b> %{y:.1f} km/h<extra></extra>",
),
row=2,
col=1,
)
# Mozgási átlagsebesség referenciavonal
fig.add_trace(
go.Scatter(
x=[df["cum_dist_km"].iloc[0], df["cum_dist_km"].iloc[-1]],
y=[stats["avg_speed_moving_kmh"], stats["avg_speed_moving_kmh"]],
mode="lines",
name=f"Mozgási átlag ({stats['avg_speed_moving_kmh']:.1f} km/h)",
line=dict(color="#d32f2f", width=1.5, dash="dash"),
hoverinfo="skip",
),
row=2,
col=1,
)
# --- MEGÁLLÓK JELÖLÉSE ÉS FÜGGŐLEGES SÁVOK ---
for i, stop in enumerate(stats.get("stops", []), 1):
km = stop["km_mark"]
dur_min = stop["duration_s"] / 60.0
start_clk = stop["start_time"].strftime("%H:%M:%S")
# Függőleges referenciavonal mindkét sávon át
fig.add_vline(
x=km,
line_width=1.5,
line_dash="dot",
line_color="#f57c00",
opacity=0.8,
)
# Rögzített megálló pont az alsó sebességdiagramon (0 km/h-nál)
fig.add_trace(
go.Scatter(
x=[km],
y=[0],
mode="markers+text",
name=f"Megálló #{i}",
marker=dict(size=9, color="#e65100", symbol="triangle-up"),
text=[f"M{i}"],
textposition="top center",
hovertemplate=(
f"<b>Megálló #{i}</b><br>"
f"Kezdés: {start_clk}<br>"
f"Időtartam: {dur_min:.1f} perc<br>"
f"Pozíció: {km:.2f} km<extra></extra>"
),
showlegend=False,
),
row=2,
col=1,
)
# --- FEJLÉC ÉS ELRENDEZÉS BEÁLLÍTÁSA ---
header_title = (
f"<b>Túra analitika</b> | Táv: <b>{stats['total_distance_km']:.2f} km</b> | "
f"Szint: <b>+{stats['total_ascent_m']:.0f} m / -{stats['total_descent_m']:.0f} m</b> | "
f"Mozgási idő: <b>{int(stats['moving_time_s'] // 3600):02d}:{int((stats['moving_time_s'] % 3600) // 60):02d}</b>"
)
min_ele = df["ele_smooth"].min()
fig.update_yaxes(
title_text="Magasság (m)",
range=[max(0, min_ele - 20), df["ele_smooth"].max() + 20],
row=1,
col=1,
)
fig.update_yaxes(
title_text="Sebesség (km/h)",
range=[0, max(stats["max_speed_kmh"] * 1.15, 12)],
row=2,
col=1,
)
fig.update_xaxes(
title_text="Megtett távolság (km)",
row=2,
col=1,
)
fig.update_layout(
title=dict(text=header_title, font=dict(size=15), x=0.03, y=0.98),
hovermode="x unified", # Közös vertikális leolvasó vonal az egérmozgatáskor
template="plotly_white",
legend=dict(
orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1
),
margin=dict(l=60, r=30, t=80, b=50),
height=750,
)
# 3. HTML fájlba írás
fig.write_html(output_html, include_plotlyjs="cdn")
print(f"[OK] Interaktív HTML sikeresen legenerálva: {output_html}")
# --- Futtatási példa ---
if __name__ == "__main__":
from gpx_time_analyzer import analyze_gpx, load_gpx_to_dataframe
GPX_INPUT = "turautes.gpx"
try:
raw_df = load_gpx_to_dataframe(GPX_INPUT)
df_processed, trip_stats = analyze_gpx(raw_df)
generate_interactive_html(
df_processed, trip_stats, output_html="magassag_sebesseg_profil.html"
)
except FileNotFoundError:
print(f"[HIBA] Nem található fájl: {GPX_INPUT}")
Fő beállítások működése
hovermode="x unified": Nem kell külön az egyes vonalakra célozni az egérrel; a kurzor pozíciójánál megjelenő függőleges segédvonal automatikusan egyetlen közös infóablakba fűzi a magasságot, a másodpercre pontos időt és a sebességet.include_plotlyjs="cdn": A generált.htmlfájl mérete minimális marad (néhány száz kilobájt), mert a megjelenítő motort a böngésző a Cloudflare CDN-ről tölti be, így azonnal továbbküldhető vagy beágyazható weboldalba.Szinkronizált skálázás: A nagyítás (box zoom / pan) és a tengelymozgatás automatikusan összekapcsolva mozog mindkét panelen a
shared_xaxes=Truemiatt.
Megjegyzések
Megjegyzés küldése