diff --git a/src/app.Rmd b/src/app.Rmd index 14419c2..ac3400d 100644 --- a/src/app.Rmd +++ b/src/app.Rmd @@ -12,6 +12,7 @@ library(httr) library(jsonlite) library(trajr) + # UI Definition ui <- fluidPage( titlePanel("Flight Trajectory Analysis - GUI"), @@ -214,20 +215,7 @@ server <- function(input, output, session) { rv$current_route <- route_df # Create trajectory object - lat_ref <- route_df$lat[1] - lon_ref <- route_df$lon[1] - meters_per_deg_lat <- 111320 - meters_per_deg_lon <- 111320 * cos(lat_ref * pi / 180) - - x_meters <- (route_df$lon - lon_ref) * meters_per_deg_lon - y_meters <- (route_df$lat - lat_ref) * meters_per_deg_lat - time_seconds <- route_df$time - route_df$time[1] - - rv$current_trj <- TrajFromCoords( - data.frame(x = x_meters, y = y_meters, time = time_seconds), - xCol = "x", yCol = "y", timeCol = "time" - ) - + rv$current_trj <- createTrajFromRoute(route_df) status(paste("Successfully analyzed", icao24, "with", nrow(route_df), "points")) # Switch to analysis tab @@ -266,41 +254,8 @@ server <- function(input, output, session) { trj <- rv$current_trj - duration <- TrajDuration(trj) - path_length <- TrajLength(trj) - diffusion_distance <- TrajDistance(trj) - straightness <- TrajStraightness(trj) - mean_velocity <- path_length / duration - - fractal_dim <- tryCatch({ - min_step <- path_length / 100 - max_step <- path_length / 2 - if (min_step > 0 && max_step > min_step) { - step_sizes <- exp(seq(log(min_step), log(max_step), length.out = 10)) - TrajFractalDimension(trj, stepSizes = step_sizes) - } else { - NA - } - }, error = function(e) NA) - - data.frame( - Parameter = c( - "Duration (s)", "Duration (min)", - "Path Length (m)", "Path Length (km)", - "Diffusion Distance (m)", "Diffusion Distance (km)", - "Straightness Index", - "Mean Velocity (m/s)", "Mean Velocity (km/h)", - "Fractal Dimension" - ), - Value = c( - round(duration, 2), round(duration / 60, 2), - round(path_length, 2), round(path_length / 1000, 2), - round(diffusion_distance, 2), round(diffusion_distance / 1000, 2), - round(straightness, 4), - round(mean_velocity, 2), round(mean_velocity * 3.6, 2), - round(fractal_dim, 4) - ) - ) + data.frame <- calculateRouteCharacteristics(trj) + data.frame }) # Batch analysis @@ -337,19 +292,7 @@ server <- function(input, output, session) { route_df <- as.data.frame(track_data$path) colnames(route_df) <- c("time", "lat", "lon", "alt", "heading", "on_ground") - lat_ref <- route_df$lat[1] - lon_ref <- route_df$lon[1] - meters_per_deg_lat <- 111320 - meters_per_deg_lon <- 111320 * cos(lat_ref * pi / 180) - - x_meters <- (route_df$lon - lon_ref) * meters_per_deg_lon - y_meters <- (route_df$lat - lat_ref) * meters_per_deg_lat - time_seconds <- route_df$time - route_df$time[1] - - trj <- TrajFromCoords( - data.frame(x = x_meters, y = y_meters, time = time_seconds), - xCol = "x", yCol = "y", timeCol = "time" - ) + trj <- createTrajFromRoute(route_df) duration <- TrajDuration(trj) path_length <- TrajLength(trj) @@ -402,156 +345,239 @@ server <- function(input, output, session) { # Statistics summary table output$stats_summary_table <- renderTable({ req(rv$trajectory_stats_df) - - params <- c("diffusion_distance_km", "straightness", "duration_min", - "mean_velocity_kmh", "fractal_dimension") - labels <- c("Diffusion Distance (km)", "Straightness", "Duration (min)", - "Mean Velocity (km/h)", "Fractal Dimension") - - stats_list <- lapply(seq_along(params), function(i) { - x <- rv$trajectory_stats_df[[params[i]]] - x <- x[!is.na(x)] - if (length(x) < 2) return(NULL) - - data.frame( - Parameter = labels[i], - N = length(x), - Mean = round(mean(x), 4), - Variance = round(var(x), 4), - Std_Dev = round(sd(x), 4), - Q1 = round(quantile(x, 0.25), 4), - Median = round(median(x), 4), - Q3 = round(quantile(x, 0.75), 4) - ) - }) - - do.call(rbind, stats_list[!sapply(stats_list, is.null)]) + calculateStatsSummary(rv$trajectory_stats_df) }) # Boxplots output$boxplots <- renderPlot({ req(rv$trajectory_stats_df) - - params <- c("diffusion_distance_km", "straightness", "duration_min", - "mean_velocity_kmh", "fractal_dimension") - labels <- c("Diffusion Distance (km)", "Straightness", "Duration (min)", - "Mean Velocity (km/h)", "Fractal Dimension") - - par(mfrow = c(2, 3)) - for (i in seq_along(params)) { - data <- rv$trajectory_stats_df[[params[i]]][!is.na(rv$trajectory_stats_df[[params[i]]])] - if (length(data) >= 2) { - boxplot(data, main = labels[i], ylab = labels[i], col = "lightblue", border = "darkblue") - points(1, mean(data), pch = 18, col = "red", cex = 1.5) - } - } - par(mfrow = c(1, 1)) + createBoxplots(rv$trajectory_stats_df) }) # Density plots output$density_plots <- renderPlot({ req(rv$trajectory_stats_df) - - params <- c("diffusion_distance_km", "straightness", "duration_min", - "mean_velocity_kmh", "fractal_dimension") - labels <- c("Diffusion Distance (km)", "Straightness", "Duration (min)", - "Mean Velocity (km/h)", "Fractal Dimension") - - par(mfrow = c(2, 3)) - for (i in seq_along(params)) { - data <- rv$trajectory_stats_df[[params[i]]][!is.na(rv$trajectory_stats_df[[params[i]]])] - if (length(data) >= 3) { - dens <- density(data) - plot(dens, main = paste("Density:", labels[i]), xlab = labels[i], col = "darkblue", lwd = 2) - polygon(dens, col = rgb(0, 0, 1, 0.3), border = "darkblue") - abline(v = mean(data), col = "red", lwd = 2, lty = 2) - abline(v = median(data), col = "green", lwd = 2, lty = 3) - } - } - par(mfrow = c(1, 1)) + createDensityPlots(rv$trajectory_stats_df) }) # Histograms output$histograms <- renderPlot({ req(rv$trajectory_stats_df) - - params <- c("diffusion_distance_km", "straightness", "duration_min", - "mean_velocity_kmh", "fractal_dimension") - labels <- c("Diffusion Distance (km)", "Straightness", "Duration (min)", - "Mean Velocity (km/h)", "Fractal Dimension") - - par(mfrow = c(2, 3)) - for (i in seq_along(params)) { - data <- rv$trajectory_stats_df[[params[i]]][!is.na(rv$trajectory_stats_df[[params[i]]])] - if (length(data) >= 3) { - hist(data, probability = TRUE, main = paste("Histogram:", labels[i]), - xlab = labels[i], col = "lightgray", border = "darkgray") - lines(density(data), col = "red", lwd = 2) - } - } - par(mfrow = c(1, 1)) + createHistograms(rv$trajectory_stats_df) }) # Interpretation text output$interpretation_text <- renderText({ req(rv$trajectory_stats_df) - - df <- rv$trajectory_stats_df - - text <- "========== INTERPRETATION OF TRAJECTORY PARAMETERS ==========\n\n" - - # Diffusion Distance - dd <- df$diffusion_distance_km[!is.na(df$diffusion_distance_km)] - if (length(dd) >= 2) { - text <- paste0(text, "1. DIFFUSION DISTANCE (Net Displacement):\n") - text <- paste0(text, " - Mean: ", round(mean(dd), 2), " km\n") - text <- paste0(text, " - Represents straight-line distance from origin to destination.\n") - text <- paste0(text, " - Variance: ", round(var(dd), 2), " (indicates diversity in flight distances)\n\n") - } - - # Straightness - st <- df$straightness[!is.na(df$straightness)] - if (length(st) >= 2) { - text <- paste0(text, "2. STRAIGHTNESS INDEX:\n") - text <- paste0(text, " - Mean: ", round(mean(st), 4), " (range 0-1, where 1 = perfectly straight)\n") - text <- paste0(text, " - Values close to 1 indicate efficient, direct flight paths.\n") - text <- paste0(text, " - Lower values suggest deviations due to weather, airspace, or routing.\n\n") - } - - # Duration - dur <- df$duration_min[!is.na(df$duration_min)] - if (length(dur) >= 2) { - text <- paste0(text, "3. DURATION OF TRAVEL:\n") - text <- paste0(text, " - Mean: ", round(mean(dur), 2), " minutes\n") - text <- paste0(text, " - Range: ", round(min(dur), 2), " - ", round(max(dur), 2), " minutes\n") - text <- paste0(text, " - IQR: ", round(IQR(dur), 2), " minutes (middle 50% of flights)\n\n") - } - - # Velocity - vel <- df$mean_velocity_kmh[!is.na(df$mean_velocity_kmh)] - if (length(vel) >= 2) { - text <- paste0(text, "4. MEAN TRAVEL VELOCITY:\n") - text <- paste0(text, " - Mean: ", round(mean(vel), 2), " km/h\n") - text <- paste0(text, " - Typical commercial aircraft cruise: 800-900 km/h\n") - text <- paste0(text, " - Lower values may include taxi, takeoff, and landing phases.\n\n") - } - - # Fractal Dimension - fd <- df$fractal_dimension[!is.na(df$fractal_dimension)] - if (length(fd) >= 2) { - text <- paste0(text, "5. FRACTAL DIMENSION:\n") - text <- paste0(text, " - Mean: ", round(mean(fd), 4), "\n") - text <- paste0(text, " - Value of 1.0 = perfectly straight line\n") - text <- paste0(text, " - Values closer to 2.0 = more complex, space-filling paths\n") - text <- paste0(text, " - Aircraft typically show low fractal dimension (efficient paths).\n\n") - } - - text <- paste0(text, "========== END OF ANALYSIS ==========") - - text + generateInterpretation(rv$trajectory_stats_df) }) } +# Helper function to get parameter names and labels +getTrajectoryParams <- function() { + list( + params = c("diffusion_distance_km", "straightness", "duration_min", + "mean_velocity_kmh", "fractal_dimension"), + labels = c("Diffusion Distance (km)", "Straightness", "Duration (min)", + "Mean Velocity (km/h)", "Fractal Dimension") + ) +} + +# Calculate statistics summary table +calculateStatsSummary <- function(trajectory_stats_df) { + p <- getTrajectoryParams() + + stats_list <- lapply(seq_along(p$params), function(i) { + x <- trajectory_stats_df[[p$params[i]]] + x <- x[!is.na(x)] + if (length(x) < 2) return(NULL) + + data.frame( + Parameter = p$labels[i], + N = length(x), + Mean = round(mean(x), 4), + Variance = round(var(x), 4), + Std_Dev = round(sd(x), 4), + Q1 = round(quantile(x, 0.25), 4), + Median = round(median(x), 4), + Q3 = round(quantile(x, 0.75), 4) + ) + }) + + do.call(rbind, stats_list[!sapply(stats_list, is.null)]) +} + +# Create boxplots for trajectory statistics +createBoxplots <- function(trajectory_stats_df) { + p <- getTrajectoryParams() + + par(mfrow = c(2, 3)) + for (i in seq_along(p$params)) { + data <- trajectory_stats_df[[p$params[i]]][!is.na(trajectory_stats_df[[p$params[i]]])] + if (length(data) >= 2) { + boxplot(data, main = p$labels[i], ylab = p$labels[i], col = "lightblue", border = "darkblue") + points(1, mean(data), pch = 18, col = "red", cex = 1.5) + } + } + par(mfrow = c(1, 1)) +} + +# Create density plots for trajectory statistics +createDensityPlots <- function(trajectory_stats_df) { + p <- getTrajectoryParams() + + par(mfrow = c(2, 3)) + for (i in seq_along(p$params)) { + data <- trajectory_stats_df[[p$params[i]]][!is.na(trajectory_stats_df[[p$params[i]]])] + if (length(data) >= 3) { + dens <- density(data) + plot(dens, main = paste("Density:", p$labels[i]), xlab = p$labels[i], col = "darkblue", lwd = 2) + polygon(dens, col = rgb(0, 0, 1, 0.3), border = "darkblue") + abline(v = mean(data), col = "red", lwd = 2, lty = 2) + abline(v = median(data), col = "green", lwd = 2, lty = 3) + } + } + par(mfrow = c(1, 1)) +} + +# Create histograms for trajectory statistics +createHistograms <- function(trajectory_stats_df) { + p <- getTrajectoryParams() + + par(mfrow = c(2, 3)) + for (i in seq_along(p$params)) { + data <- trajectory_stats_df[[p$params[i]]][!is.na(trajectory_stats_df[[p$params[i]]])] + if (length(data) >= 3) { + hist(data, probability = TRUE, main = paste("Histogram:", p$labels[i]), + xlab = p$labels[i], col = "lightgray", border = "darkgray") + lines(density(data), col = "red", lwd = 2) + } + } + par(mfrow = c(1, 1)) +} + +# Generate interpretation text for trajectory statistics +generateInterpretation <- function(trajectory_stats_df) { + df <- trajectory_stats_df + + text <- "========== INTERPRETATION OF TRAJECTORY PARAMETERS ==========\n\n" + + # Diffusion Distance + dd <- df$diffusion_distance_km[!is.na(df$diffusion_distance_km)] + if (length(dd) >= 2) { + text <- paste0(text, "1. DIFFUSION DISTANCE (Net Displacement):\n") + text <- paste0(text, " - Mean: ", round(mean(dd), 2), " km\n") + text <- paste0(text, " - Represents straight-line distance from origin to destination.\n") + text <- paste0(text, " - Variance: ", round(var(dd), 2), " (indicates diversity in flight distances)\n\n") + } + + # Straightness + st <- df$straightness[!is.na(df$straightness)] + if (length(st) >= 2) { + text <- paste0(text, "2. STRAIGHTNESS INDEX:\n") + text <- paste0(text, " - Mean: ", round(mean(st), 4), " (range 0-1, where 1 = perfectly straight)\n") + text <- paste0(text, " - Values close to 1 indicate efficient, direct flight paths.\n") + text <- paste0(text, " - Lower values suggest deviations due to weather, airspace, or routing.\n\n") + } + + # Duration + dur <- df$duration_min[!is.na(df$duration_min)] + if (length(dur) >= 2) { + text <- paste0(text, "3. DURATION OF TRAVEL:\n") + text <- paste0(text, " - Mean: ", round(mean(dur), 2), " minutes\n") + text <- paste0(text, " - Range: ", round(min(dur), 2), " - ", round(max(dur), 2), " minutes\n") + text <- paste0(text, " - IQR: ", round(IQR(dur), 2), " minutes (middle 50% of flights)\n\n") + } + + # Velocity + vel <- df$mean_velocity_kmh[!is.na(df$mean_velocity_kmh)] + if (length(vel) >= 2) { + text <- paste0(text, "4. MEAN TRAVEL VELOCITY:\n") + text <- paste0(text, " - Mean: ", round(mean(vel), 2), " km/h\n") + text <- paste0(text, " - Typical commercial aircraft cruise: 800-900 km/h\n") + text <- paste0(text, " - Lower values may include taxi, takeoff, and landing phases.\n\n") + } + + # Fractal Dimension + fd <- df$fractal_dimension[!is.na(df$fractal_dimension)] + if (length(fd) >= 2) { + text <- paste0(text, "5. FRACTAL DIMENSION:\n") + text <- paste0(text, " - Mean: ", round(mean(fd), 4), "\n") + text <- paste0(text, " - Value of 1.0 = perfectly straight line\n") + text <- paste0(text, " - Values closer to 2.0 = more complex, space-filling paths\n") + text <- paste0(text, " - Aircraft typically show low fractal dimension (efficient paths).\n\n") + } + + text <- paste0(text, "========== END OF ANALYSIS ==========") + + text +} + +createTrajFromRoute <- function(route_df) { + tryCatch({ + lat_ref <- route_df$lat[1] + lon_ref <- route_df$lon[1] + meters_per_deg_lat <- 111320 + meters_per_deg_lon <- 111320 * cos(lat_ref * pi / 180) + + x_meters <- (route_df$lon - lon_ref) * meters_per_deg_lon + y_meters <- (route_df$lat - lat_ref) * meters_per_deg_lat + time_seconds <- route_df$time - route_df$time[1] + + trj <- TrajFromCoords( + data.frame(x = x_meters, y = y_meters, time = time_seconds), + xCol = "x", yCol = "y", timeCol = "time" + ) + + return(trj) + + }, error = function(e) { + status(paste("Error creating trajectory object:", e$message)) + }) +} + +calculateRouteCharacteristics <- function(trj) { + duration <- TrajDuration(trj) + path_length <- TrajLength(trj) + diffusion_distance <- TrajDistance(trj) + straightness <- TrajStraightness(trj) + mean_velocity <- path_length / duration + + fractal_dim <- tryCatch({ + min_step <- path_length / 100 + max_step <- path_length / 2 + if (min_step > 0 && max_step > min_step) { + step_sizes <- exp(seq(log(min_step), log(max_step), length.out = 10)) + TrajFractalDimension(trj, stepSizes = step_sizes) + } else { + NA + } + }, error = function(e) NA) + + return (data.frame( + Parameter = c( + "Duration (s)", "Duration (min)", + "Path Length (km)", + "Duffusion Distance (m)", + "Diffusion Distance (km)", + "Straightness Index", + "Mean Velocity (km/h)", + "Fractal Dimension" + ), + Value = c( + duration_s = round(duration, 2), + duration_min = round(duration / 60, 2), + path_length_km = round(path_length / 1000, 2), + diffusion_distance_m = round(diffusion_distance, 2), + diffusion_distance_km = round(diffusion_distance / 1000, 2), + straightness_index = round(straightness, 4), + mean_velocity_kmh = round(mean_velocity *3.6, 2), + fractal_dimension = round(fractal_dim, 4) + ) + ) + ) +} + # Run the application shinyApp(ui = ui, server = server) ``` \ No newline at end of file