Rev 123 | Rev 126 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed
Rev 123 | Rev 125 | ||
---|---|---|---|
Line 39... | Line 39... | ||
39 | // direction:
|
39 | // direction:
|
40 | rAngle = atan2(dx, dy); |
40 | rAngle = atan2(dx, dy); |
41 | return d; |
41 | return d; |
42 | }
|
42 | }
|
43 | 43 | ||
- | 44 | /// dumps some core variables of a tree to a string.
|
|
- | 45 | QString Tree::dump() |
|
- | 46 | {
|
|
- | 47 | QString result = QString("id %1 species %2 dbh %3 h %4 x/y %5/%6 ru# %7 LRI %8") |
|
- | 48 | .arg(mId).arg(mSpecies->id()).arg(mDbh).arg(mHeight) |
|
- | 49 | .arg(mPosition.x()).arg(mPosition.y()) |
|
- | 50 | .arg(mRU->index()).arg(mLRI); |
|
- | 51 | return result; |
|
- | 52 | }
|
|
44 | 53 | ||
45 | void Tree::setup() |
54 | void Tree::setup() |
46 | {
|
55 | {
|
47 | if (mDbh<=0 || mHeight<=0) |
56 | if (mDbh<=0 || mHeight<=0) |
48 | return; |
57 | return; |
Line 50... | Line 59... | ||
50 | Q_ASSERT_X(mSpecies!=0, "Tree::setup()", "species is NULL"); |
59 | Q_ASSERT_X(mSpecies!=0, "Tree::setup()", "species is NULL"); |
51 | mStamp = mSpecies->stamp(mDbh, mHeight); |
60 | mStamp = mSpecies->stamp(mDbh, mHeight); |
52 | 61 | ||
53 | calcBiomassCompartments(); |
62 | calcBiomassCompartments(); |
54 | mNPPReserve = mLeafMass; // initial value |
63 | mNPPReserve = mLeafMass; // initial value |
- | 64 | mDbhDelta = 0.1; // initial value: used in growth() to estimate diameter increment |
|
55 | 65 | ||
56 | }
|
66 | }
|
57 | 67 | ||
58 | //////////////////////////////////////////////////
|
68 | //////////////////////////////////////////////////
|
59 | //// Light functions (Pattern-stuff)
|
69 | //// Light functions (Pattern-stuff)
|
60 | //////////////////////////////////////////////////
|
70 | //////////////////////////////////////////////////
|
61 | 71 | ||
62 | #define NOFULLDBG
|
72 | #define NOFULLDBG
|
63 | //#define NOFULLOPT
|
73 | //#define NOFULLOPT
|
64 | /*
|
- | |
65 | void Tree::applyStamp()
|
- | |
66 | {
|
- | |
67 | Q_ASSERT(m_grid!=0);
|
- | |
68 | if (!m_stamp)
|
- | |
69 | return;
|
- | |
70 | 74 | ||
71 | QPoint pos = m_grid->indexAt(m_Position);
|
- | |
72 | int offset = m_stamp->offset();
|
- | |
73 | pos-=QPoint(offset, offset);
|
- | |
74 | QPoint p;
|
- | |
75 | - | ||
76 | float local_dom; // height of Z* on the current position
|
- | |
77 | int x,y;
|
- | |
78 | float value;
|
- | |
79 | QPoint dbg(10,20);
|
- | |
80 | #ifndef NOFULLDBG
|
- | |
81 | qDebug() <<"indexstampx indexstamy gridx gridy local_dom_height stampvalue 1-value*la/dom grid_before gridvalue_after";
|
- | |
82 | #endif
|
- | |
83 | for (x=0;x<m_stamp->size();++x) {
|
- | |
84 | for (y=0;y<m_stamp->size(); ++y) {
|
- | |
85 | p = pos + QPoint(x,y);
|
- | |
86 | // debug pixel
|
- | |
87 | //if (p==dbg)
|
- | |
88 | // qDebug() << "#" << id() << "value;"<<(*m_stamp)(x,y)<<"domH"<<dom;
|
- | |
89 | - | ||
90 | if (m_grid->isIndexValid(p)) {
|
- | |
91 | // mulitplicative:
|
- | |
92 | //m_grid->valueAtIndex(p)*=(*m_stamp)(x,y);
|
- | |
93 | // additiv:
|
- | |
94 | // multiplicatie, v2
|
- | |
95 | // a optimization for 2m vs 10m grids: local_dom = m_dominanceGrid->valueAtIndex(p.x()/5, p.y()/5);
|
- | |
96 | // effect is about 20% of the application time
|
- | |
97 | local_dom = m_dominanceGrid->valueAt( m_grid->cellCoordinates(p) ); // todo: could be done more effectively (here 2 transormations are performed)...
|
- | |
98 | if (local_dom<=0.f) {
|
- | |
99 | //qDebug() << "invalid height at " << m_grid->cellCoordinates(p) << "of" << local_dom;
|
- | |
100 | local_dom = 2.;
|
- | |
101 | }
|
- | |
102 | value = (*m_stamp)(x,y);
|
- | |
103 | value = 1. - value*lafactor / local_dom;
|
- | |
104 | value = qMax(value, 0.02f);
|
- | |
105 | #ifndef NOFULLDBG
|
- | |
106 | qDebug() << x << y << p.x() << p.y() << local_dom << (*m_stamp)(x,y) << 1. - (*m_stamp)(x,y)*lafactor / local_dom << m_grid->valueAtIndex(p) << m_grid->valueAtIndex(p)*value;
|
- | |
107 | #endif
|
- | |
108 | - | ||
109 | m_grid->valueAtIndex(p)*= value;
|
- | |
110 | }
|
- | |
111 | }
|
- | |
112 | }
|
- | |
113 | - | ||
114 | m_statPrint++; // count # of stamp applications...
|
- | |
115 | }
|
- | |
116 | */
|
- | |
117 | 75 | ||
118 | void Tree::applyStamp() |
76 | void Tree::applyStamp() |
119 | {
|
77 | {
|
120 | Q_ASSERT(mGrid!=0 && mStamp!=0 && mRU!=0); |
78 | Q_ASSERT(mGrid!=0 && mStamp!=0 && mRU!=0); |
121 | 79 | ||
Line 151... | Line 109... | ||
151 | }
|
109 | }
|
152 | 110 | ||
153 | m_statPrint++; // count # of stamp applications... |
111 | m_statPrint++; // count # of stamp applications... |
154 | }
|
112 | }
|
155 | 113 | ||
156 | /*
|
- | |
157 | void Tree::heightGrid_old()
|
- | |
158 | {
|
- | |
159 | // height of Z*
|
- | |
160 | const float cellsize = m_dominanceGrid->cellsize();
|
- | |
161 | if (m_Height < cellsize/2.) {
|
- | |
162 | float &dom = m_dominanceGrid->valueAt(m_Position); // modifyable reference
|
- | |
163 | dom = qMax(dom, m_Height/2.f);
|
- | |
164 | } else {
|
- | |
165 | QPoint p = m_dominanceGrid->indexAt(m_Position); // pos of tree on height grid
|
- | |
166 | - | ||
167 | int ringcount = int(floor(m_Height / cellsize));
|
- | |
168 | int ix, iy;
|
- | |
169 | int ring;
|
- | |
170 | QPoint pos;
|
- | |
171 | float hdom;
|
- | |
172 | float h_out = fmod(m_Height, cellsize) / 2.;
|
- | |
173 | for (ix=-ringcount;ix<=ringcount;ix++)
|
- | |
174 | for (iy=-ringcount; iy<=+ringcount; iy++) {
|
- | |
175 | ring = qMax(abs(ix), abs(iy));
|
- | |
176 | QPoint pos(ix+p.x(), iy+p.y());
|
- | |
177 | if (m_dominanceGrid->isIndexValid(pos)) {
|
- | |
178 | // apply value....
|
- | |
179 | if (ring==0) {
|
- | |
180 | hdom = m_Height- cellsize/4.;
|
- | |
181 | } else if (ring==abs(ringcount)) {
|
- | |
182 | // outermost ring: use again height/2.
|
- | |
183 | hdom = h_out;
|
- | |
184 | } else {
|
- | |
185 | hdom = m_Height- ring*cellsize;
|
- | |
186 | }
|
- | |
187 | m_dominanceGrid->valueAtIndex(pos) = qMax(m_dominanceGrid->valueAtIndex(pos), hdom);
|
- | |
188 | }
|
- | |
189 | - | ||
190 | }
|
- | |
191 | }
|
- | |
192 | - | ||
193 | } */
|
- | |
194 | 114 | ||
195 | /** heightGrid()
|
115 | /** heightGrid()
|
196 | This function calculates the "dominant height field". This grid is coarser as the fine-scaled light-grid.
|
116 | This function calculates the "dominant height field". This grid is coarser as the fine-scaled light-grid.
|
197 | 117 | ||
198 | */
|
118 | */
|
Line 288... | Line 208... | ||
288 | mLRI = 0.f; |
208 | mLRI = 0.f; |
289 | qDebug() << "Tree #"<< id() << "value" << sum << "eigenvalue" << eigenvalue << "Impact" << mLRI; |
209 | qDebug() << "Tree #"<< id() << "value" << sum << "eigenvalue" << eigenvalue << "Impact" << mLRI; |
290 | return mLRI; |
210 | return mLRI; |
291 | }
|
211 | }
|
292 | 212 | ||
293 | /*
|
- | |
294 | double Tree::readStampMul()
|
- | |
295 | {
|
- | |
296 | if (!m_stamp)
|
- | |
297 | return 0.;
|
- | |
298 | const Stamp *reader = m_stamp->reader();
|
- | |
299 | if (!reader)
|
- | |
300 | return 0.;
|
- | |
301 | QPoint pos_reader = m_grid->indexAt(m_Position);
|
- | |
302 | - | ||
303 | int offset_reader = reader->offset();
|
- | |
304 | int offset_writer = m_stamp->offset();
|
- | |
305 | int d_offset = offset_writer - offset_reader;
|
- | |
306 | - | ||
307 | QPoint pos_writer=pos_reader - QPoint(offset_writer, offset_writer);
|
- | |
308 | pos_reader-=QPoint(offset_reader, offset_reader);
|
- | |
309 | QPoint p;
|
- | |
310 | - | ||
311 | float dom_height = (*m_dominanceGrid)[m_Position];
|
- | |
312 | float local_dom;
|
- | |
313 | - | ||
314 | int x,y;
|
- | |
315 | double sum=0.;
|
- | |
316 | double value, own_value;
|
- | |
317 | for (x=0;x<reader->size();++x) {
|
- | |
318 | for (y=0;y<reader->size(); ++y) {
|
- | |
319 | p = pos_reader + QPoint(x,y);
|
- | |
320 | if (m_grid->isIndexValid(p)) {
|
- | |
321 | local_dom = m_dominanceGrid->valueAt( m_grid->cellCoordinates(p) );
|
- | |
322 | own_value = 1. - m_stamp->offsetValue(x,y,d_offset)*lafactor /local_dom; // old: dom_height;
|
- | |
323 | own_value = qMax(own_value, 0.02);
|
- | |
324 | value = m_grid->valueAtIndex(p) / own_value; // remove impact of focal tree
|
- | |
325 | if (value>0.)
|
- | |
326 | sum += value * (*reader)(x,y);
|
- | |
327 | //value = (1. - m_stamp->offsetValue(x,y,d_offset)/dom_height);
|
- | |
328 | //value = (1 - m_grid->valueAtIndex(p)) / (m_stamp->offsetValue(x,y,d_offset)/dom_height * lafactor);
|
- | |
329 | if (isDebugging()) {
|
- | |
330 | qDebug() << "Tree" << id() << "Coord" << p << "gridvalue" << m_grid->valueAtIndex(p) << "value" << value << "reader" << (*reader)(x,y) << "writer" << m_stamp->offsetValue(x,y,d_offset);
|
- | |
331 | }
|
- | |
332 | - | ||
333 | }
|
- | |
334 | }
|
- | |
335 | }
|
- | |
336 | mImpact = sum;
|
- | |
337 | // read dominance field...
|
- | |
338 | if (dom_height < m_Height) {
|
- | |
339 | // if tree is higher than Z*, the dominance height
|
- | |
340 | // a part of the crown is in "full light".
|
- | |
341 | m_statAboveZ++;
|
- | |
342 | mImpact = 1. - (1. - mImpact)*dom_height/m_Height;
|
- | |
343 | }
|
- | |
344 | if (mImpact > 1)
|
- | |
345 | mImpact = 1.f;
|
- | |
346 | //qDebug() << "Tree #"<< id() << "value" << sum << "Impact" << mImpact;
|
- | |
347 | return mImpact;
|
- | |
348 | }*/
|
- | |
349 | 213 | ||
350 | void Tree::readStampMul() |
214 | void Tree::readStampMul() |
351 | {
|
215 | {
|
352 | if (!mStamp) |
216 | if (!mStamp) |
353 | return; |
217 | return; |
Line 378... | Line 242... | ||
378 | grid_value = mGrid->ptr(rx, ry); |
242 | grid_value = mGrid->ptr(rx, ry); |
379 | for (x=0;x<reader_size;++x) { |
243 | for (x=0;x<reader_size;++x) { |
380 | 244 | ||
381 | //p = pos_reader + QPoint(x,y);
|
245 | //p = pos_reader + QPoint(x,y);
|
382 | //if (m_grid->isIndexValid(p)) {
|
246 | //if (m_grid->isIndexValid(p)) {
|
383 | local_dom = mHeightGrid->valueAtIndex((rx+x)/5, ry/5); |
- | |
- | 247 | local_dom = mHeightGrid->valueAtIndex((rx+x)/5, ry/5); // ry: gets ++ed in outer loop, rx not |
|
384 | //local_dom = m_dominanceGrid->valueAt( m_grid->cellCoordinates(p) );
|
248 | //local_dom = m_dominanceGrid->valueAt( m_grid->cellCoordinates(p) );
|
385 | own_value = 1. - mStamp->offsetValue(x,y,d_offset)*lafactor /local_dom; // old: dom_height; |
- | |
- | 249 | ||
- | 250 | own_value = 1. - mStamp->offsetValue(x,y,d_offset)*lafactor / local_dom; // old: dom_height; |
|
386 | own_value = qMax(own_value, 0.02); |
251 | own_value = qMax(own_value, 0.02); |
387 | value = *grid_value++ / own_value; // remove impact of focal tree |
252 | value = *grid_value++ / own_value; // remove impact of focal tree |
388 | //if (value>0.)
|
253 | //if (value>0.)
|
389 | sum += value * (*reader)(x,y); |
254 | sum += value * (*reader)(x,y); |
390 | 255 | ||
Line 446... | Line 311... | ||
446 | double gpp = raw_gpp * 0.6; // aging |
311 | double gpp = raw_gpp * 0.6; // aging |
447 | double npp = gpp * 0.47; // respiration loss |
312 | double npp = gpp * 0.47; // respiration loss |
448 | 313 | ||
449 | partitioning(npp); |
314 | partitioning(npp); |
450 | 315 | ||
451 | calcBiomassCompartments(); |
- | |
- | 316 | mStamp = mSpecies->stamp(mDbh, mHeight); // get new stamp for updated dimensions |
|
452 | 317 | ||
453 | }
|
318 | }
|
454 | 319 | ||
455 | 320 | ||
456 | // just used to test the DBG_IF_x macros...
|
321 | // just used to test the DBG_IF_x macros...
|
Line 510... | Line 375... | ||
510 | double net_stem = gross_stem - senescence_stem; |
375 | double net_stem = gross_stem - senescence_stem; |
511 | 376 | ||
512 | // flow back to reserve pool:
|
377 | // flow back to reserve pool:
|
513 | double to_reserve = qMin(reserve_size, net_stem); |
378 | double to_reserve = qMin(reserve_size, net_stem); |
514 | net_stem -= to_reserve; |
379 | net_stem -= to_reserve; |
- | 380 | ||
- | 381 | DBG_IF_X(mId == 1 , "Tree::partitioning", "dump", dump() |
|
- | 382 | + QString("npp %1 npp_reserve %9 sen_fol %2 sen_stem %3 sen_root %4 net_fol %5 net_stem %6 net_root %7 to_reserve %8") |
|
- | 383 | .arg(npp).arg(senescence_foliage).arg(senescence_stem).arg(senescence_root) |
|
- | 384 | .arg(net_foliage).arg(net_stem).arg(net_root).arg(to_reserve).arg(mNPPReserve) ); |
|
515 | 385 | ||
516 | // update of compartments
|
386 | // update of compartments
|
517 | mLeafMass += net_foliage; |
387 | mLeafMass += net_foliage; |
- | 388 | mLeafMass = qMax(mLeafMass, 0.f); |
|
- | 389 | mLeafArea = mLeafMass * mSpecies->specificLeafArea(); |
|
- | 390 | ||
518 | mRootMass += net_root; |
391 | mRootMass += net_root; |
- | 392 | mRootMass = qMax(mRootMass, 0.f); |
|
519 | 393 | ||
520 | // calculate the dimension of growth
|
- | |
- | 394 | // calculate the dimensions of growth (diameter, height)
|
|
521 | grow_diameter(net_stem); |
395 | grow_diameter(net_stem); |
522 | 396 | ||
523 | //DBG_IF(apct_foliage<0, "Tree::partitioning", "foliage out of range");
|
- | |
524 | //DBG_IF_X(mId==1, "Tree::partitioning", "foliage out of range", test_cntr());
|
- | |
- | 397 | // calculate stem biomass using the allometric equation
|
|
- | 398 | mStemMass = mSpecies->biomassStem(mDbh); |
|
525 | 399 | ||
526 | }
|
400 | }
|
527 | 401 | ||
- | 402 | ||
- | 403 | /** Determination of diamter and height growth based on increment of the stem mass (@net_stem_npp).
|
|
- | 404 | Refer to XXX for equations and variables.
|
|
- | 405 | This function updates the dbh and height of the tree.
|
|
- | 406 | */
|
|
528 | inline void Tree::grow_diameter(const double &net_stem_npp) |
407 | inline void Tree::grow_diameter(const double &net_stem_npp) |
529 | {
|
408 | {
|
530 | // determine dh-ratio of increment
|
409 | // determine dh-ratio of increment
|
531 | // height increment is a function of light competition:
|
410 | // height increment is a function of light competition:
|
532 | double rel_height_growth = relative_height_growth(); // [0..1] |
- | |
- | 411 | double hd_growth = relative_height_growth(); // hd of height growth |
|
- | 412 | //DBG_IF_X(rel_height_growth<0 || rel_height_growth>1., "Tree::grow_dimater", "rel_height_growth out of bound.", dump());
|
|
533 | 413 | ||
- | 414 | const double volume_factor = mSpecies->volumeFactor(); |
|
534 | 415 | ||
- | 416 | double factor_diameter = 1. / ( volume_factor * (mDbh + mDbhDelta)*(mDbh + mDbhDelta) * ( 2. * mHeight/mDbh + hd_growth) ); |
|
- | 417 | double delta_d_estimate = factor_diameter * net_stem_npp; // estimated dbh-inc using last years increment |
|
- | 418 | ||
- | 419 | // using that dbh-increment we estimate a stem-mass-increment and the residual (Eq. 9)
|
|
- | 420 | double stem_estimate = volume_factor * (mDbh + delta_d_estimate)*(mDbh + delta_d_estimate)*(mHeight + delta_d_estimate*hd_growth); |
|
- | 421 | double stem_residual = stem_estimate - (mStemMass + net_stem_npp); |
|
- | 422 | ||
- | 423 | // the final increment is then:
|
|
- | 424 | double d_increment = factor_diameter * (net_stem_npp - stem_residual); // Eq. (11) |
|
- | 425 | DBG_IF_X(mId == 1 || d_increment<0., "Tree::grow_dimater", "increment < 0.", dump() |
|
- | 426 | + QString("\nhdz %1 factor_diameter %2 stem_residual %3 delta_d_estimate %4 d_increment %5 final residual(kg) %6") |
|
- | 427 | .arg(hd_growth).arg(factor_diameter).arg(stem_residual).arg(delta_d_estimate).arg(d_increment) |
|
- | 428 | .arg( volume_factor * (mDbh + d_increment)*(mDbh + d_increment)*(mHeight + d_increment*hd_growth)-((mStemMass + net_stem_npp)) )); |
|
- | 429 | ||
- | 430 | DBGMODE(
|
|
- | 431 | double res_final = volume_factor * (mDbh + d_increment)*(mDbh + d_increment)*(mHeight + d_increment*hd_growth)-((mStemMass + net_stem_npp)); |
|
- | 432 | DBG_IF_X(res_final > 1, "Tree::grow_diameter", "final residual stem estimate > 1kg", dump()); |
|
- | 433 | DBG_IF_X(d_increment > 10. || d_increment*hd_growth/100. >10., "Tree::grow_diameter", "growth out of bound:",QString("d-increment %1 h-increment %2 ").arg(d_increment).arg(d_increment*hd_growth/100.) + dump()); |
|
- | 434 | //dbgstruct["sen_demand"]=sen_demand;
|
|
- | 435 | ||
- | 436 | ||
- | 437 | ); |
|
- | 438 | d_increment = qMax(d_increment, 0.); |
|
- | 439 | ||
- | 440 | // update state variables
|
|
- | 441 | mDbh += d_increment; |
|
- | 442 | mDbhDelta = d_increment; // save for next year's growth |
|
- | 443 | mHeight += d_increment * hd_growth * 0.01; |
|
535 | }
|
444 | }
|
536 | 445 | ||
- | 446 | ||
- | 447 | /// return the HD ratio of this year's increment based on the light status.
|
|
537 | inline double Tree::relative_height_growth() |
448 | inline double Tree::relative_height_growth() |
538 | {
|
449 | {
|
539 | double hd_low, hd_high; |
450 | double hd_low, hd_high; |
540 | mSpecies->hdRange(mDbh, hd_low, hd_high); |
451 | mSpecies->hdRange(mDbh, hd_low, hd_high); |
541 | 452 | ||
542 | // scale according to LRI: if having much light (LRI=1), the result is hd_low (for open grown trees)
|
- | |
543 | double result = hd_high - (hd_high-hd_low)*mLRI; |
- | |
544 | return result; |
- | |
- | 453 | DBG_IF_X(hd_low>hd_high, "Tree::relative_height_growth", "hd low higher dann hd_high for ", dump()); |
|
- | 454 | DBG_IF_X(hd_low < 20 || hd_high>250, "Tree::relative_height_growth", "hd out of range ", dump() + QString(" hd-low: %1 hd-high: %2").arg(hd_low).arg(hd_high)); |
|
- | 455 | ||
- | 456 | // scale according to LRI: if receiving much light (LRI=1), the result is hd_low (for open grown trees)
|
|
- | 457 | double hd_ratio = hd_high - (hd_high-hd_low)*mLRI; |
|
- | 458 | return hd_ratio; |
|
545 | }
|
459 | }
|