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 | |||
183 | werner | 20 | #include "dynamicstandout.h" |
21 | |||
808 | werner | 22 | #include "debugtimer.h" |
23 | #include "statdata.h" |
||
184 | werner | 24 | #include "model.h" |
189 | iland | 25 | #include "resourceunit.h" |
184 | werner | 26 | #include "species.h" |
27 | #include "expressionwrapper.h" |
||
28 | |||
183 | werner | 29 | DynamicStandOut::DynamicStandOut() |
30 | { |
||
184 | werner | 31 | setName("dynamic stand output by species/RU", "dynamicstand"); |
802 | werner | 32 | setDescription("Userdefined outputs for tree aggregates for each stand or species.\n"\ |
261 | werner | 33 | "Technically, each field is calculated 'live', i.e. it is looped over all trees, and eventually the statistics (percentiles) "\ |
1157 | werner | 34 | "are calculated. The aggregated values are not scaled to any area unit.\n" \ |
802 | werner | 35 | "!!!Specifying the aggregation\n" \ |
36 | "The ''by_species'' and ''by_ru'' option allow to define the aggregation level. When ''by_species'' is set to ''true'', " \ |
||
37 | "a row for each species will be created, otherwise all trees of all species are aggregated to one row. " \ |
||
1157 | werner | 38 | "Similarly, ''by_ru''=''true'' means outputs for each resource unit, while a value of ''false'' aggregates over the full project area.\n" \ |
802 | werner | 39 | "!!!Specifying filters\n" \ |
521 | werner | 40 | "You can use the 'rufilter' and 'treefilter' XML settings to reduce the limit the output to a subset of resource units / trees. " \ |
41 | "Both filters are valid expressions (for resource unit level and tree level, respectively). For example, a ''treefilter'' of 'speciesindex=0' reduces the output to just one species.\n" \ |
||
802 | werner | 42 | "The ''condition'' filter is (when present) evaluated and the output is only executed when ''condition'' is true (variable='year') This can be used to constrain the output to specific years (e.g. 'in(year,100,200,300)' produces output only for the given year.\n" \ |
43 | "!!!Specifying data columns\n" |
||
521 | werner | 44 | "Each field is defined as: ''field.aggregatio''n (separated by a dot). A ''field'' is a valid [Expression]. ''Aggregation'' is one of the following: " \ |
598 | werner | 45 | "mean, sum, min, max, p25, p50, p75, p5, 10, p90, p95 (pXX=XXth percentile), sd (std.dev.).\n" \ |
46 | "Complex expression are allowed, e.g: if(dbh>50,1,0).sum (-> counts trees with dbh>50)"); |
||
570 | werner | 47 | columns() << OutputColumn::year() << OutputColumn::ru() << OutputColumn::id() << OutputColumn::species(); |
184 | werner | 48 | // other colums are added during setup... |
183 | werner | 49 | } |
184 | werner | 50 | |
598 | werner | 51 | const QStringList aggList = QStringList() << "mean" << "sum" << "min" << "max" << "p25" << "p50" << "p75" << "p5"<< "p10" << "p90" << "p95" << "sd"; |
184 | werner | 52 | |
53 | void DynamicStandOut::setup() |
||
54 | { |
||
55 | QString filter = settings().value(".rufilter",""); |
||
520 | werner | 56 | QString tree_filter = settings().value(".treefilter",""); |
184 | werner | 57 | QString fieldList = settings().value(".columns", ""); |
802 | werner | 58 | QString condition = settings().value(".condition", ""); |
184 | werner | 59 | if (fieldList.isEmpty()) |
60 | return; |
||
61 | mRUFilter.setExpression(filter); |
||
520 | werner | 62 | mTreeFilter.setExpression(tree_filter); |
802 | werner | 63 | mCondition.setExpression(condition); |
184 | werner | 64 | // clear columns |
570 | werner | 65 | columns().erase(columns().begin()+4, columns().end()); |
184 | werner | 66 | mFieldList.clear(); |
67 | |||
68 | // setup fields |
||
69 | if (!fieldList.isEmpty()) { |
||
353 | werner | 70 | QRegExp rx("([^\\.]+).(\\w+)[,\\s]*"); // two parts: before dot and after dot, and , + whitespace at the end |
184 | werner | 71 | int pos=0; |
353 | werner | 72 | QString field, aggregation; |
184 | werner | 73 | TreeWrapper tw; |
74 | while ((pos = rx.indexIn(fieldList, pos)) != -1) { |
||
75 | pos += rx.matchedLength(); |
||
353 | werner | 76 | |
77 | field = rx.cap(1); // field / expresssion |
||
78 | aggregation = rx.cap(2); |
||
184 | werner | 79 | mFieldList.append(SDynamicField()); |
80 | // parse field |
||
353 | werner | 81 | if (field.count()>0 && !field.contains('(')) { |
184 | werner | 82 | // simple expression |
353 | werner | 83 | mFieldList.back().var_index = tw.variableIndex(field); |
184 | werner | 84 | } else { |
85 | // complex expression |
||
86 | mFieldList.back().var_index=-1; |
||
353 | werner | 87 | mFieldList.back().expression = field; |
184 | werner | 88 | } |
89 | |||
353 | werner | 90 | mFieldList.back().agg_index = aggList.indexOf(aggregation); |
91 | if (mFieldList.back().agg_index==-1) |
||
184 | werner | 92 | throw IException(QString("Invalid aggregate expression for dynamic output: %1\nallowed:%2") |
353 | werner | 93 | .arg(aggregation).arg(aggList.join(" "))); |
184 | werner | 94 | |
353 | werner | 95 | QString stripped_field=QString("%1_%2").arg(field, aggregation); |
184 | werner | 96 | stripped_field.replace(QRegExp("[\\[\\]\\,\\(\\)<>=!\\s]"), "_"); |
97 | stripped_field.replace("__", "_"); |
||
98 | columns() << OutputColumn(stripped_field, field, OutDouble); |
||
99 | } |
||
802 | werner | 100 | } |
101 | } |
||
102 | |||
103 | void DynamicStandOut::exec() |
||
104 | { |
||
105 | if (mFieldList.count()==0) |
||
106 | return; |
||
107 | if (!mCondition.isEmpty()) |
||
108 | if (!mCondition.calculate(GlobalSettings::instance()->currentYear())) |
||
109 | return; |
||
110 | |||
111 | DebugTimer t("dynamic stand output"); |
||
112 | |||
113 | bool per_species = GlobalSettings::instance()->settings().valueBool("output.dynamicstand.by_species", true); |
||
114 | bool per_ru = GlobalSettings::instance()->settings().valueBool("output.dynamicstand.by_ru", true); |
||
115 | |||
116 | if (per_ru) { |
||
117 | // when looping over resource units, do it differently (old way) |
||
855 | werner | 118 | extractByResourceUnit(per_species); |
802 | werner | 119 | return; |
184 | werner | 120 | } |
802 | werner | 121 | |
122 | Model *m = GlobalSettings::instance()->model(); |
||
123 | QVector<double> data; //statistics data |
||
124 | TreeWrapper tw; |
||
125 | Expression custom_expr; |
||
126 | |||
127 | StatData stat; // statistcs helper class |
||
128 | // grouping |
||
129 | QVector<const Tree*> trees; |
||
130 | for (QList<Species*>::const_iterator species = m->speciesSet()->activeSpecies().constBegin();species!=m->speciesSet()->activeSpecies().constEnd();++species) { |
||
131 | trees.clear(); |
||
132 | AllTreeIterator all_trees(m); |
||
855 | werner | 133 | while (const Tree *t = all_trees.nextLiving()) { |
802 | werner | 134 | if (per_species && t->species() != *species) |
135 | continue; |
||
136 | trees.push_back(t); |
||
137 | } |
||
138 | if (trees.size()==0) |
||
139 | continue; |
||
140 | // dynamic calculations |
||
141 | foreach (const SDynamicField &field, mFieldList) { |
||
142 | |||
143 | if (!field.expression.isEmpty()) { |
||
144 | // setup dynamic dynamic expression if present |
||
145 | custom_expr.setExpression(field.expression); |
||
146 | custom_expr.setModelObject(&tw); |
||
147 | } |
||
148 | |||
149 | // fetch data values from the trees |
||
150 | data.clear(); |
||
151 | foreach(const Tree *t, trees) { |
||
152 | tw.setTree(t); |
||
153 | if (field.var_index>=0) |
||
154 | data.push_back(tw.value(field.var_index)); |
||
155 | else |
||
156 | data.push_back(custom_expr.execute()); |
||
157 | } |
||
158 | // constant values (if not already present) |
||
159 | if (isRowEmpty()) { |
||
160 | *this << currentYear() << -1 << -1; |
||
161 | if (per_species) |
||
162 | *this << (*species)->id(); |
||
163 | else |
||
164 | *this << ""; |
||
165 | } |
||
166 | |||
167 | // calculate statistics |
||
168 | stat.setData(data); |
||
169 | // aggregate |
||
170 | double value; |
||
171 | switch (field.agg_index) { |
||
172 | case 0: value = stat.mean(); break; |
||
173 | case 1: value = stat.sum(); break; |
||
174 | case 2: value = stat.min(); break; |
||
175 | case 3: value = stat.max(); break; |
||
176 | case 4: value = stat.percentile25(); break; |
||
177 | case 5: value = stat.median(); break; |
||
178 | case 6: value = stat.percentile75(); break; |
||
179 | case 7: value = stat.percentile(5); break; |
||
180 | case 8: value = stat.percentile(10); break; |
||
181 | case 9: value = stat.percentile(90); break; |
||
182 | case 10: value = stat.percentile(95); break; |
||
183 | case 11: value = stat.standardDev(); break; |
||
184 | |||
185 | default: value = 0.; break; |
||
186 | } |
||
187 | // add current value to output |
||
188 | *this << value; |
||
189 | } |
||
190 | if (!isRowEmpty()) |
||
191 | writeRow(); |
||
192 | |||
193 | if (!per_species) |
||
194 | break; |
||
195 | } |
||
196 | |||
184 | werner | 197 | } |
198 | |||
199 | |||
855 | werner | 200 | void DynamicStandOut::extractByResourceUnit(const bool by_species) |
184 | werner | 201 | { |
202 | |||
203 | if (mFieldList.count()==0) |
||
204 | return; |
||
205 | |||
206 | Model *m = GlobalSettings::instance()->model(); |
||
207 | QVector<double> data; //statistics data |
||
208 | StatData stat; // statistcs helper class |
||
209 | TreeWrapper tw; |
||
210 | RUWrapper ruwrapper; |
||
211 | mRUFilter.setModelObject(&ruwrapper); |
||
212 | |||
213 | Expression custom_expr; |
||
214 | |||
802 | werner | 215 | |
189 | iland | 216 | foreach(ResourceUnit *ru, m->ruList()) { |
574 | werner | 217 | if (ru->id()==-1) |
218 | continue; // do not include if out of project area |
||
219 | |||
184 | werner | 220 | // test filter |
221 | if (!mRUFilter.isEmpty()) { |
||
189 | iland | 222 | ruwrapper.setResourceUnit(ru); |
184 | werner | 223 | if (!mRUFilter.execute()) |
224 | continue; |
||
225 | } |
||
455 | werner | 226 | foreach(const ResourceUnitSpecies *rus, ru->ruSpecies()) { |
855 | werner | 227 | if (by_species && rus->constStatistics().count()==0) |
184 | werner | 228 | continue; |
229 | |||
520 | werner | 230 | |
184 | werner | 231 | // dynamic calculations |
232 | foreach (const SDynamicField &field, mFieldList) { |
||
233 | |||
234 | if (!field.expression.isEmpty()) { |
||
235 | // setup dynamic dynamic expression if present |
||
236 | custom_expr.setExpression(field.expression); |
||
237 | custom_expr.setModelObject(&tw); |
||
238 | } |
||
239 | data.clear(); |
||
520 | werner | 240 | bool has_trees = false; |
184 | werner | 241 | foreach(const Tree &tree, ru->trees()) { |
855 | werner | 242 | if (by_species && tree.species()->index()!=rus->species()->index()) |
184 | werner | 243 | continue; |
855 | werner | 244 | if (tree.isDead()) |
245 | continue; |
||
184 | werner | 246 | tw.setTree(&tree); |
520 | werner | 247 | |
248 | // apply treefilter |
||
249 | if (!mTreeFilter.isEmpty()) { |
||
250 | mTreeFilter.setModelObject(&tw); |
||
251 | if (!mTreeFilter.execute()) |
||
252 | continue; |
||
253 | } |
||
254 | has_trees = true; |
||
255 | |||
184 | werner | 256 | if (field.var_index>=0) |
257 | data.push_back(tw.value(field.var_index)); |
||
258 | else |
||
259 | data.push_back(custom_expr.execute()); |
||
260 | } |
||
520 | werner | 261 | |
262 | // do nothing if no trees are avaiable |
||
263 | if (!has_trees) |
||
264 | continue; |
||
265 | |||
266 | |||
855 | werner | 267 | if (isRowEmpty()) { |
268 | *this << currentYear() << ru->index() << ru->id(); |
||
269 | if (by_species) |
||
270 | *this << rus->species()->id(); |
||
271 | else |
||
272 | *this << ""; |
||
273 | } |
||
274 | |||
184 | werner | 275 | // calculate statistics |
276 | stat.setData(data); |
||
277 | // aggregate |
||
278 | double value; |
||
279 | switch (field.agg_index) { |
||
218 | werner | 280 | case 0: value = stat.mean(); break; |
281 | case 1: value = stat.sum(); break; |
||
282 | case 2: value = stat.min(); break; |
||
283 | case 3: value = stat.max(); break; |
||
284 | case 4: value = stat.percentile25(); break; |
||
285 | case 5: value = stat.median(); break; |
||
286 | case 6: value = stat.percentile75(); break; |
||
287 | case 7: value = stat.percentile(5); break; |
||
288 | case 8: value = stat.percentile(10); break; |
||
289 | case 9: value = stat.percentile(90); break; |
||
290 | case 10: value = stat.percentile(95); break; |
||
598 | werner | 291 | case 11: value = stat.standardDev(); break; |
218 | werner | 292 | |
293 | default: value = 0.; break; |
||
184 | werner | 294 | } |
295 | // add current value to output |
||
296 | *this << value; |
||
297 | |||
298 | } // foreach (field) |
||
520 | werner | 299 | if (!isRowEmpty()) |
300 | writeRow(); |
||
855 | werner | 301 | if (!by_species) |
302 | break; |
||
184 | werner | 303 | } //foreach species |
304 | } // foreach ressource unit |
||
305 | |||
306 | } |