Subversion Repositories public iLand

Rev

Rev 1159 | Rev 1164 | 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"
1162 werner 29
#include "species.h"
106 Werner 30
 
31
#include "helper.h"
290 werner 32
#include "random.h"
137 Werner 33
#include "expression.h"
140 Werner 34
#include "expressionwrapper.h"
284 werner 35
#include "environment.h"
287 werner 36
#include "csvfile.h"
549 werner 37
#include "mapgrid.h"
699 werner 38
#include "snapshot.h"
1062 werner 39
#include "grasscover.h"
106 Werner 40
 
697 werner 41
/** @class StandLoader
42
    @ingroup tools
43
    loads (initializes) trees for a "stand" from various sources.
44
    StandLoader initializes trees on the landscape. It reads (usually) from text files, creates the
45
    trees and distributes the trees on the landscape (on the ResoureceUnit or on a stand defined by a grid).
46
 
47
    See http://iland.boku.ac.at/initialize+trees
48
  */
381 werner 49
// provide a mapping between "Picus"-style and "iLand"-style species Ids
1159 werner 50
static QVector<int> picusSpeciesIds = QVector<int>() << 0 << 1 << 17;
51
static QStringList iLandSpeciesIds = QStringList() << "piab" << "piab" << "fasy";
732 werner 52
 
290 werner 53
StandLoader::~StandLoader()
54
{
55
    if (mRandom)
56
        delete mRandom;
732 werner 57
    if (mHeightGridResponse)
58
        delete mHeightGridResponse;
290 werner 59
}
106 Werner 60
 
61
 
281 werner 62
void StandLoader::copyTrees()
63
{
64
    // we assume that all stands are equal, so wie simply COPY the trees and modify them afterwards
65
    const Grid<ResourceUnit*> &ruGrid=mModel->RUgrid();
66
    ResourceUnit **p = ruGrid.begin();
67
    if (!p)
68
        throw IException("Standloader: invalid resource unit pointer!");
69
    ++p; // skip the first...
70
    const QVector<Tree> &tocopy = mModel->ru()->trees();
71
    for (; p!=ruGrid.end(); ++p) {
72
        QRectF rect = (*p)->boundingBox();
73
        foreach(const Tree& tree, tocopy) {
74
            Tree &newtree = (*p)->newTree();
75
            newtree = tree; // copy tree data...
739 werner 76
            newtree.setPosition(tree.position()+rect.topLeft());
281 werner 77
            newtree.setRU(*p);
78
            newtree.setNewId();
79
        }
80
    }
431 werner 81
    if (logLevelInfo()) qDebug() << Tree::statCreated() << "trees loaded / copied.";
281 werner 82
}
83
 
284 werner 84
/** main routine of the stand setup.
85
*/
106 Werner 86
void StandLoader::processInit()
87
{
88
    GlobalSettings *g = GlobalSettings::instance();
192 werner 89
    XmlHelper xml(g->settings().node("model.initialization"));
191 werner 90
 
281 werner 91
    QString copy_mode = xml.value("mode", "copy");
92
    QString type = xml.value("type", "");
192 werner 93
    QString  fileName = xml.value("file", "");
106 Werner 94
 
732 werner 95
    bool height_grid_enabled = xml.valueBool("heightGrid.enabled", false);
1157 werner 96
    mHeightGridTries = xml.valueInt("heightGrid.maxTries", 10);
732 werner 97
    QScopedPointer<const MapGrid> height_grid; // use a QScopedPointer to guarantee that the resource is freed at the end of the processInit() function
98
    if (height_grid_enabled) {
99
        QString init_height_grid_file = GlobalSettings::instance()->path(xml.value("heightGrid.fileName"), "init");
100
        qDebug() << "initialization: using predefined tree heights map" << init_height_grid_file;
101
 
102
        QScopedPointer<const MapGrid> p(new MapGrid(init_height_grid_file, false));
743 werner 103
        if (!p->isValid()) {
104
            throw IException(QString("Error when loading grid with tree heights for stand initalization: file %1 not found or not valid.").arg(init_height_grid_file));
105
        }
732 werner 106
        height_grid.swap(p);
107
        mInitHeightGrid = height_grid.data();
108
 
109
        QString expr=xml.value("heightGrid.fitFormula", "polygon(x, 0,0, 0.8,1, 1.1, 1, 1.25,0)");
110
        mHeightGridResponse = new Expression(expr);
1070 werner 111
        mHeightGridResponse->linearize(0., 2.);
732 werner 112
    }
113
 
106 Werner 114
    Tree::resetStatistics();
281 werner 115
 
116
    // one global init-file for the whole area:
284 werner 117
    if (copy_mode=="single") {
1057 werner 118
        // useful for 1ha simulations only...
119
        if (GlobalSettings::instance()->model()->ruList().size()>1)
120
            throw IException("Error initialization: 'mode' is 'single' but more than one resource unit is simulated (consider using another 'mode').");
121
 
122
        loadInitFile(fileName, type, 0, GlobalSettings::instance()->model()->ru()); // this is the first resource unit
299 werner 123
        evaluateDebugTrees();
281 werner 124
        return;
284 werner 125
    }
281 werner 126
 
137 Werner 127
 
281 werner 128
    // call a single tree init for each resource unit
129
    if (copy_mode=="unit") {
284 werner 130
        foreach( const ResourceUnit *const_ru, g->model()->ruList()) {
131
            ResourceUnit *ru = const_cast<ResourceUnit*>(const_ru);
132
            // set environment
133
            g->model()->environment()->setPosition(ru->boundingBox().center());
134
            type = xml.value("type", "");
135
            fileName = xml.value("file", "");
319 werner 136
            if (fileName.isEmpty())
137
                continue;
549 werner 138
            loadInitFile(fileName, type, 0, ru);
431 werner 139
            if (logLevelInfo()) qDebug() << "loaded" << fileName << "on" << ru->boundingBox() << "," << ru->trees().count() << "trees.";
284 werner 140
        }
299 werner 141
        evaluateDebugTrees();
284 werner 142
        return;
281 werner 143
    }
299 werner 144
 
732 werner 145
    // map-modus: load a init file for each "polygon" in the standgrid
549 werner 146
    if (copy_mode=="map") {
147
        if (!g->model()->standGrid() || !g->model()->standGrid()->isValid())
148
            throw IException(QString("Stand-Initialization: model.initialization.mode is 'map' but there is no valid stand grid defined (model.world.standGrid)"));
149
        QString map_file_name = GlobalSettings::instance()->path(xml.value("mapFileName"), "init");
150
 
151
        CSVFile map_file(map_file_name);
152
        if (map_file.rowCount()==0)
153
            throw IException(QString("Stand-Initialization: the map file %1 is empty or missing!").arg(map_file_name));
154
        int ikey = map_file.columnIndex("id");
155
        int ivalue = map_file.columnIndex("filename");
156
        if (ikey<0 || ivalue<0)
157
            throw IException(QString("Stand-Initialization: the map file %1 does not contain the mandatory columns 'id' and 'filename'!").arg(map_file_name));
158
        QString file_name;
159
        for (int i=0;i<map_file.rowCount();i++) {
160
            int key = map_file.value(i, ikey).toInt();
161
            if (key>0) {
162
                file_name = map_file.value(i, ivalue).toString();
550 werner 163
                if (logLevelInfo()) qDebug() << "loading" << file_name << "for grid id" << key;
734 werner 164
                if (!file_name.isEmpty())
165
                    loadInitFile(file_name, type, key, NULL);
549 werner 166
            }
167
        }
732 werner 168
        mInitHeightGrid = 0;
549 werner 169
        evaluateDebugTrees();
170
        return;
171
    }
904 werner 172
 
173
    // standgrid mode: load one large init file
174
    if (copy_mode=="standgrid") {
175
        fileName = GlobalSettings::instance()->path(fileName, "init");
176
        if (!QFile::exists(fileName))
177
            throw IException(QString("load-ini-file: file '%1' does not exist.").arg(fileName));
178
        QString content = Helper::loadTextFile(fileName);
179
        // this processes the init file (also does the checking) and
180
        // stores in a QHash datastrucutre
181
        parseInitFile(content, fileName);
182
 
183
        // setup the random distribution
184
        QString density_func = xml.value("model.initialization.randomFunction", "1-x^2");
185
        if (logLevelInfo())  qDebug() << "density function:" << density_func;
186
        if (!mRandom || (mRandom->densityFunction()!= density_func)) {
187
            if (mRandom)
188
                delete mRandom;
189
            mRandom=new RandomCustomPDF(density_func);
190
            if (logLevelInfo()) qDebug() << "new probabilty density function:" << density_func;
191
        }
192
 
193
        if (mStandInitItems.isEmpty())
194
            throw IException("StandLoader::processInit: 'mode' is 'standgrid' but the init file is either empty or contains no 'stand_id'-column.");
195
        QHash<int, QVector<InitFileItem> >::const_iterator it = mStandInitItems.constBegin();
196
        while (it!=mStandInitItems.constEnd()) {
197
            mInitItems = it.value(); // copy the items...
198
            executeiLandInitStand(it.key());
199
            ++it;
200
        }
201
        qDebug() << "finished setup of trees.";
1157 werner 202
        evaluateDebugTrees();
904 werner 203
        return;
204
 
205
    }
699 werner 206
    if (copy_mode=="snapshot") {
207
        // load a snapshot from a file
208
        Snapshot shot;
549 werner 209
 
699 werner 210
        QString input_db = GlobalSettings::instance()->path(fileName);
211
        shot.loadSnapshot(input_db);
212
        return;
213
    }
281 werner 214
    throw IException("StandLoader::processInit: invalid initalization.mode!");
215
}
216
 
967 werner 217
void StandLoader::processAfterInit()
218
{
219
    XmlHelper xml(GlobalSettings::instance()->settings().node("model.initialization"));
220
 
221
    QString mode = xml.value("mode", "copy");
222
    if (mode=="standgrid") {
223
        // load a file with saplings per polygon
224
        QString  filename = xml.value("saplingFile", "");
225
        if (filename.isEmpty())
226
            return;
227
        filename = GlobalSettings::instance()->path(filename, "init");
228
        if (!QFile::exists(filename))
229
            throw IException(QString("load-sapling-ini-file: file '%1' does not exist.").arg(filename));
230
        CSVFile init_file(filename);
231
        int istandid = init_file.columnIndex("stand_id");
232
        if (istandid==-1)
233
            throw IException("Sapling-Init: the init file contains no 'stand_id' column (required in 'standgrid' mode).");
234
 
235
        int stand_id = -99999;
236
        int ilow = -1, ihigh = 0;
237
        int total = 0;
238
        for (int i=0;i<init_file.rowCount();++i) {
239
            int row_stand = init_file.value(i, istandid).toInt();
240
            if (row_stand != stand_id) {
241
                if (stand_id>=0) {
242
                    // process stand
243
                    ihigh = i-1; // up to the last
244
                    total += loadSaplingsLIF(stand_id, init_file, ilow, ihigh);
245
                }
246
                ilow = i; // mark beginning of new stand
247
                stand_id = row_stand;
248
            }
249
        }
250
        if (stand_id>=0)
251
            total += loadSaplingsLIF(stand_id, init_file, ilow, init_file.rowCount()-1); // the last stand
252
        qDebug() << "initialization of sapling: total created:" << total;
253
 
254
    }
255
 
256
}
257
 
281 werner 258
void StandLoader::evaluateDebugTrees()
259
{
137 Werner 260
    // evaluate debugging
261
    QString dbg_str = GlobalSettings::instance()->settings().paramValueString("debug_tree");
299 werner 262
    int counter=0;
137 Werner 263
    if (!dbg_str.isEmpty()) {
733 werner 264
        if (dbg_str == "debugstamp") {
734 werner 265
            qDebug() << "debug_tree = debugstamp: try touching all trees...";
733 werner 266
            // try to force an error if a stamp is invalid
267
            AllTreeIterator at(GlobalSettings::instance()->model());
753 werner 268
            double total_offset=0.;
733 werner 269
            while (Tree *t=at.next()) {
270
                total_offset += t->stamp()->offset();
734 werner 271
                if (!GlobalSettings::instance()->model()->grid()->isIndexValid(t->positionIndex()))
272
                    qDebug() << "evaluateDebugTrees: debugstamp: invalid position found!";
733 werner 273
            }
734 werner 274
            qDebug() << "debug_tree = debugstamp: try touching all trees finished...";
733 werner 275
            return;
276
        }
277
        TreeWrapper tw;
278
        Expression dexp(dbg_str, &tw); // load expression dbg_str and enable external model variables
137 Werner 279
        AllTreeIterator at(GlobalSettings::instance()->model());
280
        double result;
281
        while (Tree *t = at.next()) {
140 Werner 282
            tw.setTree(t);
137 Werner 283
            result = dexp.execute();
299 werner 284
            if (result) {
137 Werner 285
                t->enableDebugging();
299 werner 286
                counter++;
287
            }
137 Werner 288
        }
732 werner 289
        qDebug() << "evaluateDebugTrees: enabled debugging for" << counter << "trees.";
137 Werner 290
    }
106 Werner 291
}
292
 
904 werner 293
 
732 werner 294
/// load a single init file. Calls loadPicusFile() or loadiLandFile()
295
/// @param fileName file to load
296
/// @param type init mode. allowed: "picus"/"single" or "iland"/"distribution"
549 werner 297
int StandLoader::loadInitFile(const QString &fileName, const QString &type, int stand_id, ResourceUnit *ru)
281 werner 298
{
284 werner 299
    QString pathFileName = GlobalSettings::instance()->path(fileName, "init");
300
    if (!QFile::exists(pathFileName))
301
        throw IException(QString("StandLoader::loadInitFile: File %1 does not exist!").arg(pathFileName));
281 werner 302
 
549 werner 303
    if (type=="picus" || type=="single") {
304
        if (stand_id>0)
305
            throw IException(QLatin1String("StandLoader::loadInitFile: initialization type %1 currently not supported for stand initilization mode!")+type);
294 werner 306
        return loadPicusFile(pathFileName, ru);
549 werner 307
    }
384 werner 308
    if (type=="iland" || type=="distribution")
549 werner 309
        return loadiLandFile(pathFileName, ru, stand_id);
281 werner 310
 
384 werner 311
    throw IException(QLatin1String("StandLoader::loadInitFile: unknown initalization.type:")+type);
281 werner 312
}
313
 
393 werner 314
int StandLoader::loadPicusFile(const QString &fileName, ResourceUnit *ru)
106 Werner 315
{
294 werner 316
    QString content = Helper::loadTextFile(fileName);
317
    if (content.isEmpty()) {
318
        qDebug() << "file not found: " + fileName;
393 werner 319
        return 0;
294 werner 320
    }
393 werner 321
    return loadSingleTreeList(content, ru, fileName);
294 werner 322
}
323
 
389 werner 324
/** load a list of trees (given by content) to a resource unit. Param fileName is just for error reporting.
325
  returns the number of loaded trees.
326
  */
327
int StandLoader::loadSingleTreeList(const QString &content, ResourceUnit *ru, const QString &fileName)
294 werner 328
{
106 Werner 329
    if (!ru)
330
        ru = mModel->ru();
284 werner 331
    Q_ASSERT(ru!=0);
106 Werner 332
 
284 werner 333
    QPointF offset = ru->boundingBox().topLeft();
334
    SpeciesSet *speciesSet = ru->speciesSet(); // of default RU
335
 
384 werner 336
    QString my_content(content);
337
    // cut out the <trees> </trees> part if present
338
    if (content.contains("<trees>")) {
339
        QRegExp rx(".*<trees>(.*)</trees>.*");
340
        rx.indexIn(content, 0);
341
        if (rx.capturedTexts().count()<1)
389 werner 342
            return 0;
384 werner 343
        my_content = rx.cap(1).trimmed();
344
    }
345
 
948 werner 346
    CSVFile infile;
347
    infile.loadFromString(my_content);
384 werner 348
 
138 Werner 349
 
948 werner 350
    int iID =  infile.columnIndex("id");
351
    int iX = infile.columnIndex("x");
352
    int iY = infile.columnIndex("y");
353
    int iBhd = infile.columnIndex("bhdfrom");
384 werner 354
    if (iBhd<0)
948 werner 355
        iBhd = infile.columnIndex("dbh");
384 werner 356
    double height_conversion = 100.;
948 werner 357
    int iHeight =infile.columnIndex("treeheight");
384 werner 358
    if (iHeight<0) {
948 werner 359
        iHeight = infile.columnIndex("height");
384 werner 360
        height_conversion = 1.; // in meter
361
    }
948 werner 362
    int iSpecies = infile.columnIndex("species");
363
    int iAge = infile.columnIndex("age");
204 werner 364
    if (iX==-1 || iY==-1 || iBhd==-1 || iSpecies==-1 || iHeight==-1)
948 werner 365
        throw IException(QString("Initfile %1 is not valid!\nRequired columns are: x,y, bhdfrom or dbh, species, treeheight or height.").arg(fileName));
106 Werner 366
 
138 Werner 367
    double dbh;
384 werner 368
    bool ok;
389 werner 369
    int cnt=0;
384 werner 370
    QString speciesid;
948 werner 371
    for (int i=1;i<infile.rowCount();i++) {
372
        dbh = infile.value(i, iBhd).toDouble();
285 werner 373
 
948 werner 374
        //if (dbh<5.)
375
        //    continue;
376
 
106 Werner 377
        QPointF f;
378
        if (iX>=0 && iY>=0) {
948 werner 379
           f.setX( infile.value(i, iX).toDouble() );
380
           f.setY( infile.value(i, iY).toDouble() );
106 Werner 381
           f+=offset;
285 werner 382
 
106 Werner 383
        }
285 werner 384
        // position valid?
385
        if (!mModel->heightGrid()->valueAt(f).isValid())
386
            continue;
387
        Tree &tree = ru->newTree();
388
        tree.setPosition(f);
247 werner 389
        if (iID>=0)
948 werner 390
            tree.setId(infile.value(i, iID).toInt());
106 Werner 391
 
138 Werner 392
        tree.setDbh(dbh);
948 werner 393
        tree.setHeight(infile.value(i,iHeight).toDouble()/height_conversion); // convert from Picus-cm to m if necessary
384 werner 394
 
948 werner 395
        speciesid = infile.value(i,iSpecies).toString();
270 werner 396
        int picusid = speciesid.toInt(&ok);
397
        if (ok) {
384 werner 398
            int idx = picusSpeciesIds.indexOf(picusid);
270 werner 399
            if (idx==-1)
400
                throw IException(QString("Loading init-file: invalid Picus-species-id. Species: %1").arg(picusid));
138 Werner 401
            speciesid = iLandSpeciesIds[idx];
270 werner 402
        }
138 Werner 403
        Species *s = speciesSet->species(speciesid);
404
        if (!ru || !s)
389 werner 405
            throw IException(QString("Loading init-file: either resource unit or species invalid. Species: %1").arg(speciesid));
388 werner 406
        tree.setSpecies(s);
107 Werner 407
 
388 werner 408
        ok = true;
409
        if (iAge>=0)
948 werner 410
           tree.setAge(infile.value(i, iAge).toInt(&ok), tree.height()); // this is a *real* age
388 werner 411
        if (iAge<0 || !ok || tree.age()==0)
412
           tree.setAge(0, tree.height()); // no real tree age available
413
 
138 Werner 414
        tree.setRU(ru);
106 Werner 415
        tree.setup();
389 werner 416
        cnt++;
106 Werner 417
    }
389 werner 418
    return cnt;
106 Werner 419
    //qDebug() << "loaded init-file contained" << lines.count() <<"lines.";
420
    //qDebug() << "lines: " << lines;
421
}
287 werner 422
 
393 werner 423
/** initialize trees on a resource unit based on dbh distributions.
424
  use a fairly clever algorithm to determine tree positions.
425
  see http://iland.boku.ac.at/initialize+trees
426
  @param content tree init file (including headers) in a string
427
  @param ru resource unit
428
  @param fileName source file name (for error reporting)
429
  @return number of trees added
430
  */
549 werner 431
int StandLoader::loadDistributionList(const QString &content, ResourceUnit *ru, int stand_id, const QString &fileName)
287 werner 432
{
904 werner 433
    int total_count = parseInitFile(content, fileName, ru);
434
    if (total_count==0)
435
        return 0;
436
 
437
 
438
    // setup the random distribution
439
    QString density_func = GlobalSettings::instance()->settings().value("model.initialization.randomFunction", "1-x^2");
440
    if (logLevelInfo())  qDebug() << "density function:" << density_func;
441
    if (!mRandom || (mRandom->densityFunction()!= density_func)) {
442
        if (mRandom)
443
            delete mRandom;
444
        mRandom=new RandomCustomPDF(density_func);
445
        if (logLevelInfo()) qDebug() << "new probabilty density function:" << density_func;
446
    }
447
    if (stand_id>0) {
448
        // execute stand based initialization
449
        executeiLandInitStand(stand_id);
450
    } else {
451
        // exeucte the initialization based on single resource units
452
        executeiLandInit(ru);
453
        ru->cleanTreeList();
454
    }
455
    return total_count;
456
 
457
}
458
 
459
int StandLoader::parseInitFile(const QString &content, const QString &fileName, ResourceUnit* ru)
460
{
287 werner 461
    if (!ru)
462
        ru = mModel->ru();
463
    Q_ASSERT(ru!=0);
464
    SpeciesSet *speciesSet = ru->speciesSet(); // of default RU
465
    Q_ASSERT(speciesSet!=0);
466
 
431 werner 467
    //DebugTimer t("StandLoader::loadiLandFile");
294 werner 468
    CSVFile infile;
469
    infile.loadFromString(content);
470
 
287 werner 471
    int icount = infile.columnIndex("count");
472
    int ispecies = infile.columnIndex("species");
473
    int idbh_from = infile.columnIndex("dbh_from");
474
    int idbh_to = infile.columnIndex("dbh_to");
475
    int ihd = infile.columnIndex("hd");
476
    int iage = infile.columnIndex("age");
311 werner 477
    int idensity = infile.columnIndex("density");
287 werner 478
    if (icount<0 || ispecies<0 || idbh_from<0 || idbh_to<0 || ihd<0 || iage<0)
479
        throw IException(QString("load-ini-file: file '%1' containts not all required fields (count, species, dbh_from, dbh_to, hd, age).").arg(fileName));
480
 
904 werner 481
    int istandid = infile.columnIndex("stand_id");
294 werner 482
    mInitItems.clear();
904 werner 483
    mStandInitItems.clear();
484
 
287 werner 485
    InitFileItem item;
384 werner 486
    bool ok;
393 werner 487
    int total_count = 0;
287 werner 488
    for (int row=0;row<infile.rowCount();row++) {
968 werner 489
        item.count = infile.value(row, icount).toDouble();
549 werner 490
        total_count += item.count;
491
        item.dbh_from = infile.value(row, idbh_from).toDouble();
492
        item.dbh_to = infile.value(row, idbh_to).toDouble();
493
        item.hd = infile.value(row, ihd).toDouble();
775 werner 494
        if (item.hd==0. || item.dbh_from / 100. * item.hd < 4.)
782 werner 495
            qWarning() << QString("load init file: file '%1' tries to init trees below 4m height. hd=%2, dbh=%3.").arg(fileName).arg(item.hd).arg(item.dbh_from) ;
496
            //throw IException(QString("load init file: file '%1' tries to init trees below 4m height. hd=%2, dbh=%3.").arg(fileName).arg(item.hd).arg(item.dbh_from) );
549 werner 497
        ok = true;
498
        if (iage>=0)
499
            item.age = infile.value(row, iage).toInt(&ok);
500
        if (iage<0 || !ok)
501
            item.age = 0;
384 werner 502
 
549 werner 503
        item.species = speciesSet->species(infile.value(row, ispecies).toString());
504
        if (idensity>=0)
505
            item.density = infile.value(row, idensity).toDouble();
506
        else
507
            item.density = 0.;
971 werner 508
        if (item.density<-1)
549 werner 509
            throw IException(QString("load-ini-file: invalid value for density. Allowed range is -1..1: '%1' in file '%2', line %3.")
510
                             .arg(item.density)
511
                             .arg(fileName)
512
                             .arg(row));
513
        if (!item.species) {
514
            throw IException(QString("load-ini-file: unknown speices '%1' in file '%2', line %3.")
515
                             .arg(infile.value(row, ispecies).toString())
516
                             .arg(fileName)
517
                             .arg(row));
518
        }
904 werner 519
        if (istandid>=0) {
520
            int standid = infile.value(row,istandid).toInt();
521
            mStandInitItems[standid].push_back(item);
522
        } else {
523
            mInitItems.push_back(item);
524
        }
287 werner 525
    }
393 werner 526
    return total_count;
287 werner 527
 
528
}
529
 
904 werner 530
 
549 werner 531
int StandLoader::loadiLandFile(const QString &fileName, ResourceUnit *ru, int stand_id)
294 werner 532
{
533
    if (!QFile::exists(fileName))
534
        throw IException(QString("load-ini-file: file '%1' does not exist.").arg(fileName));
535
    QString content = Helper::loadTextFile(fileName);
549 werner 536
    return loadDistributionList(content, ru, stand_id, fileName);
294 werner 537
}
538
 
287 werner 539
// evenlist: tentative order of pixel-indices (within a 5x5 grid) used as tree positions.
540
// e.g. 12 = centerpixel, 0: upper left corner, ...
541
int evenlist[25] = { 12, 6, 18, 16, 8, 22, 2, 10, 14, 0, 24, 20, 4,
542
                     1, 13, 15, 19, 21, 3, 7, 11, 17, 23, 5, 9};
290 werner 543
int unevenlist[25] = { 11,13,7,17, 1,19,5,21, 9,23,3,15,
544
                       6,18,2,10,4,24,12,0,8,14,20,22};
545
 
546
 
549 werner 547
 
287 werner 548
// sort function
549
bool sortPairLessThan(const QPair<int, double> &s1, const QPair<int, double> &s2)
311 werner 550
{
551
    return s1.second < s2.second;
552
}
553
 
549 werner 554
struct SInitPixel {
732 werner 555
    double basal_area; // accumulated basal area
556
    QPoint pixelOffset; // location of the pixel
557
    ResourceUnit *resource_unit; // pointer to the resource unit the pixel belongs to
558
    double h_max; // predefined maximum height at given pixel (if available from LIDAR or so)
971 werner 559
    bool locked; // pixel is dedicated to a single species
560
    SInitPixel(): basal_area(0.), resource_unit(0), h_max(-1.), locked(false) {}
549 werner 561
};
562
 
563
bool sortInitPixelLessThan(const SInitPixel &s1, const SInitPixel &s2)
564
{
565
    return s1.basal_area < s2.basal_area;
566
}
567
 
971 werner 568
bool sortInitPixelUnlocked(const SInitPixel &s1, const SInitPixel &s2)
569
{
570
    return !s1.locked && s2.locked;
571
}
549 werner 572
 
311 werner 573
/**
574
*/
549 werner 575
 
290 werner 576
void StandLoader::executeiLandInit(ResourceUnit *ru)
287 werner 577
{
290 werner 578
 
287 werner 579
    QPointF offset = ru->boundingBox().topLeft();
580
    QPoint offsetIdx = GlobalSettings::instance()->model()->grid()->indexAt(offset);
581
 
582
    // a multimap holds a list for all trees.
583
    // key is the index of a 10x10m pixel within the resource unit
584
    QMultiMap<int, int> tree_map;
549 werner 585
    //QHash<int,SInitPixel> tcount;
586
 
287 werner 587
    QVector<QPair<int, double> > tcount; // counts
588
    for (int i=0;i<100;i++)
589
        tcount.push_back(QPair<int,double>(i,0.));
590
 
591
    int key;
311 werner 592
    double rand_val, rand_fraction;
312 werner 593
    int total_count = 0;
290 werner 594
    foreach(const InitFileItem &item, mInitItems) {
311 werner 595
        rand_fraction = fabs(double(item.density));
287 werner 596
        for (int i=0;i<item.count;i++) {
597
            // create trees
598
            int tree_idx = ru->newTreeIndex();
599
            Tree &tree = ru->trees()[tree_idx]; // get reference to modify tree
707 werner 600
            tree.setDbh(nrandom(item.dbh_from, item.dbh_to));
287 werner 601
            tree.setHeight(tree.dbh()/100. * item.hd); // dbh from cm->m, *hd-ratio -> meter height
602
            tree.setSpecies(item.species);
384 werner 603
            if (item.age<=0)
388 werner 604
                tree.setAge(0,tree.height());
381 werner 605
            else
388 werner 606
                tree.setAge(item.age, tree.height());
287 werner 607
            tree.setRU(ru);
608
            tree.setup();
312 werner 609
            total_count++;
311 werner 610
 
611
            // calculate random value. "density" is from 1..-1.
612
            rand_val = mRandom->get();
613
            if (item.density<0)
614
                rand_val = 1. - rand_val;
705 werner 615
            rand_val = rand_val * rand_fraction + drandom()*(1.-rand_fraction);
311 werner 616
 
287 werner 617
            // key: rank of target pixel
618
            // first: index of target pixel
619
            // second: sum of target pixel
311 werner 620
            key = limit(int(100*rand_val), 0, 99); // get from random number generator
287 werner 621
            tree_map.insert(tcount[key].first, tree_idx); // store tree in map
622
            tcount[key].second+=tree.basalArea(); // aggregate the basal area for each 10m pixel
312 werner 623
            if ( (total_count < 20 && i%2==0)
624
                || (total_count<100 && i%10==0 )
625
                || (i%30==0) ) {
287 werner 626
                qSort(tcount.begin(), tcount.end(), sortPairLessThan);
312 werner 627
            }
287 werner 628
        }
629
        qSort(tcount.begin(), tcount.end(), sortPairLessThan);
630
    }
631
 
632
    int bits, index, pos;
633
    int c;
634
    QList<int> trees;
635
    QPoint tree_pos;
289 werner 636
 
287 werner 637
    for (int i=0;i<100;i++) {
638
        trees = tree_map.values(i);
639
        c = trees.count();
290 werner 640
        QPointF pixel_center = ru->boundingBox().topLeft() + QPointF((i/10)*10. + 5., (i%10)*10. + 5.);
641
        if (!mModel->heightGrid()->valueAt(pixel_center).isValid()) {
642
            // no trees on that pixel: let trees die
643
            foreach(int tree_idx, trees) {
644
                ru->trees()[tree_idx].die();
645
            }
646
            continue;
647
        }
648
 
287 werner 649
        bits = 0;
650
        index = -1;
290 werner 651
        double r;
287 werner 652
        foreach(int tree_idx, trees) {
653
            if (c>18) {
654
                index = (index + 1)%25;
655
            } else {
656
                int stop=1000;
290 werner 657
                index = 0;
287 werner 658
                do {
290 werner 659
                    //r = drandom();
660
                    //if (r<0.5)  // skip position with a prob. of 50% -> adds a little "noise"
661
                    //    index++;
662
                    //index = (index + 1)%25; // increase and roll over
663
 
664
                    // search a random position
665
                    r = drandom();
666
                    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 667
                } while (isBitSet(bits, index)==true && stop--);
668
                if (!stop)
669
                    qDebug() << "executeiLandInit: found no free bit.";
670
                setBit(bits, index, true); // mark position as used
671
            }
290 werner 672
            // get position from fixed lists (one for even, one for uneven resource units)
673
            pos = ru->index()%2?evenlist[index]:unevenlist[index];
287 werner 674
            tree_pos = offsetIdx  // position of resource unit
675
                       + QPoint(5*(i/10), 5*(i%10)) // relative position of 10x10m pixel
676
                       + QPoint(pos/5, pos%5); // relative position within 10x10m pixel
677
            //qDebug() << tree_no++ << "to" << index;
678
            ru->trees()[tree_idx].setPosition(tree_pos);
679
        }
680
    }
681
}
549 werner 682
 
683
 
550 werner 684
 
685
// Initialization routine based on a stand map.
686
// Basically a list of 10m pixels for a given stand is retrieved
687
// and the filled with the same procedure as the resource unit based init
688
// see http://iland.boku.ac.at/initialize+trees
549 werner 689
void StandLoader::executeiLandInitStand(int stand_id)
690
{
691
 
692
    const MapGrid *grid = GlobalSettings::instance()->model()->standGrid();
901 werner 693
    if (mCurrentMap)
694
        grid = mCurrentMap;
549 werner 695
 
732 werner 696
    // get a list of positions of all pixels that belong to our stand
549 werner 697
    QList<int> indices = grid->gridIndices(stand_id);
550 werner 698
    if (indices.isEmpty()) {
699
        qDebug() << "stand" << stand_id << "not in project area. No init performed.";
700
        return;
549 werner 701
    }
702
    // a multiHash holds a list for all trees.
703
    // key is the location of the 10x10m pixel
704
    QMultiHash<QPoint, int> tree_map;
705
    QList<SInitPixel> pixel_list; // working list of all 10m pixels
732 werner 706
    pixel_list.reserve(indices.size());
549 werner 707
 
708
    foreach (int i, indices) {
709
       SInitPixel p;
710
       p.pixelOffset = grid->grid().indexOf(i); // index in the 10m grid
732 werner 711
       p.resource_unit = GlobalSettings::instance()->model()->ru( grid->grid().cellCenterPoint(p.pixelOffset));
712
       if (mInitHeightGrid)
713
           p.h_max = mInitHeightGrid->grid().constValueAtIndex(p.pixelOffset);
549 werner 714
       pixel_list.append(p);
715
    }
1157 werner 716
    double area_factor = grid->area(stand_id) / cRUArea;
549 werner 717
 
732 werner 718
    int key=0;
549 werner 719
    double rand_val, rand_fraction;
720
    int total_count = 0;
732 werner 721
    int total_tries = 0;
722
    int total_misses = 0;
723
    if (mInitHeightGrid && !mHeightGridResponse)
724
        throw IException("executeiLandInitStand: trying to initialize with height grid but without response function.");
971 werner 725
 
726
    Species *last_locked_species=0;
549 werner 727
    foreach(const InitFileItem &item, mInitItems) {
971 werner 728
        if (item.density>1.) {
729
            // special case with single-species-area
730
            if (total_count==0) {
731
                // randomize the pixels
732
                for (QList<SInitPixel>::iterator it=pixel_list.begin();it!=pixel_list.end();++it)
733
                    it->basal_area = drandom();
734
                qSort(pixel_list.begin(), pixel_list.end(), sortInitPixelLessThan);
735
                for (QList<SInitPixel>::iterator it=pixel_list.begin();it!=pixel_list.end();++it)
736
                    it->basal_area = 0.;
737
            }
738
 
739
            if (item.species != last_locked_species) {
740
                last_locked_species=item.species;
741
                qSort(pixel_list.begin(), pixel_list.end(), sortInitPixelUnlocked);
742
            }
743
        } else {
744
            qSort(pixel_list.begin(), pixel_list.end(), sortInitPixelLessThan);
745
            last_locked_species=0;
746
        }
747
        rand_fraction = item.density;
549 werner 748
        int count = item.count * area_factor + 0.5; // round
732 werner 749
        double init_max_height = item.dbh_to/100. * item.hd;
549 werner 750
        for (int i=0;i<count;i++) {
751
 
732 werner 752
            bool found = false;
753
            int tries = mHeightGridTries;
754
            while (!found &&--tries) {
755
                // calculate random value. "density" is from 1..-1.
971 werner 756
                if (item.density <= 1.) {
757
                    rand_val = mRandom->get();
758
                    if (item.density<0)
759
                        rand_val = 1. - rand_val;
760
                    rand_val = rand_val * rand_fraction + drandom()*(1.-rand_fraction);
761
                } else {
762
                    // limited area: limit potential area using the "density" input parameter
763
                    rand_val = drandom() * qMin(item.density/100., 1.);
764
                }
732 werner 765
                ++total_tries;
549 werner 766
 
732 werner 767
                // key: rank of target pixel
768
                key = limit(int(pixel_list.count()*rand_val), 0, pixel_list.count()-1); // get from random number generator
549 werner 769
 
732 werner 770
                if (mInitHeightGrid) {
771
                    // calculate how good the selected pixel fits w.r.t. the predefined height
772
                    double p_value = pixel_list[key].h_max>0.?mHeightGridResponse->calculate(init_max_height/pixel_list[key].h_max):0.;
773
                    if (drandom() < p_value)
774
                        found = true;
775
                } else {
776
                    found = true;
777
                }
971 werner 778
                if (!last_locked_species && pixel_list[key].locked)
779
                    found = false;
732 werner 780
            }
781
            if (tries<0) ++total_misses;
549 werner 782
 
783
            // create a tree
784
            ResourceUnit *ru = pixel_list[key].resource_unit;
785
            int tree_idx = ru->newTreeIndex();
786
            Tree &tree = ru->trees()[tree_idx]; // get reference to modify tree
787
            tree.setDbh(nrandom(item.dbh_from, item.dbh_to));
788
            tree.setHeight(tree.dbh()/100. * item.hd); // dbh from cm->m, *hd-ratio -> meter height
789
            tree.setSpecies(item.species);
790
            if (item.age<=0)
791
                tree.setAge(0,tree.height());
792
            else
793
                tree.setAge(item.age, tree.height());
794
            tree.setRU(ru);
795
            tree.setup();
796
            total_count++;
797
 
798
            // store in the multiHash the position of the pixel and the tree_idx in the resepctive resource unit
799
            tree_map.insert(pixel_list[key].pixelOffset, tree_idx);
800
            pixel_list[key].basal_area+=tree.basalArea(); // aggregate the basal area for each 10m pixel
971 werner 801
            if (last_locked_species)
802
                pixel_list[key].locked = true;
549 werner 803
 
804
            // resort list
971 werner 805
            if (last_locked_species==0 && ((total_count < 20 && i%2==0)
549 werner 806
                || (total_count<100 && i%10==0 )
971 werner 807
                || (i%30==0)) ) {
549 werner 808
                qSort(pixel_list.begin(), pixel_list.end(), sortInitPixelLessThan);
809
            }
810
        }
811
    }
732 werner 812
    if (total_misses>0 || total_tries > total_count) {
744 werner 813
        if (logLevelInfo()) qDebug() << "init for stand" << stand_id << "treecount:" << total_count << ", tries:" << total_tries << ", misses:" << total_misses << ", %miss:" << qRound(total_misses*100 / (double)total_count);
732 werner 814
    }
549 werner 815
 
816
    int bits, index, pos;
817
    int c;
818
    QList<int> trees;
819
    QPoint tree_pos;
820
 
821
    foreach(const SInitPixel &p, pixel_list) {
822
        trees = tree_map.values(p.pixelOffset);
823
        c = trees.count();
824
        bits = 0;
825
        index = -1;
826
        double r;
827
        foreach(int tree_idx, trees) {
828
            if (c>18) {
829
                index = (index + 1)%25;
830
            } else {
831
                int stop=1000;
832
                index = 0;
833
                do {
834
                    // search a random position
835
                    r = drandom();
836
                    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)
837
                } while (isBitSet(bits, index)==true && stop--);
838
                if (!stop)
839
                    qDebug() << "executeiLandInit: found no free bit.";
840
                setBit(bits, index, true); // mark position as used
841
            }
842
            // get position from fixed lists (one for even, one for uneven resource units)
843
            pos = p.resource_unit->index()%2?evenlist[index]:unevenlist[index];
844
            tree_pos = p.pixelOffset * cPxPerHeight; // convert to LIF index
600 werner 845
            tree_pos += QPoint(pos/cPxPerHeight, pos%cPxPerHeight);
550 werner 846
 
549 werner 847
            p.resource_unit->trees()[tree_idx].setPosition(tree_pos);
734 werner 848
            // test if tree position is valid..
849
            if (!GlobalSettings::instance()->model()->grid()->isIndexValid(tree_pos))
850
                qDebug() << "Standloader: invalid position!";
549 werner 851
        }
852
    }
971 werner 853
    if (logLevelInfo())
854
        qDebug() << "init for stand" << stand_id << "with area" << "area (m2)" << grid->area(stand_id) << "count of 10m pixels:"  << indices.count() << "initialized trees:" << total_count;
550 werner 855
 
549 werner 856
}
600 werner 857
 
858
/// a (hacky) way of adding saplings of a certain age to a stand defined by 'stand_id'.
859
int StandLoader::loadSaplings(const QString &content, int stand_id, const QString &fileName)
860
{
777 werner 861
    Q_UNUSED(fileName);
603 werner 862
    const MapGrid *stand_grid;
863
    if (mCurrentMap)
864
        stand_grid = mCurrentMap; // if set
865
    else
866
        stand_grid = GlobalSettings::instance()->model()->standGrid(); // default
867
 
600 werner 868
    QList<int> indices = stand_grid->gridIndices(stand_id); // list of 10x10m pixels
869
    if (indices.isEmpty()) {
870
        qDebug() << "stand" << stand_id << "not in project area. No init performed.";
871
        return -1;
872
    }
1157 werner 873
    double area_factor = stand_grid->area(stand_id) / cRUArea; // multiplier for grid (e.g. 2 if stand has area of 2 hectare)
600 werner 874
 
875
    // parse the content of the init-file
876
    // species
877
    CSVFile init;
878
    init.loadFromString(content);
879
    int ispecies = init.columnIndex("species");
880
    int icount = init.columnIndex("count");
881
    int iheight = init.columnIndex("height");
882
    int iage = init.columnIndex("age");
883
    if (ispecies==-1 || icount==-1)
884
        throw IException("Error while loading saplings: columns 'species' or 'count' are missing!!");
885
 
886
    const SpeciesSet *set = GlobalSettings::instance()->model()->ru()->speciesSet();
887
    double height, age;
888
    int total = 0;
889
    for (int row=0;row<init.rowCount();++row) {
890
        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)
891
        const Species *species = set->species(init.value(row, ispecies).toString());
892
        if (!species)
893
            throw IException(QString("Error while loading saplings: invalid species '%1'.").arg(init.value(row, ispecies).toString()));
894
        height = iheight==-1?0.05: init.value(row, iheight).toDouble();
895
        age = iage==-1?1:init.value(row,iage).toDouble();
896
 
897
        int misses = 0;
898
        int hits = 0;
899
        while (hits < pxcount) {
900
           int rnd_index = irandom(0, indices.count()-1);
901
           QPoint offset=stand_grid->grid().indexOf(indices[rnd_index]);
902
           ResourceUnit *ru = GlobalSettings::instance()->model()->ru(stand_grid->grid().cellCenterPoint(offset));
903
           //
904
           offset = offset * cPxPerHeight; // index of 10m patch -> to lif pixel coordinates
905
           int in_p = irandom(0, cPxPerHeight*cPxPerHeight-1); // index of lif-pixel
906
           offset += QPoint(in_p / cPxPerHeight, in_p % cPxPerHeight);
1162 werner 907
           SaplingCell *sc = GlobalSettings::instance()->model()->saplings()->cell(offset);
908
           if (sc && sc->max_height()>height) {
909
           //if (!ru || ru->saplingHeightForInit(offset) > height) {
600 werner 910
               misses++;
911
           } else {
912
               // ok
913
               hits++;
1162 werner 914
               if (sc)
915
                   sc->addSapling(height, age, species->index());
916
               //ru->resourceUnitSpecies(species).changeSapling().addSapling(offset, height, age);
600 werner 917
           }
918
           if (misses > 3*pxcount) {
919
               qDebug() << "tried to add" << pxcount << "saplings at stand" << stand_id << "but failed in finding enough free positions. Added" << hits << "and stopped.";
920
               break;
921
           }
922
        }
923
        total += hits;
924
 
925
    }
926
    return total;
927
}
966 werner 928
 
967 werner 929
bool LIFValueHigher(const float *a, const float *b)
966 werner 930
{
967 werner 931
    return *a > *b;
966 werner 932
}
933
 
967 werner 934
int StandLoader::loadSaplingsLIF(int stand_id, const CSVFile &init, int low_index, int high_index)
966 werner 935
{
936
    const MapGrid *stand_grid;
937
    if (mCurrentMap)
938
        stand_grid = mCurrentMap; // if set
939
    else
940
        stand_grid = GlobalSettings::instance()->model()->standGrid(); // default
941
 
967 werner 942
    if (!stand_grid->isValid(stand_id))
943
        return 0;
944
 
966 werner 945
    QList<int> indices = stand_grid->gridIndices(stand_id); // list of 10x10m pixels
946
    if (indices.isEmpty()) {
947
        qDebug() << "stand" << stand_id << "not in project area. No init performed.";
967 werner 948
        return 0;
966 werner 949
    }
950
    // prepare space for LIF-pointers (2m Pixel)
951
    QVector<float*> lif_ptrs;
967 werner 952
    lif_ptrs.reserve(indices.size() * cPxPerHeight * cPxPerHeight);
966 werner 953
    for (int l=0;l<indices.size();++l){
954
        QPoint offset=stand_grid->grid().indexOf(indices[l]);
955
        offset = offset * cPxPerHeight; // index of 10m patch -> to lif pixel coordinates
967 werner 956
        for (int y=0;y<cPxPerHeight;++y)
957
            for(int x=0;x<cPxPerHeight;++x)
958
                lif_ptrs.push_back( GlobalSettings::instance()->model()->grid()->ptr(offset.x()+x, offset.y()+y) );
966 werner 959
    }
960
    // sort based on LIF-Value
967 werner 961
    std::sort(lif_ptrs.begin(), lif_ptrs.end(), LIFValueHigher); // higher: highest values first
966 werner 962
 
963
 
1157 werner 964
    double area_factor = stand_grid->area(stand_id) / cRUArea; // multiplier for grid (e.g. 2 if stand has area of 2 hectare)
966 werner 965
 
966
    // parse the content of the init-file
967
    // species
968
    int ispecies = init.columnIndex("species");
969
    int icount = init.columnIndex("count");
970
    int iheight = init.columnIndex("height");
971
    int iheightfrom = init.columnIndex("height_from");
972
    int iheightto = init.columnIndex("height_to");
973
    int iage = init.columnIndex("age");
974
    int itopage = init.columnIndex("age4m");
975
    int iminlif = init.columnIndex("min_lif");
976
    if ((iheightfrom==-1) ^ (iheightto==-1))
977
        throw IException("Error while loading saplings: height not correctly provided. Use either 'height' or 'height_from' and 'height_to'.");
978
    if (ispecies==-1 || icount==-1)
979
        throw IException("Error while loading saplings: columns 'species' or 'count' are missing!!");
980
 
981
    const SpeciesSet *set = GlobalSettings::instance()->model()->ru()->speciesSet();
982
    double height, age;
983
    int total = 0;
967 werner 984
    for (int row=low_index;row<=high_index;++row) {
985
        double pxcount = init.value(row, icount).toDouble() * area_factor; // no. of pixels that should be filled (sapling grid is the same resolution as the lif-grid)
966 werner 986
        const Species *species = set->species(init.value(row, ispecies).toString());
987
        if (!species)
988
            throw IException(QString("Error while loading saplings: invalid species '%1'.").arg(init.value(row, ispecies).toString()));
989
        height = iheight==-1?0.05: init.value(row, iheight).toDouble();
990
        age = iage==-1?1:init.value(row,iage).toDouble();
991
        double age4m = itopage==-1?10:init.value(row, itopage).toDouble();
992
        double height_from = iheightfrom==-1?-1.: init.value(row, iheightfrom).toDouble();
993
        double height_to = iheightto==-1?-1.: init.value(row, iheightto).toDouble();
994
        double min_lif = iminlif==-1?1.: init.value(row, iminlif).toDouble();
995
        // find LIF-level in the pixels
996
        int min_lif_index = 0;
1157 werner 997
        if (min_lif < 1.) {
998
            for (QVector<float*>::ConstIterator it=lif_ptrs.constBegin(); it!=lif_ptrs.constEnd(); ++it, ++min_lif_index)
999
                if (**it <= min_lif)
1000
                    break;
1001
            if (pxcount < min_lif_index) {
1002
                // not enough LIF pixels available
1003
                min_lif_index = static_cast<int>(pxcount); // try the brightest pixels (ie with the largest value for the LIF)
1004
            }
1005
        } else {
1006
            // No LIF threshold: the full range of pixels is valid
1007
            min_lif_index = lif_ptrs.size();
966 werner 1008
        }
1009
 
1157 werner 1010
 
1011
 
967 werner 1012
        double hits = 0.;
966 werner 1013
        while (hits < pxcount) {
1157 werner 1014
            int rnd_index = irandom(0, min_lif_index-1);
1015
            if (iheightfrom!=-1) {
1016
                height = limit(nrandom(height_from, height_to), 0.05,4.);
1017
                if (age<=1.)
1018
                    age = qMax(qRound(height/4. * age4m),1); // assume a linear relationship between height and age
1019
            }
1020
            QPoint offset = GlobalSettings::instance()->model()->grid()->indexOf(lif_ptrs[rnd_index]);
1162 werner 1021
            ResourceUnit *ru;
1022
            SaplingCell *sc = GlobalSettings::instance()->model()->saplings()->cell(offset, true, &ru);
1023
            if (sc) {
1024
                if (SaplingTree *st=sc->addSapling(static_cast<float>(height), static_cast<int>(age), species->index()))
1025
                    hits+=ru->resourceUnitSpecies(st->species_index)->species()->saplingGrowthParameters().representedStemNumberByHeight(st->height);
1157 werner 1026
            } else {
1027
                hits++; // avoid an infinite loop
1028
            }
967 werner 1029
 
1030
 
966 werner 1031
        }
1032
        total += pxcount;
1033
 
1034
    }
1062 werner 1035
 
1036
    // initialize grass cover
1037
    if (init.columnIndex("grass_cover")>-1) {
1038
        int grass_cover_value = init.value(low_index, "grass_cover").toInt();
1039
        if (grass_cover_value<0 || grass_cover_value>100)
1040
            throw IException(QString("The grass cover percentage (column 'grass_cover') for stand '%1' is '%2', which is invalid (expected: 0-100)").arg(stand_id).arg(grass_cover_value));
1041
        GlobalSettings::instance()->model()->grassCover()->setInitialValues(lif_ptrs, grass_cover_value);
1042
    }
1043
 
1044
 
966 werner 1045
    return total;
1046
}