| |
| server <- function(input, output, session) { |
| stations <- reactive({ |
| get_imgw_stations() |
| }) |
|
|
| |
| style_change_trigger <- reactiveVal(0) |
| stations_before_id <- reactiveVal("waterway_line_label") |
| current_raster_layers <- reactiveVal(character(0)) |
| map_initialized <- reactiveVal(FALSE) |
|
|
| |
| current_station_uid <- reactiveVal(NULL) |
| station_data <- reactiveVal(NULL) |
| station_fetching <- reactiveVal(FALSE) |
| station_fetch_message <- reactiveVal("") |
| station_fetch_error <- reactiveVal(NULL) |
| prev_dates <- reactiveVal(NULL) |
|
|
| |
| fetch_stage <- reactiveVal(0) |
| fetch_queue <- reactiveVal(NULL) |
| fetch_queue_idx <- reactiveVal(0) |
| parsed_data_list <- reactiveVal(list()) |
| fetch_current_token <- reactiveVal(NULL) |
|
|
| |
| url_initialized <- reactiveVal(FALSE) |
|
|
| |
| map_bounds <- list( |
| lng_min = 14.0, |
| lat_min = 49.0, |
| lng_max = 24.5, |
| lat_max = 55.0 |
| ) |
|
|
| |
| loading_status <- reactiveVal(FALSE) |
|
|
| |
| observe({ |
| is_loading <- loading_status() |
|
|
| |
| inputs_to_toggle <- c( |
| "temporal_resolution", |
| "date_range", |
| "zoom_home", |
| "main_nav", |
| "basemap", |
| "show_labels", |
| "stations_table" |
| ) |
|
|
| if (is_loading) { |
| for (inp in inputs_to_toggle) { |
| shinyjs::disable(inp) |
| } |
| |
| |
| } else { |
| for (inp in inputs_to_toggle) { |
| shinyjs::enable(inp) |
| } |
| } |
| }) |
|
|
| |
| parse_url_params <- function(query_string) { |
| if (is.null(query_string) || query_string == "" || query_string == "?") { |
| return(list()) |
| } |
| |
| qs <- sub("^\\?", "", query_string) |
| if (qs == "") { |
| return(list()) |
| } |
|
|
| pairs <- strsplit(qs, "&")[[1]] |
| params <- list() |
| for (pair in pairs) { |
| kv <- strsplit(pair, "=")[[1]] |
| if (length(kv) == 2) { |
| params[[kv[1]]] <- URLdecode(kv[2]) |
| } |
| } |
| params |
| } |
|
|
| |
| broadcast_state <- function(view_override = NULL) { |
| st <- isolate(selected_station()) |
| |
| station_id <- if (!is.null(st) && nrow(st) > 0) as.character(st$id[1]) else NULL |
| station_name <- if (!is.null(st) && nrow(st) > 0) as.character(st$name[1]) else NULL |
| landname <- if (!is.null(st) && nrow(st) > 0 && "station_type" %in% names(st) && "data_order" %in% names(st) && !is.na(st$station_type[1]) && !is.na(st$data_order[1])) paste0(st$station_type[1], "-", st$data_order[1]) else NULL |
| resolution <- isolate(input$temporal_resolution) |
|
|
| |
| main_tab <- isolate(input$main_nav) |
| view <- if (!is.null(view_override)) { |
| view_override |
| } else if (!is.null(main_tab)) { |
| if (main_tab == "Map View") { |
| "map" |
| } else if (main_tab == "Stations Info") { |
| "station-info" |
| } else if (main_tab == "Dashboard") { |
| subtab <- isolate(input$dashboard_subtabs) |
| if (!is.null(subtab) && subtab == "Data") { |
| "dashboard-data" |
| } else { |
| "dashboard-plots" |
| } |
| } else { |
| "map" |
| } |
| } else { |
| "map" |
| } |
|
|
| date_range_val <- isolate(input$date_range) |
| start_date <- if (!is.null(date_range_val)) as.character(date_range_val[1]) else NULL |
| end_date <- if (!is.null(date_range_val)) as.character(date_range_val[2]) else NULL |
|
|
| session$sendCustomMessage("updateParentURL", list( |
| station = station_id, |
| stationName = station_name, |
| landname = landname, |
| resolution = resolution, |
| view = view, |
| start = start_date, |
| end = end_date |
| )) |
| } |
|
|
| |
| observe({ |
| req(!url_initialized()) |
| |
| query <- session$clientData$url_search |
| if (is.null(query)) { |
| |
| url_initialized(TRUE) |
| return() |
| } |
| params <- parse_url_params(query) |
|
|
| if (length(params) == 0) { |
| url_initialized(TRUE) |
| return() |
| } |
|
|
| |
| if (!is.null(params$resolution) && params$resolution %in% c("hourly", "daily", "monthly")) { |
| updateRadioButtons(session, "temporal_resolution", selected = params$resolution) |
| } |
|
|
| |
| if (!is.null(params$start) && !is.null(params$end)) { |
| start_date <- tryCatch(as.Date(params$start), error = function(e) NULL) |
| end_date <- tryCatch(as.Date(params$end), error = function(e) NULL) |
| if (!is.null(start_date) && !is.null(end_date)) { |
| updateDateRangeInput(session, "date_range", start = start_date, end = end_date) |
| prev_dates(c(start_date, end_date)) |
| } |
| } |
|
|
| |
| if (!is.null(params$station)) { |
| station <- params$station |
|
|
| |
| all_stations <- stations() |
| st <- all_stations %>% |
| filter(id == station | name == station) %>% |
| slice(1) |
|
|
| if (nrow(st) > 0) { |
| shinyjs::delay(200, { |
| current_station_uid(paste(st$id[1], st$name[1], sep = "|")) |
| }) |
| } |
| } |
|
|
| |
| if (!is.null(params$view)) { |
| view <- params$view |
| shinyjs::delay(600, { |
| if (view == "map") { |
| session$sendCustomMessage("switchTab", list(tabId = "Map View")) |
| } else if (view == "station-info") { |
| session$sendCustomMessage("switchTab", list(tabId = "Stations Info")) |
| } else if (view %in% c("dashboard-plots", "dashboard-data")) { |
| session$sendCustomMessage("switchTab", list(tabId = "Dashboard")) |
| if (view == "dashboard-data") { |
| shinyjs::delay(800, { |
| shinyjs::runjs("$('a[data-value=\"Data\"]').tab('show');") |
| }) |
| } |
| } |
| }) |
| } |
|
|
| url_initialized(TRUE) |
| }) |
|
|
| resolution_key <- reactive({ |
| res <- input$temporal_resolution |
| if (is.null(res) || !res %in% c("hourly", "daily", "monthly")) "daily" else res |
| }) |
|
|
| params_by_resolution <- list( |
| hourly = c( |
| synop = "Temperature, humidity, wind, pressure, clouds, visibility, precipitation, snow, sunshine, soil", |
| klimat = "Temperature, humidity, wind, clouds, visibility", |
| opad = "Not available for hourly", |
| `tylko dok. papierowe` = "Paper archives only" |
| ), |
| daily = c( |
| synop = "Temperature, precipitation, snow, sunshine, weather phenomena, soil", |
| klimat = "Temperature, precipitation, snow", |
| opad = "Precipitation, snow depth, snowfall type", |
| `tylko dok. papierowe` = "Paper archives only" |
| ), |
| monthly = c( |
| synop = "Temperature, precipitation, snow, sunshine, wind, humidity, cloudiness, weather days", |
| klimat = "Temperature, precipitation, snow, wind, humidity, cloudiness", |
| opad = "Precipitation, snow days, snow cover days", |
| `tylko dok. papierowe` = "Paper archives only" |
| ) |
| ) |
|
|
| parse_station_uid <- function(uid) { |
| uid <- as.character(uid)[1] |
| if (is.na(uid) || uid == "") { |
| return(list(id = NA_character_, name = NA_character_)) |
| } |
| if (!grepl("|", uid, fixed = TRUE)) { |
| return(list(id = uid, name = NA_character_)) |
| } |
| list( |
| id = sub("\\|.*$", "", uid), |
| name = sub("^[^|]*\\|", "", uid) |
| ) |
| } |
|
|
| build_station_popup <- function(st_row, res = NULL) { |
| if (is.null(res)) res <- resolution_key() |
| if (is.null(st_row) || nrow(st_row) == 0) { |
| return("Station") |
| } |
|
|
| resolution_label <- switch(res, |
| hourly = "Hourly", |
| daily = "Daily", |
| monthly = "Monthly", |
| "Daily" |
| ) |
|
|
| selected_start <- switch(res, |
| hourly = if ("date_start_hourly" %in% names(st_row)) st_row$date_start_hourly[1] else NA, |
| daily = if ("date_start_daily" %in% names(st_row)) st_row$date_start_daily[1] else NA, |
| monthly = if ("date_start_monthly" %in% names(st_row)) st_row$date_start_monthly[1] else NA, |
| if ("date_start" %in% names(st_row)) st_row$date_start[1] else NA |
| ) |
| selected_end <- switch(res, |
| hourly = if ("date_end_hourly" %in% names(st_row)) st_row$date_end_hourly[1] else NA, |
| daily = if ("date_end_daily" %in% names(st_row)) st_row$date_end_daily[1] else NA, |
| monthly = if ("date_end_monthly" %in% names(st_row)) st_row$date_end_monthly[1] else NA, |
| if ("date_end" %in% names(st_row)) st_row$date_end[1] else NA |
| ) |
|
|
| if (is.na(selected_start) && "date_start" %in% names(st_row)) selected_start <- st_row$date_start[1] |
| if (is.na(selected_end) && "date_end" %in% names(st_row)) selected_end <- st_row$date_end[1] |
| if (!is.na(selected_end) && !is.na(selected_start) && selected_end < selected_start) selected_end <- NA |
|
|
| start_fmt <- if (is.na(selected_start)) "Unknown" else as.character(selected_start) |
| end_fmt <- if (is.na(selected_end)) "Present" else as.character(selected_end) |
| date_range <- paste0(start_fmt, " to ", end_fmt) |
|
|
| station_name <- if ("name" %in% names(st_row)) as.character(st_row$name[1]) else "Unknown" |
| station_id <- if ("id" %in% names(st_row)) as.character(st_row$id[1]) else "N/A" |
| station_wmo <- if ("id_9" %in% names(st_row)) as.character(st_row$id_9[1]) else "N/A" |
| station_type <- if ("station_type" %in% names(st_row)) as.character(st_row$station_type[1]) else "Unknown" |
| params_summary <- if ("params_summary" %in% names(st_row)) as.character(st_row$params_summary[1]) else "Unknown" |
| elevation <- if ("elevation" %in% names(st_row)) as.character(st_row$elevation[1]) else "" |
| elev_fmt <- if (is.na(elevation) || elevation == "") "N/A" else paste0(elevation, " m") |
|
|
| paste0( |
| "<div style='font-size:16px; max-width: 350px;'>", |
| "<b>", htmltools::htmlEscape(station_name), "</b><br>", |
| "<span style='font-size:80%; color:#555;'>Code: ", htmltools::htmlEscape(station_id), "</span><br>", |
| "<span style='font-size:80%; color:#555;'>WMO ID: ", htmltools::htmlEscape(station_wmo), "</span><br>", |
| "<span style='font-size:80%; color:#555;'>Type: ", htmltools::htmlEscape(station_type), "</span><br>", |
| "<span style='font-size:80%; color:#555;'>Parameters (", resolution_label, "): ", htmltools::htmlEscape(params_summary), "</span><br>", |
| "<span style='font-size:80%; color:#555;'>Active: ", date_range, "</span><br>", |
| "<span style='font-size:80%; color:#555;'>Elevation: ", elev_fmt, "</span>", |
| "</div>" |
| ) |
| } |
|
|
| filtered_stations <- reactive({ |
| req(stations(), input$date_range) |
|
|
| st_data <- stations() |
| res <- resolution_key() |
| user_start <- input$date_range[1] |
| user_end <- input$date_range[2] |
| res_start_col <- paste0("date_start_", res) |
| res_end_col <- paste0("date_end_", res) |
|
|
| if (!res_end_col %in% names(st_data)) st_data[[res_end_col]] <- st_data$date_end |
| if (!"date_end_hourly" %in% names(st_data)) st_data$date_end_hourly <- st_data$date_end |
| if (!"date_end_daily" %in% names(st_data)) st_data$date_end_daily <- st_data$date_end |
| if (!"date_end_monthly" %in% names(st_data)) st_data$date_end_monthly <- st_data$date_end |
|
|
| st_data <- st_data %>% |
| filter( |
| !is.na(!!sym(res_start_col)), |
| !!sym(res_start_col) <= user_end, |
| is.na(!!sym(res_end_col)) | !!sym(res_end_col) >= user_start |
| ) |
|
|
| if (!"data_order" %in% names(st_data)) st_data$data_order <- NA_character_ |
| data_order_trim <- tolower(trimws(st_data$data_order)) |
| params_by_data_order <- params_by_resolution[[res]] |
| params_by_station_type <- c( |
| Synoptic = params_by_data_order[["synop"]], |
| Climatological = params_by_data_order[["klimat"]], |
| Precipitation = params_by_data_order[["opad"]] |
| ) |
|
|
| st_data$params_summary <- unname(params_by_data_order[data_order_trim]) |
| missing_params <- is.na(st_data$params_summary) | st_data$params_summary == "" |
| st_data$params_summary[missing_params] <- unname(params_by_station_type[st_data$station_type[missing_params]]) |
| st_data$params_summary[is.na(st_data$params_summary) | st_data$params_summary == ""] <- "Unknown" |
| st_data$station_uid <- paste(st_data$id, st_data$name, sep = "|") |
|
|
| st_data |
| }) |
|
|
| |
| observe({ |
| df <- filtered_stations() |
| |
| choices <- setNames(df$station_uid, paste0(df$name, " (", df$id, ")")) |
| updateSelectizeInput(session, "station_selector", |
| choices = c("All" = "", choices), |
| server = TRUE |
| ) |
| }) |
|
|
| |
| observeEvent(input$station_selector, { |
| uid <- input$station_selector |
| if (is.null(uid) || uid == "") { |
| return() |
| } |
|
|
| current_station_uid(uid) |
| station_fetch_error(NULL) |
|
|
| st <- filtered_stations() %>% |
| filter(station_uid == uid) %>% |
| slice(1) |
| if (nrow(st) > 0) { |
| highlight_selected_station(st, move_map = TRUE) |
| |
| broadcast_state() |
| } |
| }) |
|
|
| selected_station <- reactive({ |
| uid <- current_station_uid() |
| req(uid) |
| parsed <- parse_station_uid(uid) |
| st <- stations() |
| if (is.na(parsed$id) || parsed$id == "") { |
| return(NULL) |
| } |
|
|
| if (!is.na(parsed$name) && parsed$name != "") { |
| row <- st %>% |
| filter(id == parsed$id, name == parsed$name) %>% |
| slice(1) |
| if (nrow(row) > 0) { |
| return(row) |
| } |
| } |
| row <- st %>% |
| filter(id == parsed$id) %>% |
| slice(1) |
| if (nrow(row) == 0) { |
| return(NULL) |
| } |
| row |
| }) |
|
|
| highlight_selected_station <- function(st_row, move_map = TRUE) { |
| req(nrow(st_row) > 0) |
|
|
| lon_val <- suppressWarnings(as.numeric(st_row$lon[1])) |
| lat_val <- suppressWarnings(as.numeric(st_row$lat[1])) |
| if (is.na(lon_val) || is.na(lat_val)) { |
| return(invisible(NULL)) |
| } |
|
|
| popup_html <- NULL |
| if ("popup_content" %in% names(st_row)) { |
| popup_candidate <- as.character(st_row$popup_content[1]) |
| if (!is.na(popup_candidate) && nzchar(popup_candidate)) { |
| popup_html <- popup_candidate |
| } |
| } |
| if (is.null(popup_html)) { |
| popup_html <- build_station_popup(st_row, res = isolate(resolution_key())) |
| } |
|
|
| highlight_df <- data.frame( |
| popup_content = popup_html, |
| lon = lon_val, |
| lat = lat_val, |
| stringsAsFactors = FALSE |
| ) |
| highlight_sf <- sf::st_as_sf(highlight_df, coords = c("lon", "lat"), crs = 4326) |
|
|
| proxy <- maplibre_proxy("map") |
| if (move_map) { |
| proxy <- proxy %>% fly_to(center = c(lon_val, lat_val), zoom = 8) |
| } |
|
|
| proxy %>% |
| clear_layer("selected-highlight") %>% |
| add_circle_layer( |
| id = "selected-highlight", |
| source = highlight_sf, |
| circle_color = "#e53935", |
| circle_radius = 10, |
| circle_stroke_color = "#b71c1c", |
| circle_stroke_width = 2, |
| circle_opacity = 0.45, |
| tooltip = get_column("popup_content") |
| ) |
| } |
|
|
| output$station_count <- renderText({ |
| req(filtered_stations()) |
| paste(nrow(filtered_stations()), "stations displayed.") |
| }) |
|
|
| |
| output$map <- renderMaplibre({ |
| maplibre( |
| style = ofm_positron_style, |
| center = c(19.1451, 51.9194), |
| zoom = 5.5 |
| ) %>% |
| add_navigation_control(show_compass = FALSE, visualize_pitch = FALSE, position = "top-left") |
| }) |
|
|
| |
| observeEvent(input$zoom_home, { |
| maplibre_proxy("map") %>% |
| fly_to(center = c(19.1451, 51.9194), zoom = 5.5) |
| }) |
|
|
| |
| label_layer_ids <- c( |
| |
| "waterway_line_label", "water_name_point_label", "water_name_line_label", |
| "highway-name-path", "highway-name-minor", "highway-name-major", |
| "highway-shield-non-us", "highway-shield-us-interstate", "road_shield_us", |
| "airport", "label_other", "label_village", "label_town", "label_state", |
| "label_city", "label_city_capital", "label_country_3", "label_country_2", "label_country_1", |
| |
| "road_oneway", "road_oneway_opposite", "poi_r20", "poi_r7", "poi_r1", "poi_transit", |
| |
| "waterway-line-label", "water-name-point-label", "water-name-line-label", |
| "highway-shield-non-us", "highway-shield-us-interstate", "road-shield-us", |
| "label-other", "label-village", "label-town", "label-state", |
| "label-city", "label-city-capital", "label-country-3", "label-country-2", "label-country-1", |
| |
| "place_villages", "place_town", "place_country_2", "place_country_1", |
| "place_state", "place_continent", "place_city_r6", "place_city_r5", |
| "place_city_dot_r7", "place_city_dot_r4", "place_city_dot_r2", "place_city_dot_z7", |
| "place_capital_dot_z7", "place_capital", "roadname_minor", "roadname_sec", |
| "roadname_pri", "roadname_major", "motorway_name", "watername_ocean", |
| "watername_sea", "watername_lake", "watername_lake_line", "poi_stadium", |
| "poi_park", "poi_zoo", "airport_label", "country-label", "state-label", |
| "settlement-major-label", "settlement-minor-label", "settlement-subdivision-label", |
| "road-label", "waterway-label", "natural-point-label", "poi-label", "airport-label" |
| ) |
|
|
| |
| |
| non_label_layer_ids <- c( |
| "background", "park", "water", "landcover_ice_shelf", "landcover_glacier", |
| "landuse_residential", "landcover_wood", "waterway", "building", |
| "tunnel_motorway_casing", "tunnel_motorway_inner", "aeroway-taxiway", |
| "aeroway-runway-casing", "aeroway-area", "aeroway-runway", |
| "road_area_pier", "road_pier", "highway_path", "highway_minor", |
| "highway_major_casing", "highway_major_inner", "highway_major_subtle", |
| "highway_motorway_casing", "highway_motorway_inner", "highway_motorway_subtle", |
| "railway_transit", "railway_transit_dashline", "railway_service", |
| "railway_service_dashline", "railway", "railway_dashline", |
| "highway_motorway_bridge_casing", "highway_motorway_bridge_inner", |
| "boundary_3", "boundary_2", "boundary_disputed" |
| ) |
|
|
| apply_label_visibility <- function(proxy, show_labels) { |
| visibility <- if (isTRUE(show_labels)) "visible" else "none" |
| for (layer_id in label_layer_ids) { |
| tryCatch( |
| { |
| proxy %>% set_layout_property(layer_id, "visibility", visibility) |
| }, |
| error = function(e) { |
| |
| } |
| ) |
| } |
| } |
|
|
| observeEvent(input$basemap, |
| { |
| proxy <- maplibre_proxy("map") |
| basemap <- input$basemap |
|
|
| |
| old_layers <- isolate(current_raster_layers()) |
| if (length(old_layers) > 0) { |
| for (layer_id in old_layers) { |
| proxy %>% clear_layer(layer_id) |
| } |
| current_raster_layers(character(0)) |
| } |
|
|
| if (basemap %in% c("ofm_positron", "ofm_bright")) { |
| |
| style_url <- switch(basemap, |
| "ofm_positron" = ofm_positron_style, |
| "ofm_bright" = ofm_bright_style |
| ) |
|
|
| proxy %>% |
| set_style(style_url, preserve_layers = FALSE) |
|
|
| |
| stations_before_id("waterway_line_label") |
|
|
| current_session <- shiny::getDefaultReactiveDomain() |
| selected_basemap <- basemap |
|
|
| later::later(function() { |
| shiny::withReactiveDomain(current_session, { |
| |
| current_basemap <- isolate(input$basemap) |
| if (current_basemap != selected_basemap) { |
| return() |
| } |
|
|
| |
| apply_label_visibility(maplibre_proxy("map"), isolate(input$show_labels)) |
| style_change_trigger(isolate(style_change_trigger()) + 1) |
| }) |
| }, delay = 0.5) |
| } else if (basemap == "sentinel") { |
| |
| |
| proxy %>% |
| set_style(ofm_positron_style, preserve_layers = FALSE) |
|
|
| current_session <- shiny::getDefaultReactiveDomain() |
| selected_basemap <- basemap |
|
|
| later::later(function() { |
| shiny::withReactiveDomain(current_session, { |
| current_basemap <- isolate(input$basemap) |
| if (current_basemap != selected_basemap) { |
| return() |
| } |
|
|
| unique_suffix <- as.numeric(Sys.time()) * 1000 |
| source_id <- paste0("sentinel_source_", unique_suffix) |
| layer_id <- paste0("sentinel_layer_", unique_suffix) |
|
|
| |
| maplibre_proxy("map") %>% |
| add_raster_source(id = source_id, tiles = sentinel_url, tileSize = 256, attribution = sentinel_attribution) %>% |
| add_layer( |
| id = layer_id, |
| type = "raster", |
| source = source_id, |
| paint = list("raster-opacity" = 1), |
| before_id = "background" |
| ) |
|
|
| |
| for (layer_id_kill in non_label_layer_ids) { |
| tryCatch( |
| { |
| maplibre_proxy("map") %>% set_layout_property(layer_id_kill, "visibility", "none") |
| }, |
| error = function(e) { |
| |
| } |
| ) |
| } |
|
|
| |
| apply_label_visibility(maplibre_proxy("map"), isolate(input$show_labels)) |
|
|
| |
| stations_before_id("waterway_line_label") |
| current_raster_layers(c(layer_id)) |
| style_change_trigger(isolate(style_change_trigger()) + 1) |
| }) |
| }, delay = 0.5) |
| } |
| }, |
| ignoreInit = TRUE |
| ) |
|
|
| |
| check_and_limit_dates <- function(start, end, resolution, anchor = "end") { |
| limit_days <- Inf |
| limit_msg <- "" |
|
|
| if (resolution == "hourly") { |
| limit_days <- 93 |
| limit_msg <- "3 months" |
| } else if (resolution == "daily") { |
| limit_days <- 366 |
| limit_msg <- "1 year" |
| } else if (resolution == "monthly") { |
| limit_days <- 2191 |
| limit_msg <- "6 years" |
| } |
|
|
| diff <- as.numeric(end - start) |
| if (diff > limit_days) { |
| new_start <- start |
| new_end <- end |
|
|
| if (anchor == "start") { |
| new_end <- start + limit_days |
| } else { |
| new_start <- end - limit_days |
| } |
| return(list(start = new_start, end = new_end, limited = TRUE, msg = limit_msg)) |
| } |
| return(list(start = start, end = end, limited = FALSE)) |
| } |
|
|
| |
| observeEvent(input$temporal_resolution, |
| { |
| req(input$temporal_resolution) |
| req(input$date_range) |
|
|
| start <- as.Date(input$date_range[1]) |
| end <- as.Date(input$date_range[2]) |
| res_val <- input$temporal_resolution |
|
|
| if (res_val == "monthly") { |
| |
| new_start <- end - 365 * 6 |
| res <- list(start = new_start, end = end, limited = TRUE, msg = "6 years") |
| } else { |
| res <- check_and_limit_dates(start, end, res_val, anchor = "end") |
| } |
|
|
| if (res$limited) { |
| updateDateRangeInput(session, "date_range", start = res$start, end = res$end) |
| showNotification(paste0("Range adjusted to ", res$msg, " for ", res_val, " resolution."), type = "warning") |
| prev_dates(c(res$start, res$end)) |
| } |
| }, |
| ignoreInit = TRUE |
| ) |
|
|
| |
| observeEvent(input$date_range, |
| { |
| req(input$date_range) |
| req(input$temporal_resolution) |
|
|
| current_dates <- input$date_range |
| current_start <- as.Date(current_dates[1]) |
| current_end <- as.Date(current_dates[2]) |
|
|
| last_dates <- prev_dates() |
|
|
| anchor <- "end" |
| if (!is.null(last_dates)) { |
| last_start <- as.Date(last_dates[1]) |
| if (current_start != last_start) { |
| anchor <- "start" |
| } |
| } |
|
|
| res <- check_and_limit_dates(current_start, current_end, input$temporal_resolution, anchor = anchor) |
|
|
| if (res$limited) { |
| updateDateRangeInput(session, "date_range", start = res$start, end = res$end) |
| showNotification(paste0("Limit exceeded. Adjusted to ", res$msg, "."), type = "warning") |
| prev_dates(c(res$start, res$end)) |
| } else { |
| prev_dates(current_dates) |
| } |
| }, |
| ignoreInit = TRUE |
| ) |
|
|
| |
| observeEvent(input$show_labels, |
| { |
| apply_label_visibility(maplibre_proxy("map"), input$show_labels) |
| }, |
| ignoreInit = TRUE |
| ) |
|
|
| |
| observe({ |
| req(filtered_stations()) |
| style_change_trigger() |
|
|
| st_data <- filtered_stations() |
| res <- resolution_key() |
| resolution_label <- switch(res, |
| hourly = "Hourly", |
| daily = "Daily", |
| monthly = "Monthly", |
| "Daily" |
| ) |
|
|
| st_data$popup_content <- vapply( |
| seq_len(nrow(st_data)), |
| function(i) build_station_popup(st_data[i, , drop = FALSE], res = res), |
| FUN.VALUE = character(1) |
| ) |
|
|
| st_sf <- st_as_sf(st_data, coords = c("lon", "lat"), crs = 4326) |
| st_sf$circle_color <- "navy" |
| st_sf$circle_radius <- 6 |
| st_sf$circle_stroke_width <- 0 |
| st_sf$circle_opacity <- 0.7 |
| before_id_value <- stations_before_id() |
|
|
| current_session <- shiny::getDefaultReactiveDomain() |
| later::later(function() { |
| shiny::withReactiveDomain(current_session, { |
| maplibre_proxy("map") %>% |
| clear_layer("stations_layer") %>% |
| add_circle_layer( |
| id = "stations_layer", |
| source = st_sf, |
| circle_color = get_column("circle_color"), |
| circle_radius = get_column("circle_radius"), |
| circle_stroke_width = get_column("circle_stroke_width"), |
| circle_opacity = 0.7, |
| tooltip = get_column("popup_content"), |
| before_id = before_id_value |
| ) |
| }) |
| }, delay = 1.2) |
|
|
| selected_uid <- isolate(current_station_uid()) |
| if (!is.null(selected_uid) && selected_uid != "") { |
| sel <- st_data %>% |
| filter(station_uid == selected_uid) %>% |
| slice(1) |
| if (nrow(sel) > 0) { |
| later::later(function() { |
| shiny::withReactiveDomain(current_session, { |
| highlight_selected_station(sel, move_map = FALSE) |
| }) |
| }, delay = 1.25) |
| } else { |
| later::later(function() { |
| shiny::withReactiveDomain(current_session, { |
| maplibre_proxy("map") %>% clear_layer("selected-highlight") |
| }) |
| }, delay = 1.25) |
| } |
| } |
| }) |
|
|
| |
| observeEvent(input$map_feature_click, { |
| clicked <- input$map_feature_click |
| if (is.null(clicked)) { |
| return() |
| } |
|
|
| layer_name <- clicked$layer_id |
| if (is.null(layer_name)) layer_name <- clicked$layer |
| if (is.null(layer_name) || layer_name != "stations_layer") { |
| return() |
| } |
|
|
| props <- clicked$properties |
| if (is.null(props)) { |
| return() |
| } |
|
|
| uid <- props$station_uid |
| if (is.null(uid) || uid == "") { |
| id_val <- props$id |
| name_val <- props$name |
| if (!is.null(id_val) && !is.null(name_val)) { |
| uid <- paste0(id_val, "|", name_val) |
| } else if (!is.null(id_val)) { |
| uid <- as.character(id_val) |
| } |
| } |
| if (is.null(uid) || uid == "") { |
| return() |
| } |
|
|
| uid <- as.character(uid)[1] |
| current_station_uid(uid) |
| station_fetch_error(NULL) |
|
|
| st <- filtered_stations() %>% |
| filter(station_uid == uid) %>% |
| slice(1) |
| if (nrow(st) > 0) { |
| highlight_selected_station(st, move_map = TRUE) |
| broadcast_state() |
| } |
| }) |
|
|
| |
| output$stations_table <- DT::renderDataTable({ |
| req(filtered_stations(), input$temporal_resolution) |
| st_data <- filtered_stations() |
| resolution_col <- paste0("date_start_", resolution_key()) |
| end_col <- paste0("date_end_", resolution_key()) |
| if (!end_col %in% names(st_data)) st_data[[end_col]] <- st_data$date_end |
|
|
| display_df <- st_data %>% |
| transmute( |
| UID = station_uid, |
| ID = id, |
| Name = name, |
| `WMO ID` = id_9, |
| Type = station_type, |
| Parameters = params_summary, |
| Lat = round(lat, 4), |
| Lon = round(lon, 4), |
| `Elevation (m)` = elevation, |
| `Data Start` = as.character(!!sym(resolution_col)), |
| `Data End` = ifelse(is.na(!!sym(end_col)), "Present", as.character(!!sym(end_col))) |
| ) |
|
|
| DT::datatable( |
| display_df, |
| selection = "none", |
| rownames = FALSE, |
| options = list( |
| pageLength = 25, |
| scrollX = TRUE, |
| dom = "frtip", |
| order = list(list(2, "asc")), |
| columnDefs = list(list(visible = FALSE, targets = 0)) |
| ), |
| class = "compact stripe hover", |
| callback = DT::JS(" |
| table.on('dblclick', 'tr', function() { |
| var rowData = table.row(this).data(); |
| if (rowData !== undefined && rowData !== null) { |
| Shiny.setInputValue('table_station_dblclick', rowData[0], {priority: 'event'}); |
| } |
| }); |
| ") |
| ) |
| }) |
|
|
| observeEvent(input$table_station_dblclick, { |
| uid <- input$table_station_dblclick |
| req(uid) |
|
|
| uid <- as.character(uid)[1] |
| current_station_uid(uid) |
| station_fetch_error(NULL) |
|
|
| st <- filtered_stations() %>% |
| filter(station_uid == uid) %>% |
| slice(1) |
| if (nrow(st) > 0) { |
| highlight_selected_station(st, move_map = TRUE) |
| broadcast_state(view_override = "dashboard-plots") |
| } |
| }) |
|
|
| reset_fetch <- function() { |
| fetch_stage(0) |
| fetch_current_token(as.numeric(Sys.time())) |
| session$sendCustomMessage("unfreezeUI", list()) |
| } |
|
|
| |
| observeEvent(input$cancel_loading, { |
| loading_status(FALSE) |
| station_fetching(FALSE) |
| reset_fetch() |
| showNotification("Loading cancelled by user.", type = "warning") |
| }) |
|
|
| |
| observeEvent(list(current_station_uid(), input$temporal_resolution, input$date_range), |
| { |
| req(url_initialized()) |
| uid <- current_station_uid() |
| req(uid, input$date_range) |
|
|
| st <- selected_station() |
| req(!is.null(st), nrow(st) > 0) |
|
|
| station_data(NULL) |
| station_fetching(TRUE) |
| loading_status(TRUE) |
| station_fetch_error(NULL) |
|
|
| |
| broadcast_state() |
|
|
| start_date <- as.Date(input$date_range[1]) |
| end_date <- as.Date(input$date_range[2]) |
| res <- resolution_key() |
|
|
| session$sendCustomMessage("freezeUI", list( |
| text = paste0("Initializing fetch for ", st$name[1], "..."), |
| station = st$name[1] |
| )) |
|
|
| |
| filesQueue <- imgw_fetch_station_data( |
| station_id = st$id[1], |
| resolution = res, |
| start_date = start_date, |
| end_date = end_date, |
| data_order = st$data_order[1], |
| station_type = st$station_type[1], |
| station_id_9 = st$id_9[1] |
| ) |
|
|
| if (is.null(filesQueue) || nrow(filesQueue) == 0) { |
| if (isTRUE(attr(filesQueue, "server_unreachable"))) { |
| station_fetch_error("The IMGW data server (danepubliczne.imgw.pl) is currently unavailable. Please try again later.") |
| } else { |
| station_fetch_error("No downloadable IMGW files were found for this station within the selected date range.") |
| } |
| station_fetching(FALSE) |
| loading_status(FALSE) |
| reset_fetch() |
| updateNavbarPage(session, "main_nav", selected = "Dashboard") |
| return() |
| } |
|
|
| fetch_queue(filesQueue) |
| fetch_queue_idx(1) |
| parsed_data_list(list()) |
| fetch_current_token(as.numeric(Sys.time())) |
|
|
| fetch_stage(2) |
| }, |
| ignoreInit = TRUE |
| ) |
|
|
| |
| observe({ |
| req(fetch_stage() == 2) |
| idx <- fetch_queue_idx() |
| q <- fetch_queue() |
|
|
| if (idx > nrow(q)) { |
| fetch_stage(6) |
| } else { |
| |
| item <- q[idx, ] |
| msg <- sprintf("Fetching %s (%d/%d)...", basename(item$url), idx, nrow(q)) |
|
|
| st <- selected_station() |
| session$sendCustomMessage("freezeUI", list( |
| text = msg, |
| station = st$name[1] |
| )) |
|
|
| fetch_stage(4) |
| } |
| }) |
|
|
| |
| observe({ |
| req(fetch_stage() == 4) |
| idx <- fetch_queue_idx() |
| q <- fetch_queue() |
| item <- q[idx, ] |
| token <- fetch_current_token() |
| current_session <- shiny::getDefaultReactiveDomain() |
| st <- selected_station() |
| res <- resolution_key() |
|
|
| |
| later::later(function() { |
| shiny::withReactiveDomain(current_session, { |
| if (isolate(fetch_current_token()) != token) { |
| return() |
| } |
|
|
| parsed_df <- tryCatch( |
| { |
| imgw_read_station_zip_url( |
| zip_url = item$url, |
| resolution = res, |
| source_type = item$source, |
| station_id = st$id[1], |
| station_id_9 = st$id_9[1] |
| ) |
| }, |
| error = function(e) { |
| message("Error fetching data piece: ", conditionMessage(e)) |
| NULL |
| } |
| ) |
|
|
| if (!is.null(parsed_df) && nrow(parsed_df) > 0) { |
| p_list <- isolate(parsed_data_list()) |
| p_list[[length(p_list) + 1]] <- parsed_df |
| parsed_data_list(p_list) |
| } |
|
|
| |
| fetch_queue_idx(idx + 1) |
| fetch_stage(2) |
| }) |
| }, delay = 0.05) |
| }) |
|
|
| |
| observe({ |
| req(fetch_stage() == 6) |
| token <- fetch_current_token() |
| current_session <- shiny::getDefaultReactiveDomain() |
| st <- selected_station() |
| start_date <- as.Date(input$date_range[1]) |
| end_date <- as.Date(input$date_range[2]) |
| res <- resolution_key() |
|
|
| session$sendCustomMessage("freezeUI", list( |
| text = "Merging datasets...", |
| station = st$name[1] |
| )) |
|
|
| later::later(function() { |
| shiny::withReactiveDomain(current_session, { |
| if (isolate(fetch_current_token()) != token) { |
| return() |
| } |
| p_list <- isolate(parsed_data_list()) |
|
|
| merged_df <- tryCatch( |
| { |
| imgw_process_downloaded_parts( |
| parts = p_list, |
| station_id_value = st$id[1], |
| station_id_9_value = st$id_9[1], |
| resolution = res, |
| start_date = start_date, |
| end_date = end_date, |
| data_order = st$data_order[1], |
| station_type = st$station_type[1] |
| ) |
| }, |
| error = function(e) { |
| message("Error merging chunks: ", conditionMessage(e)) |
| NULL |
| } |
| ) |
|
|
| if (is.null(merged_df) || nrow(merged_df) == 0) { |
| station_fetch_error("No valid data rows found after merging files within this date range.") |
| station_data(NULL) |
| } else { |
| station_data(merged_df) |
| } |
|
|
| station_fetching(FALSE) |
| loading_status(FALSE) |
| fetch_stage(0) |
| fetch_current_token(as.numeric(Sys.time())) |
|
|
| if (!is.null(merged_df) && nrow(merged_df) > 0) { |
| |
| session$sendCustomMessage("freezeUI", list( |
| text = "Plotting data...", |
| station = st$name[1] |
| )) |
|
|
| |
| session$onFlushed(function() { |
| session$sendCustomMessage("waitForPlots", list()) |
| }, once = TRUE) |
| } else { |
| session$sendCustomMessage("unfreezeUI", list()) |
| } |
|
|
| |
| updateNavbarPage(session, "main_nav", selected = "Dashboard") |
| broadcast_state() |
| }) |
| }, delay = 0.05) |
| }) |
|
|
| |
| observeEvent(input$main_nav, { |
| req(url_initialized()) |
| broadcast_state() |
| }) |
| observeEvent(input$dashboard_subtabs, { |
| req(url_initialized()) |
| broadcast_state() |
| }) |
|
|
| |
| output$dashboard_station_header <- renderUI({ |
| st <- selected_station() |
| if (is.null(st)) { |
| return(NULL) |
| } |
|
|
| loaded <- station_data() |
| |
| res_label <- switch(resolution_key(), |
| hourly = "Hourly", |
| daily = "Daily", |
| monthly = "Monthly", |
| "Daily" |
| ) |
| res_class <- if (res_label == "Hourly") "bg-primary" else "bg-success" |
|
|
| |
| if (is.null(loaded) || nrow(loaded) == 0) { |
| if (isTRUE(station_fetching())) { |
| dates_text <- "Loading..." |
| } else { |
| dates_text <- "No data loaded" |
| } |
| } else { |
| dates_text <- paste(min(loaded$date, na.rm = TRUE), "to", max(loaded$date, na.rm = TRUE)) |
| dates_text <- paste0(dates_text, " (", nrow(loaded), " rows)") |
| } |
|
|
| |
| val_safe <- function(x, suffix = "") { |
| if (is.null(x) || is.na(x) || x == "") { |
| return("-") |
| } |
| paste0(x, suffix) |
| } |
|
|
| |
| card( |
| style = "margin-bottom: 20px; border-left: 5px solid #007bff;", |
| card_body( |
| padding = 15, |
| layout_columns( |
| fill = FALSE, |
| |
| div( |
| strong("Station"), br(), |
| span(st$name[1], style = "font-size: 1.1rem;"), br(), |
| tags$small(class = "text-muted", paste("ID:", st$id[1], "| WMO:", val_safe(st$id_9[1]))) |
| ), |
| |
| div( |
| strong("Location"), br(), |
| span(paste0(round(st$lat[1], 4), "°N, ", round(st$lon[1], 4), "°E")), br(), |
| tags$small(class = "text-muted", paste("Type:", st$station_type[1])) |
| ), |
| |
| div( |
| strong("Technical"), br(), |
| span(val_safe(st$elevation[1], " m")), br(), |
| span(class = paste("badge", res_class), res_label) |
| ), |
| |
| div( |
| strong("Data Selection"), br(), |
| span(dates_text) |
| ), |
| |
| div( |
| class = "d-flex align-items-center justify-content-end", |
| downloadButton( |
| "downloadData", |
| label = "Export Excel", |
| class = "btn-sm btn-primary", |
| icon = icon("file-excel") |
| ) |
| ) |
| ) |
| ) |
| ) |
| }) |
|
|
| |
| get_plot_df <- reactive({ |
| df <- station_data() |
| req(df) |
|
|
| |
| df_translated <- translate_imgw_columns(df) |
|
|
| |
| if (!"DateTime" %in% names(df_translated)) { |
| if ("Date/Time" %in% names(df_translated)) { |
| df_translated$DateTime <- df_translated$`Date/Time` |
| } else if ("Date" %in% names(df_translated)) { |
| df_translated$DateTime <- as.POSIXct(df_translated$Date, tz = "UTC") |
| } else if ("Year" %in% names(df_translated) && "Month" %in% names(df_translated) && "Day" %in% names(df_translated) && "Hour" %in% names(df_translated)) { |
| df_translated$DateTime <- as.POSIXct(paste(df_translated$Year, df_translated$Month, df_translated$Day, df_translated$Hour, sep = "-"), format = "%Y-%m-%d-%H", tz = "UTC") |
| } else if ("Year" %in% names(df_translated) && "Month" %in% names(df_translated) && "Day" %in% names(df_translated)) { |
| df_translated$DateTime <- as.POSIXct(paste(df_translated$Year, df_translated$Month, df_translated$Day, sep = "-"), format = "%Y-%m-%d", tz = "UTC") |
| } else if ("Year" %in% names(df_translated) && "Month" %in% names(df_translated)) { |
| df_translated$DateTime <- as.POSIXct(paste(df_translated$Year, df_translated$Month, "01", sep = "-"), format = "%Y-%m-%d", tz = "UTC") |
| } |
| } |
|
|
| df_translated |
| }) |
|
|
| output$dashboard_plots_ui <- renderUI({ |
| df <- get_plot_df() |
| req(df) |
|
|
| plot_list <- tagList() |
|
|
| |
| temp_cols <- c("Temp [°C]", "Mean Daily Temp [°C]", "Mean Monthly Temp [°C]", "Max Temp [°C]", "Min Temp [°C]") |
| has_temp <- any(temp_cols %in% names(df)) && any(vapply(intersect(temp_cols, names(df)), function(col) any(!is.na(df[[col]])), logical(1))) |
| if (has_temp) { |
| plot_list <- tagList(plot_list, div(class = "col-12 col-lg-6", plotlyOutput("temperature_plot", height = "320px"))) |
| } |
|
|
| |
| precip_col <- if ("Precip 6h [mm]" %in% names(df)) "Precip 6h [mm]" else if ("Daily Precip [mm]" %in% names(df)) "Daily Precip [mm]" else if ("Monthly Precip [mm]" %in% names(df)) "Monthly Precip [mm]" else NULL |
| has_precip <- !is.null(precip_col) && !all(is.na(df[[precip_col]])) |
| if (has_precip) { |
| plot_list <- tagList(plot_list, div(class = "col-12 col-lg-6", plotlyOutput("precipitation_plot", height = "320px"))) |
| } |
|
|
| |
| wind_col <- if ("Wind Speed [m/s]" %in% names(df)) "Wind Speed [m/s]" else if ("Mean Wind Speed [m/s]" %in% names(df)) "Mean Wind Speed [m/s]" else NULL |
| has_wind <- !is.null(wind_col) && !all(is.na(df[[wind_col]])) |
| if (has_wind) { |
| plot_list <- tagList(plot_list, div(class = "col-12 col-lg-6", plotlyOutput("wind_plot", height = "320px"))) |
| } |
|
|
| |
| hum_col <- if ("Relative Humidity [%]" %in% names(df)) "Relative Humidity [%]" else if ("Mean Rel Humidity [%]" %in% names(df)) "Mean Rel Humidity [%]" else NULL |
| has_hum <- !is.null(hum_col) && !all(is.na(df[[hum_col]])) |
| if (has_hum) { |
| plot_list <- tagList(plot_list, div(class = "col-12 col-lg-6", plotlyOutput("humidity_plot", height = "320px"))) |
| } |
|
|
| |
| has_press <- any(c("Station Pressure [hPa]", "Sea Level Pressure [hPa]") %in% names(df) & !all(is.na(df[[if ("Station Pressure [hPa]" %in% names(df)) "Station Pressure [hPa]" else "Sea Level Pressure [hPa]"]]))) |
| if (has_press) { |
| plot_list <- tagList(plot_list, div(class = "col-12 col-lg-6", plotlyOutput("pressure_plot", height = "320px"))) |
| } |
|
|
| |
| sun_col <- if ("Sunshine [h]" %in% names(df)) "Sunshine [h]" else if ("Monthly Sunshine [h]" %in% names(df)) "Monthly Sunshine [h]" else NULL |
| has_sun <- !is.null(sun_col) && !all(is.na(df[[sun_col]])) |
| if (has_sun) { |
| plot_list <- tagList(plot_list, div(class = "col-12 col-lg-6", plotlyOutput("sunshine_plot", height = "320px"))) |
| } |
|
|
| |
| snow_col <- if ("Snow Depth [cm]" %in% names(df)) "Snow Depth [cm]" else if ("Fresh Snow Depth [cm]" %in% names(df)) "Fresh Snow Depth [cm]" else NULL |
| has_snow <- !is.null(snow_col) && !all(is.na(df[[snow_col]])) |
| if (has_snow) { |
| plot_list <- tagList(plot_list, div(class = "col-12 col-lg-6", plotlyOutput("snow_plot", height = "320px"))) |
| } |
|
|
| div(class = "row g-3", plot_list) |
| }) |
|
|
| |
| output$downloadData <- downloadHandler( |
| filename = function() { |
| st <- selected_station() |
| req(st) |
| res <- resolution_key() |
| sprintf("imgw_%s_%s_%s.xlsx", st$id, res, format(Sys.Date(), "%Y%m%d")) |
| }, |
| content = function(file) { |
| df <- station_data() |
| req(df) |
|
|
| |
| df_export <- translate_imgw_columns(df) |
|
|
| |
| |
| |
|
|
| writexl::write_xlsx(df_export, path = file) |
| } |
| ) |
|
|
|
|
| output$temperature_plot <- renderPlotly({ |
| create_temperature_plot_imgw(get_plot_df(), resolution_key()) |
| }) |
|
|
| output$precipitation_plot <- renderPlotly({ |
| create_precipitation_plot_imgw(get_plot_df(), resolution_key()) |
| }) |
|
|
| output$wind_plot <- renderPlotly({ |
| res <- resolution_key() |
| if (res == "hourly") { |
| create_wind_rose_imgw(get_plot_df(), res) |
| } else { |
| create_wind_plot_imgw(get_plot_df(), res) |
| } |
| }) |
|
|
| output$pressure_plot <- renderPlotly({ |
| create_pressure_plot_imgw(get_plot_df(), resolution_key()) |
| }) |
|
|
| output$humidity_plot <- renderPlotly({ |
| create_humidity_plot_imgw(get_plot_df(), resolution_key()) |
| }) |
|
|
| output$sunshine_plot <- renderPlotly({ |
| create_sunshine_plot_imgw(get_plot_df(), resolution_key()) |
| }) |
|
|
| output$snow_plot <- renderPlotly({ |
| create_snow_plot_imgw(get_plot_df(), resolution_key()) |
| }) |
|
|
| output$station_data_table <- DT::renderDataTable({ |
| df <- station_data() |
| req(df) |
|
|
| df_display <- as.data.frame(df, stringsAsFactors = FALSE) |
|
|
| |
| raw_cols <- names(df_display) |
| status_candidates <- raw_cols[grepl("^w+", raw_cols)] |
|
|
| for (scol in status_candidates) { |
| base1 <- sub("^w", "", scol) |
| base2 <- sub("^ww", "", scol) |
|
|
| base_var <- NULL |
| if (base1 %in% raw_cols && base1 != scol) { |
| base_var <- base1 |
| } else if (base2 %in% raw_cols && base2 != scol) { |
| base_var <- base2 |
| } else if (scol == "wwlgs" && "wlgs" %in% raw_cols) { |
| base_var <- "wlgs" |
| } |
|
|
| if (!is.null(base_var)) { |
| vals <- df_display[[base_var]] |
| flags <- df_display[[scol]] |
|
|
| formatted_vals <- vapply(seq_along(vals), function(i) { |
| v <- vals[i] |
| f <- flags[i] |
|
|
| v_is_empty <- is.na(v) || trimws(as.character(v)) == "" |
| f_is_empty <- is.na(f) || trimws(as.character(f)) == "" |
|
|
| if (v_is_empty && f_is_empty) { |
| return(NA_character_) |
| } |
|
|
| v_str <- if (v_is_empty) "" else as.character(v) |
|
|
| |
| if (!v_is_empty && base_var == "ropt") { |
| v_str <- switch(v_str, |
| "5" = "Dew / Frost / Rime / Fog", |
| "6" = "Rain / Drizzle", |
| "7" = "Sleet / Snow / Ice Pellets", |
| "8" = "Hail / Ice Grains", |
| "9" = "Mixed Sleet / Hail / Snow", |
| v_str |
| ) |
| } else if (!v_is_empty && base_var == "roop") { |
| v_str <- switch(toupper(v_str), |
| "S" = "Snow", |
| "W" = "Rain / Water", |
| "M" = "Mixed", |
| "L" = "Lód / Ice", |
| v_str |
| ) |
| } else if (!v_is_empty && base_var %in% c("rosw", "Dew Occurrence", "dzbl", "Lightning [0/1]", "dzps", "Snow Cover [0/1]", "Snow Cover")) { |
| if (v_str == "0") { |
| v_str <- "No" |
| } else if (v_str == "1") v_str <- "Yes" |
| } else if (!v_is_empty && base_var %in% c("sgr", "sgrn", "Ground State [code]", "Ground State")) { |
| v_str <- switch(toupper(v_str), |
| "Z" = "Frozen", |
| "R" = "Thawed", |
| v_str |
| ) |
| } else if (!v_is_empty && base_var %in% c("pksn", "Snow Depth [cm]")) { |
| if (v_str == "997") { |
| v_str <- "< 0.5" |
| } else if (v_str == "998") { |
| v_str <- "Patchy" |
| } else if (v_str == "999") v_str <- "Unmeasurable" |
| } else if (!v_is_empty && base_var %in% c("pogb", "pogu", "Current Weather [code]", "Past Weather [code]", "Current Weather", "Past Weather")) { |
| if (exists("WMO_WEATHER_CODES")) { |
| |
| num_key <- as.character(as.numeric(v_str)) |
| if (num_key %in% names(WMO_WEATHER_CODES)) { |
| v_str <- WMO_WEATHER_CODES[[num_key]] |
| } |
| } |
| } |
|
|
| if (!f_is_empty) { |
| f_val <- trimws(as.character(f)) |
| if (f_val == "9" && v_is_empty) { |
| |
| v_str <- "0" |
| f_html <- "" |
| } else { |
| f_display <- switch(f_val, |
| "8" = "no observations", |
| "9" = "0", |
| paste0("[", f_val, "]") |
| ) |
|
|
| |
| if (f_display == "0") { |
| if (v_str == "") v_str <- "0" |
| f_html <- "" |
| } else { |
| f_html <- paste0("<span class='text-muted' style='font-size:0.85em; margin-left:4px;' title='Quality Flag'>", f_display, "</span>") |
| } |
| } |
| } else { |
| f_html <- "" |
| } |
|
|
| paste0(v_str, f_html) |
| }, character(1)) |
|
|
| df_display[[base_var]] <- formatted_vals |
| df_display[[scol]] <- NULL |
| } |
| } |
|
|
| |
| for (col in c("ropt", "Precip Type 6h")) { |
| if (col %in% names(df_display)) { |
| df_display[[col]] <- vapply(df_display[[col]], function(v) { |
| if (is.na(v) || trimws(as.character(v)) == "") { |
| return("") |
| } |
| switch(trimws(as.character(v)), |
| "5" = "Dew / Frost / Rime / Fog", |
| "6" = "Rain / Drizzle", |
| "7" = "Sleet / Snow / Ice Pellets", |
| "8" = "Hail / Ice Grains", |
| "9" = "Mixed Sleet / Hail / Snow", |
| as.character(v) |
| ) |
| }, character(1)) |
| } |
| } |
|
|
| for (col in c("roop", "Precip Type")) { |
| if (col %in% names(df_display)) { |
| df_display[[col]] <- vapply(df_display[[col]], function(v) { |
| if (is.na(v) || trimws(as.character(v)) == "") { |
| return("") |
| } |
| switch(trimws(toupper(as.character(v))), |
| "S" = "Snow", |
| "W" = "Rain / Water", |
| "M" = "Mixed", |
| "L" = "Lód / Ice", |
| as.character(v) |
| ) |
| }, character(1)) |
| } |
| } |
|
|
| for (col in c("rosw", "Dew Occurrence", "dzbl", "Lightning [0/1]", "dzps", "Snow Cover [0/1]", "Snow Cover")) { |
| if (col %in% names(df_display)) { |
| df_display[[col]] <- vapply(df_display[[col]], function(v) { |
| if (is.na(v) || trimws(as.character(v)) == "") { |
| return("") |
| } |
| val <- trimws(as.character(v)) |
| if (val == "0") { |
| return("No") |
| } |
| if (val == "1") { |
| return("Yes") |
| } |
| return(val) |
| }, character(1)) |
| } |
| } |
|
|
| for (col in c("sgr", "sgrn", "Ground State [code]", "Ground State")) { |
| if (col %in% names(df_display)) { |
| df_display[[col]] <- vapply(df_display[[col]], function(v) { |
| if (is.na(v) || trimws(as.character(v)) == "") { |
| return("") |
| } |
| switch(trimws(toupper(as.character(v))), |
| "Z" = "Frozen", |
| "R" = "Thawed", |
| as.character(v) |
| ) |
| }, character(1)) |
| } |
| } |
|
|
| for (col in c("pksn", "Snow Depth [cm]")) { |
| if (col %in% names(df_display)) { |
| df_display[[col]] <- vapply(df_display[[col]], function(v) { |
| if (is.na(v) || trimws(as.character(v)) == "") { |
| return("") |
| } |
| val <- trimws(as.character(v)) |
| if (val == "997") { |
| return("< 0.5") |
| } |
| if (val == "998") { |
| return("Patchy") |
| } |
| if (val == "999") { |
| return("Unmeasurable") |
| } |
| return(val) |
| }, character(1)) |
| } |
| } |
|
|
| for (col in c("pogb", "pogu", "Current Weather [code]", "Past Weather [code]", "Current Weather", "Past Weather")) { |
| if (col %in% names(df_display)) { |
| df_display[[col]] <- vapply(df_display[[col]], function(v) { |
| if (is.na(v) || trimws(as.character(v)) == "") { |
| return("") |
| } |
| if (exists("WMO_WEATHER_CODES")) { |
| num_key <- as.character(as.numeric(trimws(as.character(v)))) |
| if (num_key %in% names(WMO_WEATHER_CODES)) { |
| return(WMO_WEATHER_CODES[[num_key]]) |
| } |
| } |
| return(as.character(v)) |
| }, character(1)) |
| } |
| } |
|
|
| |
| df_display <- translate_imgw_columns(df_display) |
|
|
| |
| cols_to_remove <- c( |
| "Station ID", "Station Name", "Year", "Month", "Day", "Hour", "Minute", |
| "station_id", "station_name", "year", "month", "day", "hour", "minute", |
| "Date", "date", "Source", "source", "Source File", "source_file", |
| "Resolution", "resolution", "Source Rank", "source_rank" |
| ) |
| df_display <- df_display[, !names(df_display) %in% cols_to_remove, drop = FALSE] |
|
|
| |
| empty_cols <- names(df_display)[vapply(df_display, function(x) { |
| if (is.character(x)) { |
| all(is.na(x) | x == "") |
| } else { |
| all(is.na(x)) |
| } |
| }, logical(1))] |
| if (length(empty_cols) > 0) { |
| df_display <- df_display[, !names(df_display) %in% empty_cols, drop = FALSE] |
| } |
|
|
| |
| status_cols <- grep("^Status:", names(df_display), value = TRUE) |
| if (length(status_cols) > 0) { |
| df_display <- df_display[, !names(df_display) %in% status_cols, drop = FALSE] |
| } |
|
|
| list_cols <- vapply(df_display, is.list, logical(1)) |
| if (any(list_cols)) { |
| df_display[list_cols] <- lapply(df_display[list_cols], function(x) { |
| vapply(x, function(v) paste(v, collapse = ", "), character(1)) |
| }) |
| } |
| if ("Date/Time" %in% names(df_display)) { |
| df_display$`Date/Time` <- format(df_display$`Date/Time`, "%Y-%m-%d %H:%M:%S") |
| } |
|
|
| |
| col_priority_patterns <- c( |
| "Date/Time", |
| |
| "Temp \\[", "Max Temp", "Min Temp", "Mean.*Temp", "Dew Point", |
| "Wet Bulb", |
| |
| "Precip", "Rain Duration", "Snow Duration", "Rain\\+Snow", |
| |
| "Station Pressure", "Sea Level Pressure", "Pressure Tend", "Pressure Change", |
| |
| "Relative Humidity", "Vapour Pressure", "Humidity Deficit", |
| |
| "Wind Speed", "Wind Dir", "Wind Gust", "Max Gust", "Gust Hour", "Gust Minute", |
| "Wind >=", |
| |
| "Sunshine", "Actinometry", |
| |
| "Snow Depth", "Snow Water", "Fresh Snow", "Snow Plot", "Snow Type", |
| "Snow Cover", |
| |
| "Cloud", "Visibility", "Auto Visibility", |
| |
| "Ground State", "Soil Temp", "Lower Isotherm", "Upper Isotherm", |
| |
| "Ice Indicator", "Ventilation", |
| |
| "Hail", "Fog", "Mist", "Rime", "Glaze", "Blizzard", "Haze", |
| "Thunder", "Dew", "Frost", "Lightning", |
| |
| "Current Weather", "Past Weather", "Precip Type" |
| ) |
|
|
| current_cols <- names(df_display) |
| ordered_cols <- character(0) |
| for (pat in col_priority_patterns) { |
| matches <- grep(pat, current_cols, ignore.case = TRUE, value = TRUE) |
| matches <- setdiff(matches, ordered_cols) |
| ordered_cols <- c(ordered_cols, matches) |
| } |
| remaining <- setdiff(current_cols, ordered_cols) |
| df_display <- df_display[, c(ordered_cols, remaining), drop = FALSE] |
|
|
| DT::datatable( |
| df_display, |
| escape = FALSE, |
| options = list( |
| pageLength = 25, |
| scrollX = TRUE, |
| dom = "frtip", |
| order = if ("Date/Time" %in% names(df_display)) list(list(which(names(df_display) == "Date/Time") - 1, "desc")) else list(list(0, "asc")) |
| ), |
| rownames = FALSE, |
| class = "compact stripe hover" |
| ) |
| }) |
|
|
| output$dashboard_content <- renderUI({ |
| uid <- current_station_uid() |
| if (is.null(uid) || uid == "") { |
| return( |
| tags$div( |
| style = "height: 560px; display: flex; align-items: center; justify-content: center; color: #999;", |
| "Select a station from the map or Stations Info table to load data." |
| ) |
| ) |
| } |
|
|
| if (isTRUE(station_fetching())) { |
| return( |
| tags$div( |
| style = "padding: 20px;", |
| uiOutput("dashboard_station_header"), |
| tags$div( |
| class = "alert alert-info d-flex align-items-center gap-2", |
| tags$div(class = "spinner-border spinner-border-sm", role = "status"), |
| tags$span(station_fetch_message()) |
| ) |
| ) |
| ) |
| } |
|
|
| err <- station_fetch_error() |
| if (!is.null(err) && nzchar(err)) { |
| return( |
| tags$div( |
| style = "padding: 20px;", |
| uiOutput("dashboard_station_header"), |
| tags$div(class = "alert alert-warning", err) |
| ) |
| ) |
| } |
|
|
| df <- station_data() |
| if (is.null(df) || nrow(df) == 0) { |
| return( |
| tags$div( |
| style = "padding: 20px;", |
| uiOutput("dashboard_station_header"), |
| tags$div(class = "alert alert-secondary", "No data loaded for the current selection.") |
| ) |
| ) |
| } |
|
|
| tagList( |
| uiOutput("dashboard_station_header"), |
| navset_card_pill( |
| id = "dashboard_subtabs", |
| nav_panel( |
| title = "Plots", |
| uiOutput("dashboard_plots_ui") |
| ), |
| nav_panel( |
| title = "Data", |
| div( |
| style = "margin-top: 10px;", |
| DT::dataTableOutput("station_data_table") |
| ) |
| ) |
| ) |
| ) |
| }) |
| } |
|
|