From 330f5ac3f6ebf4243bc14911c173584a2fe8e6e0 Mon Sep 17 00:00:00 2001 From: clampr Date: Fri, 20 Nov 2020 11:06:22 +0100 Subject: [PATCH] Parquet, Units, Cache Refactoring --- README.md | 3 +- examples/daily/aggregation-regional.py | 3 +- examples/daily/aggregation.py | 4 +- examples/daily/chart.py | 6 +- examples/daily/compare-aggregate.py | 2 +- examples/daily/compare.py | 2 +- examples/hourly/chart.py | 6 +- examples/hourly/interpolation.py | 7 +- examples/hourly/simple.py | 5 +- meteostat/__init__.py | 5 +- meteostat/core.py | 34 ++++---- meteostat/daily.py | 108 ++++++++++++++++--------- meteostat/hourly.py | 104 +++++++++++++++--------- meteostat/stations.py | 52 ++++++++---- meteostat/units.py | 98 ++++++++++++++++++++++ setup.py | 4 +- 16 files changed, 321 insertions(+), 122 deletions(-) create mode 100644 meteostat/units.py diff --git a/README.md b/README.md index f97fabd..f89d3fc 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ The Meteostat Python library provides a simple API for accessing open weather an ## Installation The Meteostat Python package is available through [PyPI](https://pypi.org/project/meteostat/): + ``` pip install meteostat ``` @@ -38,7 +39,7 @@ data = Daily(station, start = datetime(2018, 1, 1), end = datetime(2018, 12, 31) data = data.fetch() # Plot line chart including average, minimum and maximum temperature -data.plot(x = 'time', y = ['tavg', 'tmin', 'tmax'], kind = 'line') +data.plot(y = ['tavg', 'tmin', 'tmax'], kind = 'line') plt.show() ``` diff --git a/examples/daily/aggregation-regional.py b/examples/daily/aggregation-regional.py index 395e3d4..1c483da 100644 --- a/examples/daily/aggregation-regional.py +++ b/examples/daily/aggregation-regional.py @@ -2,7 +2,8 @@ from datetime import datetime import matplotlib.pyplot as plt -stations = Stations(country = 'US', daily = datetime(2005, 1, 1)).sample(5).fetch() +stations = Stations(country = 'US', daily = datetime(2005, 1, 1)) +stations = stations.fetch(limit = 5, sample = True) data = Daily(stations, max_threads = 5, start = datetime(1980, 1, 1), end = datetime(2019, 12, 31)) data = data.normalize().aggregate(freq = '1Y', spatial = True).fetch() diff --git a/examples/daily/aggregation.py b/examples/daily/aggregation.py index f30d074..d7512df 100644 --- a/examples/daily/aggregation.py +++ b/examples/daily/aggregation.py @@ -2,8 +2,8 @@ from datetime import datetime import matplotlib.pyplot as plt -data = Daily(['10637'], start = datetime(2018, 1, 1), end = datetime(2018, 12, 31)) +data = Daily('10637', start = datetime(2018, 1, 1), end = datetime(2018, 12, 31)) data = data.normalize().aggregate(freq = '1W').fetch() -data.plot(x = 'time', y = ['tavg', 'tmin', 'tmax'], kind = 'line') +data.plot(y = ['tavg', 'tmin', 'tmax'], kind = 'line') plt.show() diff --git a/examples/daily/chart.py b/examples/daily/chart.py index 216e183..867724d 100644 --- a/examples/daily/chart.py +++ b/examples/daily/chart.py @@ -2,12 +2,14 @@ from datetime import datetime import matplotlib.pyplot as plt -# Hourly +# Get a weather station stations = Stations(lat = 49.2497, lon = -123.1193) station = stations.fetch(1) +# Get daily data data = Daily(station, start = datetime(2018, 1, 1), end = datetime(2018, 12, 31)) data = data.fetch() -data.plot(x = 'time', y = ['tavg', 'tmin', 'tmax'], kind = 'line') +# Plot chart +data.plot(y = ['tavg', 'tmin', 'tmax'], kind = 'line') plt.show() diff --git a/examples/daily/compare-aggregate.py b/examples/daily/compare-aggregate.py index c069288..583569e 100644 --- a/examples/daily/compare-aggregate.py +++ b/examples/daily/compare-aggregate.py @@ -14,7 +14,7 @@ # Plot data fig, ax = plt.subplots(figsize = (8, 6)) -data.groupby(['station']).plot(x = 'time', y = 'tmax', kind = 'line', legend = True, ax = ax, style='.-', ylabel = 'Max. Annual Temperature (°C)', title = 'Max. Temperature Report') +data.unstack('station')['tmax'].plot(kind = 'line', legend = True, ax = ax, style='.-', ylabel = 'Max. Annual Temperature (°C)', title = 'Max. Temperature Report') plt.legend(names) # Show plot diff --git a/examples/daily/compare.py b/examples/daily/compare.py index b6dc2bc..d1a99d4 100644 --- a/examples/daily/compare.py +++ b/examples/daily/compare.py @@ -13,7 +13,7 @@ data = data.fetch() # Plot data -ax = data.set_index('time').groupby(['station'])['tavg'].plot(kind = 'line', legend = True, ylabel = 'Avg. Daily Temperature °C', title = 'Average Temperature Report for 2019') +data.unstack('station')['tavg'].plot(kind = 'line', legend = True, ylabel = 'Avg. Daily Temperature °C', title = 'Average Temperature Report for 2019') plt.legend(names) # Show plot diff --git a/examples/hourly/chart.py b/examples/hourly/chart.py index d084d86..50068fa 100644 --- a/examples/hourly/chart.py +++ b/examples/hourly/chart.py @@ -2,12 +2,14 @@ from datetime import datetime import matplotlib.pyplot as plt -# Hourly +# Get a weather station stations = Stations(lat = 50, lon = 8) station = stations.fetch(1) +# Get hourly data data = Hourly(station, start = datetime(2010, 1, 1), end = datetime(2020, 1, 1, 23, 59)) data = data.fetch() -data.plot(x = 'time', y = ['temp'], kind = 'line') +# Plot chart +data.plot(y = 'temp', kind = 'line') plt.show() diff --git a/examples/hourly/interpolation.py b/examples/hourly/interpolation.py index 4677fb8..2fe1936 100644 --- a/examples/hourly/interpolation.py +++ b/examples/hourly/interpolation.py @@ -3,10 +3,9 @@ import matplotlib.pyplot as plt # Hourly -station = ['10637'] -data = Hourly(station, start = datetime(2020, 8, 1), end = datetime(2020, 8, 4, 23, 59)) +data = Hourly('10730', start = datetime(2020, 8, 1), end = datetime(2020, 8, 4, 23, 59)) data = data.normalize() -data = data.interpolate().fetch() -data.plot(x = 'time', y = ['temp'], kind = 'line') +data = data.interpolate(limit = 6).fetch() +data.plot(y = 'temp', kind = 'line') plt.show() diff --git a/examples/hourly/simple.py b/examples/hourly/simple.py index 094d020..d78d91f 100644 --- a/examples/hourly/simple.py +++ b/examples/hourly/simple.py @@ -1,4 +1,5 @@ from meteostat import Stations, Hourly +from meteostat.units import fahrenheit, direction, condition from datetime import datetime # Hourly @@ -6,4 +7,6 @@ station = stations.fetch(1) data = Hourly(station, start = datetime(2020, 1, 1), end = datetime(2020, 1, 1, 23, 59)) -print(data.fetch()) +data = data.convert({ 'temp': fahrenheit, 'wdir': direction, 'coco': condition }) +data = data.fetch() +print(data) diff --git a/meteostat/__init__.py b/meteostat/__init__.py index 7b1e48c..e31b394 100644 --- a/meteostat/__init__.py +++ b/meteostat/__init__.py @@ -1,4 +1,7 @@ """ +█▀▄▀█ █▀▀ ▀█▀ █▀▀ █▀█ █▀ ▀█▀ ▄▀█ ▀█▀ +█░▀░█ ██▄ ░█░ ██▄ █▄█ ▄█ ░█░ █▀█ ░█░ + A Python library for accessing open weather and climate data Meteorological data provided by Meteostat (https://dev.meteostat.net) @@ -9,7 +12,7 @@ """ __appname__ = "meteostat" -__version__ = "0.2.0" +__version__ = "0.3.0" from .core import Core from .stations import Stations diff --git a/meteostat/core.py b/meteostat/core.py index 7eaf493..baf697b 100644 --- a/meteostat/core.py +++ b/meteostat/core.py @@ -1,4 +1,7 @@ """ +█▀▄▀█ █▀▀ ▀█▀ █▀▀ █▀█ █▀ ▀█▀ ▄▀█ ▀█▀ +█░▀░█ ██▄ ░█░ ██▄ █▄█ ▄█ ░█░ █▀█ ░█░ + Core Class Base class that provides methods which are used across the package @@ -11,6 +14,7 @@ """ import os +import errno import time import hashlib import pandas as pd @@ -19,9 +23,6 @@ class Core: - # Temporary class storage - _temp = None - # Base URL of the Meteostat bulk data interface _endpoint = 'https://bulk.meteostat.net/' @@ -40,7 +41,7 @@ def _get_file_path(self, path = False): # Get file ID file_id = hashlib.md5(path.encode('utf-8')).hexdigest() # Return path - return self._cache_dir + os.sep + file_id + return self._cache_dir + os.sep + self._cache_subdir + os.sep + file_id else: # Return false return False @@ -48,11 +49,14 @@ def _get_file_path(self, path = False): def _file_in_cache(self, file_path = False): # Make sure the cache directory exists - if not os.path.exists(self._cache_dir): + if not os.path.exists(self._cache_dir + os.sep + self._cache_subdir): try: - os.makedirs(self._cache_dir) - except: - raise Exception('Cannot create cache directory') + os.makedirs(self._cache_dir + os.sep + self._cache_subdir) + except OSError as e: + if e.errno == errno.EEXIST: + pass + else: + raise Exception('Cannot create cache directory') if file_path: # Return the file path if it exists @@ -75,17 +79,15 @@ def _download_file(self, path = None): if path[-6:-3] == 'csv': # Read CSV file from Meteostat endpoint - try: - df = pd.read_csv(self._endpoint + path, compression = 'gzip', names = self._columns, parse_dates = self._parse_dates) - except: - return False + df = pd.read_csv(self._endpoint + path, compression = 'gzip', names = self._columns, dtype = self._types, parse_dates = self._parse_dates) # Set weather station ID if self.__class__.__name__ == 'Hourly' or self.__class__.__name__ == 'Daily': df['station'] = path[-12:-7] + df = df.set_index(['station', 'time']) - # Save as Feather - df.to_feather(local_path) + # Save as Parquet + df.to_parquet(local_path) return { 'path': local_path, @@ -131,10 +133,10 @@ def clear_cache(self, max_age = None): now = time.time() # Go through all files - for file in os.listdir(self._cache_dir): + for file in os.listdir(self._cache_dir + os.sep + self._cache_subdir): # Get full path - path = os.path.join(self._cache_dir, file) + path = os.path.join(self._cache_dir + os.sep + self._cache_subdir, file) # Check if file is older than max_age if now - os.path.getmtime(path) > max_age and os.path.isfile(path): diff --git a/meteostat/daily.py b/meteostat/daily.py index 8c3963c..2308d36 100644 --- a/meteostat/daily.py +++ b/meteostat/daily.py @@ -14,11 +14,15 @@ import datetime import pandas as pd from meteostat.core import Core +from meteostat import units from math import nan from copy import copy class Daily(Core): + # The cache subdirectory + _cache_subdir = 'daily' + # The list of weather Stations _stations = None @@ -29,7 +33,7 @@ class Daily(Core): _end = None # The data frame - _data = pd.DataFrame(index = ['station', 'time']) + _data = pd.DataFrame() # Columns _columns = [ @@ -46,12 +50,25 @@ class Daily(Core): 'tsun' ] + # Data tapes + _types = { + 'tavg': 'float64', + 'tmin': 'float64', + 'tmax': 'float64', + 'prcp': 'float64', + 'snow': 'float64', + 'wdir': 'float64', + 'wspd': 'float64', + 'wpgt': 'float64', + 'pres': 'float64', + 'tsun': 'float64' + } + # Columns for date parsing _parse_dates = { 'time': [0] } # Default aggregation functions _aggregations = { - 'time': 'first', 'tavg': 'mean', 'tmin': 'min', 'tmax': 'max', @@ -80,9 +97,11 @@ def _get_data(self, stations = None): for file in files: if os.path.isfile(file['path']) and os.path.getsize(file['path']) > 0: - df = pd.read_feather(file['path']) - self._data = self._data.append(df[(df['time'] >= self._start) & (df['time'] <= self._end)]) + df = pd.read_parquet(file['path']) + + time = df.index.get_level_values('time') + self._data = self._data.append(df.loc[(time >= self._start) & (time <= self._end)]) def __init__( self, @@ -110,6 +129,9 @@ def __init__( if isinstance(stations, pd.DataFrame): self._stations = stations else: + if not isinstance(stations, list): + stations = [stations] + self._stations = pd.DataFrame(stations, columns = ['id']) # Set start date @@ -124,80 +146,83 @@ def __init__( except: raise Exception('Cannot read daily data') + # Clear cache + self.clear_cache() + def normalize(self): # Create temporal instance - self._temp = copy(self) - - # List of columns - columns = ['station', 'time'] - - # Dynamically append columns - for column in self._temp._columns[1:]: - columns.append(column) + temp = copy(self) # Create result DataFrame - result = pd.DataFrame(columns = columns) + result = pd.DataFrame(columns = temp._columns[1:]) # Go through list of weather stations - for station in self._temp._stations['id'].tolist(): + for station in temp._stations['id'].tolist(): # Create data frame - df = pd.DataFrame(columns = columns) + df = pd.DataFrame(columns = temp._columns[1:]) # Add time series - df['time'] = pd.date_range(self._temp._start, self._temp._end, freq = '1D') + df['time'] = pd.date_range(temp._start, temp._end, freq = '1D') # Add station ID df['station'] = station # Add columns - for column in columns[2:]: + for column in temp._columns[1:]: # Add column to DataFrame df[column] = nan result = pd.concat([result, df], axis = 0) + # Set index + result = result.set_index(['station', 'time']) + # Merge data - self._temp._data = pd.concat([self._temp._data, result], axis = 0).groupby(['station', 'time'], as_index = False).first() + temp._data = pd.concat([temp._data, result], axis = 0).groupby(['station', 'time'], as_index = True).first() # Return class instance - try: - return self._temp - finally: - self._temp = None + return temp def interpolate(self, limit = 3): # Create temporal instance - self._temp = copy(self) + temp = copy(self) # Apply interpolation - self._temp._data = self._temp._data.groupby('station').apply(lambda group: group.interpolate(method = 'linear', limit = limit, limit_direction = 'both', axis = 0)) + temp._data = temp._data.groupby('station').apply(lambda group: group.interpolate(method = 'linear', limit = limit, limit_direction = 'both', axis = 0)) # Return class instance - try: - return self._temp - finally: - self._temp = None + return temp def aggregate(self, freq = None, functions = None, spatial = False): # Create temporal instance - self._temp = copy(self) + temp = copy(self) # Update default aggregations if functions is not None: - self._temp._aggregations.update(functions) + temp._aggregations.update(functions) # Time aggregation - self._temp._data = self._temp._data.groupby(['station', pd.Grouper(key = 'time', freq = freq)]).agg(self._aggregations) + temp._data = temp._data.groupby(['station', pd.Grouper(level = 'time', freq = freq)]).agg(temp._aggregations) # Spatial aggregation if spatial: - self._temp._data = self._temp._data.groupby([pd.Grouper(key = 'time', freq = freq)]).mean() + temp._data = temp._data.groupby([pd.Grouper(level = 'time', freq = freq)]).mean() # Return class instance - try: - return self._temp - finally: - self._temp = None + return temp + + def convert(self, units): + + # Create temporal instance + temp = copy(self) + + # Change data units + for parameter, unit in units.items(): + + temp._data[parameter] = temp._data[parameter].apply(unit) + + # Return class instance + return temp def coverage(self, parameter = None): @@ -215,5 +240,12 @@ def count(self): def fetch(self): - # Return data frame - return copy(self._data) + # Copy DataFrame + temp = copy(self._data) + + # Remove station index if it's a single station + if len(self._stations.index) == 1 and 'station' in temp.index.names: + temp = temp.reset_index(level = 'station', drop = True) + + # Return data frame + return temp diff --git a/meteostat/hourly.py b/meteostat/hourly.py index 9481236..8cbe4fe 100644 --- a/meteostat/hourly.py +++ b/meteostat/hourly.py @@ -14,11 +14,15 @@ import datetime import pandas as pd from meteostat.core import Core +from meteostat import units from math import floor, nan from copy import copy class Hourly(Core): + # The cache subdirectory + _cache_subdir = 'hourly' + # The list of weather Stations _stations = None @@ -48,12 +52,25 @@ class Hourly(Core): 'coco' ] + # Data tapes + _types = { + 'temp': 'float64', + 'dwpt': 'float64', + 'rhum': 'float64', + 'prcp': 'float64', + 'snow': 'float64', + 'wdir': 'float64', + 'wspd': 'float64', + 'wpgt': 'float64', + 'tsun': 'float64', + 'coco': 'float64' + } + # Columns for date parsing _parse_dates = { 'time': [0, 1] } # Default aggregation functions _aggregations = { - 'time': 'first', 'temp': 'mean', 'dwpt': 'mean', 'rhum': 'mean', @@ -83,9 +100,11 @@ def _get_data(self, stations = None): for file in files: if os.path.isfile(file['path']) and os.path.getsize(file['path']) > 0: - df = pd.read_feather(file['path']) - self._data = self._data.append(df[(df['time'] >= self._start) & (df['time'] <= self._end)]) + df = pd.read_parquet(file['path']) + + time = df.index.get_level_values('time') + self._data = df.loc[(time >= self._start) & (time <= self._end)] def __init__( self, @@ -113,6 +132,9 @@ def __init__( if isinstance(stations, pd.DataFrame): self._stations = stations else: + if not isinstance(stations, list): + stations = [stations] + self._stations = pd.DataFrame(stations, columns = ['id']) # Set start date @@ -127,80 +149,83 @@ def __init__( except: raise Exception('Cannot read hourly data') + # Clear cache + self.clear_cache() + def normalize(self): # Create temporal instance - self._temp = copy(self) - - # List of columns - columns = ['station', 'time'] - - # Dynamically append columns - for column in self._temp._columns[2:]: - columns.append(column) + temp = copy(self) # Create result DataFrame - result = pd.DataFrame(columns = columns) + result = pd.DataFrame(columns = temp._columns[2:]) # Go through list of weather stations - for station in self._temp._stations['id'].tolist(): + for station in temp._stations['id'].tolist(): # Create data frame - df = pd.DataFrame(columns = columns) + df = pd.DataFrame(columns = temp._columns[2:]) # Add time series - df['time'] = pd.date_range(self._temp._start, self._temp._end, freq='1H') + df['time'] = pd.date_range(temp._start, temp._end, freq = '1H') # Add station ID df['station'] = station # Add columns - for column in columns[2:]: + for column in temp._columns[2:]: # Add column to DataFrame df[column] = nan result = pd.concat([result, df], axis = 0) + # Set index + result = result.set_index(['station', 'time']) + # Merge data - self._temp._data = pd.concat([self._temp._data, result], axis = 0).groupby(['station', 'time'], as_index = False).first() + temp._data = pd.concat([temp._data, result], axis = 0).groupby(['station', 'time'], as_index = True).first() # Return class instance - try: - return self._temp - finally: - self._temp = None + return temp def interpolate(self, limit = 3): # Create temporal instance - self._temp = copy(self) + temp = copy(self) # Apply interpolation - self._temp._data = self._temp._data.groupby('station').apply(lambda group: group.interpolate(method = 'linear', limit = limit, limit_direction = 'both', axis = 0)) + temp._data = temp._data.groupby(level = 'station').apply(lambda group: group.interpolate(method = 'linear', limit = limit, limit_direction = 'both', axis = 0)) # Return class instance - try: - return self._temp - finally: - self._temp = None + return temp def aggregate(self, freq = None, functions = None, spatial = False): # Create temporal instance - self._temp = copy(self) + temp = copy(self) # Update default aggregations if functions is not None: - self._temp._aggregations.update(functions) + temp._aggregations.update(functions) # Time aggregation - self._temp._data = self._temp._data.groupby(['station', pd.Grouper(key = 'time', freq = freq)]).agg(self._temp._aggregations) + temp._data = temp._data.groupby(['station', pd.Grouper(level = 'time', freq = freq)]).agg(temp._aggregations) # Spatial aggregation if spatial: - self._temp._data = self._temp._data.groupby([pd.Grouper(key = 'time', freq = freq)]).mean() + temp._data = temp._data.groupby([pd.Grouper(key = 'time', freq = freq)]).mean() # Return class instance - try: - return self._temp - finally: - self._temp = None + return temp + + def convert(self, units): + + # Create temporal instance + temp = copy(self) + + # Change data units + for parameter, unit in units.items(): + + temp._data[parameter] = temp._data[parameter].apply(unit) + + # Return class instance + return temp def coverage(self, parameter = None): @@ -218,5 +243,12 @@ def count(self): def fetch(self): + # Copy DataFrame + temp = copy(self._data) + + # Remove station index if it's a single station + if len(self._stations.index) == 1 and 'station' in temp.index.names: + temp = temp.reset_index(level = 'station', drop = True) + # Return data frame - return copy(self._data) + return temp diff --git a/meteostat/stations.py b/meteostat/stations.py index ac55400..31a87a2 100644 --- a/meteostat/stations.py +++ b/meteostat/stations.py @@ -17,6 +17,9 @@ class Stations(Core): + # The cache subdirectory + _cache_subdir = 'stations' + # The list of selected weather Stations _stations = None @@ -38,6 +41,19 @@ class Stations(Core): 'daily_end' ] + _types = { + 'id': 'string', + 'name': 'object', + 'country': 'string', + 'region': 'string', + 'wmo': 'string', + 'icao': 'string', + 'latitude': 'float64', + 'longitude': 'float64', + 'elevation': 'float64', + 'timezone': 'string' + } + # Columns for date parsing _parse_dates = [10, 11, 12, 13] @@ -74,7 +90,7 @@ def __init__( # Get all weather stations try: file = self._load(['stations/lib.csv.gz'])[0] - self._stations = pd.read_feather(file['path']) + self._stations = pd.read_parquet(file['path']) except: raise Exception('Cannot read weather station directory') @@ -102,6 +118,9 @@ def __init__( if hourly != None: self._inventory(None, hourly) + # Clear cache + self.clear_cache() + def _identifier(self, id = None, wmo = None, icao = None): # Get station by Meteostat ID @@ -191,30 +210,35 @@ def _inventory(self, daily = None, hourly = None): return self - def sample(self, limit = 1): + def convert(self, units): # Create temporal instance - self._temp = copy(self) + temp = copy(self) - # Randomize the order of weather stations - self._temp._stations = self._temp._stations.sample(limit) + # Change data units + for parameter, unit in units.items(): + + temp._stations[parameter] = temp._stations[parameter].apply(unit) # Return class instance - try: - return self._temp - finally: - self._temp = None + return temp def count(self): # Return number of weather stations in current selection return len(self._stations.index) - def fetch(self, limit = False): + def fetch(self, limit = False, sample = False): + + # Copy DataFrame + temp = copy(self._stations) - if limit: - # Return data frame with limit - return copy(self._stations.head(limit)) + if sample and limit: + # Return limited number of sampled entries + return temp.sample(limit) + elif limit: + # Return limited number of entries + return temp.head(limit) else: # Return all entries - return copy(self._stations) + return temp diff --git a/meteostat/units.py b/meteostat/units.py new file mode 100644 index 0000000..6dea18e --- /dev/null +++ b/meteostat/units.py @@ -0,0 +1,98 @@ +""" +Meteorological Data Units + +Convert a Pandas Series to any meteorological data unit + +The code is licensed under the MIT license. +""" + +from math import nan, isnan + +# Convert Celsius to Fahrenheit +def fahrenheit(value): + + return round((value * 9/5) + 32, 1) + +# Convert Celsius to Kelvin +def kelvin(value): + + return round(value + 273.15, 1) + +# Convert millimeters to inches +def inches(value): + + return round(value / 25.4, 1) + +# Convert meters to feet +def feet(value): + + return round(value / 0.3048, 1) + +# Convert kilometers per hour to meters per second +def ms(value): + + return round(value / 3.6, 1) + +# Convert kilometers per hour to miles per hour +def mph(value): + + return round(value * 0.6214, 1) + +# Convert degrees to wind direction +def direction(value): + + if (value >= 337 and value <= 360) or value <= 23: + return 'N' + elif value >= 24 and value <= 68: + return 'NE' + elif value >= 69 and value <= 113: + return 'E' + elif value >= 114 and value <= 158: + return 'SE' + elif value >= 159 and value <= 203: + return 'S' + elif value >= 204 and value <= 248: + return 'SW' + elif value >= 249 and value <= 293: + return 'W' + elif value >= 294 and value <= 336: + return 'NW' + else: + return nan + + +# Convert Meteostat condition code to descriptive string +def condition(value): + + if isnan(value) or value < 1 or value > 27: + return nan + else: + return [ + 'Clear', + 'Fair', + 'Cloudy', + 'Overcast', + 'Fog', + 'Freezing Fog', + 'Light Rain', + 'Rain', + 'Heavy Rain', + 'Freezing Rain', + 'Heavy Freezing Rain', + 'Sleet', + 'Heavy Sleet', + 'Light Snowfall', + 'Snowfall', + 'Heavy Snowfall', + 'Rain Shower', + 'Heavy Rain Shower', + 'Sleet Shower', + 'Heavy Sleet Shower', + 'Snow Shower', + 'Heavy Snow Shower', + 'Lightning', + 'Hail', + 'Thunderstorm', + 'Heavy Thunderstorm', + 'Storm', + ][int(value) - 1] diff --git a/setup.py b/setup.py index 59a7b30..5f82512 100644 --- a/setup.py +++ b/setup.py @@ -9,13 +9,13 @@ # Setup setup( name = 'meteostat', - version = '0.2.0', + version = '0.3.0', author = 'Meteostat', author_email = 'info@meteostat.net', description = 'Access and analyze historical weather and climate data with Python.', long_description = long_description, long_description_content_type = 'text/markdown', - url = 'https://github.com/meteostat/meteostat-python', + url = 'https://dev.meteostat.net/python/', packages = find_packages(), include_package_data = True, install_requires = ['pandas', 'pyarrow'],