Skip to content

Commit 7119a61

Browse files
committed
renamed dataproviders cgms9->cgms8, cgms11->cgms12, this changes are now reflected in all docstrings and code as well.
1 parent 20b54a1 commit 7119a61

File tree

8 files changed

+171
-288
lines changed

8 files changed

+171
-288
lines changed

pcse/db/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# Allard de Wit ([email protected]), April 2014
44

55
from . import pcse
6-
from . import cgms9
7-
from . import cgms11
6+
from . import cgms8
7+
from . import cgms12
8+
from . import cgms14
89
from .nasapower import NASAPowerWeatherDataProvider
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Copyright (c) 2004-2014 Alterra, Wageningen-UR
33
# Allard de Wit ([email protected]), April 2014
44
"""Tools for reading weather data and timer, soil and site parameters
5-
from a CGMS11 compatible database.
5+
from a CGMS12 compatible database.
66
"""
77

88
from .data_providers import WeatherObsGridDataProvider
Lines changed: 7 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def __init__(self, engine, crop_no):
6767

6868

6969
class WeatherObsGridDataProvider(WeatherDataProvider):
70-
"""Retrieves meteodata from the WEATHER_OBS_GRID table in a CGMS11
70+
"""Retrieves meteodata from the WEATHER_OBS_GRID table in a CGMS12
7171
compatible database.
7272
7373
:param engine: SqlAlchemy engine object providing DB access
@@ -218,7 +218,7 @@ def _make_WeatherDataContainer(self, row, t):
218218

219219

220220
class AgroManagementDataProvider(list):
221-
"""Class for providing agromanagement data from the CROP_CALENDAR table in a CGMS11 database.
221+
"""Class for providing agromanagement data from the CROP_CALENDAR table in a CGMS12 database.
222222
223223
:param engine: SqlAlchemy engine object providing DB access
224224
:param grid_no: Integer grid ID, maps to the grid_no column in the table
@@ -272,11 +272,11 @@ def __init__(self, engine, grid_no, crop_no, campaign_year, campaign_start=None)
272272
raise exc.PCSEError(msg)
273273

274274
# Determine the start date/type. Only sowing|emergence is accepted by PCSE/WOFOST
275-
cgms11_start_type = str(row.start_type).strip()
275+
cgms12_start_type = str(row.start_type).strip()
276276
self.crop_start_date = check_date(row.start_date)
277-
if cgms11_start_type == "FIXED_SOWING":
277+
if cgms12_start_type == "FIXED_SOWING":
278278
self.crop_start_type = "sowing"
279-
elif cgms11_start_type == "FIXED_EMERGENCE":
279+
elif cgms12_start_type == "FIXED_EMERGENCE":
280280
self.crop_start_type = "emergence"
281281
else:
282282
msg = "Unsupported START_TYPE in CROP_CALENDAR table: %s" % row.start_type
@@ -348,7 +348,7 @@ def _parse_yaml(self, input):
348348
def set_campaign_start_date(self, start_date):
349349
"""Updates the value for the campaign_start_date.
350350
351-
This is useful only when the INITIAL_SOIL_WATER table in CGMS11 defines a different
351+
This is useful only when the INITIAL_SOIL_WATER table in CGMS12 defines a different
352352
campaign_start
353353
"""
354354
self.campaign_start_date = check_date(start_date)
@@ -363,132 +363,9 @@ def __str__(self):
363363
return msg
364364

365365

366-
class TimerDataProvider(dict):
367-
"""Class for providing timerdata from the CROP_CALENDAR table in a CGMS11 database.
368-
369-
:param engine: SqlAlchemy engine object providing DB access
370-
:param grid_no: Integer grid ID, maps to the GRID_NO column in the table
371-
:param crop_no: Integer crop ID, maps to the CROP_NO column in the table
372-
:param campaign_year: Integer campaign year, maps to the YEAR column in the table.
373-
The campaign year usually refers to the year of the harvest. Thus for crops
374-
crossing calendar years, the start_date can be in the previous year.
375-
376-
Note that there is a difficulty in the CGMS database for determining the START_DATE
377-
of the system. The START_DATE can be determined by the column GIVEN_STARTDATE_WATBAL
378-
in the INITIAL_SOIL_WATER table. However, for retrieving this value we also need
379-
to have the soil number (STU_NO) which we do not have yet and which is not needed
380-
for retrieving the crop calendar itself.
381-
382-
For the moment, we therefore do set the START_DATE equal to the CROP_START_DATA
383-
in the TimerDataProvider and provide a method `set_START_DATE()` for resetting
384-
it to another value.
385-
"""
386-
387-
# Note that the following entries must be available:
388-
# CAMPAIGNYEAR: year of the agricultural campaign (e.g. harvest year)
389-
# START_DATE: date of the start of the simulation
390-
# END_DATE: date last possible day of the simulation
391-
# CROP_START_TYPE: "emergence" or "sowing"
392-
# CROP_START_DATE: date of the start of the crop simulation
393-
# CROP_END_TYPE: "maturity" | "harvest" |"earliest"
394-
# CROP_END_DATE: date of the end of the crop simulation in case of CROP_END_TYPE == "harvest" | "earliest"
395-
# MAX_DURATION: maximum number of days of the crop simulation
396-
397-
def __init__(self, engine, grid_no, crop_no, campaign_year):
398-
dict.__init__(self)
399-
400-
loggername = "%s.%s" % (self.__class__.__module__,
401-
self.__class__.__name__)
402-
self.logger = logging.getLogger(loggername)
403-
404-
self.grid_no = int(grid_no)
405-
self.crop_no = int(crop_no)
406-
self.campaign_year = int(campaign_year)
407-
self.crop_name = fetch_crop_name(engine, self.crop_no)
408-
self.db_resource = str(engine)[7:-1]
409-
410-
metadata = MetaData(engine)
411-
table_cc = Table("crop_calendar", metadata, autoload=True)
412-
413-
r = select([table_cc], and_(table_cc.c.grid_no == self.grid_no,
414-
table_cc.c.crop_no == self.crop_no,
415-
table_cc.c.year == self.campaign_year)).execute()
416-
row = r.fetchone()
417-
r.close()
418-
if row is None:
419-
msg = "Failed deriving crop calendar for grid_no %s, crop_no %s " % (grid_no, crop_no)
420-
raise exc.PCSEError(msg)
421-
422-
# Get the campaign year
423-
self["CAMPAIGNYEAR"] = row.year
424-
425-
# Determine the start type. Only sowing|emergence is accepted by PCSE/WOFOST
426-
start_type = str(row.start_type).strip()
427-
if start_type == "FIXED_SOWING":
428-
self["CROP_START_TYPE"] = "sowing"
429-
elif start_type == "FIXED_EMERGENCE":
430-
self["CROP_START_TYPE"] = "emergence"
431-
else:
432-
msg = "Unsupported START_TYPE in CROP_CALENDAR table: %s" % row.start_type
433-
raise exc.PCSEError(msg)
434-
435-
# Start date for the crop, we force a datetime.date value
436-
self["CROP_START_DATE"] = check_date(row.start_date)
437-
# Set the system START_DATE equal to the CROP_START_DATE
438-
self["START_DATE"] = self["CROP_START_DATE"]
439-
440-
# set END_TYPE and CROP_END_DATE (if applicable) for the crop: maturity|harvest|earliest
441-
crop_end_type = str(row.end_type).strip().lower()
442-
if crop_end_type == "maturity":
443-
self["CROP_END_TYPE"] = crop_end_type
444-
self["CROP_END_DATE"] = None
445-
elif crop_end_type in ["harvest", "earliest"]:
446-
self["CROP_END_TYPE"] = crop_end_type
447-
self["CROP_END_DATE"] = check_date(row.end_date)
448-
else:
449-
msg = ("Unrecognized option for END_TYPE in table "
450-
"CROP_CALENDAR: %s" % crop_end_type)
451-
raise exc.PCSEError(msg)
452-
453-
# Maximum duration of crop cycle
454-
max_dur = int(row.max_duration)
455-
if crop_end_type == "maturity":
456-
if max_dur < 150:
457-
msg = "Found low value for MAX_DURATION (%i days). Forcing to 300 days!"
458-
self.logger.warn(msg, max_dur)
459-
max_dur = 300
460-
elif crop_end_type in ["harvest", "earliest"]:
461-
# days to end of crop cycle
462-
days_to_harvest = (self["CROP_END_DATE"] - self["CROP_START_DATE"]).days
463-
if max_dur < days_to_harvest:
464-
# recalculate max duration and add some days
465-
omax_dur = max_dur
466-
max_dur = days_to_harvest + 10
467-
msg = "MAX_DURATION (%i) lower then the days-to-harvest (%i). forcing MAX_DURATION to %i days"
468-
self.logger.warn(msg, omax_dur, days_to_harvest, max_dur)
469-
470-
self["MAX_DURATION"] = max_dur
471-
# simulation end date equals CROP_START_DATE + MAX_DURATION
472-
self["END_DATE"] = self["CROP_START_DATE"] + datetime.timedelta(days=max_dur)
473-
474-
def set_START_DATE(self, start_date):
475-
"""Updates the value for START_DATE in TimerDataProvider
476-
"""
477-
self["START_DATE"] = check_date(start_date)
478-
479-
def __str__(self):
480-
msg = ("Timer data for crop_no=%i (%s) derived from: %s\n" %
481-
(self.crop_no, self.crop_name, self.db_resource))
482-
t = " %s: %s\n"
483-
for k in sorted(self.keys()):
484-
msg += t % (k, self[k])
485-
486-
return msg
487-
488-
489366
class SoilDataProviderSingleLayer(dict):
490367
"""Class for providing soil data from the ROOTING_DEPTH AND
491-
SOIL_PHYSICAL_GROUP tableS in a CGMS9/11 database. This
368+
SOIL_PHYSICAL_GROUP tableS in a CGMS8/12 database. This
492369
applies to the single layered soil only.
493370
494371
:param engine: SqlAlchemy engine object providing DB access

pcse/db/cgms8/__init__.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright (c) 2004-2014 Alterra, Wageningen-UR
3+
# Allard de Wit ([email protected]), April 2014
4+
"""Tools for reading weather data and parameters from a CGMS8 database
5+
6+
Source code partially comes from the CGMS12 data providers as the database has a similar structure
7+
for many of the soil and crop related tables.
8+
"""
9+
from ..cgms12.data_providers import SoilDataProviderSingleLayer as SoilDataProvider
10+
from ..cgms12.data_providers import SiteDataProvider, STU_Suitability, CropDataProvider
11+
from ..cgms12.data_providers import SoilDataIterator as SoilDataIterator_CGMS12
12+
from .data_providers import AgroManagementDataProvider, GridWeatherDataProvider
13+
14+
15+
class SoilDataIterator(SoilDataIterator_CGMS12):
16+
"""Soil data iterator for CGMS8.
17+
18+
The only difference is that in CGMS8 the table is called 'ELEMENTARY_MAPPING_UNIT' and
19+
in CGMS12 it is called 'EMU'
20+
"""
21+
emu_table_name = "elementary_mapping_unit"
Lines changed: 128 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,138 @@
55
Data providers for weather, agromanagement, soil, crop and site data. Also
66
a 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

1212
from sqlalchemy import MetaData, select, Table, and_
1313
import yaml
1414

15-
from ...util import check_date
15+
from ...util import check_date, wind10to2
1616
from ... 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

20138
class 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

Comments
 (0)