sobota 28. decembra 2013

Implementácia bakalárskej práce - Webová vizualizácia dát predpovede počasia z lokálnej meteostanice

V tomto článku opisujem spôsob ako som implementoval systém, ktorý automaticky zobrazuje stav počasia na webovej stránke. Tento systém meria aktuálne hodnoty meteorologických veličín v okolí Technickej univerzity v Košiciach. Na vytvorenú stránku som pridal grafické priebehy vybraných veličín, pričom niektoré som doplnil aj o predikciu ich budúceho priebehu. Táto predikcia je realizovaná pomocou neurónových sietí, ktoré sú učené metódou spätného šírenia chyby. Výsledná stránka je prístupná na serveri http://neuron.tuke.sk/vysnovsky, kde je pridaná aj moja bakalárska práca.

Implementácia systému

Na meranie meteorologických veličín som použil meteorologickú stanicu, ktorá je umiestnená na streche Ústavu výpočtovej techniky.

Celý systém som rozdelil na dva servery:
  • jeden sa stará o získavanie a uchovávanie dát z meteostanice
  • druhý server som použil pre samotnú webovú stránku


Predtým ako popíšem funkcie oboch serverov, priblížim vám použité programy tretích strán, ktoré v mojom systéme využívam. Konkrétne sú to Cron, DWT a Gnuplot.

Cron

Celý systém musí pracovať automaticky, bez ďalších zásahov človeka. To som musel nejako vyriešiť, a keďže na oboch serveroch je ako operačný systém použitý linux, tak som využil program Cron. Vďaka nemu dokážem spúšťať skripty alebo príkazy v presne stanovený čas. Program Cron som nakonfiguroval editáciou súboru crontab. Najjednoduchšie sa to vykoná napísaním nasledujúceho príkazu do terminálu.

crontab -e

Tým som sa dostal do textového editora, kde môžem crontab nakonfigurovať. Každý záznam v konfiguračnom súbore má presne stanovenú štruktúru (pozri ďalej). Viac sa o programe Cron a jeho konfigurácií dočítate na Wikipédií.

# * * * * *  príkaz na spustenie
# ┬ ┬ ┬ ┬ ┬
# │ │ │ │ │
# │ │ │ │ │
# │ │ │ │ └───── deň v týždni (0 - 6) (0 je Nedeľa)
# │ │ │ └────────── mesiac (1 - 12)
# │ │ └─────────────── deň v mesiaci (1 - 31)
# │ └──────────────────── hodina (0 - 23)
# └───────────────────────── minúta (0 - 59)
Prebrané z Wikipédie

DWT

DWT je jednoduchý program napísaný v jazyku C, ktorý slúži na komunikáciu s meteorologickou stanicou cez sériový port počítača. Tento program si môžete stiahnúť na adrese http://ct.id.au/weather/software.

Gnuplot

Na vytvorenej webovej stránke s počasím chcem vykresľovať historické priebehy niektorých veličín. Na to využívam program Gnuplot. Tento program slúži na tvorbu najrozličnejších druhov grafov. Spúšťa sa z terminálu, čo mi práve vyhovuje, keďže ho má spúšťať Cron. Jeho hlavnou výhodou je, že výstupné grafy sa dajú jednoducho prispôsobovať.

Zjednodušená bloková schéma systému

Server pre získavanie dát z meteostanice

Hlavnou úlohou tohto servera je uchovávať a poskytovať namerané údaje. To som implementoval tak, že na servri sú cez HTTP protokol (pomocou lighttpd) prístupné textové súbory s dátami. Konkrétne som vytvoril súbory current_weather.txt a current_data.txt. Do súboru current_weather.txt zapisujem aktuálne hodnoty počasia a do súboru current_data.txt postupne pridávam nové merania, čím vzniká krátkodobý archív dát.

Pre získanie dát z meteostanice som využil program DWT. Ten zavolám nasledujúcim príkazom:

dwt FRMT '+%.1{OUT_TEMP} %.0{BAROMETER} %{WIND_SPEED} %{WIND_DIRECTION} %.1{DEW_POINT} %.0{SOLAR_RAD} %{SUNRISE} %{SUNSET} %{OUT_HUMIDITY} %.2{ET_TODAY} %.2{ET_MONTH} %.2{ET_MONTH} %{FORECAST_ICON}'


Program mi vráti hodnoty aktuálneho počasia naformátované podľa spusteného príkazu. Tieto hodnoty si ukladám do textových súborov, ktoré neskôr využívam na zostavenie stránky.

Proces získavania dát z meteostanice som zautomatizoval pomocou programu Cron, ktorého konfiguračný súbor vyzerá takto:

# skript na získanie aktuálnych hodnôt počasia 
0,5,10,15,20,25,30,35,40,45,50,55 * * * * /cesta_s_súboru/cron_weather_server.sh

# skript na pridanie ďalšieho riadku do archívu
0,15,30,45 * * * * /cesta_s_súboru/cron_current_data.sh

Zobrazený Cron spúšťa dva skripty:
  • jeden pre získania aktuálneho stavu počasia, ktorý sa spúšťa každých 5 minút
  • druhý pre zapísanie ďalšieho riadku s aktuálnymi hodnotami počasia do súboru, ktorý slúži ako archív dát - ten sa spúšťa každých 15 minút

Oba tieto skripty sú jednoduché a ich obsah je zobrazený na nasledujúcich riadkoch.

# cron_weather_server.sh

# zapisanie aktualineho pocasia do suboru
dwt FRMT '+%.1{OUT_TEMP} %.0{BAROMETER} %{WIND_SPEED} %{WIND_DIRECTION} %.1{DEW_POINT} %.0{SOLAR_RAD} %{SUNRISE} %{SUNSET} %{OUT_HUMIDITY} %.2{ET_TODAY} %.2{ET_MONTH} %.2{ET_MONTH} %{FORECAST_ICON}' > current_weather.txt
# cron_current_data.sh

# pridanie aktualneho datumu a času
date +'%H:%M:%S %d-%m-%Y ' | tr -d "\n\r" >> current_data.txt

# pridanie aktuálnych hodnôt počasia
dwt FRMT '+%.1{OUT_TEMP} %.0{BAROMETER} %{WIND_SPEED} %{WIND_DIRECTION} %.1{DEW_POINT} %.0{SOLAR_RAD} %{SUNRISE} %{SUNSET} %{OUT_HUMIDITY} %.2{ET_TODAY} %.2{ET_MONTH} %.2{ET_MONTH} %{FORECAST_ICON}' >> current_data.txt

Server pre webovú stránku

Tento server som použil pre generovanie a poskytovanie samotnej webovej stránky. Aj na ňom som nastavil Cron, čiže generovanie stránky je opäť automatické.

# stránka s aktuálnymi hodnotami počasia
0,5,10,15,20,25,30,35,40,45,50,55 * * * * /cesta_s_súboru/cron_neuron.sh

# grafy
0,15,30,45 * * * * /cesta_s_súboru/graphs.sh

Aktuálne dáta z prvého servera získavam pomocou príkazu wget.

wget -q -N http://IP_adresa_servera/cesta_k_suboru/current_data.txt

Zo stiahnutých dát následne vytváram HTML stránku pomocou jednoduchécho BASH skriptu.

#!/bin/bash

# vojdeme do adresára, z ktorého bol skript spustený
cd $(dirname $0)

# začiatok HTML
echo "<!DOCTYPE html>
<html lang=\"sk\">
<head>
  <meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">
  <meta name=\"author\" content=\"Martin Vyšňovský (martinvysnovsky@gmail.com)\">
  <title>Počasie TUKE</title>
</head>
<body>" > pocasie.html

# získame aktuálne dáta
weather_string=$(wget -O- -q http://IP_adresa_servera/cesta_k_suboru/current_weather.txt)

IFS=" " read -a weather <<< "$weather_string"

# teplota vzduchu
echo "<span class=\"temperature\">${weather[0]}°C</span>
        <a href=\"#\" id=\"more_values\" title=\"Zobraziť viac hodnôt\">Viac</a>" >> pocasie.html

# smer vetra
case "${weather[3]}" in
  'N')
    wind_way_string="zo severu"
    ;;
  'NNE')
    wind_way_string="zo severo-severovýchodu"
    ;;
  'NE')
    wind_way_string="zo severovýchodu"
    ;;
  'ENE')
    wind_way_string="z východo-severovýchodu"
    ;;
  'E')
    wind_way_string="z východu"
    ;;
  'ESE')
    wind_way_string="z východo-juhovýchodu"
    ;;
  'SE')
    wind_way_string="z juhovýchodu"
    ;;
  'SSE')
    wind_way_string="z juho-juhovýchodu"
    ;;
  'S')
    wind_way_string="z juhu"
    ;;
  'SSW')
    wind_way_string="z juho-juhozápadu"
    ;;
  'SW')
    wind_way_string="z juhozápadu"
    ;;
  'WSW')
    wind_way_string="zo západo-juhozápadu"
    ;;
  'W')
    wind_way_string="zo západu"
    ;;
  'WNW')
    wind_way_string="zo západo-severozápadu"
    ;;
  'NW')
    wind_way_string="zo severozápadu"
    ;;
  'NNW')
    wind_way_string="zo severo-severozápadu"
    ;;
  *)
    wind_way_string=""
    ;;
esac

# ostatné hodnoty
echo "
  <div>Tlak vzduchu: <span>${weather[1]}hPa</span></div>
  <div>Rýchlosť vetra: <span>${weather[2]}km/h</span></div>
  <div>Smer: <span>$wind_way_string</span></div>
  <div>Slnečné žiarenie: <span>${weather[5]}W/m2</span></div>
  <div>Východ slnka: <span>${weather[6]:0:1}:${weather[6]:1}</span></div>
  <div>Západ slnka: <span>${weather[7]:0:2}:${weather[7]:2}</span></div>
  <div>Vlhkosť vzduchu: <span>${weather[8]}%</span></div>
  <div>Dnešné zrážky: <span>${weather[9]}mm</span></div>
  <div>Mesačné zrážky: <span>${weather[10]}mm</span></div>
</body>
</html>
" >> pocasie.html

# vytvorený súbor skopírujeme do adresára, kde je stránka a nahradíme ním neaktuálny súbor
mv ./pocasie.html cesta_do_www_adresára

Generovanie grafov

Na stránke som chcel mať zobrazené aj grafické priebehy niektorých meteorologických hodnôt. Tieto grafy som vygeneroval pomocou programu Gnuplot pomocou nasledujúceho príkazu.

gnuplot temperature.gnu

Tento príkaz spustí Gnuplot a vygeneruje graf podľa požiadaviek definovaných vo vstupnom súbore (v tomto prípade v temperature.gnu).

# nastavenia
# -----------------------------------------------------
time_format = '"%H:%M:%S %d-%m-%Y"'
min = -15
max = 15

# -----------------------------------------------------
set macros

# rozmery a formát výstupného súboru
# -----------------------------------------------------
set terminal pngcairo enhanced size 3000,500 font "arial,10"
set output "temperature.png"

# výpočet dátumov
# -----------------------------------------------------
timestamp = system("date +%s")

now = system(sprintf("date +'%s' -d @%s", time_format, timestamp))
from = system(sprintf("date +'%s' -d @%d", time_format, timestamp - (6 * 24 * 60 * 60)))
to = system(sprintf("date +'%s' -d @%d", time_format, timestamp + (1 * 24 * 60 * 60)))

# nastavenia pre X-ovú os
# -----------------------------------------------------
set xdata time
set timefmt @time_format
set xrange [@from:@to]
set xtics 6*60*60 nomirror offset 0,2
set mxtics 6
set format x "%H:%M"
unset xlabel

# nastavenia pre Y-ovú os
# -----------------------------------------------------
set yrange [min:max]
unset ylabel
set label "Teplota [°C]" at @from,max font "Times Italic,10" rotate right offset screen 0.01,screen -0.02

# -----------------------------------------------------
set border 11 lc rgb "#333333"

# mriežka
# -----------------------------------------------------
set style line 10 lc rgb '#dddddd' lt 1 lw 0.5
set grid ytics back ls 10
set grid xtics back ls 10

# skrytie legendy
unset key

# štýly pre čiary
# -----------------------------------------------------
set style line 1 lt 1 lw 3 pt 3 linecolor rgb "#FF6666"
set style line 2 lt 1 lw 3 pt 3 linecolor rgb "#999999"

# popisky na X-ovej osi
# -----------------------------------------------------
timestamp6 = timestamp - (6 * 24 * 60 * 60)
timestamp5 = timestamp - (5 * 24 * 60 * 60)
timestamp4 = timestamp - (4 * 24 * 60 * 60)
timestamp3 = timestamp - (3 * 24 * 60 * 60)
timestamp2 = timestamp - (2 * 24 * 60 * 60)
timestamp1 = timestamp - (1 * 24 * 60 * 60)
timestamp0 = int(timestamp)
timestamp_1 = timestamp + (1 * 24 * 60 * 60)

now6 = system(sprintf("date +'%s' -d @%d", time_format, timestamp6))
now5 = system(sprintf("date +'%s' -d @%d", time_format, timestamp5))
now4 = system(sprintf("date +'%s' -d @%d", time_format, timestamp4))
now3 = system(sprintf("date +'%s' -d @%d", time_format, timestamp3))
now2 = system(sprintf("date +'%s' -d @%d", time_format, timestamp2))
now1 = system(sprintf("date +'%s' -d @%d", time_format, timestamp1))
now0 = now
now_1 = system(sprintf("date +'%s' -d @%d", time_format, timestamp_1))

midnight5 = "\"00:00:00 ".system(sprintf("echo \"%s\" | cut -b 10-", now5))."\""
midnight4 = "\"00:00:00 ".system(sprintf("echo \"%s\" | cut -b 10-", now4))."\""
midnight3 = "\"00:00:00 ".system(sprintf("echo \"%s\" | cut -b 10-", now3))."\""
midnight2 = "\"00:00:00 ".system(sprintf("echo \"%s\" | cut -b 10-", now2))."\""
midnight1 = "\"00:00:00 ".system(sprintf("echo \"%s\" | cut -b 10-", now1))."\""
midnight0 = "\"00:00:00 ".system(sprintf("echo \"%s\" | cut -b 10-", now0))."\""
midnight_1 = "\"00:00:00 ".system(sprintf("echo \"%s\" | cut -b 10-", now_1))."\""

set style arrow 1 heads size screen 0.003,90 lw 1 lc rgb "#333333"
set style arrow 2 nohead lw 1 lc rgb "#cccccc"

set arrow from @from,min to @midnight5,min as 1
set arrow from @midnight5,min to @midnight4,min as 1
set arrow from @midnight4,min to @midnight3,min as 1
set arrow from @midnight3,min to @midnight2,min as 1
set arrow from @midnight2,min to @midnight1,min as 1
set arrow from @midnight1,min to @midnight0,min as 1
set arrow from @midnight0,min to @midnight_1,min as 1
set arrow from @midnight_1,min to @to,min as 1

set arrow from @midnight5,min to @midnight5,max as 2
set arrow from @midnight4,min to @midnight4,max as 2
set arrow from @midnight3,min to @midnight3,max as 2
set arrow from @midnight2,min to @midnight2,max as 2
set arrow from @midnight1,min to @midnight1,max as 2
set arrow from @midnight0,min to @midnight0,max as 2
set arrow from @midnight_1,min to @midnight_1,max as 2

label_y = 0.03

noon6 = "\"12:00:00 ".system(sprintf("echo \"%s\" | cut -b 10-", now6))."\""
noon5 = "\"12:00:00 ".system(sprintf("echo \"%s\" | cut -b 10-", now5))."\""
noon4 = "\"12:00:00 ".system(sprintf("echo \"%s\" | cut -b 10-", now4))."\""
noon3 = "\"12:00:00 ".system(sprintf("echo \"%s\" | cut -b 10-", now3))."\""
noon2 = "\"12:00:00 ".system(sprintf("echo \"%s\" | cut -b 10-", now2))."\""
noon1 = "\"12:00:00 ".system(sprintf("echo \"%s\" | cut -b 10-", now1))."\""
noon0 = "\"12:00:00 ".system(sprintf("echo \"%s\" | cut -b 10-", now0))."\""
noon_1 = "\"12:00:00 ".system(sprintf("echo \"%s\" | cut -b 10-", now_1))."\""

days = "Sobota Nedeľa Pondelok Utorok Streda Štvrtok Piatok"
day_of_week = int(system("date +%w") + 1)

day6 = word(days, (day_of_week + 7 - 6) % 7 + 1)
day5 = word(days, (day_of_week + 7 - 5) % 7 + 1)
day4 = word(days, (day_of_week + 7 - 4) % 7 + 1)
day3 = word(days, (day_of_week + 7 - 3) % 7 + 1)
day2 = word(days, (day_of_week + 7 - 2) % 7 + 1)
day1 = word(days, (day_of_week + 7 - 1) % 7 + 1)
day0 = word(days, day_of_week + 1)
day_1 = word(days, (day_of_week + 7 + 1) % 7 + 1)

day6 = day6 . system(sprintf("date +'%s' -d @%d", ' %d.%m.', timestamp6))
day5 = day5 . system(sprintf("date +'%s' -d @%d", ' %d.%m.', timestamp5))
day4 = day4 . system(sprintf("date +'%s' -d @%d", ' %d.%m.', timestamp4))
day3 = day3 . system(sprintf("date +'%s' -d @%d", ' %d.%m.', timestamp3))
day2 = day2 . system(sprintf("date +'%s' -d @%d", ' %d.%m.', timestamp2))
day1 = day1 . system(sprintf("date +'%s' -d @%d", ' %d.%m.', timestamp1))
day0 = day0 . system(sprintf("date +'%s' -d @%d", ' %d.%m.', timestamp0))
day_1 = day_1 . system(sprintf("date +'%s' -d @%d", ' %d.%m.', timestamp_1))

hour = system(sprintf("date +'%s' -d @%d", '%H', timestamp0))

if (hour < 9) set label day6 at @noon6,screen label_y center textcolor rgb "#666666"; else if (hour < 18) set label day6 at @from,screen label_y left textcolor rgb "#666666"; else ;

set label day5 at @noon5,screen label_y center textcolor rgb "#666666"
set label day4 at @noon4,screen label_y center textcolor rgb "#666666"
set label day3 at @noon3,screen label_y center textcolor rgb "#666666"
set label day2 at @noon2,screen label_y center textcolor rgb "#666666"
set label day1 at @noon1,screen label_y center textcolor rgb "#666666"
set label day0 at @noon0,screen label_y center textcolor rgb "#666666"

if (hour > 18) set label day_1 at @noon_1,screen label_y center textcolor rgb "#666666"; else if (hour > 6) set label day_1 at @to,screen label_y right textcolor rgb "#666666"; else ;

# hlavička
# -----------------------------------------------------
set tmarg 1.3
set rmarg 6
set lmarg 6
months = "január február marec apríl máj jún júl august september november december"

set label system("date +'%d. ' -d @" . timestamp). word(months, int(system('date +%m -d @' . timestamp))) . system("date +' %Y' -d @" . timestamp) . system("date +' %H:%I' -d @" . timestamp) at @now,screen 0.98 center

# čiara a popisok, ktoré oddeľuje predikciu od historických hodnôt
# -----------------------------------------------------
# vertikálna čiara
set arrow from @now,min to @now,max nohead lc rgb "#666666" lw 2

# popisok
position1 = system(sprintf("date +'%s' -d @%d", time_format, timestamp + (1 * 60 * 60)))
position2 = system(sprintf("date +'%s' -d @%d", time_format, timestamp + (4 * 60 * 60)))
set arrow from @position1,(max-1) to @position2,(max-1) head lc rgb "#666666" lw 2
set label "Predikcia (beta)" at @position1,(max-2) font "Times Italic,14" textcolor rgb "#666666"

# vygenerovanie grafu
# -----------------------------------------------------
plot "./current_data.txt" using 1:3 title "Teplota" with lines ls 1 axes x1y1 smooth csplines, \
  "./prediction_temperature.txt" using (system(sprintf("date +'%s' -d @%d", @time_format, timestamp + (($1-290)*919)))):2 title "Predikovaná teplota" with lines ls 2 axes x1y1 smooth csplines

Výstupný formát som nastavil na PNG, čiže Gnuplot nám vygeneruje obrázok, ktorý potom zobrazujem na webovej stránke.


Graf priebehu teploty vygenerovaný cez Gnuplot

Predikcia počasia

Na predikciu hodnôt meteorologických veličín som využil neurónovú sieť. Tú som vytvoril v programe SNNS, kde som ju učil metódou spätného šírenia chyby. Neurónová sieť mala na vstupe 288 neurónov, ktoré reprezentovali namerané dáta za posledných 72 hodín. Výstupom z neurónovej siete bola predikcia na nasledujúcich 24 hodín. Túto predikciu som potom zakomponoval do vytvorených grafov.

Záver

Vytvorená webová stránka už funguje dlhšiu dobu a za ten čas nebolo potrebné vykonať žiaden vonkajší zásah. Výsledok môžete posúdiť sami na mojej školskej stránke - http://neuron.tuke.sk/vysnovsky/pocasie. V prípade akýchkoľvek nejasností alebo otázok neváhajte pod článkom zanechať komentár. Rád vám naň odpoviem.


Žiadne komentáre:

Zverejnenie komentára