Subversion Repositories public iLand

Rev

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