# app.R library(shiny) library(dplyr) # library(lubridate) # 不要なので削除 library(leaflet) set.seed(123) # ========================= # 1. ダミーデータ作成 # ========================= # 工事現場(北海道の適当な範囲でランダム) sites <- tibble::tibble( site_id = 1:5, site_name = paste0("工事現場", 1:5), city = c("札幌市", "札幌市", "旭川市", "函館市", "帯広市"), lat = runif(5, 42.0, 44.5), # 北海道っぽい緯度 lng = runif(5, 141.0, 143.5), # 北海道っぽい経度 start = as.Date("2025-12-01"), end = as.Date("2025-12-31"), required_guards = c(3, 4, 2, 5, 3) ) cities <- sites$city # 警備員(住所は市レベルで、対応可能期間は12月中でバラバラ) guards <- tibble::tibble( guard_id = 1:20, guard_name = paste0("警備員", 1:20), city = sample(cities, 20, replace = TRUE), lat = runif(20, 42.0, 44.5), lng = runif(20, 141.0, 143.5), available_from = sample(seq(as.Date("2025-12-01"), as.Date("2025-12-15"), by = "day"), 20, replace = TRUE), available_to = sample(seq(as.Date("2025-12-16"), as.Date("2025-12-31"), by = "day"), 20, replace = TRUE) ) %>% # from <= to になるように補正 mutate( tmp_min = pmin(available_from, available_to), tmp_max = pmax(available_from, available_to), available_from = tmp_min, available_to = tmp_max ) %>% select(-tmp_min, -tmp_max) # ========================= # 2. UI # ========================= ui <- fluidPage( titlePanel("北海道・工事現場 × 警備員マッチング(ダミーデータ)"), sidebarLayout( sidebarPanel( dateInput( "selected_date", "日付を選択", value = as.Date("2025-12-01"), min = as.Date("2025-12-01"), max = as.Date("2025-12-31") ), checkboxInput("show_guards", "警備員の位置も地図に表示", TRUE), width = 3 ), mainPanel( leafletOutput("map", height = 500), br(), h4("工事現場別マッチング状況(選択日ベース)"), tableOutput("summary"), width = 9 ) ) ) # ========================= # 3. Server # ========================= server <- function(input, output, session) { # 選択日ごとのデータ daily_data <- reactive({ d <- input$selected_date # その日に稼働中の現場(期間内) active_sites <- sites %>% filter(start <= d, end >= d) # その日に勤務可能な警備員 available_guards <- guards %>% filter(available_from <= d, available_to >= d) list( date = d, active_sites = active_sites, available_guards = available_guards ) }) # マッチング集計(ここでは「同じ市にいる警備員数」で集計) summary_tbl <- reactive({ dd <- daily_data() active_sites <- dd$active_sites available_guards <- dd$available_guards guards_by_city <- available_guards %>% count(city, name = "available_guards") active_sites %>% left_join(guards_by_city, by = "city") %>% mutate( available_guards = ifelse(is.na(available_guards), 0L, available_guards), shortage = pmax(required_guards - available_guards, 0L) ) %>% select( 現場ID = site_id, 現場名 = site_name, 市区町村 = city, 必要人数 = required_guards, "対応可能人数(同一市内)" = available_guards, "不足人数" = shortage ) }) # 地図描画 output$map <- renderLeaflet({ dd <- daily_data() # ベースマップ(北海道あたりを初期表示) m <- leaflet() %>% addTiles() %>% setView(lng = 142.0, lat = 43.0, zoom = 6) # 工事現場 if (nrow(dd$active_sites) > 0) { m <- m %>% addCircleMarkers( data = dd$active_sites, ~lng, ~lat, radius = 8, color = "blue", fillOpacity = 0.8, label = ~paste0(site_name, "(必要人数: ", required_guards, "人)"), group = "sites" ) } # 警備員 if (input$show_guards && nrow(dd$available_guards) > 0) { m <- m %>% addCircleMarkers( data = dd$available_guards, ~lng, ~lat, radius = 5, color = "red", fillOpacity = 0.7, label = ~paste0(guard_name, "(", city, "在住)"), group = "guards" ) } m %>% addLayersControl( overlayGroups = c("sites", "guards"), options = layersControlOptions(collapsed = FALSE) ) }) # 集計テーブル output$summary <- renderTable({ summary_tbl() }) } shinyApp(ui, server)