Subversion Repositories public iLand

Rev

Rev 736 | Rev 743 | 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
 
106 Werner 21
#include "global.h"
22
#include "standloader.h"
23
 
290 werner 24
 
106 Werner 25
#include "grid.h"
26
#include "model.h"
189 iland 27
#include "resourceunit.h"
106 Werner 28
#include "speciesset.h"
29
 
30
#include "helper.h"
290 werner 31
#include "random.h"
137 Werner 32
#include "expression.h"
140 Werner 33
#include "expressionwrapper.h"
284 werner 34
#include "environment.h"
287 werner 35
#include "csvfile.h"
549 werner 36
#include "mapgrid.h"
699 werner 37
#include "snapshot.h"
106 Werner 38
 
697 werner 39
/** @class StandLoader
40
    @ingroup tools
41
    loads (initializes) trees for a "stand" from various sources.
42
    StandLoader initializes trees on the landscape. It reads (usually) from text files, creates the
43
    trees and distributes the trees on the landscape (on the ResoureceUnit or on a stand defined by a grid).
44
 
45
    See http://iland.boku.ac.at/initialize+trees
46
  */
381 werner 47
// provide a mapping between "Picus"-style and "iLand"-style species Ids
384 werner 48
QVector<int> picusSpeciesIds = QVector<int>() << 0 << 1 << 17;
155 werner 49
QStringList iLandSpeciesIds = QStringList() << "piab" << "piab" << "fasy";
732 werner 50
 
290 werner 51
StandLoader::~StandLoader()
52
{
53
    if (mRandom)
54
        delete mRandom;
732 werner 55
    if (mHeightGridResponse)
56
        delete mHeightGridResponse;
290 werner 57
}
106 Werner 58
 
59
 
281 werner 60
void StandLoader::copyTrees()
61
{
62
    // we assume that all stands are equal, so wie simply COPY the trees and modify them afterwards
63
    const Grid<ResourceUnit*> &ruGrid=mModel->RUgrid();
64
    ResourceUnit **p = ruGrid.begin();
65
    if (!p)
66
        throw IException("Standloader: invalid resource unit pointer!");
67
    ++p; // skip the first...
68
    const QVector<Tree> &tocopy = mModel->ru()->trees();
69
    for (; p!=ruGrid.end(); ++p) {
70
        QRectF rect = (*p)->boundingBox();
71
        foreach(const Tree& tree, tocopy) {
72
            Tree &newtree = (*p)->newTree();
73
            newtree = tree; // copy tree data...
739 werner 74
            newtree.setPosition(tree.position()+rect.topLeft());
281 werner 75
            newtree.setRU(*p);
76
            newtree.setNewId();
77
        }
78
    }
431 werner 79
    if (logLevelInfo()) qDebug() << Tree::statCreated() << "trees loaded / copied.";
281 werner 80
}
81
 
284 werner 82
/** main routine of the stand setup.
83
*/
106 Werner 84
void StandLoader::processInit()
85
{
86
    GlobalSettings *g = GlobalSettings::instance();
192 werner 87
    XmlHelper xml(g->settings().node("model.initialization"));
191 werner 88
 
281 werner 89
    QString copy_mode = xml.value("mode", "copy");
90
    QString type = xml.value("type", "");
192 werner 91
    QString  fileName = xml.value("file", "");
106 Werner 92
 
732 werner 93
    bool height_grid_enabled = xml.valueBool("heightGrid.enabled", false);
736 werner 94
    mHeightGridTries = xml.valueDouble("heightGrid.maxTries", 10.);
732 werner 95
    QScopedPointer<const MapGrid> height_grid; // use a QScopedPointer to guarantee that the resource is freed at the end of the processInit() function
96
    if (height_grid_enabled) {
97
        QString init_height_grid_file = GlobalSettings::instance()->path(xml.value("heightGrid.fileName"), "init");
98
        qDebug() << "initialization: using predefined tree heights map" << init_height_grid_file;
99
 
100
        QScopedPointer<const MapGrid> p(new MapGrid(init_height_grid_file, false));
101
        height_grid.swap(p);
102
        mInitHeightGrid = height_grid.data();
103
 
104
        QString expr=xml.value("heightGrid.fitFormula", "polygon(x, 0,0, 0.8,1, 1.1, 1, 1.25,0)");
105
        mHeightGridResponse = new Expression(expr);
106
        mHeightGridResponse->setLinearizationEnabled(true);
107
    }
108
 
106 Werner 109
    Tree::resetStatistics();
281 werner 110
 
111
    // one global init-file for the whole area:
284 werner 112
    if (copy_mode=="single") {
281 werner 113
        loadInitFile(fileName, type);
299 werner 114
        evaluateDebugTrees();
281 werner 115
        return;
284 werner 116
    }
281 werner 117
 
137 Werner 118
 
281 werner 119
    // call a single tree init for each resource unit
120
    if (copy_mode=="unit") {
284 werner 121
        foreach( const ResourceUnit *const_ru, g->model()->ruList()) {
122
            ResourceUnit *ru = const_cast<ResourceUnit*>(const_ru);
123
            // set environment
124
            g->model()->environment()->setPosition(ru->boundingBox().center());
125
            type = xml.value("type", "");
126
            fileName = xml.value("file", "");
319 werner 127
            if (fileName.isEmpty())
128
                continue;
549 werner 129
            loadInitFile(fileName, type, 0, ru);
431 werner 130
            if (logLevelInfo()) qDebug() << "loaded" << fileName << "on" << ru->boundingBox() << "," << ru->trees().count() << "trees.";
284 werner 131
        }
299 werner 132
        evaluateDebugTrees();
284 werner 133
        return;
281 werner 134
    }
299 werner 135
 
732 werner 136
    // map-modus: load a init file for each "polygon" in the standgrid
549 werner 137
    if (copy_mode=="map") {
138
        if (!g->model()->standGrid() || !g->model()->standGrid()->isValid())
139
            throw IException(QString("Stand-Initialization: model.initialization.mode is 'map' but there is no valid stand grid defined (model.world.standGrid)"));
140
        QString map_file_name = GlobalSettings::instance()->path(xml.value("mapFileName"), "init");
141
 
142
        CSVFile map_file(map_file_name);
143
        if (map_file.rowCount()==0)
144
            throw IException(QString("Stand-Initialization: the map file %1 is empty or missing!").arg(map_file_name));
145
        int ikey = map_file.columnIndex("id");
146
        int ivalue = map_file.columnIndex("filename");
147
        if (ikey<0 || ivalue<0)
148
            throw IException(QString("Stand-Initialization: the map file %1 does not contain the mandatory columns 'id' and 'filename'!").arg(map_file_name));
149
        QString file_name;
150
        for (int i=0;i<map_file.rowCount();i++) {
151
            int key = map_file.value(i, ikey).toInt();
152
            if (key>0) {
153
                file_name = map_file.value(i, ivalue).toString();
550 werner 154
                if (logLevelInfo()) qDebug() << "loading" << file_name << "for grid id" << key;
734 werner 155
                if (!file_name.isEmpty())
156
                    loadInitFile(file_name, type, key, NULL);
549 werner 157
            }
158
        }
732 werner 159
        mInitHeightGrid = 0;
549 werner 160
        evaluateDebugTrees();
161
        return;
162
    }
699 werner 163
    if (copy_mode=="snapshot") {
164
        // load a snapshot from a file
165
        Snapshot shot;
549 werner 166
 
699 werner 167
        QString input_db = GlobalSettings::instance()->path(fileName);
168
        shot.loadSnapshot(input_db);
169
        return;
170
    }
281 werner 171
    throw IException("StandLoader::processInit: invalid initalization.mode!");
172
}
173
 
174
void StandLoader::evaluateDebugTrees()
175
{
137 Werner 176
    // evaluate debugging
177
    QString dbg_str = GlobalSettings::instance()->settings().paramValueString("debug_tree");
299 werner 178
    int counter=0;
137 Werner 179
    if (!dbg_str.isEmpty()) {
733 werner 180
        if (dbg_str == "debugstamp") {
734 werner 181
            qDebug() << "debug_tree = debugstamp: try touching all trees...";
733 werner 182
            // try to force an error if a stamp is invalid
183
            AllTreeIterator at(GlobalSettings::instance()->model());
184
            double total_offset;
185
            while (Tree *t=at.next()) {
186
                total_offset += t->stamp()->offset();
734 werner 187
                if (!GlobalSettings::instance()->model()->grid()->isIndexValid(t->positionIndex()))
188
                    qDebug() << "evaluateDebugTrees: debugstamp: invalid position found!";
733 werner 189
            }
734 werner 190
            qDebug() << "debug_tree = debugstamp: try touching all trees finished...";
733 werner 191
            return;
192
        }
193
        TreeWrapper tw;
194
        Expression dexp(dbg_str, &tw); // load expression dbg_str and enable external model variables
137 Werner 195
        AllTreeIterator at(GlobalSettings::instance()->model());
196
        double result;
197
        while (Tree *t = at.next()) {
140 Werner 198
            tw.setTree(t);
137 Werner 199
            result = dexp.execute();
299 werner 200
            if (result) {
137 Werner 201
                t->enableDebugging();
299 werner 202
                counter++;
203
            }
137 Werner 204
        }
732 werner 205
        qDebug() << "evaluateDebugTrees: enabled debugging for" << counter << "trees.";
137 Werner 206
    }
106 Werner 207
}
208
 
732 werner 209
/// load a single init file. Calls loadPicusFile() or loadiLandFile()
210
/// @param fileName file to load
211
/// @param type init mode. allowed: "picus"/"single" or "iland"/"distribution"
549 werner 212
int StandLoader::loadInitFile(const QString &fileName, const QString &type, int stand_id, ResourceUnit *ru)
281 werner 213
{
284 werner 214
    QString pathFileName = GlobalSettings::instance()->path(fileName, "init");
215
    if (!QFile::exists(pathFileName))
216
        throw IException(QString("StandLoader::loadInitFile: File %1 does not exist!").arg(pathFileName));
281 werner 217
 
549 werner 218
    if (type=="picus" || type=="single") {
219
        if (stand_id>0)
220
            throw IException(QLatin1String("StandLoader::loadInitFile: initialization type %1 currently not supported for stand initilization mode!")+type);
294 werner 221
        return loadPicusFile(pathFileName, ru);
549 werner 222
    }
384 werner 223
    if (type=="iland" || type=="distribution")
549 werner 224
        return loadiLandFile(pathFileName, ru, stand_id);
281 werner 225
 
384 werner 226
    throw IException(QLatin1String("StandLoader::loadInitFile: unknown initalization.type:")+type);
281 werner 227
}
228
 
393 werner 229
int StandLoader::loadPicusFile(const QString &fileName, ResourceUnit *ru)
106 Werner 230
{
294 werner 231
    QString content = Helper::loadTextFile(fileName);
232
    if (content.isEmpty()) {
233
        qDebug() << "file not found: " + fileName;
393 werner 234
        return 0;
294 werner 235
    }
393 werner 236
    return loadSingleTreeList(content, ru, fileName);
294 werner 237
}
238
 
389 werner 239
/** load a list of trees (given by content) to a resource unit. Param fileName is just for error reporting.
240
  returns the number of loaded trees.
241
  */
242
int StandLoader::loadSingleTreeList(const QString &content, ResourceUnit *ru, const QString &fileName)
294 werner 243
{
106 Werner 244
    if (!ru)
245
        ru = mModel->ru();
284 werner 246
    Q_ASSERT(ru!=0);
106 Werner 247
 
284 werner 248
    QPointF offset = ru->boundingBox().topLeft();
249
    SpeciesSet *speciesSet = ru->speciesSet(); // of default RU
250
 
384 werner 251
    QString my_content(content);
252
    // cut out the <trees> </trees> part if present
253
    if (content.contains("<trees>")) {
254
        QRegExp rx(".*<trees>(.*)</trees>.*");
255
        rx.indexIn(content, 0);
256
        if (rx.capturedTexts().count()<1)
389 werner 257
            return 0;
384 werner 258
        my_content = rx.cap(1).trimmed();
259
    }
260
 
294 werner 261
    QStringList lines=my_content.split('\n');
106 Werner 262
    if (lines.count()<2)
389 werner 263
        return 0;
384 werner 264
    // drop comments
265
    while (!lines.isEmpty() && lines.front().startsWith('#') )
266
        lines.pop_front();
267
    while (!lines.isEmpty() && lines.last().isEmpty())
268
        lines.removeLast();
269
 
106 Werner 270
    char sep='\t';
271
    if (!lines[0].contains(sep))
272
        sep=';';
155 werner 273
    QStringList headers = lines[0].trimmed().split(sep);
138 Werner 274
 
247 werner 275
    int iID = headers.indexOf("id");
106 Werner 276
    int iX = headers.indexOf("x");
277
    int iY = headers.indexOf("y");
278
    int iBhd = headers.indexOf("bhdfrom");
384 werner 279
    if (iBhd<0)
280
        iBhd = headers.indexOf("dbh");
281
    double height_conversion = 100.;
106 Werner 282
    int iHeight = headers.indexOf("treeheight");
384 werner 283
    if (iHeight<0) {
284
        iHeight = headers.indexOf("height");
285
        height_conversion = 1.; // in meter
286
    }
106 Werner 287
    int iSpecies = headers.indexOf("species");
170 werner 288
    int iAge = headers.indexOf("age");
204 werner 289
    if (iX==-1 || iY==-1 || iBhd==-1 || iSpecies==-1 || iHeight==-1)
384 werner 290
        throw IException(QString("Initfile %1 is not valid!\nObligatory columns are: x,y, bhdfrom or dbh, species, treeheight or height.").arg(fileName));
106 Werner 291
 
138 Werner 292
    double dbh;
384 werner 293
    bool ok;
389 werner 294
    int cnt=0;
384 werner 295
    QString speciesid;
106 Werner 296
    for (int i=1;i<lines.count();i++) {
297
        QString &line = lines[i];
138 Werner 298
        dbh = line.section(sep, iBhd, iBhd).toDouble();
299
        if (dbh<5.)
300
            continue;
285 werner 301
 
106 Werner 302
        QPointF f;
303
        if (iX>=0 && iY>=0) {
304
           f.setX( line.section(sep, iX, iX).toDouble() );
305
           f.setY( line.section(sep, iY, iY).toDouble() );
306
           f+=offset;
285 werner 307
 
106 Werner 308
        }
285 werner 309
        // position valid?
310
        if (!mModel->heightGrid()->valueAt(f).isValid())
311
            continue;
312
        Tree &tree = ru->newTree();
313
        tree.setPosition(f);
247 werner 314
        if (iID>=0)
315
            tree.setId(line.section(sep, iID, iID).toInt() );
106 Werner 316
 
138 Werner 317
        tree.setDbh(dbh);
384 werner 318
        tree.setHeight(line.section(sep, iHeight, iHeight).toDouble()/height_conversion); // convert from Picus-cm to m if necessary
319
 
389 werner 320
        speciesid = line.section(sep, iSpecies, iSpecies).trimmed();
270 werner 321
        int picusid = speciesid.toInt(&ok);
322
        if (ok) {
384 werner 323
            int idx = picusSpeciesIds.indexOf(picusid);
270 werner 324
            if (idx==-1)
325
                throw IException(QString("Loading init-file: invalid Picus-species-id. Species: %1").arg(picusid));
138 Werner 326
            speciesid = iLandSpeciesIds[idx];
270 werner 327
        }
138 Werner 328
        Species *s = speciesSet->species(speciesid);
329
        if (!ru || !s)
389 werner 330
            throw IException(QString("Loading init-file: either resource unit or species invalid. Species: %1").arg(speciesid));
388 werner 331
        tree.setSpecies(s);
107 Werner 332
 
388 werner 333
        ok = true;
334
        if (iAge>=0)
335
           tree.setAge(line.section(sep, iAge, iAge).toInt(&ok), tree.height()); // this is a *real* age
336
        if (iAge<0 || !ok || tree.age()==0)
337
           tree.setAge(0, tree.height()); // no real tree age available
338
 
138 Werner 339
        tree.setRU(ru);
106 Werner 340
        tree.setup();
389 werner 341
        cnt++;
106 Werner 342
    }
389 werner 343
    return cnt;
106 Werner 344
    //qDebug() << "loaded init-file contained" << lines.count() <<"lines.";
345
    //qDebug() << "lines: " << lines;
346
}
287 werner 347
 
393 werner 348
/** initialize trees on a resource unit based on dbh distributions.
349
  use a fairly clever algorithm to determine tree positions.
350
  see http://iland.boku.ac.at/initialize+trees
351
  @param content tree init file (including headers) in a string
352
  @param ru resource unit
353
  @param fileName source file name (for error reporting)
354
  @return number of trees added
355
  */
549 werner 356
int StandLoader::loadDistributionList(const QString &content, ResourceUnit *ru, int stand_id, const QString &fileName)
287 werner 357
{
358
    if (!ru)
359
        ru = mModel->ru();
360
    Q_ASSERT(ru!=0);
361
    SpeciesSet *speciesSet = ru->speciesSet(); // of default RU
362
    Q_ASSERT(speciesSet!=0);
363
 
431 werner 364
    //DebugTimer t("StandLoader::loadiLandFile");
294 werner 365
    CSVFile infile;
366
    infile.loadFromString(content);
367
 
287 werner 368
    int icount = infile.columnIndex("count");
369
    int ispecies = infile.columnIndex("species");
370
    int idbh_from = infile.columnIndex("dbh_from");
371
    int idbh_to = infile.columnIndex("dbh_to");
372
    int ihd = infile.columnIndex("hd");
373
    int iage = infile.columnIndex("age");
311 werner 374
    int idensity = infile.columnIndex("density");
287 werner 375
    if (icount<0 || ispecies<0 || idbh_from<0 || idbh_to<0 || ihd<0 || iage<0)
376
        throw IException(QString("load-ini-file: file '%1' containts not all required fields (count, species, dbh_from, dbh_to, hd, age).").arg(fileName));
377
 
294 werner 378
    mInitItems.clear();
287 werner 379
    InitFileItem item;
384 werner 380
    bool ok;
393 werner 381
    int total_count = 0;
287 werner 382
    for (int row=0;row<infile.rowCount();row++) {
549 werner 383
        item.count = infile.value(row, icount).toInt();
384
        total_count += item.count;
385
        item.dbh_from = infile.value(row, idbh_from).toDouble();
386
        item.dbh_to = infile.value(row, idbh_to).toDouble();
387
        item.hd = infile.value(row, ihd).toDouble();
388
        ok = true;
389
        if (iage>=0)
390
            item.age = infile.value(row, iage).toInt(&ok);
391
        if (iage<0 || !ok)
392
            item.age = 0;
384 werner 393
 
549 werner 394
        item.species = speciesSet->species(infile.value(row, ispecies).toString());
395
        if (idensity>=0)
396
            item.density = infile.value(row, idensity).toDouble();
397
        else
398
            item.density = 0.;
399
        if (item.density<-1 || item.density>1)
400
            throw IException(QString("load-ini-file: invalid value for density. Allowed range is -1..1: '%1' in file '%2', line %3.")
401
                             .arg(item.density)
402
                             .arg(fileName)
403
                             .arg(row));
404
        if (!item.species) {
405
            throw IException(QString("load-ini-file: unknown speices '%1' in file '%2', line %3.")
406
                             .arg(infile.value(row, ispecies).toString())
407
                             .arg(fileName)
408
                             .arg(row));
409
        }
410
        mInitItems.push_back(item);
287 werner 411
    }
290 werner 412
    // setup the random distribution
413
    QString density_func = GlobalSettings::instance()->settings().value("model.initialization.randomFunction", "1-x^2");
431 werner 414
    if (logLevelInfo())  qDebug() << "density function:" << density_func;
290 werner 415
    if (!mRandom || (mRandom->densityFunction()!= density_func)) {
416
        if (mRandom)
417
            delete mRandom;
418
        mRandom=new RandomCustomPDF(density_func);
431 werner 419
        if (logLevelInfo()) qDebug() << "new probabilty density function:" << density_func;
290 werner 420
    }
549 werner 421
    if (stand_id>0) {
422
        // execute stand based initialization
423
        executeiLandInitStand(stand_id);
424
    } else {
425
        // exeucte the initialization based on single resource units
426
        executeiLandInit(ru);
427
        ru->cleanTreeList();
428
    }
393 werner 429
    return total_count;
287 werner 430
 
431
}
432
 
549 werner 433
int StandLoader::loadiLandFile(const QString &fileName, ResourceUnit *ru, int stand_id)
294 werner 434
{
435
    if (!QFile::exists(fileName))
436
        throw IException(QString("load-ini-file: file '%1' does not exist.").arg(fileName));
437
    QString content = Helper::loadTextFile(fileName);
549 werner 438
    return loadDistributionList(content, ru, stand_id, fileName);
294 werner 439
}
440
 
287 werner 441
// evenlist: tentative order of pixel-indices (within a 5x5 grid) used as tree positions.
442
// e.g. 12 = centerpixel, 0: upper left corner, ...
443
int evenlist[25] = { 12, 6, 18, 16, 8, 22, 2, 10, 14, 0, 24, 20, 4,
444
                     1, 13, 15, 19, 21, 3, 7, 11, 17, 23, 5, 9};
290 werner 445
int unevenlist[25] = { 11,13,7,17, 1,19,5,21, 9,23,3,15,
446
                       6,18,2,10,4,24,12,0,8,14,20,22};
447
 
448
 
549 werner 449
 
287 werner 450
// sort function
451
bool sortPairLessThan(const QPair<int, double> &s1, const QPair<int, double> &s2)
311 werner 452
{
453
    return s1.second < s2.second;
454
}
455
 
549 werner 456
struct SInitPixel {
732 werner 457
    double basal_area; // accumulated basal area
458
    QPoint pixelOffset; // location of the pixel
459
    ResourceUnit *resource_unit; // pointer to the resource unit the pixel belongs to
460
    double h_max; // predefined maximum height at given pixel (if available from LIDAR or so)
461
    SInitPixel(): basal_area(0.), resource_unit(0), h_max(-1.) {}
549 werner 462
};
463
 
464
bool sortInitPixelLessThan(const SInitPixel &s1, const SInitPixel &s2)
465
{
466
    return s1.basal_area < s2.basal_area;
467
}
468
 
469
 
311 werner 470
/**
471
*/
549 werner 472
 
290 werner 473
void StandLoader::executeiLandInit(ResourceUnit *ru)
287 werner 474
{
290 werner 475
 
287 werner 476
    QPointF offset = ru->boundingBox().topLeft();
477
    QPoint offsetIdx = GlobalSettings::instance()->model()->grid()->indexAt(offset);
478
 
479
    // a multimap holds a list for all trees.
480
    // key is the index of a 10x10m pixel within the resource unit
481
    QMultiMap<int, int> tree_map;
549 werner 482
    //QHash<int,SInitPixel> tcount;
483
 
287 werner 484
    QVector<QPair<int, double> > tcount; // counts
485
    for (int i=0;i<100;i++)
486
        tcount.push_back(QPair<int,double>(i,0.));
487
 
488
    int key;
311 werner 489
    double rand_val, rand_fraction;
312 werner 490
    int total_count = 0;
290 werner 491
    foreach(const InitFileItem &item, mInitItems) {
311 werner 492
        rand_fraction = fabs(double(item.density));
287 werner 493
        for (int i=0;i<item.count;i++) {
494
            // create trees
495
            int tree_idx = ru->newTreeIndex();
496
            Tree &tree = ru->trees()[tree_idx]; // get reference to modify tree
707 werner 497
            tree.setDbh(nrandom(item.dbh_from, item.dbh_to));
287 werner 498
            tree.setHeight(tree.dbh()/100. * item.hd); // dbh from cm->m, *hd-ratio -> meter height
499
            tree.setSpecies(item.species);
384 werner 500
            if (item.age<=0)
388 werner 501
                tree.setAge(0,tree.height());
381 werner 502
            else
388 werner 503
                tree.setAge(item.age, tree.height());
287 werner 504
            tree.setRU(ru);
505
            tree.setup();
312 werner 506
            total_count++;
311 werner 507
 
508
            // calculate random value. "density" is from 1..-1.
509
            rand_val = mRandom->get();
510
            if (item.density<0)
511
                rand_val = 1. - rand_val;
705 werner 512
            rand_val = rand_val * rand_fraction + drandom()*(1.-rand_fraction);
311 werner 513
 
287 werner 514
            // key: rank of target pixel
515
            // first: index of target pixel
516
            // second: sum of target pixel
311 werner 517
            key = limit(int(100*rand_val), 0, 99); // get from random number generator
287 werner 518
            tree_map.insert(tcount[key].first, tree_idx); // store tree in map
519
            tcount[key].second+=tree.basalArea(); // aggregate the basal area for each 10m pixel
312 werner 520
            if ( (total_count < 20 && i%2==0)
521
                || (total_count<100 && i%10==0 )
522
                || (i%30==0) ) {
287 werner 523
                qSort(tcount.begin(), tcount.end(), sortPairLessThan);
312 werner 524
            }
287 werner 525
        }
526
        qSort(tcount.begin(), tcount.end(), sortPairLessThan);
527
    }
528
 
529
    int bits, index, pos;
530
    int c;
531
    QList<int> trees;
532
    QPoint tree_pos;
289 werner 533
 
287 werner 534
    for (int i=0;i<100;i++) {
535
        trees = tree_map.values(i);
536
        c = trees.count();
290 werner 537
        QPointF pixel_center = ru->boundingBox().topLeft() + QPointF((i/10)*10. + 5., (i%10)*10. + 5.);
538
        if (!mModel->heightGrid()->valueAt(pixel_center).isValid()) {
539
            // no trees on that pixel: let trees die
540
            foreach(int tree_idx, trees) {
541
                ru->trees()[tree_idx].die();
542
            }
543
            continue;
544
        }
545
 
287 werner 546
        bits = 0;
547
        index = -1;
290 werner 548
        double r;
287 werner 549
        foreach(int tree_idx, trees) {
550
            if (c>18) {
551
                index = (index + 1)%25;
552
            } else {
553
                int stop=1000;
290 werner 554
                index = 0;
287 werner 555
                do {
290 werner 556
                    //r = drandom();
557
                    //if (r<0.5)  // skip position with a prob. of 50% -> adds a little "noise"
558
                    //    index++;
559
                    //index = (index + 1)%25; // increase and roll over
560
 
561
                    // search a random position
562
                    r = drandom();
563
                    index = limit(int(25 *  r*r), 0, 24); // use rnd()^2 to search for locations -> higher number of low indices (i.e. 50% of lookups in first 25% of locations)
287 werner 564
                } while (isBitSet(bits, index)==true && stop--);
565
                if (!stop)
566
                    qDebug() << "executeiLandInit: found no free bit.";
567
                setBit(bits, index, true); // mark position as used
568
            }
290 werner 569
            // get position from fixed lists (one for even, one for uneven resource units)
570
            pos = ru->index()%2?evenlist[index]:unevenlist[index];
287 werner 571
            tree_pos = offsetIdx  // position of resource unit
572
                       + QPoint(5*(i/10), 5*(i%10)) // relative position of 10x10m pixel
573
                       + QPoint(pos/5, pos%5); // relative position within 10x10m pixel
574
            //qDebug() << tree_no++ << "to" << index;
575
            ru->trees()[tree_idx].setPosition(tree_pos);
576
        }
577
    }
578
}
549 werner 579
 
580
// provide a hashing function for the QPoint type (needed from stand init function below)
581
inline uint qHash(const QPoint &key)
582
 {
583
     return qHash(key.x()) ^ qHash(key.y());
584
 }
585
 
550 werner 586
 
587
// Initialization routine based on a stand map.
588
// Basically a list of 10m pixels for a given stand is retrieved
589
// and the filled with the same procedure as the resource unit based init
590
// see http://iland.boku.ac.at/initialize+trees
549 werner 591
void StandLoader::executeiLandInitStand(int stand_id)
592
{
593
 
594
    const MapGrid *grid = GlobalSettings::instance()->model()->standGrid();
595
 
732 werner 596
    // get a list of positions of all pixels that belong to our stand
549 werner 597
    QList<int> indices = grid->gridIndices(stand_id);
550 werner 598
    if (indices.isEmpty()) {
599
        qDebug() << "stand" << stand_id << "not in project area. No init performed.";
600
        return;
549 werner 601
    }
602
    // a multiHash holds a list for all trees.
603
    // key is the location of the 10x10m pixel
604
    QMultiHash<QPoint, int> tree_map;
605
    QList<SInitPixel> pixel_list; // working list of all 10m pixels
732 werner 606
    pixel_list.reserve(indices.size());
549 werner 607
 
608
    foreach (int i, indices) {
609
       SInitPixel p;
610
       p.pixelOffset = grid->grid().indexOf(i); // index in the 10m grid
732 werner 611
       p.resource_unit = GlobalSettings::instance()->model()->ru( grid->grid().cellCenterPoint(p.pixelOffset));
612
       if (mInitHeightGrid)
613
           p.h_max = mInitHeightGrid->grid().constValueAtIndex(p.pixelOffset);
549 werner 614
       pixel_list.append(p);
615
    }
616
    double area_factor = grid->area(stand_id) / 10000.;
617
 
732 werner 618
    int key=0;
549 werner 619
    double rand_val, rand_fraction;
620
    int total_count = 0;
732 werner 621
    int total_tries = 0;
622
    int total_misses = 0;
623
    if (mInitHeightGrid && !mHeightGridResponse)
624
        throw IException("executeiLandInitStand: trying to initialize with height grid but without response function.");
549 werner 625
    foreach(const InitFileItem &item, mInitItems) {
626
        rand_fraction = fabs(double(item.density));
627
        int count = item.count * area_factor + 0.5; // round
732 werner 628
        double init_max_height = item.dbh_to/100. * item.hd;
549 werner 629
        for (int i=0;i<count;i++) {
630
 
732 werner 631
            bool found = false;
632
            int tries = mHeightGridTries;
633
            while (!found &&--tries) {
634
                // calculate random value. "density" is from 1..-1.
635
                rand_val = mRandom->get();
636
                if (item.density<0)
637
                    rand_val = 1. - rand_val;
638
                rand_val = rand_val * rand_fraction + drandom()*(1.-rand_fraction);
639
                ++total_tries;
549 werner 640
 
732 werner 641
                // key: rank of target pixel
642
                key = limit(int(pixel_list.count()*rand_val), 0, pixel_list.count()-1); // get from random number generator
549 werner 643
 
732 werner 644
                if (mInitHeightGrid) {
645
                    // calculate how good the selected pixel fits w.r.t. the predefined height
646
                    double p_value = pixel_list[key].h_max>0.?mHeightGridResponse->calculate(init_max_height/pixel_list[key].h_max):0.;
647
                    if (drandom() < p_value)
648
                        found = true;
649
                } else {
650
                    found = true;
651
                }
652
            }
653
            if (tries<0) ++total_misses;
549 werner 654
 
655
            // create a tree
656
            ResourceUnit *ru = pixel_list[key].resource_unit;
657
            int tree_idx = ru->newTreeIndex();
658
            Tree &tree = ru->trees()[tree_idx]; // get reference to modify tree
659
            tree.setDbh(nrandom(item.dbh_from, item.dbh_to));
660
            tree.setHeight(tree.dbh()/100. * item.hd); // dbh from cm->m, *hd-ratio -> meter height
661
            tree.setSpecies(item.species);
662
            if (item.age<=0)
663
                tree.setAge(0,tree.height());
664
            else
665
                tree.setAge(item.age, tree.height());
666
            tree.setRU(ru);
667
            tree.setup();
668
            total_count++;
669
 
670
            // store in the multiHash the position of the pixel and the tree_idx in the resepctive resource unit
671
            tree_map.insert(pixel_list[key].pixelOffset, tree_idx);
672
            pixel_list[key].basal_area+=tree.basalArea(); // aggregate the basal area for each 10m pixel
673
 
674
            // resort list
675
            if ( (total_count < 20 && i%2==0)
676
                || (total_count<100 && i%10==0 )
677
                || (i%30==0) ) {
678
                qSort(pixel_list.begin(), pixel_list.end(), sortInitPixelLessThan);
679
            }
680
        }
681
        qSort(pixel_list.begin(), pixel_list.end(), sortInitPixelLessThan);
682
    }
732 werner 683
    if (total_misses>0 || total_tries > total_count) {
684
        qDebug() << "init for stand" << stand_id << "treecount:" << total_count << ", tries:" << total_tries << ", misses:" << total_misses << ", %miss:" << qRound(total_misses*100 / (double)total_count);
685
    }
549 werner 686
 
687
    int bits, index, pos;
688
    int c;
689
    QList<int> trees;
690
    QPoint tree_pos;
691
 
692
    foreach(const SInitPixel &p, pixel_list) {
693
        trees = tree_map.values(p.pixelOffset);
694
        c = trees.count();
695
        bits = 0;
696
        index = -1;
697
        double r;
698
        foreach(int tree_idx, trees) {
699
            if (c>18) {
700
                index = (index + 1)%25;
701
            } else {
702
                int stop=1000;
703
                index = 0;
704
                do {
705
                    // search a random position
706
                    r = drandom();
707
                    index = limit(int(25 *  r*r), 0, 24); // use rnd()^2 to search for locations -> higher number of low indices (i.e. 50% of lookups in first 25% of locations)
708
                } while (isBitSet(bits, index)==true && stop--);
709
                if (!stop)
710
                    qDebug() << "executeiLandInit: found no free bit.";
711
                setBit(bits, index, true); // mark position as used
712
            }
713
            // get position from fixed lists (one for even, one for uneven resource units)
714
            pos = p.resource_unit->index()%2?evenlist[index]:unevenlist[index];
715
            tree_pos = p.pixelOffset * cPxPerHeight; // convert to LIF index
600 werner 716
            tree_pos += QPoint(pos/cPxPerHeight, pos%cPxPerHeight);
550 werner 717
 
549 werner 718
            p.resource_unit->trees()[tree_idx].setPosition(tree_pos);
734 werner 719
            // test if tree position is valid..
720
            if (!GlobalSettings::instance()->model()->grid()->isIndexValid(tree_pos))
721
                qDebug() << "Standloader: invalid position!";
549 werner 722
        }
723
    }
550 werner 724
    if (logLevelInfo()) qDebug() << "init for stand" << stand_id << "with area" << "area (m2)" << grid->area(stand_id) << "count of 10m pixels:"  << indices.count() << "initialized trees:" << total_count;
725
 
549 werner 726
}
600 werner 727
 
728
/// a (hacky) way of adding saplings of a certain age to a stand defined by 'stand_id'.
729
int StandLoader::loadSaplings(const QString &content, int stand_id, const QString &fileName)
730
{
603 werner 731
    const MapGrid *stand_grid;
732
    if (mCurrentMap)
733
        stand_grid = mCurrentMap; // if set
734
    else
735
        stand_grid = GlobalSettings::instance()->model()->standGrid(); // default
736
 
600 werner 737
    QList<int> indices = stand_grid->gridIndices(stand_id); // list of 10x10m pixels
738
    if (indices.isEmpty()) {
739
        qDebug() << "stand" << stand_id << "not in project area. No init performed.";
740
        return -1;
741
    }
742
    double area_factor = stand_grid->area(stand_id) / 10000.; // multiplier for grid (e.g. 2 if stand has area of 2 hectare)
743
 
744
    // parse the content of the init-file
745
    // species
746
    CSVFile init;
747
    init.loadFromString(content);
748
    int ispecies = init.columnIndex("species");
749
    int icount = init.columnIndex("count");
750
    int iheight = init.columnIndex("height");
751
    int iage = init.columnIndex("age");
752
    if (ispecies==-1 || icount==-1)
753
        throw IException("Error while loading saplings: columns 'species' or 'count' are missing!!");
754
 
755
    const SpeciesSet *set = GlobalSettings::instance()->model()->ru()->speciesSet();
756
    double height, age;
757
    int total = 0;
758
    for (int row=0;row<init.rowCount();++row) {
759
        int pxcount = init.value(row, icount).toDouble() * area_factor + 0.5; // no. of pixels that should be filled (sapling grid is the same resolution as the lif-grid)
760
        const Species *species = set->species(init.value(row, ispecies).toString());
761
        if (!species)
762
            throw IException(QString("Error while loading saplings: invalid species '%1'.").arg(init.value(row, ispecies).toString()));
763
        height = iheight==-1?0.05: init.value(row, iheight).toDouble();
764
        age = iage==-1?1:init.value(row,iage).toDouble();
765
 
766
        int misses = 0;
767
        int hits = 0;
768
        while (hits < pxcount) {
769
           int rnd_index = irandom(0, indices.count()-1);
770
           QPoint offset=stand_grid->grid().indexOf(indices[rnd_index]);
771
           ResourceUnit *ru = GlobalSettings::instance()->model()->ru(stand_grid->grid().cellCenterPoint(offset));
772
           //
773
           offset = offset * cPxPerHeight; // index of 10m patch -> to lif pixel coordinates
774
           int in_p = irandom(0, cPxPerHeight*cPxPerHeight-1); // index of lif-pixel
775
           offset += QPoint(in_p / cPxPerHeight, in_p % cPxPerHeight);
776
           if (ru->saplingHeightForInit(offset) > height) {
777
               misses++;
778
           } else {
779
               // ok
780
               hits++;
781
               ru->resourceUnitSpecies(species).changeSapling().addSapling(offset);
782
           }
783
           if (misses > 3*pxcount) {
784
               qDebug() << "tried to add" << pxcount << "saplings at stand" << stand_id << "but failed in finding enough free positions. Added" << hits << "and stopped.";
785
               break;
786
           }
787
        }
788
        total += hits;
789
 
790
    }
791
    return total;
792
}