Subversion Repositories public iLand

Rev

Rev 1217 | Rev 1220 | Go to most recent revision | Only display areas with differences | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 1217 Rev 1218
1
Redirecting to URL 'https://iland.boku.ac.at/svn/iland/tags/release_1.0/src/core/standloader.cpp':
1
Redirecting to URL 'https://iland.boku.ac.at/svn/iland/tags/release_1.0/src/core/standloader.cpp':
2
/********************************************************************************************
2
/********************************************************************************************
3
**    iLand - an individual based forest landscape and disturbance model
3
**    iLand - an individual based forest landscape and disturbance model
4
**    http://iland.boku.ac.at
4
**    http://iland.boku.ac.at
5
**    Copyright (C) 2009-  Werner Rammer, Rupert Seidl
5
**    Copyright (C) 2009-  Werner Rammer, Rupert Seidl
6
**
6
**
7
**    This program is free software: you can redistribute it and/or modify
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
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
9
**    the Free Software Foundation, either version 3 of the License, or
10
**    (at your option) any later version.
10
**    (at your option) any later version.
11
**
11
**
12
**    This program is distributed in the hope that it will be useful,
12
**    This program is distributed in the hope that it will be useful,
13
**    but WITHOUT ANY WARRANTY; without even the implied warranty of
13
**    but WITHOUT ANY WARRANTY; without even the implied warranty of
14
**    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
**    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
**    GNU General Public License for more details.
15
**    GNU General Public License for more details.
16
**
16
**
17
**    You should have received a copy of the GNU General Public License
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/>.
18
**    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
********************************************************************************************/
19
********************************************************************************************/
20
20
21
#include "global.h"
21
#include "global.h"
22
#include "standloader.h"
22
#include "standloader.h"
23
23
24
24
25
#include "grid.h"
25
#include "grid.h"
26
#include "model.h"
26
#include "model.h"
27
#include "resourceunit.h"
27
#include "resourceunit.h"
28
#include "speciesset.h"
28
#include "speciesset.h"
29
#include "species.h"
29
#include "species.h"
30
30
31
#include "helper.h"
31
#include "helper.h"
32
#include "random.h"
32
#include "random.h"
33
#include "expression.h"
33
#include "expression.h"
34
#include "expressionwrapper.h"
34
#include "expressionwrapper.h"
35
#include "environment.h"
35
#include "environment.h"
36
#include "csvfile.h"
36
#include "csvfile.h"
37
#include "mapgrid.h"
37
#include "mapgrid.h"
38
#include "snapshot.h"
38
#include "snapshot.h"
39
#include "grasscover.h"
39
#include "grasscover.h"
40
40
41
/** @class StandLoader
41
/** @class StandLoader
42
    @ingroup tools
42
    @ingroup tools
43
    loads (initializes) trees for a "stand" from various sources.
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
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).
45
    trees and distributes the trees on the landscape (on the ResoureceUnit or on a stand defined by a grid).
46

46

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