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