Rev 445 | Rev 472 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1 | |||
373 | werner | 2 | #include "seeddispersal.h" |
3 | |||
4 | #include "globalsettings.h" |
||
5 | #include "model.h" |
||
6 | #include "helper.h" |
||
391 | werner | 7 | #include "species.h" |
373 | werner | 8 | |
9 | #include <QtGui/QImage> |
||
10 | |||
11 | /** @class SeedDispersal |
||
12 | The class encapsulates the dispersal of seeds of one species over the whole landscape. |
||
13 | |||
14 | */ |
||
15 | SeedDispersal::~SeedDispersal() |
||
16 | { |
||
17 | if (isSetup()) { |
||
18 | |||
19 | } |
||
20 | } |
||
21 | |||
391 | werner | 22 | // ************ Setup ************** |
23 | |||
24 | /** setup of the seedmaps. |
||
25 | This sets the size of the seed map and creates the seed kernel (species specific) |
||
26 | */ |
||
373 | werner | 27 | void SeedDispersal::setup() |
28 | { |
||
391 | werner | 29 | if (!GlobalSettings::instance()->model() |
30 | || !GlobalSettings::instance()->model()->heightGrid() |
||
31 | || !mSpecies) |
||
373 | werner | 32 | return; |
391 | werner | 33 | |
34 | const float seedmap_size = 20.f; |
||
373 | werner | 35 | // setup of seed map |
36 | mSeedMap.clear(); |
||
391 | werner | 37 | mSeedMap.setup(GlobalSettings::instance()->model()->heightGrid()->metricRect(), seedmap_size ); |
373 | werner | 38 | mSeedMap.initialize(0.); |
391 | werner | 39 | mIndexFactor = int(seedmap_size) / cPxSize; // ratio seed grid / lip-grid: |
459 | werner | 40 | qDebug() << "Seed map setup. Species:"<< mSpecies->id() << "kernel-size: " << mSeedMap.sizeX() << "x" << mSeedMap.sizeY() << "pixels."; |
373 | werner | 41 | |
445 | werner | 42 | if (mSpecies==0) |
43 | throw IException("Setup of SeedDispersal: Species not defined."); |
||
44 | |||
415 | werner | 45 | // settings |
445 | werner | 46 | mTM_occupancy = 1.; // is currently constant |
47 | mSpecies->treeMigKernel(mTM_as1, mTM_as2, mTM_ks); // set values |
||
48 | mTM_fecundity_cell = mSpecies->fecundity_m2() * seedmap_size*seedmap_size * mTM_occupancy; // scale to production for the whole cell |
||
49 | mNonSeedYearFraction = mSpecies->nonSeedYearFraction(); |
||
415 | werner | 50 | |
445 | werner | 51 | createKernel(mKernelSeedYear, mTM_fecundity_cell); |
415 | werner | 52 | |
53 | // the kernel for non seed years looks similar, but is simply linearly scaled down |
||
54 | // using the species parameter NonSeedYearFraction. |
||
55 | // the central pixel still gets the value of 1 (i.e. 100% probability) |
||
445 | werner | 56 | createKernel(mKernelNonSeedYear, mTM_fecundity_cell*mNonSeedYearFraction); |
415 | werner | 57 | |
417 | werner | 58 | if (QFile::exists("c:\\temp\\seedmaps")) { |
445 | werner | 59 | Helper::saveToTextFile(QString("c:\\temp\\seedmaps\\seedkernelYes_%1.csv").arg(mSpecies->id()),gridToString(mKernelSeedYear)); |
60 | Helper::saveToTextFile(QString("c:\\temp\\seedmaps\\seedkernelNo_%1.csv").arg(mSpecies->id()),gridToString(mKernelNonSeedYear)); |
||
417 | werner | 61 | } |
415 | werner | 62 | |
63 | |||
373 | werner | 64 | // setup of seed kernel |
391 | werner | 65 | // const int max_radius = 15; // pixels |
66 | // |
||
67 | // mSeedKernel.clear(); |
||
68 | // mSeedKernel.setup(mSeedMap.cellsize(), 2*max_radius + 1 , 2*max_radius + 1); |
||
69 | // mKernelOffset = max_radius; |
||
70 | // // filling of the kernel.... for simplicity: a linear kernel |
||
71 | // QPoint center = QPoint(mKernelOffset, mKernelOffset); |
||
72 | // const double max_dist = max_radius * seedmap_size; |
||
73 | // for (float *p=mSeedKernel.begin(); p!=mSeedKernel.end();++p) { |
||
74 | // double d = mSeedKernel.distance(center, mSeedKernel.indexOf(p)); |
||
75 | // *p = qMax( 1. - d / max_dist, 0.); |
||
76 | // } |
||
373 | werner | 77 | |
78 | |||
79 | // randomize seed map.... set 1/3 to "filled" |
||
375 | werner | 80 | //for (int i=0;i<mSeedMap.count(); i++) |
81 | // mSeedMap.valueAtIndex(mSeedMap.randomPosition()) = 1.; |
||
373 | werner | 82 | |
83 | |||
375 | werner | 84 | // QImage img = gridToImage(mSeedMap, true, -1., 1.); |
85 | // img.save("seedmap.png"); |
||
86 | |||
87 | // img = gridToImage(mSeedMap, true, -1., 1.); |
||
88 | // img.save("seedmap_e.png"); |
||
373 | werner | 89 | } |
90 | |||
391 | werner | 91 | // ************ Kernel ************** |
415 | werner | 92 | void SeedDispersal::createKernel(Grid<float> &kernel, const float max_seed) |
391 | werner | 93 | { |
415 | werner | 94 | |
391 | werner | 95 | double max_dist = treemig_distanceTo(0.0001); |
96 | double cell_size = mSeedMap.cellsize(); |
||
415 | werner | 97 | int max_radius = int(max_dist / cell_size); |
391 | werner | 98 | // e.g.: cell_size: regeneration grid (e.g. 400qm), px-size: light-grid (4qm) |
445 | werner | 99 | double occupation = cell_size*cell_size / (cPxSize*cPxSize * mTM_occupancy); |
391 | werner | 100 | |
415 | werner | 101 | kernel.clear(); |
391 | werner | 102 | |
415 | werner | 103 | kernel.setup(mSeedMap.cellsize(), 2*max_radius + 1 , 2*max_radius + 1); |
104 | int kernel_offset = max_radius; |
||
105 | |||
391 | werner | 106 | // filling of the kernel.... use the treemig |
415 | werner | 107 | QPoint center = QPoint(kernel_offset, kernel_offset); |
108 | const float *sk_end = kernel.end(); |
||
109 | for (float *p=kernel.begin(); p!=sk_end;++p) { |
||
110 | double d = kernel.distance(center, kernel.indexOf(p)); |
||
391 | werner | 111 | *p = d<=max_dist?treemig(d):0.f; |
112 | } |
||
113 | |||
114 | // normalize |
||
415 | werner | 115 | double sum = kernel.sum(); |
391 | werner | 116 | if (sum==0. || occupation==0.) |
117 | throw IException("create seed kernel: sum of probabilities = 0!"); |
||
118 | |||
119 | // the sum of all kernel cells has to equal 1 |
||
415 | werner | 120 | kernel.multiply(1./sum); |
391 | werner | 121 | // probabilities are derived in multiplying by seed number, and dividing by occupancy criterion |
415 | werner | 122 | kernel.multiply( max_seed / occupation); |
391 | werner | 123 | // all cells that get more seeds than the occupancy criterion are considered to have no seed limitation for regeneration |
415 | werner | 124 | for (float *p=kernel.begin(); p!=sk_end;++p) { |
391 | werner | 125 | *p = qMin(*p, 1.f); |
126 | } |
||
127 | // set the parent cell to 1 |
||
415 | werner | 128 | kernel.valueAtIndex(kernel_offset, kernel_offset)=1.f; |
129 | |||
130 | |||
391 | werner | 131 | // some final statistics.... |
415 | werner | 132 | qDebug() << "kernel setup.Species:"<< mSpecies->id() << "kernel-size: " << kernel.sizeX() << "x" << kernel.sizeY() << "pixels, sum: " << kernel.sum(); |
391 | werner | 133 | } |
134 | |||
135 | /* R-Code: |
||
136 | treemig=function(as1,as2,ks,d) # two-part exponential function, cf. Lischke & Löffler (2006), Annex |
||
137 | { |
||
138 | p1=(1-ks)*exp(-d/as1)/as1 |
||
139 | if(as2>0){p2=ks*exp(-d/as2)/as2}else{p2=0} |
||
140 | p1+p2 |
||
141 | } |
||
142 | */ |
||
143 | double SeedDispersal::treemig(const double &distance) |
||
144 | { |
||
145 | double p1 = (1.-mTM_ks)*exp(-distance/mTM_as1)/mTM_as1; |
||
146 | double p2 = 0.; |
||
147 | if (mTM_as2>0.) |
||
148 | p2 = mTM_ks*exp(-distance/mTM_as2)/mTM_as2; |
||
149 | return p1 + p2; |
||
150 | } |
||
151 | |||
152 | /// calculate the distance where the probability falls below 'value' |
||
153 | double SeedDispersal::treemig_distanceTo(const double value) |
||
154 | { |
||
155 | double dist = 0.; |
||
156 | while (treemig(dist)>value && dist<10000.) |
||
157 | dist+=10; |
||
158 | return dist; |
||
159 | } |
||
160 | |||
161 | |||
162 | // ************ Dispersal ************** |
||
163 | |||
164 | |||
375 | werner | 165 | /// debug function: loads a image of arbirtrary size... |
166 | void SeedDispersal::loadFromImage(const QString &fileName) |
||
167 | { |
||
168 | mSeedMap.clear(); |
||
169 | loadGridFromImage(fileName, mSeedMap); |
||
170 | for (float* p=mSeedMap.begin();p!=mSeedMap.end();++p) |
||
171 | *p = *p>0.8?1.f:0.f; |
||
172 | |||
173 | } |
||
174 | |||
175 | void SeedDispersal::clear() |
||
176 | { |
||
177 | mSeedMap.initialize(0.f); |
||
178 | } |
||
179 | |||
180 | void SeedDispersal::execute() |
||
181 | { |
||
445 | werner | 182 | int year = GlobalSettings::instance()->currentYear(); |
417 | werner | 183 | if (QFile::exists("c:\\temp\\seedmaps")) |
445 | werner | 184 | gridToImage(seedMap(), true, 0., 1.).save(QString("c:\\temp\\seedmaps\\seed_before_%1_%2.png").arg(mSpecies->id()).arg(year)); |
391 | werner | 185 | { |
375 | werner | 186 | DebugTimer t("seed dispersal"); |
187 | // (1) detect edges |
||
188 | edgeDetection(); |
||
189 | // (2) distribute seed probabilites from edges |
||
190 | distribute(); |
||
391 | werner | 191 | } |
417 | werner | 192 | if (QFile::exists("c:\\temp\\seedmaps")) |
445 | werner | 193 | gridToImage(seedMap(), true, 0., 1.).save(QString("c:\\temp\\seedmaps\\seed_after_%1_%2.png").arg(mSpecies->id()).arg(year)); |
375 | werner | 194 | } |
195 | |||
373 | werner | 196 | /** scans the seed image and detects "edges". |
197 | edges are then subsequently marked (set to -1). This is pass 1 of the seed distribution process. |
||
198 | */ |
||
199 | void SeedDispersal::edgeDetection() |
||
200 | { |
||
201 | float *p_above, *p, *p_below; |
||
202 | int dy = mSeedMap.sizeY(); |
||
203 | int dx = mSeedMap.sizeX(); |
||
204 | int x,y; |
||
205 | for (y=1;y<dy-1;++y){ |
||
206 | p = mSeedMap.ptr(1,y); |
||
207 | p_above = p - dx; // one line above |
||
208 | p_below = p + dx; // one line below |
||
209 | for (x=1;x<dx-1;++x,++p,++p_below, ++p_above) { |
||
210 | if (*p == 1.) { |
||
211 | if (*(p_above-1)==0.f || *p_above==0.f || *(p_above+1)==0.f || |
||
212 | *(p-1)==0.f || *(p+1)==0.f || |
||
213 | *(p_below-1)==0.f || *p_below==0.f || *(p_below+1)==0.f ) |
||
214 | *p=-1; // if any surrounding pixel is 0: -> mark as edge |
||
215 | } |
||
216 | |||
217 | } |
||
218 | } |
||
219 | } |
||
220 | |||
221 | /** do the seed probability distribution. |
||
222 | This is phase 2. Apply the seed kernel for each "edge" point identified in phase 1. |
||
223 | */ |
||
224 | void SeedDispersal::distribute() |
||
225 | { |
||
226 | int x,y; |
||
227 | |||
228 | float *end = mSeedMap.end(); |
||
229 | float *p = mSeedMap.begin(); |
||
415 | werner | 230 | // choose the kernel depending whether there is a seed year for the current species or not |
231 | Grid<float> &kernel = species()->isSeedYear()?mKernelSeedYear:mKernelNonSeedYear; |
||
232 | int offset = kernel.sizeX() / 2; // offset is the index of the center pixel |
||
373 | werner | 233 | for(;p!=end;++p) { |
375 | werner | 234 | if (*p==-1.f) { |
373 | werner | 235 | // edge pixel found. Now apply the kernel.... |
236 | QPoint pt=mSeedMap.indexOf(p); |
||
415 | werner | 237 | for (y=-offset;y<=offset;++y) |
238 | for (x=-offset;x<=offset;++x) |
||
373 | werner | 239 | if (mSeedMap.isIndexValid(pt.x()+x, pt.y()+y)) { |
240 | float &val = mSeedMap.valueAtIndex(pt.x()+x, pt.y()+y); |
||
375 | werner | 241 | if (val!=-1) |
415 | werner | 242 | val = qMin(1.f - (1.f - val)*(1.f-kernel.valueAtIndex(x+offset, y+offset)),1.f ); |
373 | werner | 243 | } |
375 | werner | 244 | *p=1.f; // mark as processed |
373 | werner | 245 | } // *p==1 |
246 | } // for() |
||
247 | } |