monanadmin / monan

Repositório organizacional das atividades dos macro-grupos do MONAN - Model for Ocean-laNd-Atmosphere PredictioN
14 stars 10 forks source link

[GCC] PAD: Site de Visualização de Dados para os TC #482

Closed joaomas closed 2 months ago

joaomas commented 3 months ago

:people_holding_hands: User Story

COMO Saulo, QUEREMOS investigar a criação de um site de visualização de dados para os TC, PARA termos uma visualização de dados de saída do TC.

:clipboard: Critérios de aceite de conclusão da Issue

:pencil: Detalhamento adicional da atividade

Verificar a construção de um site simples que permita visualizar as saídas das rodadas de pre-operação do modelo

:comet: Impactos

sem impactos

:link: Dependências

sem dependências

:hammer_and_wrench: Solução

Criar um site simples em HTML e javascript definir o formato do nome das figuras a serem produzidas em cada variável

:rotating_light: Problemas encontrados

sem problemas

:white_check_mark: Conclusão

Foi criado um site simples que permite abrir figuras de saída do modelo. O script fonte em html é:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>MONAN - Pre View</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            display: flex;
            flex-direction: column;
            align-items: center;
            padding: 20px;
        }
        #header {
            width: 100%;
            display: flex;
            justify-content: space-between;
            align-items: left;
            margin-bottom: 20px;
            bgcolor: "yellow";
        }
        #title {
            margin: 0; 
            text-align: left;
        }
        #logo {
            width: 50px; 
            height: auto;
            align: left
        }
        #content {
            display: flex;
            justify-content: space-between;
            align-items: flex-start;
            width: 100%;
        }
        #left {
            flex: 1;
            margin-right: 20px;
            border-right: 1px solid #ccc;
        }
        #right {
            flex: 2;
            display: flex;
            justify-content: center;
            align-items: center;
        }
        #result {
            max-width: 100%;
            max-height: 100%;
        }
    </style>
</head>
<body>
    <div id="header">
        <img id="logo" src="./images/logo_monan_vert_200x256.png" alt="logo"> 
        <h1 id="title">MONAN - View Pre Oper</h1>
    </div>

    <div id="content">
        <div id="left">
         <label for="start_date">Start Date:</label>
         <input type="date" id="start_date">
         <br><br>
         <label for="end_date">Forecast Date:</label>
         <input type="date" id="end_date">
         <br><br>
         <label for="start_time">Forecast hour:</label>
         <input type="time" id="start_time">
         <br><br>
         <input type="checkbox" id="ams" name="domain" value="">
         <label for="ams">AMS</label>
         <input type="checkbox" id="era5" name="CI" value="">
         <label for="ERA5">ERA5</label>        
         <br><br>
         <label for="RES">Resolução:</label><br>
         <select name="res"  id="res">
          <option value="x40962">x40962</option>
          <option value="24km">24km</option>
          <option value="10km">10km</option>
          <option value="120km">120km</option>
        </select>
        <br><br>
        <label for="lev">Levels Model:</label><br>
        <select name="lev"  id="lev">
         <option value="55">55</option>
       </select>
       <br><br>
        <label for="variable">Variable:</label><br>
        <select name="var"  id="variable">
         <option value="t2m">t2m</option>
         <option value="rainnc">rainnc</option>
         <option value="surfacepressure">surfacepressure</option>
         <option value="WindMag10">WindMag10</option>
         <option value="WindMagSfc">WindMagSfc</option>
         <option value="temperature">temperature</option>
         <option value="uzonal">uzonal</option>
         <option value="umeridional">umeridional</option>
         <option value="relhum">relhum</option>
       </select>
       <br><br>
        <label for="level">Level:</label><br>
        <select name="level" id="level">
         <option value="0">0</option>
         <option value="1000">1000</option>
         <option value="850">850</option>
         <option value="500">500</option>
         <option value="200">200</option>
         <option value="100">100</option>
       </select>
        <br><br>
         <button onclick="generateImage()">Show</button>
        </div>
        <div id="right">
            <img id="result" src="" alt="Generated Image">
        </div>
    </div>

    <script>
      function generateImage() {
          var startDate = document.getElementById('start_date').value;
          var endDate = document.getElementById('end_date').value;
          var startTime = document.getElementById('start_time').value;
          var variable = document.getElementById('variable').value;
          var level = document.getElementById('level').value;
          var ams = document.getElementById('ams').checked;
          var era5 = document.getElementById('era5').checked;
          var res = document.getElementById('res').value;
          var lev = document.getElementById('lev').value;

          // Formatando o valor do dia com dois dígitos
          var startDateParts = startDate.split("-");
          var endDateParts   = endDate.split("-");
          var fortime = startTime.split(":")

          if (ams) { domain = "R"} else { domain = "G"}

          if (era5) {ci = "ERA5"} else {ci = "GFS"}

          var formattedStartDate = startDateParts[0] + startDateParts[1].padStart(2, '0') + startDateParts[2].padStart(2, '0') + "00";
          var formattedendDate = endDateParts[0] + endDateParts[1].padStart(2, '0') + endDateParts[2].padStart(2, '0');

          filename = "http://ftp.cptec.inpe.br/pesquisa/bramsrd/site_test/"+variable+level+"_MONAN_DIAG_"+domain+"_MOD_"+ci+"_"+formattedStartDate+"_"+formattedendDate+fortime[0]+"."+res+"L"+lev+".png";

          console.log(filename);

          imageUrl = filename;
          document.getElementById('result').src = imageUrl;

      }
  </script>
</body>
</html>

O index html foi colocado em http://ftp.cptec.inpe.br/pesquisa/bramsrd/site_test/

AO selecionar os campos à esquerda da página ele contrói o nome do arquivo que será aberto à direita. Esse arquivo de imagem deve estar na mesma área da página para poder ser acessada.

O nome desse arquivo deve ser:

Temp1000_G_GFS_2024041100_2024041220_24kmL55.png
     |       |     |     |           |                      |         |      |        |  
     |       |     |     |           |                      |         |      |        +--------- Número de níveis do modelo rodado
     |       |     |     |           |                      |         |      +----------------- Resolução
     |       |     |     |           |                      |         +------------------------ Hora da Previsão
     |       |     |     |           |                      +--------------------------------- Data da Previsão
     |       |     |     |           +----------------------------------------------------- Data da inicialização
     |       |     |     +---------------------------------------------------------------- Fonte das condições iniciais (GFS ou ERA5)
     |       |     +---------------------------------------------------------------------- Domínio (G = Global, R= Regional)       
     |       +---------------------------------------------------------------------------- Nivel (0 para campos 2D)
     +------------------------------------------------------------------------------------ Nome da variável

Foi ainda criado um script python para geração das figuras. O script é o seguinte:

"""plota_monan.py: 

Rev: 0.1. Date: 18Apr2024

This script get the MONAN data, converted by convert_mpas in latlon grid
and generate a lot of diferents figures to web visualization.

The name of output file must be:

MONAN_DIAG_G_MOD_GFS_2024041500_2024041909.x40962L55.nc

The output files have the names:

1. For 2D 1 level variable:

rainnc0_MONAN_DIAG_G_MOD_GFS_2024041500_2024041909.x40962L55.png

2. For 2D n Levels variables:

temperature1000_MONAN_DIAG_G_MOD_GFS_2024041500_2024041909.x40962L55.png

The general format is:

<varname><level_name>_<input_file_name>.png

If is in specific surface or meters level the level_name is "0".

usage:

   python plota_monan.py <data_ci+hour> <dataforecast+hour> <G|R>

   where:

   dataci+hour in format AAAAMMDDHH is the intial conditions data/hour
   dataforecast+hour in format AAAAMMDDHH is the forecast data/hour
   <G|R> - If is to plot Global data or AMS data

   example:

  python plota_monan.py 2024041500 2024041909 G

"""

__author__      = "Rodrigues, L.F. [LFR]"
__copyright__   = "INPE - Inst. Nac. de Pesquisas Espaciais"
#from datetime import datetime, timedelta
import sys
#import os
from netCDF4 import Dataset
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
#import glob
import math
#from PIL import 

def makefig():
   """Function to plot a specific variable"""

   #List of variables to be ploted
   var_list={
          't2m'                :'t2m0',
          'rainnc'             :'rainnc0',
          'surface_pressure'   :'surfacepressure0',
          'temperature_1000hPa':'temperature1000',
          'temperature_850hPa' :'temperature850',
          'temperature_500hPa' :'temperature500',
          'temperature_200hPa' :'temperature200',
          'temperature_100hPa' :'temperature100',
          'relhum_1000hPa'     :'relhum1000',
          'relhum_850hPa'      :'relhum850',
          'relhum_500hPa'      :'relhum500',
          'relhum_200hPa'      :'relhum200',
          'relhum_100hPa'      :'relhum100',
          'u10'                :'u100',
          'v10'                :'v100',
          'umeridional_surface':'umeridional_surface0',
          'uzonal_surface'     :'uzonal_surface0',
          'umeridional_1000hPa':'umeridional1000',
          'umeridional_850hPa' :'umeridional850',
          'umeridional_500hPa' :'umeridional500',
          'umeridional_200hPa' :'umeridional200',
          'umeridional_100hPa' :'umeridional100',
          'uzonal_1000hPa'     :'uzonal1000',
          'uzonal_850hPa'      :'uzonal850',
          'uzonal_500hPa'      :'uzonal500',
          'uzonal_200hPa'      :'uzonal200',
          'uzonal_100hPa'      :'uzonal100'
          }

   names = var_list.keys()
   #Loop in var names
   for varname in names:
      print("Fazendo {0}, na figura {1} ........ :".format(varname,var_list[varname]))

      #Getting info from NetCDF file
      var = fh.variables[varname]
      long_name = var.long_name
      unit = var.units

      print("Criando ",long_name,unit)
      val = var[:]

      outfname = var_list[varname]

      # U10,V10 or umeridional_surface,uzonal_surface are treated apart for wind magnitude
      if varname == "u10":
         valu10 = val
         continue

      # WM = sqrt(U² + V²)
      if varname == "v10":
         val = [[[math.sqrt(valu10[i][j][k]*valu10[i][j][k] + val[i][j][k]*val[i][j][k]) 
               for k in range(len(valu10[i][j]))] 
                   for j in range(len(valu10[i]))] 
                      for i in range(len(valu10))]
         long_name = "Wind magnitude at 10m"
         outfname = "WindMag100"

      if varname == "umeridional_surface":
         valuSfc = val
         continue

      # WM = sqrt(U² + V²)
      if varname == 'uzonal_surface':
         val = [[[math.sqrt(valuSfc[i][j][k]*valuSfc[i][j][k] + val[i][j][k]*val[i][j][k]) 
               for k in range(len(valuSfc[i][j]))] 
                   for j in range(len(valuSfc[i]))] 
                      for i in range(len(valuSfc))]
         long_name = "Wind magnitude on surface"
         outfname = "WindMagSfc0"

      #Change de domain to be plotted aconrdingly the selection of user (G or R)
      if domain == "R":
         m = Basemap(projection='mill',lat_ts=10,llcrnrlon=-90.0, urcrnrlon=-30.0,llcrnrlat=-55.0,urcrnrlat=15.0, resolution='c')
      else:
         m = Basemap(projection='mill',lat_ts=10,llcrnrlon=lons.min(), urcrnrlon=lons.max(),llcrnrlat=lats.min(),urcrnrlat=lats.max(), resolution='c')

      #matpplotlib section: doing the figure
      lon, lat = np.meshgrid(lons, lats)
      xi, yi = m(lon, lat)
      if varname == "t2m":
         cs = m.pcolor(xi,yi,val[0]-273.15,cmap='jet')
         unit = "C"
      elif varname == "surface_pressure":
         cs = m.pcolor(xi,yi,val[0]/100.0,cmap='jet')
         unit = "hPa"
      else:
         cs = m.pcolor(xi,yi,val[0],cmap='jet')
      m.drawcoastlines()
      m.drawstates()
      m.drawcountries()
      m.colorbar(location='right')
      plt.title("MONAN Model {0} [{1}]".format(long_name,unit))
      print("Salvando a figura...")
      plt.savefig(outfname+"_"+filename+"png",dpi=300) #saves the image 
      plt.clf()

# Getting the argumentos from user
datain = sys.argv[1]
dataprev = sys.argv[2]
domain = sys.argv[3]

filename = "MONAN_DIAG_G_MOD_GFS_{0}_{1}.x40962L55.".format(datain,dataprev)

#Open the netCDF dataset and get some values
fh=Dataset(filename+"nc")
lons = fh.variables['longitude'][:]
lats = fh.variables['latitude'][:]
lon_0 = lons.mean()
lat_0 = lats.mean()

#Call the function to make the figures
makefig()

fh.close()

Foi feito mais que uma investigação. Criou-se o site e o script para geração de figuras.

Image

:spiral_calendar: Trabalhos Futuros

  1. Falta fazer funcionar para outras variáveis qeu sejam necessárias
  2. Precisa criar um laço que percorra os horários de previsão para gerar todas as imagens. A entrada fica sendo apenas a data da rodada (e CI) e as característivas. O script percorre então da data inicial até a todas as datas de previsão com steps de "n" horas fornecidos.
  3. Mover o site e saídas para a área correta de ftp.
joaomas commented 3 months ago

@joaomas e @luflarois:

Como exemplo do que pode ser feito nesta tarefa, deixei o repositório (https://github.com/GAD-DIMNT-CPTEC/SMNAMonitoringApp/tree/main) como público para acessarem o código e testar a proposta que estamos preparando para o monitoramento do sistema de assimilação de dados atual. Para fazer o site (na verdade é mais tentativa de dashboard), eu utilizei principalmente a biblioteca Panel (https://panel.holoviz.org/). Já para preparar os dados, utilizei várias outras bibliotecas, pricipalmente o Intake (https://intake.readthedocs.io/en/latest/index.html), Zarr (https://zarr.readthedocs.io/en/stable/#) e Parquet (https://parquet.apache.org/) e para a visualização das estruturas de dados, utilizei principalmente o Hvplot (https://hvplot.holoviz.org/) e também o Matplotlib e Seaborn. Este é um approach que permite acessar os dados de forma interativa, ou seja, as figuras são feitas por meio de requisições que são feitas pelos ajustes das wigets e o processamento todo é client-side. Em compensação, fica mais pesado e lento - mas isso deve ser tratado de forma individual com cada tipo de dado a ser acessado (é possível também acelerar algumas partes da interface por meio do cache do navegador - https://panel.holoviz.org/how_to/caching/index.html). Acho que uma grande vantagem do Panel é que não é necessário "programar" o HTML e a CSS (ou o Java). Basta escrever em Python, utilizar um template e converter o script Python para HTML e JAVA. Com isso, pode-se fazer o deploy (com algumas limitações - exemplo: https://github.com/cfbastarz/panel_tests e https://cfbastarz.github.io/panel_tests/panel_app.html) no próprio GitHub pages.

Outras opções em relação ao Panel:

Outra opção, é programar um site estático a partir do qual pode-se acessar figuras que já foram plotadas e que estão armazenadas em algum servidor :)

joaomas commented 3 months ago

@luflarois Substituir Pre Oper para "Testes Contínuos".

joaomas commented 2 months ago

@deniseiras @egkhamis problemas variaveis umidade relativa, vento zonal e meridional