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