Subversion Repositories public iLand

Rev

Rev 1221 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
671 werner 1
/********************************************************************************************
2
**    iLand - an individual based forest landscape and disturbance model
3
**    http://iland.boku.ac.at
4
**    Copyright (C) 2009-  Werner Rammer, Rupert Seidl
5
**
6
**    This program is free software: you can redistribute it and/or modify
7
**    it under the terms of the GNU General Public License as published by
8
**    the Free Software Foundation, either version 3 of the License, or
9
**    (at your option) any later version.
10
**
11
**    This program is distributed in the hope that it will be useful,
12
**    but WITHOUT ANY WARRANTY; without even the implied warranty of
13
**    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
**    GNU General Public License for more details.
15
**
16
**    You should have received a copy of the GNU General Public License
17
**    along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
********************************************************************************************/
19
 
20
 
247 werner 21
/** @class Climate
22
  Climate handles climate input data and performs some basic related calculations on that data.
23
  @ingroup core
24
  @sa http://iland.boku.ac.at/ClimateData
193 werner 25
*/
26
#include "global.h"
27
#include "climate.h"
201 werner 28
#include "model.h"
977 werner 29
#include "timeevents.h"
193 werner 30
 
226 werner 31
double ClimateDay::co2 = 350.; // base value of ambient CO2-concentration (ppm)
894 werner 32
QVector<int> sampled_years; // list of sampled years to use
226 werner 33
 
34
 
894 werner 35
 
210 werner 36
void Sun::setup(double latitude_rad)
37
{
38
    mLatitude = latitude_rad;
211 werner 39
    if (mLatitude>0)
40
        mDayWithMaxLength = 182-10; // 21.juni
41
    else
42
        mDayWithMaxLength = 365-10; //southern hemisphere
210 werner 43
    // calculate length of day using  the approximation formulae of: http://herbert.gandraxa.com/length_of_day.aspx
44
    const double j = M_PI / 182.625;
45
    const double ecliptic = RAD(23.439);
46
    double m;
47
    for (int day=0;day<366;day++) {
211 werner 48
        m = 1. - tan(latitude_rad)*tan(ecliptic*cos(j*(day+10))); // day=0: winter solstice => subtract 10 days
210 werner 49
        m = limit(m, 0., 2.);
211 werner 50
        mDaylength_h[day] = acos(1-m)/M_PI * 24.; // result in hours [0..24]
210 werner 51
    }
440 werner 52
    mDayWith10_5hrs = 0;
53
    for (int day=mDayWithMaxLength;day<366;day++) {
54
        if (mDaylength_h[day]<10.5) {
55
            mDayWith10_5hrs = day;
56
            break;
57
        }
1008 werner 58
    }
59
    mDayWith14_5hrs = 0;
60
    for (int day=mDayWithMaxLength;day<366;day++) {
61
        if (mDaylength_h[day]<14.5) {
62
            mDayWith14_5hrs = day;
63
            break;
64
        }
65
    }
210 werner 66
}
67
 
68
QString Sun::dump()
69
{
211 werner 70
    QString result=QString("lat: %1, longest day: %2\ndoy;daylength").arg(mLatitude).arg(mDayWithMaxLength);
210 werner 71
    for (int i=0;i<366;i++)
72
        result+=QString("\n%1;%2").arg(i).arg(mDaylength_h[i]);
73
    return result;
74
}
75
 
193 werner 76
Climate::Climate()
77
{
78
    mLoadYears = 1;
552 werner 79
    mInvalidDay.dayOfMonth=mInvalidDay.month=mInvalidDay.year=-1;
214 werner 80
    mBegin = mEnd = 0;
280 werner 81
    mIsSetup = false;
193 werner 82
}
83
 
84
 
198 werner 85
// access functions
214 werner 86
 
255 werner 87
const ClimateDay *Climate::day(const int month, const int day) const
198 werner 88
{
201 werner 89
    if (mDayIndices.isEmpty())
90
        return &mInvalidDay;
209 werner 91
    return mStore.constBegin() + mDayIndices[mCurrentYear*12 + month] + day;
198 werner 92
}
255 werner 93
void Climate::monthRange(const int month, const ClimateDay **rBegin, const ClimateDay **rEnd) const
198 werner 94
{
209 werner 95
    *rBegin = mStore.constBegin() + mDayIndices[mCurrentYear*12 + month];
96
    *rEnd = mStore.constBegin() + mDayIndices[mCurrentYear*12 + month+1];
229 werner 97
    //qDebug() << "monthRange returning: begin:"<< (*rBegin)->toString() << "end-1:" << (*rEnd-1)->toString();
198 werner 98
}
99
 
255 werner 100
double Climate::days(const int month) const
198 werner 101
{
208 werner 102
    return (double) mDayIndices[mCurrentYear*12 + month + 1]-mDayIndices[mCurrentYear*12 + month];
198 werner 103
}
255 werner 104
int Climate::daysOfYear() const
201 werner 105
{
106
    if (mDayIndices.isEmpty())
107
        return -1;
214 werner 108
    return mEnd - mBegin;
201 werner 109
}
198 werner 110
 
255 werner 111
void Climate::toDate(const int yearday, int *rDay, int *rMonth, int *rYear) const
214 werner 112
{
113
    const ClimateDay *d = dayOfYear(yearday);
552 werner 114
    if (rDay) *rDay = d->dayOfMonth-1;
214 werner 115
    if (rMonth) *rMonth = d->month-1;
116
    if (rYear) *rYear = d->year;
117
}
198 werner 118
 
193 werner 119
void Climate::setup()
120
{
121
    GlobalSettings *g=GlobalSettings::instance();
122
    XmlHelper xml(g->settings().node("model.climate"));
123
    QString tableName =xml.value("tableName");
318 werner 124
    mName = tableName;
894 werner 125
    QString filter = xml.value("filter");
126
 
859 werner 127
    mLoadYears = (int) qMax(xml.valueDouble("batchYears", 1.),1.);
348 werner 128
    mDoRandomSampling = xml.valueBool("randomSamplingEnabled", false);
129
    mRandomYearList.clear();
130
    mRandomListIndex = -1;
131
    QString list = xml.value("randomSamplingList");
132
    if (mDoRandomSampling) {
356 werner 133
        if (!list.isEmpty()) {
134
            QStringList strlist = list.split(QRegExp("\\W+"), QString::SkipEmptyParts);
135
            foreach(const QString &s,strlist)
136
                mRandomYearList.push_back(s.toInt());
137
            // check for validity
138
            foreach(int year, mRandomYearList)
139
                if (year < 0 || year>=mLoadYears)
981 werner 140
                    throw IException("Invalid randomSamplingList! Year numbers are 0-based and must to between 0 and batchYears-1 (check value of batchYears)!!!");
356 werner 141
        }
142
 
348 werner 143
        if (mRandomYearList.count()>0)
1204 werner 144
            qDebug() << "Climate: Random sampling enabled with fixed list" << mRandomYearList.count() << "of years. climate:" << name();
348 werner 145
        else
1204 werner 146
            qDebug() << "Climate: Random sampling enabled (without a fixed list). climate:" << name();
348 werner 147
    }
313 werner 148
    mTemperatureShift = xml.valueDouble("temperatureShift", 0.);
149
    mPrecipitationShift = xml.valueDouble("precipitationShift", 1.);
335 werner 150
    if (mTemperatureShift!=0. || mPrecipitationShift!=1.)
151
        qDebug() << "Climate modifaction: add temperature:" << mTemperatureShift << ". Multiply precipitation: " << mPrecipitationShift;
152
 
735 werner 153
    mStore.resize(mLoadYears * 366 + 1); // reserve enough space (1 more than used at max)
193 werner 154
    mCurrentYear=0;
155
    mMinYear = 0;
156
    mMaxYear = 0;
157
 
894 werner 158
    // add a where-clause
159
    if (!filter.isEmpty()) {
160
        filter = QString("where %1").arg(filter);
161
        qDebug() << "adding climate table where-clause:" << filter;
162
    }
163
 
164
    QString query=QString("select year,month,day,min_temp,max_temp,prec,rad,vpd from %1 %2 order by year, month, day").arg(tableName).arg(filter);
193 werner 165
    // here add more options...
166
    mClimateQuery = QSqlQuery(g->dbclimate());
167
    mClimateQuery.exec(query);
653 werner 168
    mTMaxAvailable = true;
193 werner 169
    if (mClimateQuery.lastError().isValid()){
653 werner 170
        // fallback: if there is no max_temp try the older format:
754 werner 171
        query=QString("select year,month,day,temp,min_temp,prec,rad,vpd from %1 order by year, month, day").arg(tableName);
653 werner 172
        mClimateQuery.exec(query);
173
        mTMaxAvailable = false;
174
        if (mClimateQuery.lastError().isValid()){
175
            throw IException(QString("Error setting up climate: %1 \n %2").arg(query, mClimateQuery.lastError().text()) );
176
        }
193 werner 177
    }
653 werner 178
    // setup query
198 werner 179
    // load first chunk...
180
    load();
214 werner 181
    setupPhenology(); // load phenology
182
    // setup sun
183
    mSun.setup(Model::settings().latitude);
246 werner 184
    mCurrentYear--; // go to "-1" -> the first call to next year will go to year 0.
894 werner 185
    sampled_years.clear();
280 werner 186
    mIsSetup = true;
193 werner 187
}
188
 
189
 
190
void Climate::load()
191
{
192
    if (!mClimateQuery.isActive())
193
       throw IException(QString("Error loading climate file - query not active."));
194
 
201 werner 195
    ClimateDay lastDay = *day(11,30); // 31.december
194 werner 196
    mMinYear = mMaxYear;
193 werner 197
    QVector<ClimateDay>::iterator store=mStore.begin();
201 werner 198
 
193 werner 199
    mDayIndices.clear();
200
    ClimateDay *cday = store;
201
    int lastmon = -1;
202
    int lastyear = -1;
203
    int yeardays;
204
    for (int i=0;i<mLoadYears;i++) {
205
        yeardays = 0;
977 werner 206
        if (GlobalSettings::instance()->model()->timeEvents()) {
207
            QVariant val_temp = GlobalSettings::instance()->model()->timeEvents()->value(GlobalSettings::instance()->currentYear() + i, "model.climate.temperatureShift");
208
            QVariant val_prec = GlobalSettings::instance()->model()->timeEvents()->value(GlobalSettings::instance()->currentYear() + i, "model.climate.precipitationShift");
209
            if (val_temp.isValid())
210
                mTemperatureShift = val_temp.toDouble();
211
            if (val_prec.isValid())
212
                mPrecipitationShift = val_prec.toDouble();
213
 
981 werner 214
            if (mTemperatureShift!=0. || mPrecipitationShift!=1.) {
215
                qDebug() << "Climate modification: add temperature:" << mTemperatureShift << ". Multiply precipitation: " << mPrecipitationShift;
216
                if (mDoRandomSampling) {
1182 werner 217
                    qWarning() << "WARNING - Climate: using a randomSamplingList and temperatureShift/precipitationShift at the same time. The same offset is applied for *every instance* of a year!!";
1081 werner 218
                    //throw IException("Climate: cannot use a randomSamplingList and temperatureShift/precipitationShift at the same time. Sorry.");
981 werner 219
                }
220
            }
977 werner 221
        }
222
 
198 werner 223
        //qDebug() << "loading year" << lastyear+1;
194 werner 224
        while(1==1) {
225
            if(!mClimateQuery.next()) {
226
                // rewind to start
227
                qDebug() << "restart of climate table";
228
                lastyear=-1;
229
                if (!mClimateQuery.first())
230
                    throw IException("Error rewinding climate file!");
231
            }
193 werner 232
            yeardays++;
233
            if (yeardays>366)
234
                throw IException("Error in reading climate file: yeardays>366!");
235
 
201 werner 236
 
193 werner 237
            cday = store++; // store values directly in the QVector
201 werner 238
 
193 werner 239
            cday->year = mClimateQuery.value(0).toInt();
240
            cday->month = mClimateQuery.value(1).toInt();
552 werner 241
            cday->dayOfMonth = mClimateQuery.value(2).toInt();
653 werner 242
            if (mTMaxAvailable) {
243
                //References for calculation the temperature of the day:
244
                //Floyd, R. B., Braddock, R. D. 1984. A simple method for fitting average diurnal temperature curves.  Agricultural and Forest Meteorology 32: 107-119.
245
                //Landsberg, J. J. 1986. Physiological ecology of forest production. Academic Press Inc., 197 S.
246
 
247
                cday->min_temperature = mClimateQuery.value(3).toDouble() + mTemperatureShift;
248
                cday->max_temperature = mClimateQuery.value(4).toDouble() + mTemperatureShift;
249
                cday->temperature = 0.212*(cday->max_temperature - cday->mean_temp()) + cday->mean_temp();
250
 
251
            } else {
252
               // for compatibility: the old method
253
                cday->temperature = mClimateQuery.value(3).toDouble() + mTemperatureShift;
254
                cday->min_temperature = mClimateQuery.value(4).toDouble() + mTemperatureShift;
255
                cday->max_temperature = cday->temperature;
256
            }
414 werner 257
            cday->preciptitation = mClimateQuery.value(5).toDouble() * mPrecipitationShift;
258
            cday->radiation = mClimateQuery.value(6).toDouble();
259
            cday->vpd = mClimateQuery.value(7).toDouble();
319 werner 260
            // sanity checks
552 werner 261
            if (cday->month<1 || cday->dayOfMonth<1 || cday->month>12 || cday->dayOfMonth>31)
262
                qDebug() << QString("Invalid dates in climate table %1: year %2 month %3 day %4!").arg(name()).arg(cday->year).arg(cday->month).arg(cday->dayOfMonth);
263
            DBG_IF(cday->month<1 || cday->dayOfMonth<1 || cday->month>12 || cday->dayOfMonth>31,"Climate:load", "invalid dates");
802 werner 264
            DBG_IF(cday->temperature<-70 || cday->temperature>50,"Climate:load", "temperature out of range (-70..+50 degree C)");
255 werner 265
            DBG_IF(cday->preciptitation<0 || cday->preciptitation>200,"Climate:load", "precipitation out of range (0..200mm)");
241 werner 266
            DBG_IF(cday->radiation<0 || cday->radiation>50,"Climate:load", "radiation out of range (0..50 MJ/m2/day)");
267
            DBG_IF(cday->vpd<0 || cday->vpd>10,"Climate:load", "vpd out of range (0..10 kPa)");
268
 
193 werner 269
            if (cday->month != lastmon) {
270
                // new month...
271
                lastmon = cday->month;
272
                // save relative position of the beginning of the new month
209 werner 273
                mDayIndices.push_back( cday - mStore.constBegin() );
193 werner 274
            }
275
            if (yeardays==1) {
276
                // check on first day of the year
277
                if (lastyear!=-1 && cday->year!=lastyear+1)
552 werner 278
                    throw IException(QString("Error in reading climate file: invalid year break at y-m-d: %1-%2-%3!").arg(cday->year).arg(cday->month).arg(cday->dayOfMonth));
193 werner 279
            }
552 werner 280
            if (cday->month==12 && cday->dayOfMonth==31)
193 werner 281
                break;
282
 
283
            if (cday==mStore.end())
284
                throw IException("Error in reading climate file: read across the end!");
285
        }
286
        lastyear = cday->year;
287
    }
201 werner 288
    while (store!=mStore.end())
289
        *store++ = mInvalidDay; // save a invalid day at the end...
290
 
198 werner 291
    mDayIndices.push_back(cday- mStore.begin()); // the absolute last day...
193 werner 292
    mMaxYear = mMinYear+mLoadYears;
293
    mCurrentYear = 0;
214 werner 294
    mBegin = mStore.begin() + mDayIndices[mCurrentYear*12];
295
    mEnd = mStore.begin() + mDayIndices[(mCurrentYear+1)*12];; // point to the 1.1. of the next year
296
 
212 werner 297
    climateCalculations(lastDay); // perform additional calculations based on the climate data loaded from the database
198 werner 298
 
193 werner 299
}
300
 
301
 
302
void Climate::nextYear()
303
{
201 werner 304
 
348 werner 305
    if (!mDoRandomSampling) {
306
        // default behaviour: simply advance to next year, call load() if end reached
894 werner 307
        if (mCurrentYear >= mLoadYears-1) // need to load more data
348 werner 308
            load();
309
        else
310
            mCurrentYear++;
311
    } else {
312
        // random sampling
313
        if (mRandomYearList.isEmpty()) {
1164 werner 314
            // random without list
894 werner 315
            // make sure that the sequence of years is the same for the full landscape
316
            if (sampled_years.size()<GlobalSettings::instance()->currentYear()) {
317
                while (sampled_years.size()-1 < GlobalSettings::instance()->currentYear())
1164 werner 318
                    sampled_years.append(irandom(0,mLoadYears));
894 werner 319
            }
320
 
321
            mCurrentYear = sampled_years[GlobalSettings::instance()->currentYear()];
322
 
348 werner 323
        } else {
894 werner 324
            // random with fixed list
348 werner 325
            mRandomListIndex++;
326
            if (mRandomListIndex>=mRandomYearList.count())
327
                mRandomListIndex=0;
328
            mCurrentYear = mRandomYearList[mRandomListIndex];
329
            if (mCurrentYear >= mLoadYears)
330
                throw IException(QString("Climate: load year with random sampling: the actual year %1 is invalid. Only %2 years are loaded from the climate database.").arg(mCurrentYear).arg(mLoadYears) );
331
        }
721 werner 332
        if (logLevelDebug())
333
            qDebug() << "Climate: current year (randomized):" << mCurrentYear;
348 werner 334
    }
214 werner 335
 
342 werner 336
    ClimateDay::co2 = GlobalSettings::instance()->settings().valueDouble("model.climate.co2concentration", 380.);
754 werner 337
    if (logLevelDebug())
338
        qDebug() << "CO2 concentration" << ClimateDay::co2 << "ppm.";
214 werner 339
    mBegin = mStore.begin() + mDayIndices[mCurrentYear*12];
340
    mEnd = mStore.begin() + mDayIndices[(mCurrentYear+1)*12];; // point to the 1.1. of the next year
553 werner 341
 
342
    // some aggregates:
343
    // calculate radiation sum of the year and monthly precipitation
485 werner 344
    mAnnualRadiation = 0.;
721 werner 345
    mMeanAnnualTemperature = 0.;
346
    for (int i=0;i<12;i++)  {
347
        mPrecipitationMonth[i]=0.;
348
        mTemperatureMonth[i]=0.;
349
    }
553 werner 350
 
351
    for (const ClimateDay *d=begin();d!=end();++d) {
485 werner 352
        mAnnualRadiation+=d->radiation;
721 werner 353
        mMeanAnnualTemperature += d->temperature;
553 werner 354
        mPrecipitationMonth[d->month-1]+= d->preciptitation;
721 werner 355
        mTemperatureMonth[d->month-1] += d->temperature;
553 werner 356
    }
721 werner 357
    for (int i=0;i<12;++i) {
358
        mTemperatureMonth[i] /= days(i);
359
    }
360
    mMeanAnnualTemperature /= daysOfYear();
485 werner 361
 
362
    // calculate phenology
214 werner 363
    for(int i=0;i<mPhenology.count(); ++i)
364
        mPhenology[i].calculate();
193 werner 365
}
198 werner 366
 
720 werner 367
void Climate::climateCalculations(const ClimateDay &lastDay)
198 werner 368
{
201 werner 369
    ClimateDay *c = mStore.begin();
370
    const double tau = Model::settings().temperatureTau;
371
    // handle first day: use tissue temperature of the last day of the last year (if available)
372
    if (lastDay.isValid())
373
        c->temp_delayed = lastDay.temp_delayed + 1./tau * (c->temperature - lastDay.temp_delayed);
374
    else
375
        c->temp_delayed = c->temperature;
376
    c++;
377
    while (c->isValid()) {
1033 werner 378
        // first order dynamic delayed model (Maekela 2008)
201 werner 379
        c->temp_delayed=(c-1)->temp_delayed + 1./tau * (c->temperature - (c-1)->temp_delayed);
380
        ++c;
381
    }
198 werner 382
 
383
}
214 werner 384
 
385
 
386
void Climate::setupPhenology()
387
{
388
    mPhenology.clear();
436 werner 389
    mPhenology.push_back(Phenology(this)); // id=0
214 werner 390
    XmlHelper xml(GlobalSettings::instance()->settings().node("model.species.phenology"));
391
    int i=0;
392
    do {
393
        QDomElement n = xml.node(QString("type[%1]").arg(i));
394
        if (n.isNull())
395
            break;
396
        i++;
397
        int id;
398
        id = n.attribute("id", "-1").toInt();
399
        if (id<0) throw IException(QString("Error setting up phenology: id invalid\ndump: %1").arg(xml.dump("")));
400
        xml.setCurrentNode(n);
401
        Phenology item( id,
402
                        this,
403
                        xml.valueDouble(".vpdMin",0.5), // use relative access to node (".x")
404
                        xml.valueDouble(".vpdMax", 5),
405
                        xml.valueDouble(".dayLengthMin",10),
406
                        xml.valueDouble(".dayLengthMax",11),
407
                        xml.valueDouble(".tempMin", 2),
408
                        xml.valueDouble(".tempMax", 9) );
409
 
410
        mPhenology.push_back(item);
411
    } while(true);
412
}
413
 
414
/** return the phenology of the group... */
238 werner 415
const Phenology &Climate::phenology(const int phenologyGroup) const
214 werner 416
{
417
    const Phenology &p = mPhenology.at(phenologyGroup);
418
    if (p.id() == phenologyGroup)
419
        return p;
420
    // search...
421
    for (int i=0;i<mPhenology.count();i++)
422
        if (mPhenology[i].id()==phenologyGroup)
423
            return mPhenology[i];
435 werner 424
    throw IException(QString("Error at SpeciesSet::phenology(): invalid group: %1").arg(phenologyGroup));
214 werner 425
}
436 werner 426