Subversion Repositories public iLand

Rev

Rev 1182 | Rev 1217 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

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