Subversion Repositories public iLand

Rev

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