Subversion Repositories public iLand

Rev

Rev 1221 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
766 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
#include "spatialanalysis.h"
20
 
21
#include "globalsettings.h"
22
#include "model.h"
1017 werner 23
#include "tree.h"
24
#include "stamp.h"
767 werner 25
#include "helper.h"
1017 werner 26
#include "resourceunit.h"
1083 werner 27
#include "scriptgrid.h"
766 werner 28
 
793 werner 29
#include <QJSEngine>
30
#include <QJSValue>
767 werner 31
void SpatialAnalysis::addToScriptEngine()
32
{
33
    SpatialAnalysis *spati = new SpatialAnalysis;
793 werner 34
    QJSValue v = GlobalSettings::instance()->scriptEngine()->newQObject(spati);
767 werner 35
    GlobalSettings::instance()->scriptEngine()->globalObject().setProperty("SpatialAnalysis", v);
36
}
766 werner 37
 
767 werner 38
SpatialAnalysis::~SpatialAnalysis()
766 werner 39
{
767 werner 40
    if (mRumple)
41
        delete mRumple;
766 werner 42
}
43
 
767 werner 44
double SpatialAnalysis::rumpleIndexFullArea()
45
{
46
    if (!mRumple)
47
        mRumple = new RumpleIndex;
48
    double rum = mRumple->value();
49
    return rum;
50
}
766 werner 51
 
1080 werner 52
/// extract patches (clumps) from the grid 'src'.
53
/// Patches are defined as adjacent pixels (8-neighborhood)
54
/// Return: vector with number of pixels per patch (first element: patch 1, second element: patch 2, ...)
1085 werner 55
QList<int> SpatialAnalysis::extractPatches(Grid<double> &src, int min_size, QString fileName)
1080 werner 56
{
1088 werner 57
    mClumpGrid.setup(src.metricRect(), src.cellsize());
1080 werner 58
    mClumpGrid.wipe();
59
 
60
    // now loop over all pixels and run a floodfill algorithm
61
    QPoint start;
62
    QQueue<QPoint> pqueue; // for the flood fill algorithm
1085 werner 63
    QList<int> counts;
1080 werner 64
    int patch_index = 0;
65
    int total_size = 0;
1083 werner 66
    int patches_skipped = 0;
1080 werner 67
    for (int i=0;i<src.count();++i) {
68
        if (src[i]>0. && mClumpGrid[i]==0) {
69
            start = src.indexOf(i);
70
            pqueue.clear();
71
            patch_index++;
72
 
73
            // quick and dirty implementation of the flood fill algroithm.
74
            // based on: http://en.wikipedia.org/wiki/Flood_fill
75
            // returns the number of pixels colored
76
 
77
            pqueue.enqueue(start);
78
            int found = 0;
79
            while (!pqueue.isEmpty()) {
80
                QPoint p = pqueue.dequeue();
81
                if (!src.isIndexValid(p))
82
                    continue;
1081 werner 83
                if (src.valueAtIndex(p)>0. && mClumpGrid.valueAtIndex(p) == 0) {
1080 werner 84
                    mClumpGrid.valueAtIndex(p) = patch_index;
85
                    pqueue.enqueue(p+QPoint(-1,0));
86
                    pqueue.enqueue(p+QPoint(1,0));
87
                    pqueue.enqueue(p+QPoint(0,-1));
88
                    pqueue.enqueue(p+QPoint(0,1));
89
                    pqueue.enqueue(p+QPoint(1,1));
90
                    pqueue.enqueue(p+QPoint(-1,1));
91
                    pqueue.enqueue(p+QPoint(-1,-1));
92
                    pqueue.enqueue(p+QPoint(1,-1));
93
                    ++found;
94
                }
95
            }
1083 werner 96
            if (found<min_size) {
97
                // delete the patch again
98
                pqueue.enqueue(start);
99
                while (!pqueue.isEmpty()) {
100
                    QPoint p = pqueue.dequeue();
101
                    if (!src.isIndexValid(p))
102
                        continue;
103
                    if (mClumpGrid.valueAtIndex(p) == patch_index) {
104
                        mClumpGrid.valueAtIndex(p) = -1;
105
                        pqueue.enqueue(p+QPoint(-1,0));
106
                        pqueue.enqueue(p+QPoint(1,0));
107
                        pqueue.enqueue(p+QPoint(0,-1));
108
                        pqueue.enqueue(p+QPoint(0,1));
109
                        pqueue.enqueue(p+QPoint(1,1));
110
                        pqueue.enqueue(p+QPoint(-1,1));
111
                        pqueue.enqueue(p+QPoint(-1,-1));
112
                        pqueue.enqueue(p+QPoint(1,-1));
113
                    }
114
                }
115
                --patch_index;
116
                patches_skipped++;
117
 
118
            } else {
119
                // save the patch in the result
120
                counts.push_back(found);
121
                total_size+=found;
122
            }
1080 werner 123
        }
124
    }
1085 werner 125
    // remove the -1 again...
126
    mClumpGrid.limit(0,999999);
127
 
1083 werner 128
    qDebug() << "extractPatches: found" << patch_index << "patches, total valid pixels:" << total_size << "skipped" << patches_skipped;
1080 werner 129
    if (!fileName.isEmpty()) {
130
        qDebug() << "extractPatches: save to file:" << GlobalSettings::instance()->path(fileName);
131
        Helper::saveToTextFile(GlobalSettings::instance()->path(fileName), gridToESRIRaster(mClumpGrid) );
132
    }
133
    return counts;
134
 
135
}
136
 
767 werner 137
void SpatialAnalysis::saveRumpleGrid(QString fileName)
138
{
139
    if (!mRumple)
140
        mRumple = new RumpleIndex;
141
 
1017 werner 142
    Helper::saveToTextFile(GlobalSettings::instance()->path(fileName), gridToESRIRaster(mRumple->rumpleGrid()) );
767 werner 143
 
144
}
145
 
1017 werner 146
void SpatialAnalysis::saveCrownCoverGrid(QString fileName)
147
{
148
    calculateCrownCover();
149
    Helper::saveToTextFile(GlobalSettings::instance()->path(fileName), gridToESRIRaster(mCrownCoverGrid) );
767 werner 150
 
1017 werner 151
}
152
 
1083 werner 153
QJSValue SpatialAnalysis::patches(QJSValue grid, int min_size)
154
{
155
    ScriptGrid *sg = qobject_cast<ScriptGrid*>(grid.toQObject());
156
    if (sg) {
157
        // extract patches (keep patches with a size >= min_size
1085 werner 158
        mLastPatches = extractPatches(*sg->grid(), min_size, QString());
1083 werner 159
        // create a (double) copy of the internal clump grid, and return this grid
160
        // as a JS value
161
        QJSValue v = ScriptGrid::createGrid(mClumpGrid.toDouble(),"patch");
162
        return v;
163
    }
164
    return QJSValue();
165
}
166
 
1017 werner 167
void SpatialAnalysis::calculateCrownCover()
168
{
169
    mCrownCoverGrid.setup(GlobalSettings::instance()->model()->RUgrid().metricRect(),
170
                          GlobalSettings::instance()->model()->RUgrid().cellsize());
171
 
172
    // calculate the crown cover per resource unit. We use the "reader"-stamps of the individual trees
173
    // as they represent the crown (size). We also simply hijack the LIF grid for our calculations.
174
    FloatGrid *grid = GlobalSettings::instance()->model()->grid();
175
    grid->initialize(0.f);
176
    // we simply iterate over all trees of all resource units (not bothering about multithreading here)
177
    int x,y;
178
    AllTreeIterator ati(GlobalSettings::instance()->model());
179
    while (Tree *t = ati.nextLiving()) {
180
        // apply the reader-stamp
181
        const Stamp *reader = t->stamp()->reader();
182
        QPoint pos_reader = t->positionIndex(); // tree position
183
        pos_reader-=QPoint(reader->offset(), reader->offset());
184
        int reader_size = reader->size();
185
        int rx = pos_reader.x();
186
        int ry = pos_reader.y();
1019 werner 187
        // the reader stamps are stored such as to have a sum of 1.0 over all pixels
188
        // (i.e.: they express the percentage for each cell contributing to the full crown).
189
        // we thus calculate a the factor to "blow up" cell values; a fully covered cell has then a value of 1,
190
        // and values between 0-1 are cells that are partially covered by the crown.
1020 werner 191
        double crown_factor = reader->crownArea()/double(cPxSize*cPxSize);
1017 werner 192
 
193
        // add the reader-stamp values: multiple (partial) crowns can add up to being fully covered
194
        for (y=0;y<reader_size; ++y) {
195
            for (x=0;x<reader_size;++x) {
1019 werner 196
                 grid->valueAtIndex(rx+x, ry+y) += (*reader)(x,y)*crown_factor;
1017 werner 197
            }
198
        }
199
    }
200
    // now aggregate values for each resource unit
201
    Model *model = GlobalSettings::instance()->model();
202
    for (float *rg = mCrownCoverGrid.begin(); rg!=mCrownCoverGrid.end();++rg) {
203
        ResourceUnit *ru =  model->RUgrid().constValueAtIndex(mCrownCoverGrid.indexOf(rg));
1022 werner 204
        if (!ru) {
205
            *rg=0.f;
1017 werner 206
            continue;
1022 werner 207
        }
1017 werner 208
        float cc_sum = 0.f;
209
        GridRunner<float> runner(grid, mCrownCoverGrid.cellRect(mCrownCoverGrid.indexOf(rg)));
210
        while (float *gv = runner.next()) {
211
            if (model->heightGridValue(runner.currentIndex().x(), runner.currentIndex().y()).isValid())
1021 werner 212
                if (*gv >= 0.5f) // 0.5: half of a 2m cell is covered by a tree crown; is a bit pragmatic but seems reasonable (and works)
1017 werner 213
                    cc_sum++;
214
        }
215
        if (ru->stockableArea()>0.) {
1018 werner 216
            double value = cPxSize*cPxSize*cc_sum/ru->stockableArea();
1017 werner 217
            *rg = limit(value, 0., 1.);
218
        }
219
    }
220
}
221
 
222
 
766 werner 223
/****************************************************************************************/
224
/********************************* RumpleIndex class ************************************/
225
/****************************************************************************************/
226
 
227
 
228
void RumpleIndex::setup()
229
{
230
    mRumpleGrid.clear();
231
    if (!GlobalSettings::instance()->model()) return;
232
 
233
    // the rumple grid hast the same dimensions as the resource unit grid (i.e. 100 meters)
234
    mRumpleGrid.setup(GlobalSettings::instance()->model()->RUgrid().metricRect(),
235
                    GlobalSettings::instance()->model()->RUgrid().cellsize());
236
 
237
}
238
 
239
void RumpleIndex::calculate()
240
{
241
    if (mRumpleGrid.isEmpty())
242
        setup();
243
 
244
    mRumpleGrid.initialize(0.f);
245
    HeightGrid *hg = GlobalSettings::instance()->model()->heightGrid();
246
 
247
    // iterate over the resource units and calculate the rumple index / surface area for each resource unit
248
    HeightGridValue* hgv_8[8]; // array holding pointers to height grid values (neighborhood)
249
    float heights[9];  // array holding heights (8er neighborhood + center pixel)
250
    int total_valid_pixels = 0;
251
    float total_surface_area = 0.f;
252
    for (float *rg = mRumpleGrid.begin(); rg!=mRumpleGrid.end();++rg) {
253
        int valid_pixels = 0;
254
        float surface_area_sum = 0.f;
255
        GridRunner<HeightGridValue> runner(hg, mRumpleGrid.cellRect(mRumpleGrid.indexOf(rg)));
256
        while (runner.next()) {
257
            if (runner.current()->isValid()) {
258
                runner.neighbors8(hgv_8);
259
                bool valid = true;
260
                float *hp = heights;
261
                *hp++ = runner.current()->height;
262
                // retrieve height values from the grid
263
                for (int i=0;i<8;++i) {
264
                    *hp++ = hgv_8[i] ? hgv_8[i]->height: 0;
265
                    if (hgv_8[i] && !hgv_8[i]->isValid()) valid = false;
266
                    if (!hgv_8[i]) valid = false;
267
                }
268
                // calculate surface area only for cells which are (a) within the project area, and (b) all neighboring pixels are inside the forest area
269
                if (valid) {
270
                   valid_pixels++;
271
                   float surface_area = calculateSurfaceArea(heights, hg->cellsize());
272
                   surface_area_sum += surface_area;
273
                }
274
            }
275
        }
276
        if (valid_pixels>0) {
277
            float rumple_index = surface_area_sum / ( (float) valid_pixels * hg->cellsize()*hg->cellsize());
278
            *rg = rumple_index;
279
            total_valid_pixels += valid_pixels;
280
            total_surface_area += surface_area_sum;
281
        }
282
    }
283
    mRumpleIndex = 0.;
284
    if (total_valid_pixels>0) {
285
        float rumple_index = total_surface_area / ( (float) total_valid_pixels * hg->cellsize()*hg->cellsize());
286
        mRumpleIndex = rumple_index;
287
    }
288
    mLastYear = GlobalSettings::instance()->currentYear();
289
}
290
 
291
double RumpleIndex::value(const bool force_recalculate)
292
{
293
    if (force_recalculate ||  mLastYear != GlobalSettings::instance()->currentYear())
294
        calculate();
295
    return mRumpleIndex;
296
}
297
 
298
double RumpleIndex::test_triangle_area()
299
{
300
    // check calculation: numbers for Jenness paper
301
    float hs[9]={165, 170, 145, 160, 183, 155,122,175,190};
302
    double area = calculateSurfaceArea(hs, 100);
303
    return area;
304
}
305
 
306
inline double surface_length(float h1, float h2, float l)
307
{
308
    return sqrt((h1-h2)*(h1-h2) + l*l);
309
}
310
inline double heron_triangle_area(float a, float b, float c)
311
{
312
    float s=(a+b+c)/2.f;
313
    return sqrt(s * (s-a) * (s-b) * (s-c) );
314
}
315
 
316
/// calculate the surface area of a pixel given its height value, the height of the 8 neigboring pixels, and the cellsize
317
/// the algorithm is based on http://www.jennessent.com/downloads/WSB_32_3_Jenness.pdf
318
double RumpleIndex::calculateSurfaceArea(const float *heights, const float cellsize)
319
{
320
    // values in the height array [0..8]: own height / north/east/west/south/ NE/NW/SE/SW
321
    // step 1: calculate length on 3d surface between all edges
322
    //   8(A) * 1(B) * 5(C)       <- 0: center cell, indices in the "heights" grid, A..I: codes used by Jenness
323
    //   4(D) * 0(E) * 2(F)
324
    //   7(G) * 3(H) * 6(I)
325
 
326
    float slen[16]; // surface lengths (divided by 2)
327
    // horizontal
328
    slen[0] = surface_length(heights[8], heights[1], cellsize)/2.f;
329
    slen[1] = surface_length(heights[1], heights[5], cellsize)/2.f;
330
    slen[2] = surface_length(heights[4], heights[0], cellsize)/2.f;
331
    slen[3] = surface_length(heights[0], heights[2], cellsize)/2.f;
332
    slen[4] = surface_length(heights[7], heights[3], cellsize)/2.f;
333
    slen[5] = surface_length(heights[3], heights[6], cellsize)/2.f;
334
    // vertical
335
    slen[6] = surface_length(heights[8], heights[4], cellsize)/2.f;
336
    slen[7] = surface_length(heights[1], heights[0], cellsize)/2.f;
337
    slen[8] = surface_length(heights[5], heights[2], cellsize)/2.f;
338
    slen[9] = surface_length(heights[4], heights[7], cellsize)/2.f;
339
    slen[10] = surface_length(heights[0], heights[3], cellsize)/2.f;
340
    slen[11] = surface_length(heights[2], heights[6], cellsize)/2.f;
341
    // diagonal
342
    float cellsize_diag = cellsize * M_SQRT2;
343
    slen[12] = surface_length(heights[0], heights[8], cellsize_diag)/2.f;
344
    slen[13] = surface_length(heights[0], heights[5], cellsize_diag)/2.f;
345
    slen[14] = surface_length(heights[0], heights[7], cellsize_diag)/2.f;
346
    slen[15] = surface_length(heights[0], heights[6], cellsize_diag)/2.f;
347
 
348
    // step 2: combine the three sides of all the 8 sub triangles using Heron's formula
349
    double surface_area = 0.;
350
    surface_area += heron_triangle_area(slen[12], slen[0], slen[7]); // i
351
    surface_area += heron_triangle_area(slen[7], slen[1], slen[13]); // ii
352
    surface_area += heron_triangle_area(slen[6], slen[2], slen[12]); // iii
353
    surface_area += heron_triangle_area(slen[13], slen[8], slen[3]); // iv
354
    surface_area += heron_triangle_area(slen[2], slen[9], slen[14]); // v
355
    surface_area += heron_triangle_area(slen[3], slen[11], slen[15]); // vi
356
    surface_area += heron_triangle_area(slen[14], slen[10], slen[4]); // vii
357
    surface_area += heron_triangle_area(slen[10], slen[15], slen[5]); // viii
358
 
359
    return surface_area;
360
}
767 werner 361
 
362
/* *************************************************************************************** */
363
/* ******************************* Spatial Layers **************************************** */
364
/* *************************************************************************************** */
365
void SpatialLayeredGrid::setup()
366
{
367
    addGrid("rumple", 0);
368
}
369
 
370
void SpatialLayeredGrid::createGrid(const int grid_index)
371
{
1034 werner 372
    Q_UNUSED(grid_index); // TODO: what should happen here?
767 werner 373
}
374
 
375
 
376
int SpatialLayeredGrid::addGrid(const QString name, FloatGrid *grid)
377
{
378
    mGridNames.append(name);
379
    mGrids.append(grid);
777 werner 380
    return mGrids.size();
767 werner 381
}
382
 
383
 
384