Rev 1221 | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
671 | werner | 1 | /******************************************************************************************** |
2 | ** iLand - an individual based forest landscape and disturbance model |
||
3 | ** http://iland.boku.ac.at |
||
4 | ** Copyright (C) 2009- Werner Rammer, Rupert Seidl |
||
5 | ** |
||
6 | ** This program is free software: you can redistribute it and/or modify |
||
7 | ** it under the terms of the GNU General Public License as published by |
||
8 | ** the Free Software Foundation, either version 3 of the License, or |
||
9 | ** (at your option) any later version. |
||
10 | ** |
||
11 | ** This program is distributed in the hope that it will be useful, |
||
12 | ** but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
13 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
14 | ** GNU General Public License for more details. |
||
15 | ** |
||
16 | ** You should have received a copy of the GNU General Public License |
||
17 | ** along with this program. If not, see <http://www.gnu.org/licenses/>. |
||
18 | ********************************************************************************************/ |
||
19 | |||
373 | werner | 20 | #include "seeddispersal.h" |
21 | |||
22 | #include "globalsettings.h" |
||
23 | #include "model.h" |
||
808 | werner | 24 | #include "debugtimer.h" |
373 | werner | 25 | #include "helper.h" |
391 | werner | 26 | #include "species.h" |
989 | werner | 27 | #ifdef ILAND_GUI |
373 | werner | 28 | #include <QtGui/QImage> |
989 | werner | 29 | #endif |
373 | werner | 30 | |
31 | /** @class SeedDispersal |
||
697 | werner | 32 | @ingroup core |
373 | werner | 33 | The class encapsulates the dispersal of seeds of one species over the whole landscape. |
697 | werner | 34 | The dispersal algortihm operate on grids with a 20m resolution. |
373 | werner | 35 | |
697 | werner | 36 | See http://iland.boku.ac.at/dispersal |
37 | |||
373 | werner | 38 | */ |
764 | werner | 39 | |
40 | Grid<float> *SeedDispersal::mExternalSeedBaseMap = 0; |
||
41 | QHash<QString, QVector<double> > SeedDispersal::mExtSeedData; |
||
42 | int SeedDispersal::mExtSeedSizeX = 0; |
||
43 | int SeedDispersal::mExtSeedSizeY = 0; |
||
44 | |||
373 | werner | 45 | SeedDispersal::~SeedDispersal() |
46 | { |
||
47 | if (isSetup()) { |
||
48 | |||
49 | } |
||
50 | } |
||
51 | |||
391 | werner | 52 | // ************ Setup ************** |
53 | |||
54 | /** setup of the seedmaps. |
||
55 | This sets the size of the seed map and creates the seed kernel (species specific) |
||
56 | */ |
||
373 | werner | 57 | void SeedDispersal::setup() |
58 | { |
||
391 | werner | 59 | if (!GlobalSettings::instance()->model() |
60 | || !GlobalSettings::instance()->model()->heightGrid() |
||
61 | || !mSpecies) |
||
373 | werner | 62 | return; |
1180 | werner | 63 | mProbMode = false; |
391 | werner | 64 | |
65 | const float seedmap_size = 20.f; |
||
373 | werner | 66 | // setup of seed map |
67 | mSeedMap.clear(); |
||
391 | werner | 68 | mSeedMap.setup(GlobalSettings::instance()->model()->heightGrid()->metricRect(), seedmap_size ); |
373 | werner | 69 | mSeedMap.initialize(0.); |
1180 | werner | 70 | if (!mProbMode) { |
71 | mSourceMap.setup(mSeedMap); |
||
72 | mSourceMap.initialize(0.); |
||
73 | } |
||
764 | werner | 74 | mExternalSeedMap.clear(); |
391 | werner | 75 | mIndexFactor = int(seedmap_size) / cPxSize; // ratio seed grid / lip-grid: |
550 | werner | 76 | if (logLevelInfo()) qDebug() << "Seed map setup. Species:"<< mSpecies->id() << "kernel-size: " << mSeedMap.sizeX() << "x" << mSeedMap.sizeY() << "pixels."; |
373 | werner | 77 | |
445 | werner | 78 | if (mSpecies==0) |
79 | throw IException("Setup of SeedDispersal: Species not defined."); |
||
80 | |||
802 | werner | 81 | if (fmod(GlobalSettings::instance()->settings().valueDouble("model.world.buffer",0),seedmap_size) != 0.) |
82 | throw IException("SeedDispersal:setup(): The buffer (model.world.buffer) must be a integer multiple of the seed pixel size (currently 20m, e.g. 20,40,60,...))."); |
||
83 | |||
415 | werner | 84 | // settings |
445 | werner | 85 | mTM_occupancy = 1.; // is currently constant |
1176 | werner | 86 | // copy values for the species parameters: |
87 | mSpecies->treeMigKernel(mTM_as1, mTM_as2, mTM_ks); |
||
445 | werner | 88 | mTM_fecundity_cell = mSpecies->fecundity_m2() * seedmap_size*seedmap_size * mTM_occupancy; // scale to production for the whole cell |
89 | mNonSeedYearFraction = mSpecies->nonSeedYearFraction(); |
||
1176 | werner | 90 | XmlHelper xml(GlobalSettings::instance()->settings().node("model.settings.seedDispersal")); |
91 | mKernelThresholdArea = xml.valueDouble(".longDistanceDispersal.thresholdArea", 0.0001); |
||
92 | mKernelThresholdLDD = xml.valueDouble(".longDistanceDispersal.thresholdLDD", 0.0001); |
||
1180 | werner | 93 | mLDDSeedlings = xml.valueDouble(".longDistanceDispersal.LDDSeedlings", 0.0001); |
1176 | werner | 94 | mLDDRings = xml.valueInt(".longDistanceDispersal.rings", 4); |
415 | werner | 95 | |
1180 | werner | 96 | mLDDSeedlings = qMax(mLDDSeedlings, static_cast<float>(mKernelThresholdArea)); |
415 | werner | 97 | |
1180 | werner | 98 | // long distance dispersal |
99 | double ldd_area = setupLDD(); |
||
1176 | werner | 100 | |
1180 | werner | 101 | createKernel(mKernelSeedYear, mTM_fecundity_cell, 1. - ldd_area); |
1176 | werner | 102 | |
415 | werner | 103 | // the kernel for non seed years looks similar, but is simply linearly scaled down |
104 | // using the species parameter NonSeedYearFraction. |
||
105 | // the central pixel still gets the value of 1 (i.e. 100% probability) |
||
1180 | werner | 106 | createKernel(mKernelNonSeedYear, mTM_fecundity_cell*mNonSeedYearFraction, 1. - ldd_area); |
415 | werner | 107 | |
1167 | werner | 108 | if (mSpecies->fecunditySerotiny()>0.) { |
109 | // an extra seed map is used for storing information related to post-fire seed rain |
||
1168 | werner | 110 | mSeedMapSerotiny.clear(); |
111 | mSeedMapSerotiny.setup(GlobalSettings::instance()->model()->heightGrid()->metricRect(), seedmap_size ); |
||
112 | mSeedMapSerotiny.initialize(0.); |
||
1167 | werner | 113 | |
114 | // set up the special seed kernel for post fire seed rain |
||
1180 | werner | 115 | createKernel(mKernelSerotiny, mTM_fecundity_cell * mSpecies->fecunditySerotiny(),1.); |
1167 | werner | 116 | qDebug() << "created extra seed map and serotiny seed kernel for species" << mSpecies->name() << "with fecundity factor" << mSpecies->fecunditySerotiny(); |
117 | } |
||
118 | mHasPendingSerotiny = false; |
||
119 | |||
472 | werner | 120 | // debug info |
121 | mDumpSeedMaps = GlobalSettings::instance()->settings().valueBool("model.settings.seedDispersal.dumpSeedMapsEnabled",false); |
||
122 | if (mDumpSeedMaps) { |
||
1102 | werner | 123 | QString path = GlobalSettings::instance()->path( GlobalSettings::instance()->settings().value("model.settings.seedDispersal.dumpSeedMapsPath") ); |
472 | werner | 124 | Helper::saveToTextFile(QString("%1/seedkernelYes_%2.csv").arg(path).arg(mSpecies->id()),gridToString(mKernelSeedYear)); |
125 | Helper::saveToTextFile(QString("%1/seedkernelNo_%2.csv").arg(path).arg(mSpecies->id()),gridToString(mKernelNonSeedYear)); |
||
1168 | werner | 126 | if (!mKernelSerotiny.isEmpty()) |
127 | Helper::saveToTextFile(QString("%1/seedkernelSerotiny_%2.csv").arg(path).arg(mSpecies->id()),gridToString(mKernelSerotiny)); |
||
417 | werner | 128 | } |
1176 | werner | 129 | |
1180 | werner | 130 | |
472 | werner | 131 | // external seeds |
481 | werner | 132 | mHasExternalSeedInput = false; |
491 | werner | 133 | mExternalSeedBuffer = 0; |
134 | mExternalSeedDirection = 0; |
||
836 | werner | 135 | mExternalSeedBackgroundInput = 0.; |
472 | werner | 136 | if (GlobalSettings::instance()->settings().valueBool("model.settings.seedDispersal.externalSeedEnabled",false)) { |
764 | werner | 137 | if (GlobalSettings::instance()->settings().valueBool("model.settings.seedDispersal.seedBelt.enabled",false)) { |
138 | // external seed input specified by sectors and around the project area (seedbelt) |
||
139 | setupExternalSeedsForSpecies(mSpecies); |
||
140 | } else { |
||
141 | // external seeds specified fixedly per cardinal direction |
||
142 | // current species in list?? |
||
143 | mHasExternalSeedInput = GlobalSettings::instance()->settings().value("model.settings.seedDispersal.externalSeedSpecies").contains(mSpecies->id()); |
||
144 | QString dir = GlobalSettings::instance()->settings().value("model.settings.seedDispersal.externalSeedSource").toLower(); |
||
145 | // encode cardinal positions as bits: e.g: "e,w" -> 6 |
||
146 | mExternalSeedDirection += dir.contains("n")?1:0; |
||
147 | mExternalSeedDirection += dir.contains("e")?2:0; |
||
148 | mExternalSeedDirection += dir.contains("s")?4:0; |
||
149 | mExternalSeedDirection += dir.contains("w")?8:0; |
||
837 | werner | 150 | QStringList buffer_list = GlobalSettings::instance()->settings().value("model.settings.seedDispersal.externalSeedBuffer").split(QRegExp("([^\\.\\w]+)")); |
764 | werner | 151 | int index = buffer_list.indexOf(mSpecies->id()); |
152 | if (index>=0) { |
||
153 | mExternalSeedBuffer = buffer_list[index+1].toInt(); |
||
154 | qDebug() << "enabled special buffer for species" <<mSpecies->id() << ": distance of" << mExternalSeedBuffer << "pixels = " << mExternalSeedBuffer*20. << "m"; |
||
155 | } |
||
836 | werner | 156 | |
157 | // background seed rain (i.e. for the full landscape), use regexp |
||
837 | werner | 158 | QStringList background_input_list = GlobalSettings::instance()->settings().value("model.settings.seedDispersal.externalSeedBackgroundInput").split(QRegExp("([^\\.\\w]+)")); |
836 | werner | 159 | index = background_input_list.indexOf(mSpecies->id()); |
160 | if (index>=0) { |
||
161 | mExternalSeedBackgroundInput = background_input_list[index+1].toDouble(); |
||
162 | qDebug() << "enabled background seed input (for full area) for species" <<mSpecies->id() << ": p=" << mExternalSeedBackgroundInput; |
||
163 | } |
||
164 | |||
764 | werner | 165 | if (mHasExternalSeedInput) |
166 | qDebug() << "External seed input enabled for" << mSpecies->id(); |
||
491 | werner | 167 | } |
472 | werner | 168 | } |
415 | werner | 169 | |
373 | werner | 170 | // setup of seed kernel |
391 | werner | 171 | // const int max_radius = 15; // pixels |
172 | // |
||
173 | // mSeedKernel.clear(); |
||
174 | // mSeedKernel.setup(mSeedMap.cellsize(), 2*max_radius + 1 , 2*max_radius + 1); |
||
175 | // mKernelOffset = max_radius; |
||
176 | // // filling of the kernel.... for simplicity: a linear kernel |
||
177 | // QPoint center = QPoint(mKernelOffset, mKernelOffset); |
||
178 | // const double max_dist = max_radius * seedmap_size; |
||
179 | // for (float *p=mSeedKernel.begin(); p!=mSeedKernel.end();++p) { |
||
180 | // double d = mSeedKernel.distance(center, mSeedKernel.indexOf(p)); |
||
181 | // *p = qMax( 1. - d / max_dist, 0.); |
||
182 | // } |
||
373 | werner | 183 | |
184 | |||
185 | // randomize seed map.... set 1/3 to "filled" |
||
375 | werner | 186 | //for (int i=0;i<mSeedMap.count(); i++) |
187 | // mSeedMap.valueAtIndex(mSeedMap.randomPosition()) = 1.; |
||
373 | werner | 188 | |
189 | |||
375 | werner | 190 | // QImage img = gridToImage(mSeedMap, true, -1., 1.); |
191 | // img.save("seedmap.png"); |
||
192 | |||
193 | // img = gridToImage(mSeedMap, true, -1., 1.); |
||
764 | werner | 194 | // img.save("seedmap_e.png"); |
373 | werner | 195 | } |
196 | |||
764 | werner | 197 | void SeedDispersal::setupExternalSeeds() |
198 | { |
||
199 | mExternalSeedBaseMap = 0; |
||
200 | if (!GlobalSettings::instance()->settings().valueBool("model.settings.seedDispersal.seedBelt.enabled",false)) |
||
201 | return; |
||
202 | |||
978 | werner | 203 | DebugTimer t("setup of external seed maps."); |
764 | werner | 204 | XmlHelper xml(GlobalSettings::instance()->settings().node("model.settings.seedDispersal.seedBelt")); |
1102 | werner | 205 | int seedbelt_width =xml.valueInt(".width",10); |
764 | werner | 206 | // setup of sectors |
207 | // setup of base map |
||
208 | const float seedmap_size = 20.f; |
||
209 | mExternalSeedBaseMap = new Grid<float>; |
||
210 | mExternalSeedBaseMap->setup(GlobalSettings::instance()->model()->heightGrid()->metricRect(), seedmap_size ); |
||
211 | mExternalSeedBaseMap->initialize(0.); |
||
212 | if (mExternalSeedBaseMap->count()*4 != GlobalSettings::instance()->model()->heightGrid()->count()) |
||
947 | werner | 213 | throw IException("error in setting up external seeds: the width and height of the project area need to be a multiple of 20m when external seeds are enabled."); |
764 | werner | 214 | // make a copy of the 10m height grid in lower resolution and mark pixels that are forested and outside of |
215 | // the project area. |
||
216 | for (int y=0;y<mExternalSeedBaseMap->sizeY();y++) |
||
217 | for (int x=0;x<mExternalSeedBaseMap->sizeX();x++) { |
||
765 | werner | 218 | bool val = GlobalSettings::instance()->model()->heightGrid()->valueAtIndex(x*2,y*2).isForestOutside(); |
764 | werner | 219 | mExternalSeedBaseMap->valueAtIndex(x,y) = val?1.f:0.f; |
220 | if(GlobalSettings::instance()->model()->heightGrid()->valueAtIndex(x*2,y*2).isValid()) |
||
221 | mExternalSeedBaseMap->valueAtIndex(x,y) = -1.f; |
||
222 | } |
||
836 | werner | 223 | QString path = GlobalSettings::instance()->path(GlobalSettings::instance()->settings().value("model.settings.seedDispersal.dumpSeedMapsPath")); |
765 | werner | 224 | |
225 | if (GlobalSettings::instance()->settings().valueBool("model.settings.seedDispersal.dumpSeedMapsEnabled",false)) { |
||
989 | werner | 226 | #ifdef ILAND_GUI |
765 | werner | 227 | QImage img = gridToImage(*mExternalSeedBaseMap, true, -1., 2.); |
228 | img.save(path + "/seedbeltmap_before.png"); |
||
989 | werner | 229 | #endif |
765 | werner | 230 | } |
764 | werner | 231 | // img.save("seedmap.png"); |
232 | // now scan the pixels of the belt: paint all pixels that are close to the project area |
||
233 | // we do this 4 times (for all cardinal direcitons) |
||
234 | for (int y=0;y<mExternalSeedBaseMap->sizeY();y++) { |
||
235 | for (int x=0;x<mExternalSeedBaseMap->sizeX();x++) { |
||
236 | if (mExternalSeedBaseMap->valueAtIndex(x, y)!=1.) |
||
237 | continue; |
||
765 | werner | 238 | int look_forward = std::min(x + seedbelt_width, mExternalSeedBaseMap->sizeX()-1); |
1106 | werner | 239 | if (mExternalSeedBaseMap->valueAtIndex(look_forward, y)==-1.f) { |
764 | werner | 240 | // fill pixels |
241 | for(; x<look_forward;++x) { |
||
242 | float &v = mExternalSeedBaseMap->valueAtIndex(x, y); |
||
243 | if (v==1.f) v=2.f; |
||
244 | } |
||
245 | } |
||
246 | } |
||
247 | } |
||
248 | // right to left |
||
249 | for (int y=0;y<mExternalSeedBaseMap->sizeY();y++) { |
||
250 | for (int x=mExternalSeedBaseMap->sizeX();x>=0;--x) { |
||
251 | if (mExternalSeedBaseMap->valueAtIndex(x, y)!=1.) |
||
252 | continue; |
||
253 | int look_forward = std::max(x - seedbelt_width, 0); |
||
1106 | werner | 254 | if (mExternalSeedBaseMap->valueAtIndex(look_forward, y)==-1.f) { |
764 | werner | 255 | // fill pixels |
256 | for(; x>look_forward;--x) { |
||
257 | float &v = mExternalSeedBaseMap->valueAtIndex(x, y); |
||
258 | if (v==1.f) v=2.f; |
||
259 | } |
||
260 | } |
||
261 | } |
||
262 | } |
||
263 | // up and down *** |
||
264 | // from top to bottom |
||
265 | for (int x=0;x<mExternalSeedBaseMap->sizeX();x++) { |
||
266 | for (int y=0;y<mExternalSeedBaseMap->sizeY();y++) { |
||
267 | |||
268 | if (mExternalSeedBaseMap->valueAtIndex(x, y)!=1.) |
||
269 | continue; |
||
765 | werner | 270 | int look_forward = std::min(y + seedbelt_width, mExternalSeedBaseMap->sizeY()-1); |
764 | werner | 271 | if (mExternalSeedBaseMap->valueAtIndex(x, look_forward)==-1.) { |
272 | // fill pixels |
||
273 | for(; y<look_forward;++y) { |
||
274 | float &v = mExternalSeedBaseMap->valueAtIndex(x, y); |
||
275 | if (v==1.f) v=2.f; |
||
276 | } |
||
277 | } |
||
278 | } |
||
279 | } |
||
280 | // bottom to top *** |
||
281 | for (int y=0;y<mExternalSeedBaseMap->sizeY();y++) { |
||
282 | for (int x=mExternalSeedBaseMap->sizeX();x>=0;--x) { |
||
283 | if (mExternalSeedBaseMap->valueAtIndex(x, y)!=1.) |
||
284 | continue; |
||
285 | int look_forward = std::max(y - seedbelt_width, 0); |
||
286 | if (mExternalSeedBaseMap->valueAtIndex(x, look_forward)==-1.) { |
||
287 | // fill pixels |
||
288 | for(; y>look_forward;--y) { |
||
289 | float &v = mExternalSeedBaseMap->valueAtIndex(x, y); |
||
290 | if (v==1.f) v=2.f; |
||
291 | } |
||
292 | } |
||
293 | } |
||
294 | } |
||
1180 | werner | 295 | |
765 | werner | 296 | if (GlobalSettings::instance()->settings().valueBool("model.settings.seedDispersal.dumpSeedMapsEnabled",false)) { |
989 | werner | 297 | #ifdef ILAND_GUI |
765 | werner | 298 | QImage img = gridToImage(*mExternalSeedBaseMap, true, -1., 2.); |
972 | werner | 299 | img.save(path + "/seedbeltmap_after.png"); |
989 | werner | 300 | #endif |
765 | werner | 301 | } |
764 | werner | 302 | mExtSeedData.clear(); |
1102 | werner | 303 | int sectors_x = xml.valueInt("sizeX",0); |
304 | int sectors_y = xml.valueInt("sizeY",0); |
||
764 | werner | 305 | if(sectors_x<1 || sectors_y<1) |
306 | throw IException(QString("setup of external seed dispersal: invalid number of sectors x=%1 y=%3").arg(sectors_x).arg(sectors_y)); |
||
307 | QDomElement elem = xml.node("."); |
||
308 | for(QDomNode n = elem.firstChild(); !n.isNull(); n = n.nextSibling()) { |
||
309 | if (n.nodeName().startsWith("species")) { |
||
310 | QStringList coords = n.nodeName().split("_"); |
||
311 | if (coords.count()!=3) |
||
312 | throw IException("external seed species definition is not valid: " + n.nodeName()); |
||
313 | int x = coords[1].toInt(); |
||
314 | int y = coords[2].toInt(); |
||
315 | if (x<0 || x>=sectors_x || y<0 || y>=sectors_y) |
||
316 | throw IException(QString("invalid sector for specifiing external seed input (x y): %1 %2 ").arg(x).arg(y) ); |
||
317 | int index = y*sectors_x + x; |
||
318 | |||
319 | QString text = xml.value("." + n.nodeName()); |
||
320 | qDebug() << "processing element " << n.nodeName() << "x,y:" << x << y << text; |
||
321 | // we assume pairs of name and fraction |
||
322 | QStringList species_list = text.split(" "); |
||
323 | for (int i=0;i<species_list.count();++i) { |
||
324 | QVector<double> &space = mExtSeedData[species_list[i]]; |
||
325 | if (space.isEmpty()) |
||
326 | space.resize(sectors_x*sectors_y); // are initialized to 0s |
||
327 | double fraction = species_list[++i].toDouble(); |
||
328 | space[index] = fraction; |
||
329 | } |
||
330 | } |
||
331 | } |
||
332 | mExtSeedSizeX = sectors_x; |
||
333 | mExtSeedSizeY = sectors_y; |
||
334 | qDebug() << "setting up of external seed maps finished"; |
||
335 | } |
||
336 | |||
337 | void SeedDispersal::finalizeExternalSeeds() |
||
338 | { |
||
339 | if (mExternalSeedBaseMap) |
||
340 | delete mExternalSeedBaseMap; |
||
341 | mExternalSeedBaseMap = 0; |
||
342 | } |
||
343 | |||
1167 | werner | 344 | void SeedDispersal::seedProductionSerotiny(const QPoint &position_index) |
345 | { |
||
1168 | werner | 346 | if (mSeedMapSerotiny.isEmpty()) |
1167 | werner | 347 | throw IException("Invalid use seedProductionSerotiny(): tried to set a seed source for a non-serotinous species!"); |
1168 | werner | 348 | mSeedMapSerotiny.valueAtIndex(position_index.x()/mIndexFactor, position_index.y()/mIndexFactor)=1.f; |
1167 | werner | 349 | mHasPendingSerotiny = true; |
350 | } |
||
351 | |||
391 | werner | 352 | // ************ Kernel ************** |
1180 | werner | 353 | void SeedDispersal::createKernel(Grid<float> &kernel, const double max_seed, const double scale_area) |
391 | werner | 354 | { |
415 | werner | 355 | |
1180 | werner | 356 | double max_dist = treemig_distanceTo(mKernelThresholdArea / species()->fecundity_m2()); |
391 | werner | 357 | double cell_size = mSeedMap.cellsize(); |
415 | werner | 358 | int max_radius = int(max_dist / cell_size); |
391 | werner | 359 | // e.g.: cell_size: regeneration grid (e.g. 400qm), px-size: light-grid (4qm) |
445 | werner | 360 | double occupation = cell_size*cell_size / (cPxSize*cPxSize * mTM_occupancy); |
391 | werner | 361 | |
415 | werner | 362 | kernel.clear(); |
391 | werner | 363 | |
415 | werner | 364 | kernel.setup(mSeedMap.cellsize(), 2*max_radius + 1 , 2*max_radius + 1); |
365 | int kernel_offset = max_radius; |
||
366 | |||
1180 | werner | 367 | // filling of the kernel.... use the treemig density function |
1179 | werner | 368 | double dist_center_cell = sqrt(cell_size*cell_size/M_PI); |
415 | werner | 369 | QPoint center = QPoint(kernel_offset, kernel_offset); |
370 | const float *sk_end = kernel.end(); |
||
371 | for (float *p=kernel.begin(); p!=sk_end;++p) { |
||
372 | double d = kernel.distance(center, kernel.indexOf(p)); |
||
1178 | werner | 373 | if (d==0.) |
1179 | werner | 374 | *p = treemig_centercell(dist_center_cell); // r is the radius of a circle with the same area as a cell |
1178 | werner | 375 | else |
1180 | werner | 376 | *p = d<=max_dist?static_cast<float>(( treemig(d+dist_center_cell) + treemig(d-dist_center_cell))/2.f * cell_size*cell_size ):0.f; |
391 | werner | 377 | } |
378 | |||
379 | // normalize |
||
1102 | werner | 380 | float sum = kernel.sum(); |
391 | werner | 381 | if (sum==0. || occupation==0.) |
382 | throw IException("create seed kernel: sum of probabilities = 0!"); |
||
383 | |||
1180 | werner | 384 | // the sum of all kernel cells has to equal 1 (- long distance dispersal) |
385 | kernel.multiply(scale_area/sum); |
||
1178 | werner | 386 | |
1180 | werner | 387 | |
388 | if (mProbMode) { |
||
389 | // probabilities are derived in multiplying by seed number, and dividing by occupancy criterion |
||
390 | float fecundity_factor = static_cast<float>( max_seed / occupation); |
||
391 | kernel.multiply( fecundity_factor ); |
||
392 | // all cells that get more seeds than the occupancy criterion are considered to have no seed limitation for regeneration |
||
393 | for (float *p=kernel.begin(); p!=sk_end;++p) { |
||
394 | *p = qMin(*p, 1.f); |
||
395 | } |
||
391 | werner | 396 | } |
397 | // set the parent cell to 1 |
||
1178 | werner | 398 | //kernel.valueAtIndex(kernel_offset, kernel_offset)=1.f; |
415 | werner | 399 | |
400 | |||
391 | werner | 401 | // some final statistics.... |
1180 | werner | 402 | if (logLevelInfo()) |
403 | qDebug() << "kernel setup. Species:"<< mSpecies->id() << "kernel-size: " << kernel.sizeX() << "x" << kernel.sizeY() << "pixels, sum (after scaling): " << kernel.sum(); |
||
1176 | werner | 404 | |
1180 | werner | 405 | |
391 | werner | 406 | } |
407 | |||
1180 | werner | 408 | double SeedDispersal::setupLDD() |
1176 | werner | 409 | { |
410 | mLDDDensity.clear(); mLDDDistance.clear(); |
||
411 | if (mKernelThresholdLDD >= mKernelThresholdArea) { |
||
412 | // no long distance dispersal |
||
1180 | werner | 413 | return 0.; |
1176 | werner | 414 | |
415 | } |
||
1180 | werner | 416 | double r_min = treemig_distanceTo(mKernelThresholdArea / species()->fecundity_m2()); |
417 | double r_max = treemig_distanceTo(mKernelThresholdLDD / species()->fecundity_m2()); |
||
1176 | werner | 418 | |
419 | |||
420 | mLDDDistance.push_back(r_min); |
||
1180 | werner | 421 | double ldd_sum = 0.; |
1176 | werner | 422 | for (int i=0;i<mLDDRings;++i) { |
423 | double r_in = mLDDDistance.last(); |
||
424 | mLDDDistance.push_back(mLDDDistance.last() + (r_max-r_min)/static_cast<float>(mLDDRings)); |
||
425 | double r_out = mLDDDistance.last(); |
||
426 | // calculate the value of the kernel for the middle of the ring |
||
427 | double ring_in = treemig(r_in); // kernel value at the inner border of the ring |
||
428 | double ring_out = treemig(r_out); // kernel value at the outer border of the ring |
||
1180 | werner | 429 | double ring_val = ring_in*0.4 + ring_out*0.6; // this is the average p -- 0.4/0.6 better estimate the nonlinear behavior (fits very well for medium to large kernels, e.g. piab) |
430 | // |
||
1176 | werner | 431 | // calculate the area of the ring |
432 | double ring_area = (r_out*r_out - r_in*r_in)*M_PI; // in square meters |
||
1180 | werner | 433 | // the number of px considers the fecundity |
434 | double n_px = ring_val * ring_area * species()->fecundity_m2() / mLDDSeedlings; |
||
435 | ldd_sum += ring_val * ring_area; // this fraction of the full kernel (=1) is distributed in theis ring |
||
1176 | werner | 436 | |
437 | mLDDDensity.push_back(n_px); |
||
438 | } |
||
1204 | werner | 439 | if (logLevelInfo()) |
440 | qDebug() << "Setup LDD for" << species()->name() << ", using probability: "<< mLDDSeedlings<< ": Distances:" << mLDDDistance << ", seed pixels:" << mLDDDensity << "covered prob:" << ldd_sum; |
||
1176 | werner | 441 | |
1180 | werner | 442 | return ldd_sum; |
1176 | werner | 443 | } |
444 | |||
391 | werner | 445 | /* R-Code: |
930 | werner | 446 | treemig=function(as1,as2,ks,d) # two-part exponential function, cf. Lischke & Loeffler (2006), Annex |
391 | werner | 447 | { |
448 | p1=(1-ks)*exp(-d/as1)/as1 |
||
449 | if(as2>0){p2=ks*exp(-d/as2)/as2}else{p2=0} |
||
450 | p1+p2 |
||
451 | } |
||
452 | */ |
||
670 | werner | 453 | |
454 | /// the used kernel function |
||
455 | /// see also Appendix B of iland paper II (note the different variable names) |
||
1178 | werner | 456 | /// the function returns the seed density at a point with distance 'distance'. |
391 | werner | 457 | double SeedDispersal::treemig(const double &distance) |
458 | { |
||
459 | double p1 = (1.-mTM_ks)*exp(-distance/mTM_as1)/mTM_as1; |
||
460 | double p2 = 0.; |
||
461 | if (mTM_as2>0.) |
||
462 | p2 = mTM_ks*exp(-distance/mTM_as2)/mTM_as2; |
||
1178 | werner | 463 | double s = p1 + p2; |
464 | // 's' is the density for radius 'distance' - not for specific point with that distance. |
||
465 | // (i.e. the integral over the one-dimensional treemig function is 1, but if applied for 2d cells, the |
||
466 | // sum would be much larger as all seeds arriving at 'distance' would be arriving somewhere at the circle with radius 'distance') |
||
467 | // convert that to a density at a point, by dividing with the circumference at the circle with radius 'distance' |
||
468 | s = s / (2.*std::max(distance, 0.01)*M_PI); |
||
469 | |||
470 | return s; |
||
391 | werner | 471 | } |
472 | |||
1178 | werner | 473 | double SeedDispersal::treemig_centercell(const double &max_distance) |
474 | { |
||
475 | // use 100 steps and calculate dispersal kernel for consecutive rings |
||
476 | double sum = 0.; |
||
477 | for (int i=0;i<100;i++) { |
||
478 | double r_in = i*max_distance/100.; |
||
479 | double r_out = (i+1)*max_distance/100.; |
||
480 | double ring_area = (r_out*r_out-r_in*r_in)*M_PI; |
||
481 | // the value of each ring is: treemig(r) * area of the ring |
||
482 | sum += treemig((r_out+r_in)/2.)*ring_area; |
||
483 | } |
||
484 | return sum; |
||
485 | } |
||
486 | |||
391 | werner | 487 | /// calculate the distance where the probability falls below 'value' |
488 | double SeedDispersal::treemig_distanceTo(const double value) |
||
489 | { |
||
490 | double dist = 0.; |
||
491 | while (treemig(dist)>value && dist<10000.) |
||
492 | dist+=10; |
||
493 | return dist; |
||
494 | } |
||
495 | |||
764 | werner | 496 | void SeedDispersal::setupExternalSeedsForSpecies(Species *species) |
497 | { |
||
498 | if (!mExtSeedData.contains(species->id())) |
||
499 | return; // nothing to do |
||
500 | qDebug() << "setting up external seed map for" << species->id(); |
||
501 | QVector<double> &pcts = mExtSeedData[species->id()]; |
||
502 | mExternalSeedMap.setup(mSeedMap); |
||
503 | mExternalSeedMap.initialize(0.f); |
||
504 | for (int sector_x=0; sector_x<mExtSeedSizeX; ++sector_x) |
||
505 | for (int sector_y=0; sector_y<mExtSeedSizeY; ++sector_y) { |
||
506 | int xmin,xmax,ymin,ymax; |
||
507 | int fx = mExternalSeedMap.sizeX() / mExtSeedSizeX; // number of cells per sector |
||
508 | xmin = sector_x*fx; |
||
509 | xmax = (sector_x+1)*fx; |
||
510 | fx = mExternalSeedMap.sizeY() / mExtSeedSizeY; // number of cells per sector |
||
511 | ymin = sector_y*fx; |
||
512 | ymax = (sector_y+1)*fx; |
||
513 | // now loop over the whole sector |
||
514 | int index = sector_y*mExtSeedSizeX + sector_x; |
||
515 | double p = pcts[index]; |
||
516 | for (int y=ymin;y<ymax;++y) |
||
517 | for (int x=xmin;x<xmax;++x) { |
||
518 | // check |
||
519 | if (mExternalSeedBaseMap->valueAtIndex(x,y)==2.f) |
||
520 | if (drandom()<p) |
||
521 | mExternalSeedMap.valueAtIndex(x,y) = 1.f; // flag |
||
522 | } |
||
391 | werner | 523 | |
764 | werner | 524 | } |
1180 | werner | 525 | if (!mProbMode) { |
526 | // scale external seed values to have pixels with LAI=3 |
||
527 | for (float *p=mExternalSeedMap.begin(); p!=mExternalSeedMap.end(); ++p) |
||
528 | *p *= 3.f * mExternalSeedMap.cellsize()*mExternalSeedMap.cellsize(); |
||
529 | } |
||
764 | werner | 530 | } |
531 | |||
532 | |||
391 | werner | 533 | // ************ Dispersal ************** |
534 | |||
535 | |||
375 | werner | 536 | /// debug function: loads a image of arbirtrary size... |
537 | void SeedDispersal::loadFromImage(const QString &fileName) |
||
538 | { |
||
539 | mSeedMap.clear(); |
||
540 | loadGridFromImage(fileName, mSeedMap); |
||
541 | for (float* p=mSeedMap.begin();p!=mSeedMap.end();++p) |
||
542 | *p = *p>0.8?1.f:0.f; |
||
543 | |||
544 | } |
||
545 | |||
546 | void SeedDispersal::clear() |
||
547 | { |
||
1180 | werner | 548 | Grid<float> *seed_map = &mSeedMap; |
549 | if (!mProbMode) { |
||
550 | seed_map = &mSourceMap; |
||
551 | mSeedMap.initialize(0.f); |
||
552 | } |
||
764 | werner | 553 | if (!mExternalSeedMap.isEmpty()) { |
554 | // we have a preprocessed initial value for the external seed map (see setupExternalSeeds() et al) |
||
1180 | werner | 555 | seed_map->copy(mExternalSeedMap); |
764 | werner | 556 | return; |
557 | } |
||
558 | // clear the map |
||
1102 | werner | 559 | float background_value = static_cast<float>(mExternalSeedBackgroundInput); // there is potentitally a background probability <>0 for all pixels. |
1180 | werner | 560 | seed_map->initialize(background_value); |
472 | werner | 561 | if (mHasExternalSeedInput) { |
562 | // if external seed input is enabled, the buffer area of the seed maps is |
||
563 | // "turned on", i.e. set to 1. |
||
1180 | werner | 564 | int buf_size = GlobalSettings::instance()->settings().valueInt("model.world.buffer",0.) / static_cast<int>(seed_map->cellsize()); |
491 | werner | 565 | // if a special buffer is defined, reduce the size of the input |
566 | if (mExternalSeedBuffer>0) |
||
567 | buf_size -= mExternalSeedBuffer; |
||
472 | werner | 568 | if (buf_size>0) { |
569 | int ix,iy; |
||
1180 | werner | 570 | for (iy=0;iy<seed_map->sizeY();++iy) |
571 | for (ix=0;ix<seed_map->sizeX(); ++ix) |
||
572 | if (iy<buf_size || iy>=seed_map->sizeY()-buf_size || ix<buf_size || ix>=seed_map->sizeX()-buf_size) { |
||
491 | werner | 573 | if (mExternalSeedDirection==0) { |
574 | // seeds from all directions |
||
1180 | werner | 575 | seed_map->valueAtIndex(ix,iy)=1.f; |
491 | werner | 576 | } else { |
577 | // seeds only from specific directions |
||
578 | float value = 0.f; |
||
1180 | werner | 579 | if (isBitSet(mExternalSeedDirection,1) && ix>=seed_map->sizeX()-buf_size) value = 1; // north |
491 | werner | 580 | if (isBitSet(mExternalSeedDirection,2) && iy<buf_size) value = 1; // east |
581 | if (isBitSet(mExternalSeedDirection,3) && ix<buf_size) value = 1; // south |
||
1180 | werner | 582 | if (isBitSet(mExternalSeedDirection,4) && iy>=seed_map->sizeY()-buf_size) value = 1; // west |
583 | seed_map->valueAtIndex(ix,iy)=value; |
||
491 | werner | 584 | } |
585 | } |
||
472 | werner | 586 | } else { |
587 | qDebug() << "external seed input: Error: invalid buffer size???"; |
||
588 | } |
||
589 | } |
||
375 | werner | 590 | } |
591 | |||
1176 | werner | 592 | static int _debug_ldd=0; |
375 | werner | 593 | void SeedDispersal::execute() |
594 | { |
||
991 | werner | 595 | #ifdef ILAND_GUI |
596 | int year = GlobalSettings::instance()->currentYear(); |
||
597 | QString path; |
||
472 | werner | 598 | if (mDumpSeedMaps) { |
1102 | werner | 599 | path = GlobalSettings::instance()->path( GlobalSettings::instance()->settings().value("model.settings.seedDispersal.dumpSeedMapsPath") ); |
472 | werner | 600 | gridToImage(seedMap(), true, 0., 1.).save(QString("%1/seed_before_%2_%3.png").arg(path).arg(mSpecies->id()).arg(year)); |
618 | werner | 601 | qDebug() << "saved seed map image to" << path; |
992 | werner | 602 | } |
989 | werner | 603 | #else |
992 | werner | 604 | if (mDumpSeedMaps) |
989 | werner | 605 | qDebug() << "saving of seedmaps only supported in the iLand GUI."; |
606 | #endif |
||
1180 | werner | 607 | if (mProbMode) { |
992 | werner | 608 | |
1180 | werner | 609 | DebugTimer t("seed dispersal", true); |
1106 | werner | 610 | |
1180 | werner | 611 | // (1) detect edges |
612 | if (edgeDetection()) { |
||
613 | |||
1106 | werner | 614 | #ifdef ILAND_GUI |
1180 | werner | 615 | if (mDumpSeedMaps) { |
616 | gridToImage(seedMap(), true, -1., 1.).save(QString("%1/seed_edge_%2_%3.png").arg(path).arg(mSpecies->id()).arg(year)); |
||
617 | } |
||
1106 | werner | 618 | #endif |
619 | |||
1180 | werner | 620 | // (2) distribute seed probabilites from edges |
621 | distribute(); |
||
622 | } |
||
1167 | werner | 623 | |
1180 | werner | 624 | // special case serotiny |
625 | if (mHasPendingSerotiny) { |
||
626 | qDebug() << "calculating extra seed rain (serotiny)...."; |
||
1168 | werner | 627 | #ifdef ILAND_GUI |
1180 | werner | 628 | if (mDumpSeedMaps) { |
629 | gridToImage(mSeedMapSerotiny, true, 0., 1.).save(QString("%1/seed_serotiny_before_%2_%3.png").arg(path).arg(mSpecies->id()).arg(year)); |
||
630 | } |
||
1168 | werner | 631 | #endif |
1180 | werner | 632 | if (edgeDetection(&mSeedMapSerotiny)) |
633 | distribute(&mSeedMapSerotiny); |
||
634 | // copy back data |
||
635 | float *sero=mSeedMapSerotiny.begin(); |
||
636 | for (float* p=mSeedMap.begin();p!=mSeedMap.end();++p, ++sero) |
||
637 | *p = std::max(*p, *sero); |
||
1167 | werner | 638 | |
1180 | werner | 639 | float total = mSeedMapSerotiny.sum(); |
1168 | werner | 640 | #ifdef ILAND_GUI |
1180 | werner | 641 | if (mDumpSeedMaps) { |
642 | gridToImage(mSeedMapSerotiny, true, 0., 1.).save(QString("%1/seed_serotiny_after_%2_%3.png").arg(path).arg(mSpecies->id()).arg(year)); |
||
643 | } |
||
644 | #endif |
||
645 | mSeedMapSerotiny.initialize(0.f); // clear |
||
646 | mHasPendingSerotiny = false; |
||
647 | qDebug() << "serotiny event: extra seed input" << total << "(total sum of seed probability over all pixels of the serotiny seed map) of species" << mSpecies->name(); |
||
1168 | werner | 648 | } |
1180 | werner | 649 | |
650 | } else { |
||
651 | // distribute actual values |
||
652 | DebugTimer t("seed dispersal", true); |
||
653 | // fill seed map from source map |
||
654 | distributeSeeds(); |
||
655 | |||
619 | werner | 656 | } |
989 | werner | 657 | #ifdef ILAND_GUI |
1102 | werner | 658 | if (mDumpSeedMaps) { |
1180 | werner | 659 | //qDebug() << "finished seed dispersal for species. time: " << mSpecies->id() << t.elapsed(); |
472 | werner | 660 | gridToImage(seedMap(), true, 0., 1.).save(QString("%1/seed_after_%2_%3.png").arg(path).arg(mSpecies->id()).arg(year)); |
1102 | werner | 661 | } |
1064 | werner | 662 | |
663 | if (!mDumpNextYearFileName.isEmpty()) { |
||
664 | Helper::saveToTextFile(GlobalSettings::instance()->path(mDumpNextYearFileName), gridToESRIRaster(seedMap())); |
||
665 | qDebug() << "saved seed map for " << species()->id() << "to" << GlobalSettings::instance()->path(mDumpNextYearFileName); |
||
666 | mDumpNextYearFileName = QString(); |
||
667 | } |
||
1176 | werner | 668 | qDebug() << "LDD-count:" << _debug_ldd; |
1064 | werner | 669 | |
989 | werner | 670 | #endif |
375 | werner | 671 | } |
672 | |||
373 | werner | 673 | /** scans the seed image and detects "edges". |
674 | edges are then subsequently marked (set to -1). This is pass 1 of the seed distribution process. |
||
675 | */ |
||
1167 | werner | 676 | bool SeedDispersal::edgeDetection(Grid<float> *seed_map) |
373 | werner | 677 | { |
678 | float *p_above, *p, *p_below; |
||
1167 | werner | 679 | Grid<float> &seedmap = seed_map ? *seed_map : mSeedMap; // switch to extra seed map if provided |
680 | int dy = seedmap.sizeY(); |
||
681 | int dx = seedmap.sizeX(); |
||
373 | werner | 682 | int x,y; |
619 | werner | 683 | bool found = false; |
1106 | werner | 684 | |
685 | // fill mini-gaps |
||
1168 | werner | 686 | int n_gaps_filled=0; |
373 | werner | 687 | for (y=1;y<dy-1;++y){ |
1167 | werner | 688 | p = seedmap.ptr(1,y); |
373 | werner | 689 | p_above = p - dx; // one line above |
690 | p_below = p + dx; // one line below |
||
691 | for (x=1;x<dx-1;++x,++p,++p_below, ++p_above) { |
||
1106 | werner | 692 | if (*p < 0.999f) { |
693 | |||
694 | if ((*(p_above-1)==1.f) + (*p_above==1.f) + (*(p_above+1)==1.f) + |
||
695 | (*(p-1)==1.f) + (*(p+1)==1.f) + |
||
1168 | werner | 696 | (*(p_below-1)==1.f) + (*p_below==1.f) + (*(p_below+1)==1.f) > 3) { |
1106 | werner | 697 | *p=0.999f; // if more than 3 neighbors are active pixels, the value is high |
1168 | werner | 698 | ++n_gaps_filled; |
699 | } |
||
1106 | werner | 700 | } |
701 | |||
702 | } |
||
703 | } |
||
704 | |||
705 | |||
706 | // now detect the edges |
||
707 | int n_edges=0 ; |
||
708 | for (y=1;y<dy-1;++y){ |
||
1168 | werner | 709 | p = seedmap.ptr(1,y); |
1106 | werner | 710 | p_above = p - dx; // one line above |
711 | p_below = p + dx; // one line below |
||
712 | for (x=1;x<dx-1;++x,++p,++p_below, ++p_above) { |
||
713 | if (*p == 1.f) { |
||
619 | werner | 714 | found = true; |
1106 | werner | 715 | if ( (*(p_above-1)<0.999f && *(p_above-1)>=0.f) |
716 | || (*p_above<0.999f && *p_above>=0.f) |
||
717 | || (*(p_above+1)<0.999f && *(p_above+1)>=0.f) |
||
718 | || (*(p-1)<0.999f && *(p-1)>=0.f) |
||
719 | || (*(p+1)<0.999f && (*p+1)>=0.f) |
||
720 | || (*(p_below-1)<0.999f && *(p_below-1)>=0.f) |
||
721 | || (*p_below<0.999f && *p_below>=0.f) |
||
722 | || (*(p_below+1)<0.999f && *(p_below+1)>=0.f ) ) { |
||
723 | *p=-1.f; // if any surrounding pixel is >=0 & <0.999: -> mark as edge |
||
724 | ++n_edges; |
||
725 | } |
||
373 | werner | 726 | } |
727 | |||
728 | } |
||
729 | } |
||
1168 | werner | 730 | if (mDumpSeedMaps) |
731 | qDebug() << "species:" << mSpecies->id() << "# of gaps filled: " << n_gaps_filled << "# of edge-pixels:" << n_edges; |
||
619 | werner | 732 | return found; |
373 | werner | 733 | } |
734 | |||
735 | /** do the seed probability distribution. |
||
736 | This is phase 2. Apply the seed kernel for each "edge" point identified in phase 1. |
||
737 | */ |
||
1167 | werner | 738 | void SeedDispersal::distribute(Grid<float> *seed_map) |
373 | werner | 739 | { |
740 | int x,y; |
||
1167 | werner | 741 | Grid<float> &seedmap = seed_map ? *seed_map : mSeedMap; // switch to extra seed map if provided |
742 | float *end = seedmap.end(); |
||
743 | float *p = seedmap.begin(); |
||
415 | werner | 744 | // choose the kernel depending whether there is a seed year for the current species or not |
1168 | werner | 745 | Grid<float> *kernel = species()->isSeedYear()? &mKernelSeedYear : &mKernelNonSeedYear; |
1167 | werner | 746 | // extra case: serotiny |
747 | if (seed_map) |
||
1168 | werner | 748 | kernel = &mKernelSerotiny; |
1167 | werner | 749 | |
1168 | werner | 750 | int offset = kernel->sizeX() / 2; // offset is the index of the center pixel |
373 | werner | 751 | for(;p!=end;++p) { |
375 | werner | 752 | if (*p==-1.f) { |
373 | werner | 753 | // edge pixel found. Now apply the kernel.... |
1167 | werner | 754 | QPoint pt=seedmap.indexOf(p); |
1168 | werner | 755 | for (y=-offset;y<=offset;++y) { |
756 | for (x=-offset;x<=offset;++x) { |
||
757 | float &kernel_value = kernel->valueAtIndex(x+offset, y+offset); |
||
758 | if (kernel_value>0.f && seedmap.isIndexValid(pt.x()+x, pt.y()+y)) { |
||
1167 | werner | 759 | float &val = seedmap.valueAtIndex(pt.x()+x, pt.y()+y); |
1106 | werner | 760 | if (val!=-1.f) |
1168 | werner | 761 | val = qMin(1.f - (1.f - val)*(1.f-kernel_value),1.f ); |
373 | werner | 762 | } |
1168 | werner | 763 | } |
764 | } |
||
1176 | werner | 765 | // long distance dispersal |
766 | if (!mLDDDensity.isEmpty()) { |
||
767 | double m = species()->isSeedYear() ? 1. : mNonSeedYearFraction; |
||
768 | for (int r=0;r<mLDDDensity.size(); ++r) { |
||
1180 | werner | 769 | float ldd_val = mLDDSeedlings; // pixels will have this probability |
1176 | werner | 770 | int n = round( mLDDDensity[r]*m ); // number of pixels to activate |
771 | for (int i=0;i<n;++i) { |
||
772 | // distance and direction: |
||
773 | double radius = nrandom(mLDDDistance[r], mLDDDistance[r+1]) / seedmap.cellsize(); // choose a random distance (in pixels) |
||
774 | double phi = drandom()*2.*M_PI; // choose a random direction |
||
775 | QPoint ldd(pt.x() + radius*cos(phi), pt.y() + radius*sin(phi)); |
||
776 | if (seedmap.isIndexValid(ldd)) { |
||
777 | float &val = seedmap.valueAtIndex(ldd); |
||
778 | _debug_ldd++; |
||
779 | // use the same adding of probabilities |
||
780 | if (val!=-1.f) |
||
781 | val = qMin(1.f - (1.f - val)*(1.f-ldd_val), 1.f); |
||
782 | } |
||
783 | } |
||
784 | } |
||
785 | } |
||
375 | werner | 786 | *p=1.f; // mark as processed |
373 | werner | 787 | } // *p==1 |
788 | } // for() |
||
789 | } |
||
1180 | werner | 790 | |
1182 | werner | 791 | // because C modulo operation gives negative numbers for negative values, here a fix |
792 | // that always returns positive numbers: http://www.lemoda.net/c/modulo-operator/ |
||
793 | #define MOD(a,b) ((((a)%(b))+(b))%(b)) |
||
794 | |||
1180 | werner | 795 | void SeedDispersal::distributeSeeds(Grid<float> *seed_map) |
796 | { |
||
797 | Grid<float> &sourcemap = seed_map ? *seed_map : mSourceMap; // switch to extra seed map if provided |
||
798 | Grid<float> &kernel = mKernelSeedYear; |
||
1182 | werner | 799 | |
800 | // *** estimate seed production (based on leaf area) *** |
||
1180 | werner | 801 | // calculate number of seeds; the source map holds now m2 leaf area on 20x20m pixels |
802 | // after this step, each source cell has a value between 0 (no source) and 1 (fully covered cell) |
||
803 | float fec = species()->fecundity_m2(); |
||
804 | if (!species()->isSeedYear()) |
||
805 | fec *= mNonSeedYearFraction; |
||
806 | for (float *p=sourcemap.begin(); p!=sourcemap.end(); ++p){ |
||
807 | if (*p) { |
||
808 | // if LAI >3, then full potential is assumed, below LAI=3 a linear ramp is used |
||
809 | *p = std::min(*p / (sourcemap.cellsize()*sourcemap.cellsize()) /3.f, 3.f); |
||
810 | } |
||
811 | } |
||
812 | |||
813 | // sink mode |
||
814 | |||
1182 | werner | 815 | // // now look for each pixel in the targetmap and sum up seeds*kernel |
816 | // int idx=0; |
||
817 | // int offset = kernel.sizeX() / 2; // offset is the index of the center pixel |
||
818 | // //const Grid<ResourceUnit*> &ru_map = GlobalSettings::instance()->model()->RUgrid(); |
||
819 | // DebugTimer tsink("seed_sink"); { |
||
820 | // for (float *t=mSeedMap.begin(); t!=mSeedMap.end(); ++t, ++idx) { |
||
821 | // // skip out-of-project areas |
||
822 | // //if (!ru_map.constValueAtIndex(mSeedMap.index5(idx))) |
||
823 | // // continue; |
||
824 | // // apply the kernel |
||
825 | // QPoint sm=mSeedMap.indexOf(t)-QPoint(offset, offset); |
||
826 | // for (int iy=0;iy<kernel.sizeY();++iy) { |
||
827 | // for (int ix=0;ix<kernel.sizeX();++ix) { |
||
828 | // if (sourcemap.isIndexValid(sm.x()+ix, sm.y()+iy)) |
||
829 | // *t+=sourcemap(sm.x()+ix, sm.y()+iy) * kernel(ix, iy); |
||
830 | // } |
||
831 | // } |
||
832 | // } |
||
833 | // } // debugtimer |
||
834 | // mSeedMap.initialize(0.f); // just for debugging... |
||
1180 | werner | 835 | |
836 | int offset = kernel.sizeX() / 2; // offset is the index of the center pixel |
||
837 | // source mode |
||
838 | |||
1182 | werner | 839 | // *** seed distribution (Kernel + long distance dispersal) *** |
840 | if (GlobalSettings::instance()->model()->settings().torusMode==false) { |
||
841 | // ** standard case (no torus) ** |
||
842 | for (float *src=sourcemap.begin(); src!=sourcemap.end(); ++src) { |
||
843 | if (*src>0.f) { |
||
844 | QPoint sm=sourcemap.indexOf(src)-QPoint(offset, offset); |
||
845 | int sx = sm.x(), sy=sm.y(); |
||
846 | for (int iy=0;iy<kernel.sizeY();++iy) { |
||
847 | for (int ix=0;ix<kernel.sizeX();++ix) { |
||
848 | if (mSeedMap.isIndexValid(sx+ix, sy+iy)) |
||
849 | mSeedMap.valueAtIndex(sx+ix, sy+iy)+= *src * kernel(ix, iy); |
||
850 | } |
||
851 | } |
||
852 | // long distance dispersal |
||
853 | if (!mLDDDensity.isEmpty()) { |
||
854 | QPoint pt=sourcemap.indexOf(src); |
||
1180 | werner | 855 | |
1182 | werner | 856 | for (int r=0;r<mLDDDensity.size(); ++r) { |
857 | float ldd_val = mLDDSeedlings / fec; // pixels will have this probability [note: fecundity will be multiplied below] |
||
858 | int n; |
||
859 | if (mLDDDensity[r]<1) |
||
860 | n = drandom()<mLDDDensity[r] ? 1 : 0; |
||
861 | else |
||
862 | n = round( mLDDDensity[r] ); // number of pixels to activate |
||
863 | for (int i=0;i<n;++i) { |
||
864 | // distance and direction: |
||
865 | double radius = nrandom(mLDDDistance[r], mLDDDistance[r+1]) / mSeedMap.cellsize(); // choose a random distance (in pixels) |
||
866 | double phi = drandom()*2.*M_PI; // choose a random direction |
||
867 | QPoint ldd(pt.x() + radius*cos(phi), pt.y() + radius*sin(phi)); |
||
868 | if (mSeedMap.isIndexValid(ldd)) { |
||
869 | float &val = mSeedMap.valueAtIndex(ldd); |
||
870 | _debug_ldd++; |
||
871 | val += ldd_val; |
||
872 | } |
||
873 | } |
||
874 | } |
||
1180 | werner | 875 | } |
1182 | werner | 876 | |
1180 | werner | 877 | } |
1182 | werner | 878 | } |
879 | } else { |
||
880 | // **** seed distribution in torus mode *** |
||
881 | int seedmap_offset = sourcemap.indexAt(QPointF(0., 0.)).x(); // the seed maps have x extra rows/columns |
||
882 | QPoint torus_pos; |
||
883 | int seedpx_per_ru = static_cast<int>((cRUSize/sourcemap.cellsize())); |
||
884 | for (float *src=sourcemap.begin(); src!=sourcemap.end(); ++src) { |
||
885 | if (*src>0.f) { |
||
886 | QPoint sm=sourcemap.indexOf(src); |
||
887 | // get the origin of the resource unit *on* the seedmap in *seedmap-coords*: |
||
888 | QPoint offset_ru( ((sm.x()-seedmap_offset) / seedpx_per_ru) * seedpx_per_ru + seedmap_offset, |
||
889 | ((sm.y()-seedmap_offset) / seedpx_per_ru) * seedpx_per_ru + seedmap_offset); // coords RU origin |
||
1180 | werner | 890 | |
1182 | werner | 891 | QPoint offset_in_ru((sm.x()-seedmap_offset) % seedpx_per_ru, (sm.y()-seedmap_offset) % seedpx_per_ru ); // offset of current point within the RU |
892 | |||
893 | //QPoint sm=sourcemap.indexOf(src)-QPoint(offset, offset); |
||
894 | for (int iy=0;iy<kernel.sizeY();++iy) { |
||
895 | for (int ix=0;ix<kernel.sizeX();++ix) { |
||
896 | torus_pos = offset_ru + QPoint(MOD((offset_in_ru.x() - offset + ix), seedpx_per_ru), MOD((offset_in_ru.y() - offset + iy), seedpx_per_ru)); |
||
897 | |||
898 | if (mSeedMap.isIndexValid(torus_pos)) |
||
899 | mSeedMap.valueAtIndex(torus_pos)+= *src * kernel(ix, iy); |
||
900 | } |
||
901 | } |
||
902 | // long distance dispersal |
||
903 | if (!mLDDDensity.isEmpty()) { |
||
904 | |||
905 | for (int r=0;r<mLDDDensity.size(); ++r) { |
||
906 | float ldd_val = mLDDSeedlings / fec; // pixels will have this probability [note: fecundity will be multiplied below] |
||
907 | int n; |
||
908 | if (mLDDDensity[r]<1) |
||
909 | n = drandom()<mLDDDensity[r] ? 1 : 0; |
||
910 | else |
||
911 | n = round( mLDDDensity[r] ); // number of pixels to activate |
||
912 | for (int i=0;i<n;++i) { |
||
913 | // distance and direction: |
||
914 | double radius = nrandom(mLDDDistance[r], mLDDDistance[r+1]) / mSeedMap.cellsize(); // choose a random distance (in pixels) |
||
915 | double phi = drandom()*2.*M_PI; // choose a random direction |
||
916 | QPoint ldd( radius*cos(phi), + radius*sin(phi)); // destination (offset) |
||
917 | torus_pos = offset_ru + QPoint(MOD((offset_in_ru.x()+ldd.x()),seedpx_per_ru), MOD((offset_in_ru.y()+ldd.y()),seedpx_per_ru) ); |
||
918 | |||
919 | if (mSeedMap.isIndexValid(torus_pos)) { |
||
920 | float &val = mSeedMap.valueAtIndex(torus_pos); |
||
921 | _debug_ldd++; |
||
922 | val += ldd_val; |
||
923 | } |
||
1180 | werner | 924 | } |
925 | } |
||
926 | } |
||
1182 | werner | 927 | |
1180 | werner | 928 | } |
929 | } |
||
1182 | werner | 930 | } // torus |
1180 | werner | 931 | |
932 | |||
933 | |||
934 | // now the seed sources (0..1) are spatially distributed by the kernel (and LDD) without altering the magnitude; |
||
935 | // now we include the fecundity (=seedling potential per m2 crown area), and convert to the establishment probability p_seed. |
||
936 | // The number of (potential) seedlings per m2 on each cell is: cell * fecundity[m2] |
||
937 | // We assume that the availability of 10 potential seedlings/m2 is enough for unconstrained establishment; |
||
1181 | werner | 938 | const float n_unlimited = 100.f; |
1180 | werner | 939 | for (float *p=mSeedMap.begin(); p!=mSeedMap.end(); ++p){ |
940 | if (*p>0.f) { |
||
941 | *p = std::min(*p*fec / n_unlimited, 1.f); |
||
942 | } |
||
943 | } |
||
944 | } |