Rev 716 |
Rev 718 |
Go to most recent revision |
Blame |
Compare with Previous |
Last modification |
View Log
| RSS feed
/********************************************************************************************
** iLand - an individual based forest landscape and disturbance model
** http://iland.boku.ac.at
** Copyright (C) 2009- Werner Rammer, Rupert Seidl
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
********************************************************************************************/
#include "windmodule.h"
#include "globalsettings.h"
#include "model.h"
#include "modelcontroller.h"
#include "helper.h"
#include "tree.h"
#include "species.h"
#include "speciesset.h"
#include "resourceunit.h"
/** @defgroup windmodule iLand windmodule
The wind module is a disturbance module within the iLand framework.
FIXME !!
See http://iland.boku.ac.at/wind for the science behind the module,
and http://iland.boku.ac.at/fire+module for the implementation/ using side.
*/
/** @class WindModule
FIXME some more details
*/
double WindLayers::value(const WindCell &data, const int param_index) const
{
switch(param_index){
case 0: return data.height==9999.f?-1:data.height; // height
case 1: return data.edge; // maximum difference to neighboring cells
case 2: return data.cws_uproot; // critical wind speed for uprooting
case 3: return data.cws_break; // critical wind speed for stem breakage
case 4: return (double) data.n_killed; // trees killed on pixel
case 5: return data.basal_area_killed; // basal area killed on pixel
case 6: return data.n_iteration; // iteration in processing that the current pixel is processed (and trees are killed)
case 7: return data.crown_windspeed; // effective wind speed in the crown (m/s)
case 8: return topoAt(&data); // topo modifier of the current pixel
default: throw IException(QString("invalid variable index for a WindCell: %1").arg(param_index));
}
}
const QStringList WindLayers::names() const
{
return QStringList() << "height" << "edge" << "cwsUproot" << "cwsBreak" << "nKilled" << "basalAreaKilled" << "iteration" << "windSpeedCrown" << "topo";
}
// helper function (avoid a special ru-level grid and use the 10m cell resolution instead)
double WindLayers::topoAt(const WindCell *cell) const
{
QPoint pos = mGrid->indexOf(cell);
return mRUGrid->constValueAt(mGrid->cellCenterPoint(pos)).topoModifier;
}
WindModule::WindModule()
{
mWindDirection = 0.;
mWindSpeed = 0.;
mSimulationMode = false;
mMaxIteration = 10;
mWindDirectionVariation = 0.;
mGustModifier = 1.;
mIterationsPerMinute = 1.;
}
/// setup of general settings from the project file.
/// the function is invoked from the Plugin.
void WindModule::setup()
{
// setup the grid (using the size/resolution)
mRUGrid.setup(GlobalSettings::instance()->model()->RUgrid().metricRect(),
GlobalSettings::instance()->model()->RUgrid().cellsize());
// setup the fire spread grid
mGrid.setup(GlobalSettings::instance()->model()->heightGrid()->metricRect(), cellsize());
//mFireId = 0;
// set some global settings
XmlHelper xml(GlobalSettings::instance()->settings().node("modules.wind"));
mWindDirectionVariation = xml.valueDouble(".directionVariation", 0.) * M_PI/180.;
mWindDirection = xml.valueDouble(".direction", 0.)*M_PI/180.;
mWindSpeed = 0.; // set the wind speed explicitely to 0
mGustModifier = xml.valueDouble(".gustModifier", 1.);
mIterationsPerMinute = 1. / xml.valueDouble(".durationPerIteration", 1.);
mWindLayers.setGrid(mGrid);
mWindLayers.setRUGrid(&mRUGrid);
GlobalSettings::instance()->controller()->addLayers(&mWindLayers, "wind");
// setup the species parameters that are specific to the wind module
QString parameter_table_name = xml.value(".speciesParameter", "wind");
loadSpeciesParameter(parameter_table_name);
}
/// setup of spatial explicit variables (e.g. the wind speed modifier)
/// the function is called from the plugin-object.
void WindModule::setupResourceUnit(const ResourceUnit *ru)
{
WindRUCell &cell = mRUGrid.valueAt(ru->boundingBox().center());
cell.topoModifier = GlobalSettings::instance()->settings().valueDouble("modules.wind.topoModifier", 1.);
}
/// load specific species parameter for the wind module.
/// wind related species parameter are located in a separate sqlite-table.
void WindModule::loadSpeciesParameter(const QString &table_name)
{
QSqlQuery query(GlobalSettings::instance()->dbin());
QString sql = QString("select * from %1").arg(table_name);
query.exec(sql);
if (query.lastError().isValid()){
throw IException(QString("Error loading species parameters for wind module: %1 \n %2").arg(sql, query.lastError().text()) );
}
mSpeciesParameters.clear();
int iID=query.record().indexOf("shortName");
int iCreg = query.record().indexOf("CReg");
int iCrownArea = query.record().indexOf("crownAreaFactor");
int iCrownLength = query.record().indexOf("crownLength");
int iMOR = query.record().indexOf("MOR");
int iWet = query.record().indexOf("wetBiomassFactor");
if (iID==-1 || iCreg==-1 || iCrownArea==-1 || iCrownLength==-1 || iMOR==-1 || iWet==-1) {
throw IException(QString("Error in wind parameter table '%1'. A required column was not found.").arg(table_name));
}
QString species_id;
while (query.next()) {
species_id = query.value(iID).toString();
Species *s = GlobalSettings::instance()->model()->speciesSet()->species(species_id);
if (s) {
WindSpeciesParameters &p = mSpeciesParameters[s];
p.Creg = query.value(iCreg).toDouble();
p.crown_area_factor = query.value(iCrownArea).toDouble();
p.crown_length = query.value(iCrownLength).toDouble();
p.MOR = query.value(iMOR).toDouble();
p.wet_biomass_factor = query.value(iWet).toDouble();
}
}
qDebug() << "wind:" << mSpeciesParameters.count() << "species parameter vectors loaded.";
}
const WindSpeciesParameters &WindModule::speciesParameter(const Species *s)
{
if (!mSpeciesParameters.contains(s))
throw IException(QString("WindModule: no wind species parameter for species '%1'").arg(s->id()));
return mSpeciesParameters[s];
}
/// the run() function executes the fire events
void WindModule::run(const int iteration)
{
// initialize things in the first iteration
if (iteration<=0) {
// check if we have a wind event this year
if (!initEvent())
return;
// setup the wind data
initWindGrid();
}
bool finished = false;
mCurrentIteration = 1;
if (iteration>=0) mCurrentIteration = iteration;
DebugTimer ttotal("wind:total");
while (!finished) {
// check for edges in the stand
DebugTimer titeration("wind:Cycle");
// detect current edges in the forest
detectEdges();
// calculate the gap sizes/fetch for the current structure
calculateFetch();
// wind speed of the current iteration
mCurrentGustFactor = 1. + nrandom(-mGustModifier, mGustModifier);
// derive the impact of wind (i.e. calculate critical wind speeds and the effect of wind on the forest)
int pixels = calculateWindImpact();
mPixelAffected += pixels;
if (++mCurrentIteration > mMaxIteration)
finished = true;
if (iteration>=0) // step by step execution
finished = true;
qDebug() << "wind module: iteration" << mCurrentIteration-1
<< "this round affected:" << pixels
<< "total:" << mPixelAffected
<< "totals: killed trees:" << mTreesKilled
<< "basal-area:" << mTotalKilledBasalArea
<< "gustfactor:" << mCurrentGustFactor;
}
qDebug() << "iterations: " << mCurrentIteration << "total pixels affected:" << mPixelAffected << "totals: killed trees:" << mTreesKilled << "basal-area:" << mTotalKilledBasalArea;
}
void WindModule::initWindGrid()
{
DebugTimer t("wind:init");
// reset some statistics
mTotalKilledBasalArea = 0.;
mTreesKilled = 0;
mPixelAffected = 0;
// as long as we have 10m -> easy!
if (cellsize()==cHeightPerRU) {
WindCell *p = mGrid.begin();
for (const HeightGridValue *hgv=GlobalSettings::instance()->model()->heightGrid()->begin(); hgv!=GlobalSettings::instance()->model()->heightGrid()->end(); ++hgv, ++p) {
p->clear();
if (hgv->isValid()) {
// p->height = hgv->height;
p->n_trees = hgv->count();
} else {
// the "height" of pixels not in the project area depends on a flag provided with the "stand map"
if (hgv->isOutside())
p->height = 9999.f;
else
p->height = 0.f;
}
}
// reset resource unit grid...
for (WindRUCell *cell=mRUGrid.begin(); cell!=mRUGrid.end(); ++cell) {
cell->flag = 0;
scanResourceUnitTrees(mGrid.indexAt(mRUGrid.cellCenterPoint(mRUGrid.indexOf(cell))));
}
return;
}
throw IException("WindModule::initWindGrid: not 10m of windpixels...");
}
void WindModule::detectEdges()
{
DebugTimer t("wind:edges");
WindCell *p_above, *p, *p_below;
int dy = mGrid.sizeY();
int dx = mGrid.sizeX();
int x,y;
const float threshold = 10.f;
for (y=1;y<dy-1;++y){
p = mGrid.ptr(1,y);
p_above = p - dx; // one line above
p_below = p + dx; // one line below
for (x=1;x<dx-1;++x,++p,++p_below, ++p_above) {
p->edge = 0.f; // no edge
if (p->isValid()) {
float max_h = p->height - threshold; // max_h: if a surrounding pixel is higher than this value, then there is no edge here
if (max_h > 0) {
// check 8-neighborhood. if one of those pixels exceed the threshold, then the current
// pixel is not an "edge".
if (((p_above-1)->height < max_h) ||
((p_above)->height < max_h) ||
((p_above+1)->height < max_h) ||
((p-1)->height < max_h) ||
((p+1)->height < max_h) ||
((p_below-1)->height < max_h) ||
((p_below)->height < max_h) ||
((p_below+1)->height < max_h) ) {
// edge found:
p->edge = 1.f;
}
}
}
}
}
}
void WindModule::calculateFetch()
{
DebugTimer t("wind:fetch");
int calculated = 0;
double current_direction;
WindCell *end = mGrid.end();
for (WindCell *p=mGrid.begin(); p!=end; ++p) {
if (p->edge == 1.f) {
QPoint pt=mGrid.indexOf(p);
current_direction = mWindDirection + (mWindDirectionVariation>0.?nrandom(-mWindDirectionVariation, mWindDirectionVariation):0);
checkFetch(pt.x(), pt.y(), current_direction, p->height * 10., p->height - 10.);
++calculated;
}
}
qDebug() << "calculated fetch for" << calculated << "pixels";
}
/** calculate for each pixel the impact of wind
@return number of pixels with killed trees
*/
int WindModule::calculateWindImpact()
{
DebugTimer t("wind:impact");
int calculated = 0;
int effective = 0;
WindCell *end = mGrid.end();
for (WindCell *p=mGrid.begin(); p!=end; ++p) {
if (p->edge >= 1.f) {
QPoint pt=mGrid.indexOf(p);
if (windImpactOnPixel(pt, p))
++effective;
++calculated;
}
}
qDebug() << "calculated impact for" << calculated << "pixels";
return effective;
}
// test function
void WindModule::testFetch(double degree_direction)
{
// for (int i=0;i<350;i+=10) {
// checkFetch(100,100, i*M_PI/180., 500+i);
// }
double direction = degree_direction*M_PI/180.;
int calculated = 0;
WindCell *end = mGrid.end();
for (WindCell *p=mGrid.begin(); p!=end; ++p) {
if (p->edge == 1.f) {
QPoint pt=mGrid.indexOf(p);
checkFetch(pt.x(), pt.y(), direction, p->height * 10., p->height - 10.);
++calculated;
}
}
qDebug() << "calculated fetch for" << calculated << "pixels";
}
void WindModule::testEffect()
{
int calculated = 0;
WindCell *end = mGrid.end();
for (WindCell *p=mGrid.begin(); p!=end; ++p) {
if (p->edge >= 1.f) {
QPoint pt=mGrid.indexOf(p);
windImpactOnPixel(pt, p);
++calculated;
}
}
qDebug() << "calculated fetch for" << calculated << "pixels";
}
/** determines whether a wind event should be triggered in the current year.
returns true if so and sets all relevant properties of the event (speed, direction).
*/
bool WindModule::initEvent()
{
// when using time events, a wind event is triggered by the time event mechanism
// by setting the wind speed of the event > 0
XmlHelper xml(GlobalSettings::instance()->settings().node("modules.wind"));
mWindSpeed = xml.valueDouble(".speed");
if (mWindSpeed == 0.)
return false;
// reset the wind speed in the xml structure (avoid execution next year)
xml.setNodeValue(".speed", "0");
// get duration of the event
double duration = xml.valueDouble(".duration", 0.); // duration of the event (minutes)
mMaxIteration = duration * mIterationsPerMinute + 0.5;
if (mMaxIteration<=0)
return false;
// get wind direction of the event
mWindDirection = xml.valueDouble(".direction", 0.)*M_PI/180.;
qDebug() << "Wind: start event. Speed:" << mWindSpeed << "m/s, Duration (iterations):" << mMaxIteration << ", direction (°):" << mWindDirection/M_PI*180.;
return true;
}
/** find distance to the next shelter pixel
@param startx x-index of the starting pixel (index)
@param starty y-index of the starting pixel (index)
@param direction direction to look (rad, 0: north, pi/2: east, pi: south, 3/2pi: west)
@param max_distance maximum distance (meters) to look for
@param threshold algorithm terminates if a pixel with a height higher than threshold is found
*/
bool WindModule::checkFetch(const int startx, const int starty, const double direction, const double max_distance, const double threshold)
{
int endx = startx + (max_distance/cellsize()+0.5)*sin(direction);
int endy = starty + (max_distance/cellsize()+0.5)*cos(direction);
// "draw" a line using bresenhems algorithm (http://en.wikipedia.org/wiki/Bresenham's_line_algorithm)
int dx = abs(endx-startx);
int dy = abs(endy-starty);
int sx = startx<endx?1:-1;
int sy = starty<endy?1:-1;
int err = dx - dy;
int x = startx,y = starty;
while (true) {
if (mGrid.isIndexValid(x,y)) {
if ((x!=startx || y!=starty) && (mGrid.valueAtIndex(x,y).height > threshold)) {
double dist = sqrt(cellsize()*((x-startx)*(x-startx) + (y-starty)*(y-starty)));
mGrid.valueAtIndex(startx, starty).edge = dist;
return true;
}
// mGrid.valueAtIndex(x,y).edge = 10; // plot...
} else
break;
if (x==endx && y==endy)
break;
int e2 = 2 * err;
if (e2 > -dy) {
err -= dy;
x += sx;
}
if (e2 < dx) {
err += dx;
y += sy;
}
}
mGrid.valueAtIndex(startx, starty).edge = max_distance;
return false;
/*
function line(x0, y0, x1, y1)
dx := abs(x1-x0)
dy := abs(y1-y0)
if x0 < x1 then sx := 1 else sx := -1
if y0 < y1 then sy := 1 else sy := -1
err := dx-dy
loop
setPixel(x0,y0)
if x0 = x1 and y0 = y1 exit loop
e2 := 2*err
if e2 > -dy then
err := err - dy
x0 := x0 + sx
end if
if e2 < dx then
err := err + dx
y0 := y0 + sy
end if
end loop
*/
}
/** Main function of the wind module
@param position the integer index (x/y) of the grid cell for which the wind effect should be calculated
@param cell the content of the current wind cell (for convenience)
@return true, if trees were killed (thrown, broken)
*/
bool WindModule::windImpactOnPixel(const QPoint position, WindCell *cell)
{
QRectF pixel_rect = mGrid.cellRect(position);
ResourceUnit *ru = GlobalSettings::instance()->model()->ru(pixel_rect.center());
if (!ru)
return false;
// ************************************************
// scan the trees of the current resource unit
// ************************************************
scanResourceUnitTrees(position);
if (!cell->tree) {
// this should actually not happen any more
cell->height = 0.f;
cell->edge = 0.f;
return false;
}
// *****************************************************************************
// Calculate the wind speed at the crown top and the critical wind speeds
// *****************************************************************************
// first, calculate the windspeed in the crown
DebugTimer t2("wind:impact:speed");
const WindSpeciesParameters ¶ms = speciesParameter(cell->tree->species());
double topo_mod = mRUGrid.valueAt(pixel_rect.center()).topoModifier;
// the wind speed (10m above the canopy) is the global wind speed modified with the topography modifier
// and with some added variation.
double wind_speed_10 = mWindSpeed * topo_mod * mCurrentGustFactor; // wind speed on current resource unit 10m above the canopy
double u_crown = calculateCrownWindSpeed(cell->tree, params, cell->n_trees, wind_speed_10);
// now calculate the critical wind speed for the tallest tree on the pixel (i.e. the speed at which stem breakage/uprooting occurs)
double cws_uproot, cws_break;
calculateCrititalWindSpeed(cell->tree, params, cell->edge, cws_uproot, cws_break);
cell->cws_break = cws_break;
cell->cws_uproot = cws_uproot;
cell->crown_windspeed = u_crown;
if (u_crown < cws_uproot && u_crown < cws_break)
return false; // wind speed is too low
// whether uprooting or breaking occurs depend on the wind speed: stems break if speed is high enough, otherwise uprooting will occur
bool do_break = u_crown > cws_break;
// *****************************************************************************
// Kill the trees that are thrown/uprooted by the wind
// *****************************************************************************
QVector<Tree>::const_iterator tend = ru->trees().constEnd();
for (QVector<Tree>::const_iterator t=ru->trees().constBegin(); t!=tend; ++t) {
if (!t->isDead() &&
t->positionIndex().x()/cPxPerHeight == position.x() &&
t->positionIndex().y()/cPxPerHeight == position.y() ) {
if (!mSimulationMode) {
// all trees > 4m are killed on the cell
Tree *tree = const_cast<Tree*>(&(*t));
if (do_break) {
// the tree is breaking
// half of the stem as well as foliage/branches are moved to the soil. The other half
// of the stem remains as snag.
tree->removeDisturbance(0.5, 0.5, // 50% of the stem to soil, 50% to snag
1., 0., // 100% of branches to soil
1.); // 100% of foliage to soil
} else {
// uprooting
// regeneration is killed in case of uprooting ??
ru->clearSaplings(pixel_rect, true);
// all biomass of the tree is moved to the soil
tree->removeDisturbance(1., 0., // 100% of stem -> soil
1., 0., // 100% of branch -> soil
1.); // 100% of foliage -> soil
}
}
// statistics
cell->basal_area_killed += t->basalArea();
cell->n_killed ++;
mTotalKilledBasalArea += t->basalArea();
mTreesKilled ++;
}
}
// reset the current cell
cell->height = 0.f;
cell->edge = 0.f;
cell->n_iteration = mCurrentIteration;
cell->tree = 0;
return true;
}
/** calculate the windspeed at the top of the crown.
@param tree the tallest tree on the cell
@param n_trees number of trees that are on the 10x10m cell
@param wind_speed_10 wind speed in 10m above canopy (m/s)
*/
double WindModule::calculateCrownWindSpeed(const Tree *tree, const WindSpeciesParameters ¶ms, const int n_trees, const double wind_speed_10)
{
// frontal area index
double lambda = 2. * tree->crownRadius() * (tree->height() * params.crown_length )* params.crown_area_factor / (cellsize()*cellsize()/n_trees);
// calculate zero-plane-displacement height (Raupachs drag partitioning model (Raupach 1992, 1994))
const double cdl = 7.5;
double d0_help = sqrt(2. * cdl * lambda);
double d0 = tree->height() * (1. - (1.-exp(-d0_help))/d0_help);
const double surface_drag_coefficient = 0.003;
const double element_drag_coefficient = 0.3;
const double kaman_constant = 0.4;
// drag coefficient gamma
double gamma = sqrt(surface_drag_coefficient + lambda*element_drag_coefficient);
if (gamma > element_drag_coefficient) gamma = element_drag_coefficient;
// surface roughness
double z0 = (tree->height() - d0)*exp(-kaman_constant/gamma + 0.193);
// wind multiplier
double u_factor = log((tree->height()-d0)/z0) / log((tree->height()+10.)/z0);
double u_crown = wind_speed_10 * u_factor;
return u_crown;
}
/** calculate the critital windspeed taking into account the sheltering from upwind vegetation and the competetive state of the tree.
The calculation is performed for the largest tree on the cell.
@param tree the tallest tree on the cell
@param gap_length size of the gap in upwind direction (m)
@param rCWS_uproot OUT parameter with the critical windspeed for uprooting
@param rCWS_uproot OUT parameter with the critical windspeed for stem breakage
@return the (lower) critital windspeed
*/
double WindModule::calculateCrititalWindSpeed(const Tree *tree, const WindSpeciesParameters ¶ms, const double gap_length, double &rCWS_uproot, double &rCWS_break)
{
// relate the gap size to tree length and calcualte the f_gap factor
double rel_gap = gap_length / tree->height();
if (rel_gap>10) rel_gap=10;
double f_gap = (0.001+0.001*pow(rel_gap,0.562))/0.00465; // formulation from Peltola et al. (1999), based on Gardiner et al. (1997)
// calculate the wet stem weight (iLand internally uses always dry weights)
double stem_mass = tree->biomassStem() * params.wet_biomass_factor;
// calculate the "BAL", the basal area of surrounding trees, i.e. a competition index
// this will be changed to be related to LRI. But for the time being:
double bal = 70. * (1. - tree->lightResourceIndex());
// the turning moment coefficient incorporating the competition state
double tc = 36.8+112.2*(tree->dbh()*tree->dbh()/10000.)*tree->height()-0.460*bal-0.783*(tree->dbh()*tree->dbh()/10000.)*tree->height()*bal;
// now derive the critital wind speeds for uprooting and breakage
const double f_knot = 1.; // a reduction factor accounting for the presence of knots, currenty no reduction.
rCWS_uproot = sqrt((params.Creg*stem_mass) / (tc*f_gap)); // critical windspeed for uprooting
rCWS_break = sqrt(params.MOR*pow(tree->dbh(),3)*f_knot*M_PI / (32.*tc*f_gap)); // critical windspeed for breakage
// debug info
// qDebug() << "f_gap, bal, tc, cws_uproot, cws_break:" << f_gap << bal << tc << rCWS_uproot << rCWS_break;
return qMin(rCWS_uproot, rCWS_break);
}
// scan the content of the trees container of the resource unit
// and extract the tallest tree & species per wind pixel
void WindModule::scanResourceUnitTrees(const QPoint &position)
{
QPointF p_m = mGrid.cellCenterPoint(position);
// if this resource unit was already scanned in this wind event, then do nothing
// the flags are reset during initWindGrid()
if (mRUGrid.valueAt(p_m).flag)
return;
ResourceUnit *ru = GlobalSettings::instance()->model()->ru(p_m);
QVector<Tree>::const_iterator tend = ru->trees().constEnd();
for (QVector<Tree>::const_iterator t = ru->trees().constBegin(); t!=tend; ++t) {
if (!t->isDead()) {
const QPoint &tp = t->positionIndex();
QPoint pwind(tp.x()/cPxPerHeight, tp.y()/cPxPerHeight);
WindCell &wind=mGrid.valueAtIndex(pwind);
if (!wind.tree || t->height()>wind.tree->height()) {
wind.height = t->height();
wind.tree = &(*t);
}
}
}
// set the "processed" flag
mRUGrid.valueAt(p_m).flag = 1;
}