---
title: "Journal de Vol - FlightLog Pro"
format:
html:
toc: false
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = FALSE, warning = FALSE, message = FALSE)
library(jsonlite)
library(DT)
library(knitr)
# Chargement des données
db_file <- "data/processed/flights.json"
if (file.exists(db_file)) {
db <- fromJSON(db_file, simplifyVector = FALSE)
has_data <- length(db$flights) > 0
} else {
has_data <- FALSE
db <- list(flights = list(), global_stats = list())
}
```
## 🪂 Bienvenue dans mon journal de vol automatisé
**Workflow professionnel :** Copiez vos fichiers IGC Ultrabip → traitement automatique → site web complet. **90% automatique** avec métadonnées IGC intelligentes, gestion timezone, et analyses barométriques précises.
```{r conditional-stats, results='asis'}
if (has_data) {
cat("## 📊 Statistiques générales\n\n")
} else {
cat("## 🚀 Premiers pas\n\n")
cat("Aucun vol n'a encore été traité. Importez votre premier vol Ultrabip avec `source('scripts/journal_manager.R')` !\n\n")
}
```
```{r stats-display}
if (has_data && !is.null(db$global_stats)) {
stats <- db$global_stats
# Formatage du temps de vol total en heures et minutes
total_hours <- stats$total_airtime_hours %||% 0
hours <- floor(total_hours)
minutes <- floor((total_hours - hours) * 60)
formatted_airtime <- sprintf("%dh %02dm", hours, minutes)
# Création du tableau de stats avec nouvelles métriques
stats_df <- data.frame(
Métrique = c(
"🛩️ Nombre total de vols",
"⏱️ Temps de vol total",
"📏 Distance totale",
"🏆 Vol le plus long",
"⛰️ Altitude maximale",
"📊 Durée moyenne",
"📊 Distance moyenne",
"📈 Cumul gain altitude",
"🚀 Vitesse max record",
"📊 Vitesse moyenne",
"🪁 Meilleure finesse",
"📊 Finesse moyenne",
"⬆️ Taux montée max",
"🛰️ Vols avec GPS altitude"
),
Valeur = c(
as.character(stats$total_flights %||% 0),
formatted_airtime,
paste(stats$total_distance_km %||% 0, "km"),
paste(stats$longest_flight_km %||% 0, "km"),
paste(stats$highest_altitude_m %||% 0, "m"),
paste(round(stats$avg_flight_duration_min %||% 0), "min"),
paste(round(stats$avg_flight_distance_km %||% 0, 2), "km"),
paste(stats$total_elevation_gain_m %||% 0, "m"),
paste(stats$max_speed_kmh %||% 0, "km/h"),
paste(round(stats$avg_speed_kmh %||% 0, 1), "km/h"),
paste(stats$best_glide_ratio %||% "N/A", ":1"),
paste(round(stats$avg_glide_ratio %||% 0, 1), ":1"),
paste(stats$max_climb_rate %||% 0, "m/s"),
paste(stats$flights_with_gps_altitude %||% 0, "sur", stats$total_flights %||% 0)
)
)
kable(stats_df, format = "html", escape = FALSE) %>%
kableExtra::kable_styling(
bootstrap_options = c("striped", "hover", "condensed"),
full_width = FALSE,
position = "left"
)
}
```
```{r hike-stats, results='asis'}
# Statistiques Hike & Fly si disponibles
if (has_data && !is.null(db$global_stats) && (db$global_stats$total_hike_flights %||% 0) > 0) {
cat("## 🥾 Statistiques Hike & Fly\n\n")
hike_stats_df <- data.frame(
Métrique = c(
"🥾 Vols hike & fly",
"⏱️ Temps de marche total",
"📈 Gain altitude marche"
),
Valeur = c(
paste(db$global_stats$total_hike_flights, "vols"),
paste(db$global_stats$total_hike_hours %||% 0, "heures"),
paste(db$global_stats$total_elevation_gain_hiking %||% 0, "m")
)
)
kable(hike_stats_df, format = "html", escape = FALSE) %>%
kableExtra::kable_styling(
bootstrap_options = c("striped", "hover", "condensed"),
full_width = FALSE,
position = "left"
)
}
```
## 📍 Sites favoris
::: {.grid}
::: {.g-col-6}
**🛫 Décollages**
```{r fav-takeoff, results='asis'}
if (has_data && !is.null(db$global_stats$favorite_takeoff_sites)) {
sites <- db$global_stats$favorite_takeoff_sites
if (length(sites) > 0) {
for (i in 1:length(sites)) {
site_name <- names(sites)[i]
if (site_name != "") {
cat(sprintf("- **%s** (%d vols)\n", site_name, sites[[i]]))
}
}
} else {
cat("_Pas encore de données._")
}
} else {
cat("_Pas encore de données._")
}
```
:::
::: {.g-col-6}
**🛬 Atterrissages**
```{r fav-landing, results='asis'}
if (has_data && !is.null(db$global_stats$favorite_landing_sites)) {
sites <- db$global_stats$favorite_landing_sites
if (length(sites) > 0) {
for (i in 1:length(sites)) {
site_name <- names(sites)[i]
if (site_name != "") {
cat(sprintf("- **%s** (%d vols)\n", site_name, sites[[i]]))
}
}
} else {
cat("_Pas encore de données._")
}
} else {
cat("_Pas encore de données._")
}
```
:::
:::
## 🗓️ Derniers vols
```{r recent-flights}
#| results: asis
if (has_data && length(db$flights) > 0) {
# Tri des vols par date (les plus récents en premier)
flights_sorted <- db$flights[order(sapply(db$flights, function(f) f$date %||% ""), decreasing = TRUE)]
# Affichage des 5 derniers vols
recent_flights <- head(flights_sorted, 5)
for (flight in recent_flights) {
# Formatage de la durée en minutes et secondes
duration_min <- floor(flight$duration_minutes %||% 0)
duration_sec <- floor(((flight$duration_minutes %||% 0) - duration_min) * 60)
formatted_duration <- sprintf("%dm %02ds", duration_min, duration_sec)
cat(sprintf(
"- **[%s - %s](posts/%s/%s.qmd)** - %s, %.2f km, +%s m\n",
flight$date %||% "Date inconnue",
flight$takeoff_time %||% "Heure inconnue",
flight$id %||% "",
flight$id %||% "",
formatted_duration,
flight$total_distance_km %||% 0,
flight$elevation_gain_total %||% "?"
))
}
}
```
## 🛠️ Comment ça marche
**🎯 Workflow simple (2 étapes) :**
**📱 Votre Ultrabip a généré :** `2025-08-15-09h45m12s.igc` + `2025-08-15-09h45m12s.kml`
```r
# 1. Traitement direct IGC (pilote, date, device auto-extraits)
source("scripts/flight_processor.R")
process_flight(
file_path = "data/raw/2025-08-15-09h45m12s.igc",
metadata = list(
takeoff_site = "Moléson",
landing_site = "Bulle",
glider = "Niviuk Hook 5P",
conditions = "Thermique fort",
timezone_offset_hours = 2 # UTC+2 pour heure d'été
)
)
# 2. Publication
# quarto render && quarto preview
```
**🎉 Fonctionnalités clés :**
- ✅ **Extraction IGC intelligente** : pilote, date, device auto-nettoyés
- ✅ **Gestion timezone** : correction UTC → heure locale automatique
- ✅ **Altitude barométrique** : précision capteur DPS310 vs GPS
- ✅ **Métriques parapente** : finesse, taux montée/chute, gain altitude
- ✅ **Graphiques professionnels** : 4 visualisations + carte interactive
- ✅ **Prévention doublons** : mise à jour intelligente sans duplicata
- ✅ **Conditions météo** : section dédiée si renseignées
- ✅ **Statistiques globales** : sites favoris, records, cumuls
---
*Développé avec ❤️ pour la communauté parapente*
*Powered by [Claude](https://claude.ai) - Code optimization & smart features*
🛠️ Comment ça marche
🎯 Workflow simple (2 étapes) :
📱 Votre Ultrabip a généré :
2025-08-15-09h45m12s.igc+2025-08-15-09h45m12s.kml🎉 Fonctionnalités clés :
Développé avec ❤️ pour la communauté parapente
Powered by Claude - Code optimization & smart features