Subversion Repositories public iLand

Rev

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

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