Subversion Repositories public iLand

Rev

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

Rev 736 Rev 739
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
29
30
#include "helper.h"
30
#include "helper.h"
31
#include "random.h"
31
#include "random.h"
32
#include "expression.h"
32
#include "expression.h"
33
#include "expressionwrapper.h"
33
#include "expressionwrapper.h"
34
#include "environment.h"
34
#include "environment.h"
35
#include "csvfile.h"
35
#include "csvfile.h"
36
#include "mapgrid.h"
36
#include "mapgrid.h"
37
#include "snapshot.h"
37
#include "snapshot.h"
38
38
39
/** @class StandLoader
39
/** @class StandLoader
40
    @ingroup tools
40
    @ingroup tools
41
    loads (initializes) trees for a "stand" from various sources.
41
    loads (initializes) trees for a "stand" from various sources.
42
    StandLoader initializes trees on the landscape. It reads (usually) from text files, creates the
42
    StandLoader initializes trees on the landscape. It reads (usually) from text files, creates the
43
    trees and distributes the trees on the landscape (on the ResoureceUnit or on a stand defined by a grid).
43
    trees and distributes the trees on the landscape (on the ResoureceUnit or on a stand defined by a grid).
44

44

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