Kierunek wiatru

Zamiana kierunku wiatru wyrażonego w stopniach, na opisanego ‚ładniej’ -w nieco nawigacyjny sposób, mogłaby być w Pythonie wykonana następująco:

def wind_direction2descr_old(wind_dir):
"""
po prostu zmieniam wartość kątową na opisową kierunku wiatru
:param wind_dir: liczbowa wartość w stopniach
:return: unistring 1 lub dwie literki
"""
wind_dir_descr = u''
if wind_dir is not None and isinstance(wind_dir, (int, long, float)):
wind_dir = round(wind_dir % 360, 0)
if wind_dir < 22.5 or wind_dir >= (360 - 22.5):
wind_dir_descr = u'N'
dir_wokolo = 45
if (dir_wokolo + 22.5) > wind_dir >= (dir_wokolo - 22.5):
wind_dir_descr = u'NE'
dir_wokolo = 90
if (dir_wokolo + 22.5) > wind_dir >= (dir_wokolo - 22.5):
wind_dir_descr = u'E'
dir_wokolo = 135
if (dir_wokolo + 22.5) > wind_dir >= (dir_wokolo - 22.5):
wind_dir_descr = u'SE'
dir_wokolo = 180
if (dir_wokolo + 22.5) > wind_dir >= (dir_wokolo - 22.5):
wind_dir_descr = u'S'
dir_wokolo = 225
if (dir_wokolo + 22.5) > wind_dir >= (dir_wokolo - 22.5):
wind_dir_descr = u'SW'
dir_wokolo = 270
if (dir_wokolo + 22.5) > wind_dir >= (dir_wokolo - 22.5):
wind_dir_descr = u'W'
dir_wokolo = 315
if (dir_wokolo + 22.5) > wind_dir >= (dir_wokolo - 22.5):
wind_dir_descr = u'NW'
return wind_dir_descr

Czyli okrąg-tort – 360 stopni umownie podzielony na 8 równych kawałków, ten na górze N – jak północ, na dole S – południe, po bokach E i W oraz pomiędzy nimi: NE SE SW NW. Osiem kawałków razem, licząc od góry – zgodnie ze wskazówkami zegara da nam: ‚N’ ‚NE’ ‚E’ ‚SE’ ‚S’ ‚SW’ ‚W’ ‚NW’.
Gdy 360 stopni na 8 otrzymamy 45, tak więc dla 0 stopni ( +/- połowa 45 = 22.5 stopnia ), czyli od -22.5 do +22.5 daje N , 45 (+/- 22.5) czyli 22.5 do 67.5 daje NE itd.
Funkcja powyżej w ten właśnie sposób zamienia wartość kąta na jego nawigacyjny opis kierunku.

Wcześniej sprawdzamy, czy wejściowa dana istnieje i jest liczbą – aby uniknąć błędów, jeśli jest poza zakresem 0 – 360 stopni to przywracamy ją do tego zakresu, zaokrąglamy i porównujemy aby określić opis słowny.

Można to też zrobić tak:

def wind_direction2descr(wind_dir):
"""
po prostu zmieniam wartość kątową na opisową kierunku wiatru
:param wind_dir: liczbowa wartość w stopniach
:return: unistring 1 lub dwie literki
"""
wind_dir_descr = u''
if wind_dir is not None and isinstance(wind_dir, (int, long, float)):
index = int(((float(wind_dir) + 22.5) % 360) / 45) % 8
wind_dir_descr = [u'N', u'NE', u'E', u'SE', u'S', u'SW', u'W', u'NW', ][index]
return wind_dir_descr

…czyli przeliczyć wind_dir na liczbę 0…7, która będzie indexem tablicy oznaczeń kierunków i da nam oczekiwany wynik.

Zaraz, zaraz, jak sprawdzić, czy wynik będzie oczekiwany?
Może przy pomocy testu?

wind_dir = 0
wind_dir_descr = wind_direction2descr(wind_dir)
print wind_dir, '-->', wind_dir_descr

…można to sprawdzać na piechotę oczywiście, można też napisać ładny Python’owy test.

Na piechotę:

Test:

# -*- coding: utf-8 -*-

# TODO reload utf-8
import sys
reload(sys)
sys.setdefaultencoding("utf-8")

from datetime import datetime

import describe_weather

try:
    import unittest2 as unittest
except ImportError:
    import unittest


class DescribeWeatherTest(unittest.TestCase):

    def test_wind_direction2descr(self):

        wind_dir_test_list = [
            [0, u'N'],
            [360, u'N'],
            [22.4, u'N'],
            [337.6, u'N'],

            [45 - 22.4, u'NE'],
            [45 + 22.4, u'NE'],
            [90 - 22.4, u'E'],
            [90 + 22.4, u'E'],
            [135 - 22.4, u'SE'],
            [135 + 22.4, u'SE'],
            [180 - 22.4, u'S'],
            [180 + 22.4, u'S'],
            [225 - 22.4, u'SW'],
            [225 + 22.4, u'SW'],
            [270 - 22.4, u'W'],
            [270 + 22.4, u'W'],
            [315 - 22.4, u'NW'],
            [315 + 22.4, u'NW'],

            [None, u''],
        ]

        for test_case in wind_dir_test_list:
            wind_dir = test_case[0]
            expected = test_case[1]
            res = describe_weather.wind_direction2descr(wind_dir)
            self.assertIsInstance(res, unicode, u'nie jest UNICODE')
            self.assertEqual(res, expected, u'oczekiwano innej wartości: ' + unicode(expected)
                             + u' otrzymano: ' + unicode(res) + u' dla kierunku: ' + unicode(wind_dir))
        return None

Od początku:

# -*- coding: utf-8 -*-

– sposób kodowania znaków użyty tutaj, oraz

import sys
reload(sys)
sys.setdefaultencoding("utf-8")

– sposób kodowania użyty w wytworzonym pliku wynikowym testu,

import describe_weather

– pobieram plik, w którym zawarta jest opisana wyżej funkcja,

import unittest

– pobieram pliki wykonujące tworzone testy, tzw testy jednostkowe lub ‚unit-testy’

Na bazie pobranych unit-testów tworzę klasę:

class DescribeWeatherTest(unittest.TestCase):

a w niej metodę / funkcję wykonującą test na opisanej powyżej mojej funkcji zamiany stopni na opis kierunku wiatru:

def test_wind_direction2descr(self):

nazwa klasy tworzona jest w sposób CamelCasing, czyli wielbłądzi – ma garby (wielkie litery) jako rozdzielenie wyrazów i Test w nazwie (np. ‚OpiszMojTest’ albo ‚OpiszJakisDziwnyNieModTest’…), nazwa metody – małymi literami podkreślnik jako rozdzielenie i na końcu ‚_test’.
Taka konwencja pozwala łatwiej zrozumieć …niezrozumiałe.
Co wewnątrz testu?
Mam do przetestowania funkcje, chcę jej zadawać wartości kąta i sprawdzać, czy otrzymany wynik jest zgodny z oczekiwaniami. Np jeśli zadam 0 (zero), chciałbym uzyskać ‚N’.
Zapiszmy to jako jeden przypadeczek testowy:
[0, u’N’]
(literka u oznacza kodowanie jako unicode, choć tutaj nie ma większego znaczenia…)

jeśli zebrać przypadeczki razem i ułożyć z nich listę otrzymamy:

wind_dir_test_list = [
    [0, u'N'],
    [360, u'N'],
    [22.4, u'N'],
    [337.6, u'N'],

    [45 - 22.4, u'NE'],
    [45 + 22.4, u'NE'],
    [90 - 22.4, u'E'],
    [90 + 22.4, u'E'],
    [135 - 22.4, u'SE'],
    [135 + 22.4, u'SE'],
    [180 - 22.4, u'S'],
    [180 + 22.4, u'S'],
    [225 - 22.4, u'SW'],
    [225 + 22.4, u'SW'],
    [270 - 22.4, u'W'],
    [270 + 22.4, u'W'],
    [315 - 22.4, u'NW'],
    [315 + 22.4, u'NW'],

    [None, u''],
]

…gdzie pierwszy element to zadawana wartość, drugi – oczekiwana odpowiedź.

Jak testujemy?

for test_case in wind_dir_test_list:
    wind_dir = test_case[0]
    expected = test_case[1]
    res = describe_weather.wind_direction2descr(wind_dir)
    self.assertIsInstance(res, unicode, u'nie jest UNICODE')
    self.assertEqual(res, expected, u'oczekiwano innej wartości: ' + unicode(expected)
                     + u' otrzymano: ' + unicode(res) + u' dla kierunku: ' + unicode(wind_dir))
return None

 

Pętla pobierająca jeden przypadeczek do ‚test_case’, gdzie ‚test_case[0]’ to zadawana wartość, a test_case[1] – oczekiwany wynik,

… wykonanie funkcji (dwa człony nazwy – pierwszy to nazwa zaimportowanego pliku ‚describe_weather’, drugi – ‚wind_direction2descr’ -nazwa funkcji oraz w nawiasach dana wejściowa )
wynik zwracany do zmiennej ‚res’ – jak resource (zasoby) oraz porównywana -to już narzędzia unit-testów:

assertIsInstance – czy jest typu unicode (bo tylko taki akceptujemy zgodnie z założeniami),
oraz:
assertEqual – czy to co uzyskano ‚res’ jest identyczne (equal -> równe) z oczekiwamym ‚expected’.

W obu ‚assert`ach’ sprawdzających, ostatnią wartością jest komunikat tekstowy, który pojawia się gdy wynik jest inny niż oczekiwano.

 

Na końcu, aby wykonać wszystkie testy:

if __name__ == '__main__':

    # --- jeden sposób:
    # unittest.main()
    # --- drugi sposób (chyba ładniejszy no i dla Jenkinsa):
    import xmlrunner
    unittest.main(testRunner=xmlrunner.XMLTestRunner(output='test-reports'))

…i wykonanie (poprzez run dla PyCharm).

Narzędzie Unittest wygeneruje raport:

Running tests...
----------------------------------------------------------------------
..........................
----------------------------------------------------------------------
Ran 26 tests in 0.003s

OK

Generating XML reports...

…także do pliku:

<?xml version="1.0" ?>
<testsuite errors="0" failures="0" name="DescribeWeatherTest-20180423140707" tests="26" time="0.003">
   <testcase classname="DescribeWeatherTest" name="test_describe_weather_teraz" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_for_effect_id_find_hours_ranges" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_integer_to_effect_description" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_cisnienie" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_cisnienie_doby" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_dodatkowo_efekty_doby" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_dodatkowo_efekty_prognozy" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_prognoze_doby" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_temperature_doby" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_temperature_prognozy" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_wiatr_doby" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_wiatr_prognoze" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_wilgotnosc_popoludnia" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_zachmurzenie_doby" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opisz_zachmurzenie_popoludnia" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_opracuj_pobrana_prognoze_max" time="0.001"/>
   <testcase classname="DescribeWeatherTest" name="test_opracuj_pobrana_prognoze_najblizsza" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_semi_zachmurzenie_percent2_octascale" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_special" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_temperatura_ze_znakiem" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_wind_boufort2descr_pl" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_wind_direction2descr" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_wind_speed_mps_2boufort" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_zachmurzenie_percent2_octascale" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_zamien_tylko_interesujace_efekty_atmosferyczne_na_opis" time="0.000"/>
   <testcase classname="DescribeWeatherTest" name="test_zapamietaj_okres_od_do" time="0.000"/>
   <system-out>
<![CDATA[]]>   </system-out>
   <system-err>
<![CDATA[]]>   </system-err>
</testsuite>

Opis unittestów można znaleźć tutaj: https://docs.python.org/2/library/unittest.html

Co do wiatru, mamy jeszcze jego prędkość, można ją wyrażać w stopniach Bouforta, opisowo a także zwyczajnie – w metrach na sekundę…

Zamiana prędkości na stopnie Bouforta – w zależności od zakresu – bułka z masłem,

zaś stopnie Bouforta (z pewnością pamiętacie że zapisujemy inaczej niż stopnie Celsjusza – mianowicie bez znaczka stopni, tylko samo B) na opis, to już kiełbaska na tym maśle 🙂 :

def wind_boufort2descr_pl(wind_boufort):
    """
    zamień stopnie bouforta na opis
    :param wind_boufort: liczba całkowita 0...12
    :return: unistring-opis
    """
    wind_descr = u''
    l_w_d = [u'cisza',
             u'powiew',
             u'słaby wiatr',
             u'łagodny wiatr',
             u'umiarkowany wiatr',
             u'dość silny wiatr',
             u'silny wiatr',
             u'bardzo silny wiatr',
             u'wicher',
             u'silny sztorm',
             u'bardzo silny sztorm',
             u'gwałtowny sztorm',
             u'huragan']
    if wind_boufort is not None and isinstance(wind_boufort, (int, long, float)) and 0 <= wind_boufort <= 12:
        wind_descr = l_w_d[wind_boufort]
    return wind_descr

…oczywiście jakiś test do tej bułki.

http://pogoda.wiks.eu

Radości!

WikS

Loading Disqus Comments ...
Loading Facebook Comments ...

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *