55Data providers for weather, agromanagement, soil, crop and site data. Also
66a class for testing STU suitability for a given crop.
77
8- Data providers are compatible with a CGMS 9 database schema.
8+ Data providers are compatible with a CGMS 8 database schema.
99"""
10- import datetime
10+ import datetime as dt
1111
1212from sqlalchemy import MetaData , select , Table , and_
1313import yaml
1414
15- from ...util import check_date
15+ from ...util import check_date , wind10to2
1616from ... import exceptions as exc
17- from ..cgms11 .data_providers import fetch_crop_name
17+ from ..cgms12 .data_providers import fetch_crop_name
18+ from ...base_classes import WeatherDataProvider , WeatherDataContainer
19+
20+ #----------------------------------------------------------------------------
21+ class GridWeatherDataProvider (WeatherDataProvider ):
22+ """Retrieves meteodata from the GRID_WEATHER table in a CGMS database.
23+
24+ :param metadata: SqlAlchemy metadata object providing DB access
25+ :param grid_no: Grid ID of PyWofost run
26+ :param startdate: Retrieve meteo data starting with startdate
27+ (datetime.date object)
28+ :param enddate: Retrieve meteo data up to and including enddate
29+ (datetime.date object)
30+
31+ Note that all meteodata is first retrieved from the DB and stored
32+ internally. Therefore, no DB connections are stored within the class
33+ instance. This makes that class instances can be pickled.
34+
35+ """
36+
37+ def __init__ (self , engine , grid_no , start_date = None , end_date = None ):
38+
39+ WeatherDataProvider .__init__ (self )
40+ if start_date is None :
41+ start_date = dt .date (dt .MINYEAR , 1 , 1 )
42+ if end_date is None :
43+ end_date = dt .date (dt .MAXYEAR , 1 , 1 )
44+ self .grid_no = grid_no
45+ self .start_date = self .check_keydate (start_date )
46+ self .end_date = self .check_keydate (end_date )
47+ self .timeinterval = (end_date - start_date ).days + 1
48+
49+ metadata = MetaData (engine )
50+
51+ # Get location info (lat/lon/elevation)
52+ self ._fetch_location_from_db (metadata )
53+
54+ # Retrieved meteo data
55+ self ._fetch_grid_weather_from_db (metadata )
56+
57+ #---------------------------------------------------------------------------
58+ def _fetch_location_from_db (self , metadata ):
59+ """Retrieves latitude, longitude, elevation from 'grid' table and
60+ assigns them to self.latitude, self.longitude, self.elevation."""
61+
62+ # Pull Latitude value for grid nr from database
63+
64+ try :
65+ table_grid = Table ('grid' , metadata , autoload = True )
66+ r = select ([table_grid .c .latitude , table_grid .c .longitude ,
67+ table_grid .c .altitude ],
68+ table_grid .c .grid_no == self .grid_no ).execute ()
69+ row = r .fetchone ()
70+ r .close ()
71+ if row is None :
72+ raise Exception
73+ except Exception as e :
74+ msg = "Failed deriving location info for grid %s: %s" % (self .grid_no , e )
75+ raise exc .PCSEError (msg )
76+
77+ self .latitude = row .latitude
78+ self .longitude = row .longitude
79+ self .elevation = row .altitude
80+
81+ msg = "Succesfully retrieved location information from 'grid' table " + \
82+ "for grid %s"
83+ self .logger .info (msg % self .grid_no )
84+
85+ def _fetch_grid_weather_from_db (self , metadata ):
86+ """Retrieves the meteo data from table 'grid_weather'.
87+ """
88+
89+ try :
90+ table_gw = Table ('grid_weather' , metadata , autoload = True )
91+ r = select ([table_gw ],and_ (table_gw .c .grid_no == self .grid_no ,
92+ table_gw .c .day >= self .start_date ,
93+ table_gw .c .day <= self .end_date )
94+ ).execute ()
95+ rows = r .fetchall ()
96+
97+ c = len (rows )
98+ if c < self .timeinterval :
99+ msg = "Only %i records selected from table 'grid_weather' " + \
100+ "for grid %i, period %s -- %s."
101+ self .logger .warn (msg % (c , self .grid_no , self .start_date ,
102+ self .end_date ))
103+
104+ meteopackager = self ._make_WeatherDataContainer
105+ for row in rows :
106+ DAY = self .check_keydate (row .day )
107+ t = {"DAY" : DAY , "LAT" : self .latitude ,
108+ "LON" : self .longitude , "ELEV" : self .elevation }
109+ wdc = meteopackager (row , t )
110+ self ._store_WeatherDataContainer (wdc , DAY )
111+ except Exception as e :
112+ errstr = "Failure reading meteodata: " + str (e )
113+ raise exc .PCSEError (errstr )
114+
115+ msg = ("Successfully retrieved weather data from 'grid_weather' table "
116+ "for grid %s between %s and %s" )
117+ self .logger .info (msg % (self .grid_no , self .start_date , self .end_date ))
118+
119+ #---------------------------------------------------------------------------
120+ def _make_WeatherDataContainer (self , row , t ):
121+ """Process record from grid_weather including unit conversion."""
122+
123+ t .update ({"TMAX" : float (row .maximum_temperature ),
124+ "TMIN" : float (row .minimum_temperature ),
125+ "VAP" : float (row .vapour_pressure ),
126+ "WIND" : wind10to2 (float (row .windspeed )),
127+ "RAIN" : float (row .rainfall )/ 10. ,
128+ "E0" : float (row .e0 )/ 10. ,
129+ "ES0" : float (row .es0 )/ 10. ,
130+ "ET0" : float (row .et0 )/ 10. ,
131+ "IRRAD" : float (row .calculated_radiation )* 1000. })
132+ wdc = WeatherDataContainer (** t )
133+
134+ return wdc
135+
18136
19137
20138class AgroManagementDataProvider (list ):
21- """Class for providing agromanagement data from the CROP_CALENDAR table in a CGMS9 database.
139+ """Class for providing agromanagement data from the CROP_CALENDAR table in a CGMS8 database.
22140
23141 :param engine: SqlAlchemy engine object providing DB access
24142 :param grid_no: Integer grid ID, maps to the grid_no column in the table
@@ -71,7 +189,7 @@ def __init__(self, engine, grid_no, crop_no, campaign_year):
71189 year = int (row .year )
72190 month = int (row .start_month1 )
73191 day = int (row .start_monthday1 )
74- self .crop_start_date = check_date (datetime .date (year , month , day ))
192+ self .crop_start_date = check_date (dt .date (year , month , day ))
75193 self .campaign_start_date = self .crop_start_date
76194
77195 # Determine the start date/type. Only sowing|emergence is accepted by PCSE/WOFOST
@@ -96,13 +214,13 @@ def __init__(self, engine, grid_no, crop_no, campaign_year):
96214
97215 if self .crop_end_type == "maturity" :
98216 self .crop_end_date = "null"
99- self .campaign_end_date = self .crop_start_date + datetime .timedelta (days = self .max_duration )
217+ self .campaign_end_date = self .crop_start_date + dt .timedelta (days = self .max_duration )
100218 else :
101219 month = int (row .end_month )
102220 day = int (row .end_monthday )
103- self .crop_end_date = datetime .date (year , month , day )
221+ self .crop_end_date = dt .date (year , month , day )
104222 if self .crop_end_date <= self .crop_start_date :
105- self .crop_end_date = datetime .date (year + 1 , month , day )
223+ self .crop_end_date = dt .date (year + 1 , month , day )
106224 self .campaign_end_date = self .crop_end_date
107225
108226 input = self ._build_yaml_agromanagement ()
@@ -134,7 +252,7 @@ def _parse_yaml(self, input):
134252 def set_campaign_start_date (self , start_date ):
135253 """Updates the value for the campaign_start_date.
136254
137- This is useful only when the INITIAL_SOIL_WATER table in CGMS9 defines a different
255+ This is useful only when the INITIAL_SOIL_WATER table in CGMS8 defines a different
138256 campaign start date which should be used instead.
139257 """
140258 self .campaign_start_date = check_date (start_date )
0 commit comments