diff --git a/example/modelchain_example.ipynb b/example/modelchain_example.ipynb index 7d80f76b..696301b2 100644 --- a/example/modelchain_example.ipynb +++ b/example/modelchain_example.ipynb @@ -41,7 +41,7 @@ "import requests\n", "\n", "from windpowerlib import ModelChain, WindTurbine, create_power_curve\n", - "from windpowerlib import wind_turbine as wt" + "from windpowerlib import data as wt" ] }, { diff --git a/tests/test_data_handling.py b/tests/test_data_handling.py new file mode 100644 index 00000000..5e2bd46e --- /dev/null +++ b/tests/test_data_handling.py @@ -0,0 +1,10 @@ +""" +SPDX-FileCopyrightText: 2019 oemof developer group +SPDX-License-Identifier: MIT +""" + +from windpowerlib import data + + +def test_integretiy_check(): + pass diff --git a/tests/test_wind_turbine.py b/tests/test_wind_turbine.py index 12af8763..5d4e4bf0 100644 --- a/tests/test_wind_turbine.py +++ b/tests/test_wind_turbine.py @@ -12,11 +12,11 @@ from windpowerlib.wind_turbine import ( get_turbine_data_from_file, WindTurbine, - get_turbine_types, WindTurbineGroup, - load_turbine_data_from_oedb, ) +from windpowerlib.data import store_turbine_data_from_oedb, get_turbine_types + class TestWindTurbine: @classmethod @@ -54,7 +54,7 @@ def test_wrong_url_load_turbine_data(self): with pytest.raises( ConnectionError, match="Database connection not successful" ): - load_turbine_data_from_oedb("wrong_schema") + store_turbine_data_from_oedb("wrong_schema") @pytest.mark.filterwarnings("ignore:The WindTurbine") def test_string_representation_of_wind_turbine(self): diff --git a/windpowerlib/__init__.py b/windpowerlib/__init__.py index 86aa5125..f65b4c63 100644 --- a/windpowerlib/__init__.py +++ b/windpowerlib/__init__.py @@ -3,8 +3,8 @@ __version__ = "0.2.1dev" from .wind_turbine import WindTurbine # noqa: F401 -from .wind_turbine import get_turbine_types # noqa: F401 -from .wind_turbine import create_power_curve # noqa: F401 +from .data import get_turbine_types # noqa: F401 +from .power_curves import create_power_curve # noqa: F401 from .wind_farm import WindFarm # noqa: F401 from .wind_turbine_cluster import WindTurbineCluster # noqa: F401 from .modelchain import ModelChain # noqa: F401 diff --git a/windpowerlib/data.py b/windpowerlib/data.py new file mode 100644 index 00000000..62c3b3a7 --- /dev/null +++ b/windpowerlib/data.py @@ -0,0 +1,326 @@ +""" +The ``data`` module contains functions to handle the needed data. + +SPDX-FileCopyrightText: 2019 oemof developer group +SPDX-License-Identifier: MIT +""" + +import os +import warnings +import logging +from datetime import datetime +from shutil import copyfile + +import pandas as pd +import requests + +from windpowerlib.wind_turbine import WindTurbine + + +def get_turbine_types(turbine_library="local", print_out=True, filter_=True): + r""" + Get all provided wind turbine types provided. + + Choose by `turbine_library` whether to get wind turbine types provided by + the OpenEnergy Database ('oedb') or wind turbine types provided in your + local file(s) ('local'). + By default only turbine types for which a power coefficient curve or power + curve is provided are returned. Set `filter_=False` to see all turbine + types for which any data (e.g. hub height, rotor diameter, ...) is + provided. + + Parameters + ---------- + turbine_library : str + Specifies if the oedb turbine library ('oedb') or your local turbine + data file ('local') is evaluated. Default: 'local'. + print_out : bool + Directly prints a tabular containing the turbine types in column + 'turbine_type', the manufacturer in column 'manufacturer' and + information about whether a power (coefficient) curve exists (True) or + not (False) in columns 'has_power_curve' and 'has_cp_curve'. + Default: True. + filter_ : bool + If True only turbine types for which a power coefficient curve or + power curve is provided in the oedb turbine library are + returned. Default: True. + + Returns + ------- + :pandas:`pandas.DataFrame` + Contains turbine types in column 'turbine_type', the manufacturer in + column 'manufacturer' and information about whether a power + (coefficient) curve exists (True) or not (False) in columns + 'has_power_curve' and 'has_cp_curve'. + + Notes + ----- + If the power (coefficient) curve of the desired turbine type (or the + turbine type itself) is missing you can contact us via github or + windpowerlib@rl-institut.de. You can help us by providing data in the + format as shown in + `the data base `_. + + Examples + -------- + >>> from windpowerlib import get_turbine_types + >>> df=get_turbine_types(print_out=False) + >>> print(df[df["turbine_type"].str.contains("E-126")].iloc[0]) + manufacturer Enercon + turbine_type E-126/4200 + has_power_curve True + has_cp_curve True + Name: 5, dtype: object + >>> print(df[df["manufacturer"].str.contains("Enercon")].iloc[0]) + manufacturer Enercon + turbine_type E-101/3050 + has_power_curve True + has_cp_curve True + Name: 1, dtype: object + + """ + if turbine_library == "local": + filename = os.path.join( + os.path.dirname(__file__), "oedb", "turbine_data.csv" + ) + df = pd.read_csv(filename, index_col=0).reset_index() + elif turbine_library == "oedb": + df = fetch_turbine_data_from_oedb() + + else: + raise ValueError( + "`turbine_library` is '{}' ".format(turbine_library) + + "but must be 'local' or 'oedb'." + ) + if filter_: + cp_curves_df = df.loc[df["has_cp_curve"]][ + ["manufacturer", "turbine_type", "has_cp_curve"] + ] + p_curves_df = df.loc[df["has_power_curve"]][ + ["manufacturer", "turbine_type", "has_power_curve"] + ] + curves_df = pd.merge( + p_curves_df, cp_curves_df, how="outer", sort=True + ).fillna(False) + else: + curves_df = df[ + ["manufacturer", "turbine_type", "has_power_curve", "has_cp_curve"] + ] + if print_out: + pd.set_option("display.max_rows", len(curves_df)) + print(curves_df) + pd.reset_option("display.max_rows") + return curves_df + + +def fetch_turbine_data_from_oedb( + schema="supply", table="wind_turbine_library" +): + r""" + Fetches turbine library from the OpenEnergy database (oedb). + + Parameters + ---------- + schema : str + Database schema of the turbine library. + table : str + Table name of the turbine library. + + Returns + ------- + :pandas:`pandas.DataFrame` + Turbine data of different turbines such as 'manufacturer', + 'turbine_type', 'nominal_power'. + + """ + # url of OpenEnergy Platform that contains the oedb + oep_url = "http://oep.iks.cs.ovgu.de/" + + # load data + result = requests.get( + oep_url + "/api/v0/schema/{}/tables/{}/rows/?".format(schema, table), + ) + if not result.status_code == 200: + raise ConnectionError( + "Database connection not successful. " + "Response: [{}]".format(result.status_code) + ) + # extract data to dataframe + return pd.DataFrame(result.json()) + + +def load_turbine_data_from_oedb(schema="supply", table="wind_turbine_library"): + msg = ( + "\nUse >>store_turbine_data_from_oedb<< and not" + " >>load_turbine_data_from_oedb<<" + ) + warnings.warn(msg, FutureWarning) + return store_turbine_data_from_oedb(schema=schema, table=table) + + +def store_turbine_data_from_oedb( + schema="supply", table="wind_turbine_library" +): + r""" + Loads turbine library from the OpenEnergy database (oedb). + + Turbine data is saved to csv files ('oedb_power_curves.csv', + 'oedb_power_coefficient_curves.csv' and 'oedb_nominal_power') for offline + usage of the windpowerlib. If the files already exist they are overwritten. + + Parameters + ---------- + schema : str + Database schema of the turbine library. + table : str + Table name of the turbine library. + + Returns + ------- + :pandas:`pandas.DataFrame` + Turbine data of different turbines such as 'manufacturer', + 'turbine_type', 'nominal_power'. + + """ + turbine_data = fetch_turbine_data_from_oedb(schema=schema, table=table) + # standard file name for saving data + filename = os.path.join(os.path.dirname(__file__), "oedb", "{}.csv") + + time_stamp = datetime.now().strftime("%Y%m%d%H%M%S") + # get all power (coefficient) curves and save to file + # for curve_type in ['power_curve', 'power_coefficient_curve']: + for curve_type in ["power_curve", "power_coefficient_curve"]: + curves_df = pd.DataFrame(columns=["wind_speed"]) + for index in turbine_data.index: + if ( + turbine_data["{}_wind_speeds".format(curve_type)][index] + and turbine_data["{}_values".format(curve_type)][index] + ): + df = ( + pd.DataFrame( + data=[ + eval( + turbine_data[ + "{}_wind_speeds".format(curve_type) + ][index] + ), + eval( + turbine_data["{}_values".format(curve_type)][ + index + ] + ), + ] + ) + .transpose() + .rename( + columns={ + 0: "wind_speed", + 1: turbine_data["turbine_type"][index], + } + ) + ) + curves_df = pd.merge( + left=curves_df, right=df, how="outer", on="wind_speed" + ) + curves_df = curves_df.set_index("wind_speed").sort_index().transpose() + # power curve values in W + if curve_type == "power_curve": + curves_df *= 1000 + curves_df.index.name = "turbine_type" + copyfile( + filename.format("{}s".format(curve_type)), + filename.format("{0}s_{1}".format(curve_type, time_stamp)), + ) + curves_df.to_csv(filename.format("{}s".format(curve_type))) + + # get turbine data and save to file (excl. curves) + turbine_data_df = turbine_data.drop( + [ + "power_curve_wind_speeds", + "power_curve_values", + "power_coefficient_curve_wind_speeds", + "power_coefficient_curve_values", + "thrust_coefficient_curve_wind_speeds", + "thrust_coefficient_curve_values", + ], + axis=1, + ).set_index("turbine_type") + # nominal power in W + turbine_data_df["nominal_power"] *= 1000 + copyfile( + filename.format("turbine_data"), + filename.format("turbine_data_{0}".format(time_stamp)), + ) + check_imported_data(turbine_data_df, filename, time_stamp) + turbine_data_df.to_csv(filename.format("turbine_data")) + remove_tmp_file(filename, time_stamp) + return turbine_data + + +def remove_tmp_file(filename, time_stamp): + os.remove(filename.format("turbine_data_{0}".format(time_stamp))) + for curve_type in ["power_curve", "power_coefficient_curve"]: + copyfile( + filename.format("{0}s_{1}".format(curve_type, time_stamp)), + filename.format("{}s".format(curve_type)), + ) + os.remove(filename.format("{0}s_{1}".format(curve_type, time_stamp))) + + +def check_imported_data(data, filename, time_stamp): + data.to_csv(filename.format("turbine_data")) + try: + data = check_data_integretiy(data) + except Exception as e: + copyfile( + filename.format("turbine_data"), + filename.format("turbine_data_error{0}".format(time_stamp)), + ) + copyfile( + filename.format("turbine_data_{0}".format(time_stamp)), + filename.format("turbine_data"), + ) + for curve_type in ["power_curve", "power_coefficient_curve"]: + copyfile( + filename.format("{}s".format(curve_type)), + filename.format( + "{0}s_error_{1}".format(curve_type, time_stamp) + ), + ) + copyfile( + filename.format("{0}s_{1}".format(curve_type, time_stamp)), + filename.format("{}s".format(curve_type)), + ) + remove_tmp_file(filename, time_stamp) + raise e + return data + + +def check_data_integretiy(data): + for dataset in data.iterrows(): + ttype = dataset[0] + enercon_e126 = {"turbine_type": "{0}".format(ttype), "hub_height": 135} + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + wt = WindTurbine(**enercon_e126) + if wt.power_curve is None and dataset[1].has_power_curve is True: + logging.warning( + "{0}: No power curve but has_power_curve=True.".format( + ttype + ) + ) + if ( + wt.power_coefficient_curve is None + and dataset[1].has_cp_curve is True + ): + logging.warning( + "{0}: No cp-curve but has_cp_curve=True.".format(ttype) + ) + if dataset[1].has_power_curve is True: + if len(wt.power_curve) < 22: + logging.warning( + "{0}: power_curve is to short ({1} values),".format( + ttype, len(wt.power_curve) + ) + ) + return data diff --git a/windpowerlib/power_curves.py b/windpowerlib/power_curves.py index 00549a01..43df0071 100644 --- a/windpowerlib/power_curves.py +++ b/windpowerlib/power_curves.py @@ -266,3 +266,24 @@ def wake_losses_to_power_curve( "but is {}".format(type(wind_farm_efficiency)) ) return power_curve_df + + +def create_power_curve(wind_speed, power): + """ + A list, numpy.array, pandas.Series or other iterables can be passed to + define the wind speed and the power output. Make sure that the order is + not mutable because, values from both parameters will be used as value + pairs. + + Parameters + ---------- + wind_speed : iterable + A series of wind speed values in meter per second [m/s]. + power : iterable + A series of power values in Watt [W]. + + Returns + ------- + pandas.DataFrame + """ + return pd.DataFrame(data={"value": power, "wind_speed": wind_speed}) diff --git a/windpowerlib/wind_turbine.py b/windpowerlib/wind_turbine.py index 1c71aab9..f600de70 100644 --- a/windpowerlib/wind_turbine.py +++ b/windpowerlib/wind_turbine.py @@ -9,7 +9,6 @@ import pandas as pd import logging import warnings -import requests import os from windpowerlib.tools import WindpowerlibUserWarning from typing import NamedTuple @@ -409,215 +408,10 @@ def get_turbine_data_from_file(turbine_type, path): return wpp_df -def create_power_curve(wind_speed, power): - """ - A list, numpy.array, pandas.Series or other iterables can be passed to - define the wind speed and the power output. Make sure that the order is - not mutable because, values from both parameters will be used as value - pairs. - - Parameters - ---------- - wind_speed : iterable - A series of wind speed values in meter per second [m/s]. - power : iterable - A series of power values in Watt [W]. - - Returns - ------- - pandas.DataFrame - """ - return pd.DataFrame(data={"value": power, "wind_speed": wind_speed}) - - -def load_turbine_data_from_oedb(schema="supply", table="wind_turbine_library"): - r""" - Loads turbine library from the OpenEnergy database (oedb). - - Turbine data is saved to csv files ('oedb_power_curves.csv', - 'oedb_power_coefficient_curves.csv' and 'oedb_nominal_power') for offline - usage of the windpowerlib. If the files already exist they are overwritten. - - Parameters - ---------- - schema : str - Database schema of the turbine library. - table : str - Table name of the turbine library. - - Returns - ------- - :pandas:`pandas.DataFrame` - Turbine data of different turbines such as 'manufacturer', - 'turbine_type', 'nominal_power'. - - """ - # url of OpenEnergy Platform that contains the oedb - oep_url = "http://oep.iks.cs.ovgu.de/" - - # load data - result = requests.get( - oep_url + "/api/v0/schema/{}/tables/{}/rows/?".format(schema, table), - ) - if not result.status_code == 200: - raise ConnectionError( - "Database connection not successful. " - "Response: [{}]".format(result.status_code) - ) - # extract data to dataframe - turbine_data = pd.DataFrame(result.json()) - # standard file name for saving data - filename = os.path.join(os.path.dirname(__file__), "oedb", "{}.csv") - # get all power (coefficient) curves and save to file - # for curve_type in ['power_curve', 'power_coefficient_curve']: - for curve_type in ["power_curve", "power_coefficient_curve"]: - curves_df = pd.DataFrame(columns=["wind_speed"]) - for index in turbine_data.index: - if ( - turbine_data["{}_wind_speeds".format(curve_type)][index] - and turbine_data["{}_values".format(curve_type)][index] - ): - df = ( - pd.DataFrame( - data=[ - eval( - turbine_data[ - "{}_wind_speeds".format(curve_type) - ][index] - ), - eval( - turbine_data["{}_values".format(curve_type)][ - index - ] - ), - ] - ) - .transpose() - .rename( - columns={ - 0: "wind_speed", - 1: turbine_data["turbine_type"][index], - } - ) - ) - curves_df = pd.merge( - left=curves_df, right=df, how="outer", on="wind_speed" - ) - curves_df = curves_df.set_index("wind_speed").sort_index().transpose() - # power curve values in W - if curve_type == "power_curve": - curves_df *= 1000 - curves_df.index.name = "turbine_type" - curves_df.to_csv(filename.format("{}s".format(curve_type))) - - # get turbine data and save to file (excl. curves) - turbine_data_df = turbine_data.drop( - [ - "power_curve_wind_speeds", - "power_curve_values", - "power_coefficient_curve_wind_speeds", - "power_coefficient_curve_values", - "thrust_coefficient_curve_wind_speeds", - "thrust_coefficient_curve_values", - ], - axis=1, - ).set_index("turbine_type") - # nominal power in W - turbine_data_df["nominal_power"] *= 1000 - turbine_data_df.to_csv(filename.format("turbine_data")) - return turbine_data - - def get_turbine_types(turbine_library="local", print_out=True, filter_=True): - r""" - Get all provided wind turbine types provided. - - Choose by `turbine_library` whether to get wind turbine types provided by - the OpenEnergy Database ('oedb') or wind turbine types provided in your - local file(s) ('local'). - By default only turbine types for which a power coefficient curve or power - curve is provided are returned. Set `filter_=False` to see all turbine - types for which any data (e.g. hub height, rotor diameter, ...) is - provided. - - Parameters - ---------- - turbine_library : str - Specifies if the oedb turbine library ('oedb') or your local turbine - data file ('local') is evaluated. Default: 'local'. - print_out : bool - Directly prints a tabular containing the turbine types in column - 'turbine_type', the manufacturer in column 'manufacturer' and - information about whether a power (coefficient) curve exists (True) or - not (False) in columns 'has_power_curve' and 'has_cp_curve'. - Default: True. - filter_ : bool - If True only turbine types for which a power coefficient curve or - power curve is provided in the oedb turbine library are - returned. Default: True. - - Returns - ------- - :pandas:`pandas.DataFrame` - Contains turbine types in column 'turbine_type', the manufacturer in - column 'manufacturer' and information about whether a power - (coefficient) curve exists (True) or not (False) in columns - 'has_power_curve' and 'has_cp_curve'. - - Notes - ----- - If the power (coefficient) curve of the desired turbine type (or the - turbine type itself) is missing you can contact us via github or - windpowerlib@rl-institut.de. You can help us by providing data in the - format as shown in - `the data base `_. - - Examples - -------- - >>> from windpowerlib import wind_turbine - >>> df=wind_turbine.get_turbine_types(print_out=False) - >>> print(df[df["turbine_type"].str.contains("E-126")].iloc[0]) - manufacturer Enercon - turbine_type E-126/4200 - has_power_curve True - has_cp_curve True - Name: 5, dtype: object - >>> print(df[df["manufacturer"].str.contains("Enercon")].iloc[0]) - manufacturer Enercon - turbine_type E-101/3050 - has_power_curve True - has_cp_curve True - Name: 1, dtype: object - - """ - if turbine_library == "local": - filename = os.path.join( - os.path.dirname(__file__), "oedb", "turbine_data.csv" - ) - df = pd.read_csv(filename, index_col=0).reset_index() - elif turbine_library == "oedb": - df = load_turbine_data_from_oedb() - else: - raise ValueError( - "`turbine_library` is '{}' ".format(turbine_library) - + "but must be 'local' or 'oedb'." - ) - if filter_: - cp_curves_df = df.loc[df["has_cp_curve"]][ - ["manufacturer", "turbine_type", "has_cp_curve"] - ] - p_curves_df = df.loc[df["has_power_curve"]][ - ["manufacturer", "turbine_type", "has_power_curve"] - ] - curves_df = pd.merge( - p_curves_df, cp_curves_df, how="outer", sort=True - ).fillna(False) - else: - curves_df = df[ - ["manufacturer", "turbine_type", "has_power_curve", "has_cp_curve"] - ] - if print_out: - pd.set_option("display.max_rows", len(curves_df)) - print(curves_df) - pd.reset_option("display.max_rows") - return curves_df + print(turbine_library, print_out, filter_) + msg = ( + "\nUse >>from windpowerlib import get_turbine_types<< not" + ">>from windpowerlib.turbine import get_turbine_types<< not " + ) + raise (ImportError, msg)