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