Rev 1104 | Rev 1217 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1 | |||
1033 | 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 | ********************************************************************************************/ |
||
884 | werner | 20 | #include "global.h" |
908 | werner | 21 | #include "abe_global.h" |
884 | werner | 22 | #include "fmtreelist.h" |
23 | |||
24 | #include "forestmanagementengine.h" |
||
25 | // iLand stuff |
||
26 | #include "tree.h" |
||
27 | #include "expression.h" |
||
28 | #include "mapgrid.h" |
||
885 | werner | 29 | #include "expressionwrapper.h" |
30 | #include "model.h" |
||
913 | werner | 31 | #include "helper.h" |
885 | werner | 32 | #include "fmstand.h" |
887 | werner | 33 | #include "fomescript.h" |
884 | werner | 34 | |
907 | werner | 35 | namespace ABE { |
884 | werner | 36 | |
1095 | werner | 37 | /** @class FMTreeList |
38 | @ingroup abe |
||
39 | The FMTreeList class implements low-level functionality for selecting and harvesting of trees. |
||
40 | The functions of the class are usually accessed via Javascript. |
||
41 | |||
42 | */ |
||
43 | |||
44 | |||
885 | werner | 45 | // TODO: fix: removal fractions need to be moved to agent/units/ whatever.... |
46 | double removeFoliage() {return 0.;} |
||
47 | double removeStem() {return 1.;} |
||
48 | double removeBranch() {return 0.;} |
||
49 | |||
884 | werner | 50 | FMTreeList::FMTreeList(QObject *parent) : |
51 | QObject(parent) |
||
52 | { |
||
932 | werner | 53 | mStand = 0; |
885 | werner | 54 | setStand(0); // clear stand link |
932 | werner | 55 | mResourceUnitsLocked = false; |
885 | werner | 56 | |
884 | werner | 57 | } |
58 | |||
891 | werner | 59 | FMTreeList::FMTreeList(FMStand *stand, QObject *parent): |
60 | QObject(parent) |
||
61 | { |
||
932 | werner | 62 | mStand = 0; |
891 | werner | 63 | setStand(stand); |
932 | werner | 64 | mResourceUnitsLocked = false; |
891 | werner | 65 | } |
66 | |||
932 | werner | 67 | FMTreeList::~FMTreeList() |
68 | { |
||
69 | check_locks(); |
||
70 | } |
||
71 | |||
889 | werner | 72 | void FMTreeList::setStand(FMStand *stand) |
885 | werner | 73 | { |
932 | werner | 74 | check_locks(); |
885 | werner | 75 | mStand = stand; |
76 | if (stand) { |
||
77 | mStandId = stand->id(); |
||
78 | mNumberOfStems = stand->stems() * stand->area(); |
||
891 | werner | 79 | mOnlySimulate = stand->currentActivity()?stand->currentFlags().isScheduled() : false; |
912 | werner | 80 | mStandRect=QRectF(); |
885 | werner | 81 | } else { |
82 | mStandId = -1; |
||
83 | mNumberOfStems = 1000; |
||
891 | werner | 84 | mOnlySimulate = false; |
885 | werner | 85 | } |
932 | werner | 86 | |
885 | werner | 87 | } |
88 | |||
89 | |||
912 | werner | 90 | |
884 | werner | 91 | int FMTreeList::load(const QString &filter) |
92 | { |
||
93 | if (standId()>-1) { |
||
94 | // load all trees of the current stand |
||
95 | const MapGrid *map = ForestManagementEngine::instance()->standGrid(); |
||
96 | if (map->isValid()) { |
||
885 | werner | 97 | map->loadTrees(mStandId, mTrees, filter, mNumberOfStems); |
932 | werner | 98 | mResourceUnitsLocked = true; |
884 | werner | 99 | } else { |
909 | werner | 100 | qCDebug(abe) << "FMTreeList::load: grid is not valid - no trees loaded"; |
884 | werner | 101 | } |
102 | return mTrees.count(); |
||
103 | |||
104 | } else { |
||
909 | werner | 105 | qCDebug(abe) << "FMTreeList::load: loading *all* trees, because stand id is -1"; |
884 | werner | 106 | TreeWrapper tw; |
107 | Model *m = GlobalSettings::instance()->model(); |
||
108 | mTrees.clear(); |
||
109 | AllTreeIterator at(m); |
||
110 | if (filter.isEmpty()) { |
||
111 | while (Tree *t=at.nextLiving()) |
||
112 | if (!t->isDead()) |
||
113 | mTrees.push_back(QPair<Tree*, double>(t, 0.)); |
||
114 | } else { |
||
115 | Expression expr(filter,&tw); |
||
116 | expr.enableIncSum(); |
||
117 | qDebug() << "filtering with" << filter; |
||
118 | while (Tree *t=at.nextLiving()) { |
||
119 | tw.setTree(t); |
||
120 | if (!t->isDead() && expr.execute()) |
||
121 | mTrees.push_back(QPair<Tree*, double>(t, 0.)); |
||
122 | } |
||
123 | } |
||
124 | return mTrees.count(); |
||
125 | } |
||
126 | } |
||
127 | |||
885 | werner | 128 | int FMTreeList::removeMarkedTrees() |
129 | { |
||
130 | loadAll(); |
||
131 | int n_removed = 0; |
||
132 | for (QVector<QPair<Tree*, double> >::const_iterator it = mTrees.constBegin(); it!=mTrees.constEnd(); ++it) { |
||
133 | Tree *t = const_cast<Tree*>((*it).first); |
||
134 | if (t->isMarkedForCut()) { |
||
135 | t->remove(); |
||
136 | n_removed++; |
||
137 | } else if (t->isMarkedForHarvest()) { |
||
138 | t->remove(removeFoliage(), removeBranch(), removeStem()); |
||
139 | n_removed++; |
||
140 | } |
||
141 | } |
||
888 | werner | 142 | if (mStand->trace()) |
909 | werner | 143 | qCDebug(abe) << mStand->context() << "removeMarkedTrees: n=" << n_removed; |
1062 | werner | 144 | |
145 | return n_removed; |
||
885 | werner | 146 | } |
884 | werner | 147 | |
1070 | werner | 148 | int FMTreeList::kill(QString filter) |
149 | { |
||
150 | return remove_trees(filter, 1., false); |
||
151 | } |
||
152 | |||
885 | werner | 153 | int FMTreeList::harvest(QString filter, double fraction) |
884 | werner | 154 | { |
885 | werner | 155 | return remove_trees(filter, fraction, true); |
156 | |||
884 | werner | 157 | } |
158 | |||
887 | werner | 159 | bool FMTreeList::trace() const |
160 | { |
||
161 | return FomeScript::bridge()->standObj()->trace(); |
||
162 | } |
||
884 | werner | 163 | |
887 | werner | 164 | |
885 | werner | 165 | int FMTreeList::remove_percentiles(int pctfrom, int pctto, int number, bool management) |
166 | { |
||
167 | if (mTrees.isEmpty()) |
||
168 | return 0; |
||
169 | int index_from = limit(int(pctfrom/100. * mTrees.count()), 0, mTrees.count()); |
||
170 | int index_to = limit(int(pctto/100. * mTrees.count()), 0, mTrees.count()-1); |
||
171 | if (index_from>=index_to) |
||
172 | return 0; |
||
923 | werner | 173 | //qDebug() << "attempting to remove" << number << "trees between indices" << index_from << "and" << index_to; |
885 | werner | 174 | int i; |
175 | int count = number; |
||
176 | if (index_to-index_from <= number) { |
||
177 | // kill all |
||
178 | if (management) { |
||
179 | // management |
||
180 | for (i=index_from; i<index_to; i++) |
||
889 | werner | 181 | if (simulate()) { |
885 | werner | 182 | mTrees.at(i).first->markForHarvest(true); |
889 | werner | 183 | mStand->addScheduledHarvest(mTrees.at(i).first->volume()); |
184 | } else { |
||
885 | werner | 185 | mTrees.at(i).first->remove(removeFoliage(), removeBranch(), removeStem()); |
889 | werner | 186 | } |
885 | werner | 187 | } else { |
188 | // just kill... |
||
189 | for (i=index_from; i<index_to; i++) |
||
889 | werner | 190 | if (simulate()) { |
885 | werner | 191 | mTrees.at(i).first->markForCut(true); |
889 | werner | 192 | mStand->addScheduledHarvest(mTrees.at(i).first->volume()); |
193 | } else |
||
885 | werner | 194 | mTrees.at(i).first->remove(); |
195 | } |
||
196 | count = index_to - index_from; |
||
197 | } else { |
||
198 | // kill randomly the provided number |
||
199 | int cancel = 1000; |
||
200 | while(number>=0) { |
||
201 | int rnd_index = irandom(index_from, index_to); |
||
923 | werner | 202 | Tree *tree = mTrees[rnd_index].first; |
203 | if (tree->isDead() || tree->isMarkedForHarvest() || tree->isMarkedForCut()) { |
||
885 | werner | 204 | if (--cancel<0) { |
205 | qDebug() << "Management::kill: canceling search." << number << "trees left."; |
||
206 | count-=number; // not all trees were killed |
||
207 | break; |
||
208 | } |
||
209 | continue; |
||
210 | } |
||
211 | cancel = 1000; |
||
212 | number--; |
||
213 | if (management) { |
||
889 | werner | 214 | if (simulate()) { |
923 | werner | 215 | tree->markForHarvest(true); |
216 | mStand->addScheduledHarvest( tree->volume()); |
||
889 | werner | 217 | } else |
923 | werner | 218 | tree->remove( removeFoliage(), removeBranch(), removeStem() ); |
885 | werner | 219 | } else { |
889 | werner | 220 | if (simulate()) { |
923 | werner | 221 | tree->markForCut(true); |
222 | mStand->addScheduledHarvest( tree->volume()); |
||
889 | werner | 223 | } else |
923 | werner | 224 | tree->remove(); |
885 | werner | 225 | } |
226 | } |
||
227 | } |
||
923 | werner | 228 | if (mStand && mStand->trace()) |
229 | qCDebug(abe) << "FMTreeList::remove_percentiles:" << count << "removed."; |
||
885 | werner | 230 | // clean up the tree list... |
231 | for (int i=mTrees.count()-1; i>=0; --i) { |
||
232 | if (mTrees[i].first->isDead()) |
||
233 | mTrees.removeAt(i); |
||
234 | } |
||
235 | return count; // killed or manages |
||
236 | |||
237 | } |
||
238 | |||
239 | /** remove trees from a list and reduce the list. |
||
240 | |||
241 | */ |
||
242 | int FMTreeList::remove_trees(QString expression, double fraction, bool management) |
||
243 | { |
||
244 | TreeWrapper tw; |
||
245 | if (expression.isEmpty()) |
||
246 | expression="true"; |
||
247 | Expression expr(expression,&tw); |
||
248 | expr.enableIncSum(); |
||
249 | int n = 0; |
||
250 | QVector<QPair<Tree*, double> >::iterator tp=mTrees.begin(); |
||
251 | try { |
||
252 | while (tp!=mTrees.end()) { |
||
253 | tw.setTree(tp->first); |
||
254 | // if expression evaluates to true and if random number below threshold... |
||
255 | if (expr.calculate(tw) && drandom() <=fraction) { |
||
256 | // remove from system |
||
257 | if (management) { |
||
889 | werner | 258 | if (simulate()) { |
885 | werner | 259 | tp->first->markForHarvest(true); |
889 | werner | 260 | mStand->addScheduledHarvest(tp->first->volume()); |
1070 | werner | 261 | } else { |
262 | tp->first->markForHarvest(true); |
||
885 | werner | 263 | tp->first->remove(removeFoliage(), removeBranch(), removeStem()); // management with removal fractions |
1070 | werner | 264 | } |
885 | werner | 265 | } else { |
889 | werner | 266 | if (simulate()) { |
885 | werner | 267 | tp->first->markForCut(true); |
1070 | werner | 268 | tp->first->setDeathCutdown(); |
889 | werner | 269 | mStand->addScheduledHarvest(tp->first->volume()); |
1070 | werner | 270 | } else { |
271 | tp->first->markForCut(true); |
||
272 | tp->first->setDeathCutdown(); |
||
885 | werner | 273 | tp->first->remove(); // kill |
1070 | werner | 274 | } |
885 | werner | 275 | } |
276 | // remove from tree list |
||
277 | tp = mTrees.erase(tp); |
||
278 | n++; |
||
279 | } else { |
||
280 | ++tp; |
||
281 | } |
||
282 | } |
||
283 | } catch(const IException &e) { |
||
909 | werner | 284 | qCWarning(abe) << "treelist: remove_trees: expression:" << expression << ", msg:" << e.message(); |
885 | werner | 285 | } |
286 | return n; |
||
287 | |||
288 | } |
||
289 | |||
290 | double FMTreeList::aggregate_function(QString expression, QString filter, QString type) |
||
291 | { |
||
292 | QVector<QPair<Tree*, double> >::iterator tp=mTrees.begin(); |
||
293 | TreeWrapper tw; |
||
294 | Expression expr(expression,&tw); |
||
295 | |||
296 | double sum = 0.; |
||
297 | int n=0; |
||
298 | try { |
||
299 | |||
300 | if (filter.isEmpty()) { |
||
301 | // without filtering |
||
302 | while (tp!=mTrees.end()) { |
||
303 | tw.setTree(tp->first); |
||
304 | sum += expr.calculate(); |
||
305 | ++n; |
||
306 | ++tp; |
||
307 | } |
||
308 | } else { |
||
309 | // with filtering |
||
310 | Expression filter_expr(filter,&tw); |
||
311 | filter_expr.enableIncSum(); |
||
312 | while (tp!=mTrees.end()) { |
||
313 | tw.setTree(tp->first); |
||
314 | if (filter_expr.calculate()) { |
||
315 | sum += expr.calculate(); |
||
316 | ++n; |
||
317 | } |
||
318 | ++tp; |
||
319 | } |
||
320 | } |
||
321 | |||
322 | } catch(const IException &e) { |
||
909 | werner | 323 | qCWarning(abe) << "Treelist: aggregate function: expression:" << expression << ", filter:" << filter << ", msg:" <<e.message(); |
885 | werner | 324 | //throwError(e.message()); |
325 | } |
||
326 | if (type=="sum") |
||
327 | return sum; |
||
328 | if (type=="mean") |
||
329 | return n>0?sum/double(n):0.; |
||
330 | return 0.; |
||
331 | |||
332 | } |
||
333 | |||
930 | werner | 334 | bool FMTreeList::remove_single_tree(int index, bool harvest) |
335 | { |
||
336 | if (!mStand || index<0 || index>=mTrees.size()) |
||
337 | return false; |
||
338 | Tree *tree = mTrees.at(index).first; |
||
339 | if (harvest) { |
||
340 | if (simulate()) { |
||
341 | tree->markForHarvest(true); |
||
342 | mStand->addScheduledHarvest( tree->volume()); |
||
343 | } else |
||
344 | tree->remove( removeFoliage(), removeBranch(), removeStem() ); |
||
345 | } else { |
||
346 | if (simulate()) { |
||
347 | tree->markForCut(true); |
||
348 | mStand->addScheduledHarvest( tree->volume()); |
||
349 | } else |
||
350 | tree->remove(); |
||
351 | } |
||
352 | return true; |
||
353 | } |
||
923 | werner | 354 | |
930 | werner | 355 | |
923 | werner | 356 | bool treePairValue(const QPair<Tree*, double> &p1, const QPair<Tree*, double> &p2) |
357 | { |
||
358 | return p1.second < p2.second; |
||
359 | } |
||
360 | |||
361 | void FMTreeList::sort(QString statement) |
||
362 | { |
||
363 | TreeWrapper tw; |
||
364 | Expression sorter(statement, &tw); |
||
365 | // fill the "value" part of the tree storage with a value for each tree |
||
366 | for (int i=0;i<mTrees.count(); ++i) { |
||
367 | tw.setTree(mTrees.at(i).first); |
||
368 | mTrees[i].second = sorter.execute(); |
||
369 | } |
||
370 | // now sort the list.... |
||
371 | qSort(mTrees.begin(), mTrees.end(), treePairValue); |
||
372 | } |
||
373 | |||
374 | double FMTreeList::percentile(int pct) |
||
375 | { |
||
376 | if (mTrees.count()==0) |
||
377 | return -1.; |
||
378 | int idx = int( (pct/100.) * mTrees.count()); |
||
379 | if (idx>=0 && idx<mTrees.count()) |
||
380 | return mTrees.at(idx).second; |
||
381 | else |
||
382 | return -1; |
||
383 | } |
||
384 | |||
385 | /// random shuffle of all trees in the list |
||
386 | void FMTreeList::randomize() |
||
387 | { |
||
388 | // fill the "value" part of the tree storage with a random value for each tree |
||
389 | for (int i=0;i<mTrees.count(); ++i) { |
||
390 | mTrees[i].second = drandom(); |
||
391 | } |
||
392 | // now sort the list.... |
||
393 | qSort(mTrees.begin(), mTrees.end(), treePairValue); |
||
394 | |||
395 | } |
||
396 | |||
397 | |||
912 | werner | 398 | void FMTreeList::prepareGrids() |
399 | { |
||
400 | QRectF box = ForestManagementEngine::instance()->standGrid()->boundingBox(mStand->id()); |
||
401 | if (mStandRect==box) |
||
402 | return; |
||
403 | mStandRect = box; |
||
404 | // the memory of the grids is only reallocated if the current box is larger then the previous... |
||
405 | mStandGrid.setup(box, cHeightSize); |
||
406 | mTreeCountGrid.setup(box, cHeightSize); |
||
951 | werner | 407 | mLocalGrid.setup(box, cPxSize); |
913 | werner | 408 | // mark areas outside of the grid... |
409 | GridRunner<int> runner(ForestManagementEngine::instance()->standGrid()->grid(), box); |
||
410 | float *p=mStandGrid.begin(); |
||
411 | while (runner.next()) { |
||
412 | if (*runner.current()!=mStand->id()) |
||
914 | werner | 413 | *p=-1.f; |
913 | werner | 414 | ++p; |
415 | } |
||
951 | werner | 416 | // copy stand limits to the grid |
417 | for (int iy=0;iy<mLocalGrid.sizeY();++iy) |
||
418 | for (int ix=0;ix<mLocalGrid.sizeX();++ix) |
||
419 | mLocalGrid.valueAtIndex(ix,iy) = mStandGrid.valueAtIndex(ix/cPxPerHeight, iy/cPxPerHeight)==-1.f ? -1.f: 0.f; |
||
912 | werner | 420 | } |
885 | werner | 421 | |
912 | werner | 422 | void FMTreeList::runGrid(void (*func)(float &, int &, const Tree *, const FMTreeList *)) |
423 | { |
||
424 | if (mStandRect.isNull()) |
||
425 | prepareGrids(); |
||
885 | werner | 426 | |
1157 | werner | 427 | // set all values to 0 (within the limits of the stand grid) |
428 | for (float *p=mStandGrid.begin(); p!=mStandGrid.end(); ++p) |
||
429 | if (*p!=-1.f) |
||
430 | *p=0.f; |
||
912 | werner | 431 | mTreeCountGrid.initialize(0); |
1070 | werner | 432 | int invalid_index = 0; |
912 | werner | 433 | for (QVector<QPair<Tree*, double> >::const_iterator it=mTrees.constBegin(); it!=mTrees.constEnd(); ++it) { |
434 | const Tree* tree = it->first; |
||
435 | QPoint p = mStandGrid.indexAt(tree->position()); |
||
1070 | werner | 436 | if (mStandGrid.isIndexValid(p)) |
437 | (*func)(mStandGrid.valueAtIndex(p), mTreeCountGrid.valueAtIndex(p), tree, this); |
||
438 | else |
||
439 | ++invalid_index; |
||
912 | werner | 440 | } |
1070 | werner | 441 | if (invalid_index) |
442 | qDebug() << "FMTreeList::runGrid: invalid index: n=" << invalid_index; |
||
885 | werner | 443 | |
912 | werner | 444 | // finalization: call again for each *cell* |
445 | for (int i=0;i<mStandGrid.count();++i) |
||
446 | (*func)(mStandGrid.valueAtIndex(i), mTreeCountGrid.valueAtIndex(i), 0, this); |
||
447 | |||
448 | } |
||
449 | |||
450 | void rungrid_heightmax(float &cell, int &n, const Tree *tree, const FMTreeList *list) |
||
451 | { |
||
452 | Q_UNUSED(n); Q_UNUSED(list); |
||
453 | if (tree) |
||
454 | cell = qMax(cell, tree->height()); |
||
455 | } |
||
456 | void rungrid_basalarea(float &cell, int &n, const Tree *tree, const FMTreeList *list) |
||
457 | { |
||
458 | Q_UNUSED(list); |
||
459 | if (tree) { |
||
460 | cell += tree->basalArea(); |
||
461 | ++n; |
||
462 | } else { |
||
463 | if (n>0) |
||
464 | cell /= float(n); |
||
465 | } |
||
466 | } |
||
467 | void rungrid_volume(float &cell, int &n, const Tree *tree, const FMTreeList *list) |
||
468 | { |
||
469 | Q_UNUSED(list); |
||
470 | if (tree) { |
||
471 | cell += tree->volume(); |
||
472 | ++n; |
||
473 | } else { |
||
474 | if (n>0) |
||
475 | cell /= float(n); |
||
476 | } |
||
477 | } |
||
478 | |||
479 | void rungrid_custom(float &cell, int &n, const Tree *tree, const FMTreeList *list) |
||
480 | { |
||
481 | if (tree) { |
||
482 | *list->mRunGridCustomCell = cell; |
||
483 | TreeWrapper tw(tree); |
||
1157 | werner | 484 | cell = static_cast<float>(list->mRunGridCustom->calculate(tw)); |
912 | werner | 485 | ++n; |
486 | } |
||
487 | } |
||
488 | void FMTreeList::prepareStandGrid(QString type, QString custom_expression) |
||
489 | { |
||
490 | if(!mStand){ |
||
491 | qCDebug(abe) << "Error: FMTreeList: no current stand defined."; |
||
492 | return; |
||
493 | } |
||
494 | |||
495 | if (type==QStringLiteral("height")) { |
||
496 | return runGrid(&rungrid_heightmax); |
||
497 | } |
||
498 | |||
499 | if (type==QStringLiteral("basalArea")) |
||
500 | return runGrid(&rungrid_basalarea); |
||
501 | |||
502 | if (type==QStringLiteral("volume")) |
||
503 | return runGrid(&rungrid_volume); |
||
504 | |||
505 | if (type==QStringLiteral("custom")) { |
||
506 | mRunGridCustom = new Expression(custom_expression); |
||
507 | mRunGridCustomCell = mRunGridCustom->addVar("cell"); |
||
508 | runGrid(&rungrid_custom); |
||
509 | delete mRunGridCustom; |
||
510 | mRunGridCustom = 0; |
||
511 | return; |
||
512 | } |
||
513 | qCDebug(abe) << "FMTreeList: invalid type for prepareStandGrid: " << type; |
||
514 | } |
||
913 | werner | 515 | |
516 | void FMTreeList::exportStandGrid(QString file_name) |
||
517 | { |
||
518 | file_name = GlobalSettings::instance()->path(file_name); |
||
519 | Helper::saveToTextFile(file_name, gridToESRIRaster(mStandGrid) ); |
||
520 | qCDebug(abe) << "saved grid to file" << file_name; |
||
521 | } |
||
932 | werner | 522 | |
523 | void FMTreeList::check_locks() |
||
524 | { |
||
933 | werner | 525 | // removed the locking code again, WR20140821 |
526 | // if (mStand && mResourceUnitsLocked) { |
||
527 | // const MapGrid *map = ForestManagementEngine::instance()->standGrid(); |
||
528 | // if (map->isValid()) { |
||
529 | // map->freeLocksForStand(mStandId); |
||
530 | // mResourceUnitsLocked = false; |
||
531 | // } |
||
532 | // } |
||
932 | werner | 533 | } |
534 | |||
884 | werner | 535 | } // namespace |