Subversion Repositories public iLand

Rev

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

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