R leaflet + shiny でGFS のデータを可視化

# はじめに
[こちらの記事](https://qiita.com/ryamamoto0406/items/34f69e60ed9e33597f49)でR leaflet を用いて GFS のデータの可視化を行った。今回はこの知見をもとに、それをshiny で実装した。また、3時間ごとに予報値のデータがあるので、それをスライドバーから選べるようにした。

# GFS データの処理
基本的には[こちらの記事](https://qiita.com/ryamamoto0406/items/34f69e60ed9e33597f49)で実装したまんまである。各時刻ごとのラスターデータを作ってリストに格納していくだけ。

```R:GFS.R
library(raster)

################################################################################

#読み込むGFSデータの詳細
mode <- "3"
date <- "20210321"
time <- "0000"

grb2_dir <- "./data/GFS/GRIB2"
csv_dir <- "./data/GFS/CSV"

keywd <- "'TMP:surface'"
csv_key <- "tmp_surface"

#GFSデータを一つずつ処理
gfs_tmp_rs <- list()
for (i in 0:8) {
  j <- i + 1
  num <- 3*i
  
  filename.grb2 <- sprintf("%s/gfs_%s_%s_%s_%03d.grb2", grb2_dir, mode, date, time, num)
  filename.csv <- sprintf("%s/gfs_%s_%s_%s_%03d_%s.csv", csv_dir, mode, date, time, num, csv_key)
  
  # wgrib2 で変換（詳細は前の記事を参照）
  # system(paste("/usr/local/bin/wgrib2", filename.grb2, "-match", keywd, "-csv", filename.csv))
  
  gfs_csv <- read.csv(filename.csv, header = FALSE)
  lon_180 <- gfs_csv[(gfs_csv$V5 == 180),]
  lon_180$V5 <- lon_180$V5 * -1
  gfs_csv <- rbind(gfs_csv, lon_180)
  
  gfs_coords <- cbind(gfs_csv$V5, gfs_csv$V6)
  
  cell_size <- 1.0
  lon_min <- min(gfs_csv$V5)
  lon_max <- max(gfs_csv$V5)
  lat_min <- min(gfs_csv$V6)
  lat_max <- max(gfs_csv$V6)
  
  lon_ncols <- ((lon_max - lon_min)/cell_size)+1
  lat_nrows <- ((lat_max - lat_min)/cell_size)+1
  
  grid_gfs <- raster(nrows=lat_nrows, ncols=lon_ncols, xmn=lon_min, xmx=lon_max, ymn=lat_min, ymx=lat_max, res=cell_size, crs="+proj=longlat +datum=WGS84")
  
  gfs_tmp_rs[[j]] <- rasterize(gfs_coords, grid_gfs, gfs_csv$V7, fun=mean)
}



################################################################################

#color palette

gfs_tmp_pal <- colorNumeric(
  palette = c(
    "#000180", "#0007A7", "#0019CA", "#0035E6", "#0058F8",
    "#017FFF", "#07A7F8", "#19CAE6", "#35E6CA", "#58F8A7",
    "#80FF80", "#A7F858", "#CAE635", "#E6CA19", "#F8A707",
    "#FF8001", "#F85800", "#E63500", "#CA1900", "#A70700"
  ),
  domain = range(210, 360),  # カラーパレットの範囲
  na.color = "transparent" # NAのときの色
)
```

# leaflet + shiny で可視化
スライドバーを作りその入力値でraster データを選ぶように設定した。

```R:app.R
library(shiny)
library(leaflet)
library(dplyr)

source("GFS.R")

ui <- fluidPage(
  tags$style(type = "text/css", "#map {height: calc(100vh - 80px) !important;}"),
  leafletOutput(outputId = "map"),
  absolutePanel(
    bottom = 100,
    left = 400,
    sliderInput(inputId = "data_num", label = "Forecast time (hours later)", min = 0, max = 24, value = 0, step = 3)
  )
)

server <- function(input, output, session){
  output$map <- renderLeaflet(expr = {
    num <- input$data_num/3 + 1
    leaflet() %>%
      setView(139, 35, zoom = 2) %>%
      addProviderTiles(provider = providers$Esri.WorldGrayCanvas, group = "WorldGrayCanvas") %>%
      addProviderTiles(provider = providers$Esri.OceanBasemap, group = "OceanBaseMap") %>%
      addProviderTiles(provider = providers$OpenSeaMap, group = "OpenSeaMap") %>%
      addRasterImage(x = gfs_tmp_rs[[num]], colors = gfs_tmp_pal, opacity = 0.7, group = "GFS_Temperature", project = TRUE) %>%
      addLegend(position = 'topleft', pal = gfs_tmp_pal, values = 210:360, opacity = 0.7, title = "TMP [K]", group = "GFS_Temperature") %>%
      addLayersControl(
        baseGroups = c(
          "WorldGrayCanvas",
          "OceanBaseMap"
        ),
        overlayGroups = c(
          "OpenSeaMap",
          "GFS_Temperature"
        )
      ) %>%
      hideGroup(
        group = c(
        )
      ) %>%
      addScaleBar(position="bottomleft") %>%
      addMiniMap(position="bottomright") %>%
      addMeasure(position="topright")
  })
}

shinyApp(ui=ui, server=server)
```

スライドバーを動かすことで予報値を変えられる。
![GFS_app_1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1077734/96188fe8-afc3-10d5-19ee-cd71e3744794.gif)

# おわりに
スライドバーを動かしたときにマップのプロットが一瞬消えて画面がちかちかしてしまう。ので、これは次の記事で改訂していきたい。
また、GFS はnetcdf 形式にも変換でき、そこからgeojson などにも変換できるよう。leaflet には addGeoJSON というコマンドがある。そこで、csv → raster データではなく、geojson を直接プロットする方法も検討してみたい。