Subversion Repositories public iLand

Rev

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