Rev 1221 | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
17 | Werner | 1 | #include "lightroom.h" |
83 | Werner | 2 | #include "../tools/helper.h" |
17 | Werner | 3 | |
62 | Werner | 4 | #include <QtCore> |
5 | |||
17 | Werner | 6 | LightRoom::LightRoom() |
7 | { |
||
24 | Werner | 8 | m_roomObject = 0; |
404 | werner | 9 | m_aggregationMode = 0; |
17 | Werner | 10 | } |
11 | |||
18 | Werner | 12 | /** setup routine. |
13 | sets up datastructures (3d space, hemi grids) |
||
32 | Werner | 14 | @param dimx size of space in x direction [m] |
15 | @param dimy size of space in y direction [m] |
||
16 | @param dimz size of space in z direction [m] |
||
18 | Werner | 17 | @param cellsize metric length of cells (used for all 3 dimensions). |
18 | @param hemigridsize size of hemigrid-cells (in degrees). |
||
19 | @param latitude lat. in degrees. |
||
20 | @param diffus_frac fraction [0..1] of diffus radiation of global radiation. */ |
||
32 | Werner | 21 | void LightRoom::setup(const double dimx, const double dimy, const double dimz, |
18 | Werner | 22 | const double cellsize, const double hemigridsize, |
23 | const double latitude, const double diffus_frac) |
||
17 | Werner | 24 | { |
19 | Werner | 25 | DebugTimer t1("setup of lightroom"); |
32 | Werner | 26 | m_countX = int(dimx / cellsize); |
27 | m_countY = int(dimy / cellsize); |
||
28 | m_countZ = int(dimz / cellsize); |
||
29 | |||
17 | Werner | 30 | m_cellsize = cellsize; |
22 | Werner | 31 | if (m_countX%2==0) m_countX++; // make uneven |
32 | if (m_countY%2==0) m_countY++; |
||
32 | Werner | 33 | |
34 | QRectF rect(-m_countX/2.*cellsize, -m_countY/2.*cellsize, m_countX*cellsize, m_countY*cellsize); |
||
35 | qDebug() << "Lightroom size: " << m_countX << "/" << m_countY << "/" << m_countZ << " elements. rect: " << rect; |
||
36 | |||
37 | |||
22 | Werner | 38 | m_2dvalues.setup(rect, cellsize); |
18 | Werner | 39 | m_2dvalues.initialize(0.); |
27 | Werner | 40 | |
18 | Werner | 41 | // setup hemigrids |
42 | SolarRadiation solar; |
||
43 | solar.setLatidude(latitude); // austria |
||
44 | solar.setVegetationPeriod(0,367); // no. veg. period |
||
45 | solar.setDiffusRadFraction(diffus_frac); // 50% diffuse radiation |
||
46 | // calculate global radiation values |
||
19 | Werner | 47 | DebugTimer t2("calculate solar radiation matrix"); |
18 | Werner | 48 | solar.calculateRadMatrix(hemigridsize, m_solarGrid); |
19 | Werner | 49 | t2.showElapsed(); |
18 | Werner | 50 | m_shadowGrid.setup(hemigridsize); // setup size |
29 | Werner | 51 | m_solarrad_factor = 1. / m_solarGrid.sum(RAD(45)); // sum of rad. > 45° |
44 | Werner | 52 | m_centervalue = 0.; |
17 | Werner | 53 | } |
22 | Werner | 54 | |
27 | Werner | 55 | double LightRoom::calculateGridAtPoint(const double p_x, const double p_y, const double p_z, bool fillShadowGrid) |
24 | Werner | 56 | { |
25 | Werner | 57 | if (!m_roomObject) |
58 | return 0; |
||
59 | // check feasibility |
||
60 | if (m_roomObject->noHitGuaranteed(p_x, p_y, p_z)) { |
||
27 | Werner | 61 | //qDebug()<<"skipped"; |
25 | Werner | 62 | return 0; |
63 | } |
||
64 | |||
27 | Werner | 65 | if (fillShadowGrid) |
66 | m_shadowGrid.clear(0.); |
||
25 | Werner | 67 | |
24 | Werner | 68 | // start with 45° |
25 | Werner | 69 | int ie = m_shadowGrid.indexElevation(RAD(45)); |
70 | int ia = 0; |
||
71 | int max_a = m_shadowGrid.matrixCountAzimuth(); |
||
72 | int max_e = m_shadowGrid.matrixCountElevation(); |
||
73 | double elevation, azimuth; |
||
27 | Werner | 74 | double solar_sum=0; |
31 | Werner | 75 | int hit; |
25 | Werner | 76 | int c_hit = 0; |
77 | int c_test = 0; |
||
78 | for (;ie<max_e;ie++){ |
||
79 | for (ia=0;ia<max_a;ia++) { |
||
402 | werner | 80 | azimuth = m_shadowGrid.azimuthNorth(ia); |
25 | Werner | 81 | elevation = m_shadowGrid.elevation(ie); |
82 | hit = m_roomObject->hittest(p_x, p_y, p_z,azimuth,elevation); |
||
31 | Werner | 83 | // if inside the crown: do nothing and return. |
38 | Werner | 84 | // 20090708: if inside the crown: return "totally dark" |
31 | Werner | 85 | if (hit==-1) |
38 | Werner | 86 | return 1; |
25 | Werner | 87 | //qDebug() << "testing azimuth" << GRAD(azimuth) << "elevation" << GRAD(elevation)<<"hit"<<hit; |
88 | c_test++; |
||
31 | Werner | 89 | if (hit==1) { |
27 | Werner | 90 | // retrieve value from solar grid |
91 | // Sum(cells) of solargrid =1 -> the sum of all "shadowed" pixels therefore is already the "ratio" of shaded/total radiation |
||
92 | solar_sum += m_solarGrid.rGetByIndex(ia, ie); |
||
93 | if (fillShadowGrid) |
||
94 | m_shadowGrid.rGetByIndex(ia, ie) = m_solarGrid.rGetByIndex(ia, ie); |
||
25 | Werner | 95 | c_hit++; |
96 | } |
||
97 | } |
||
98 | } |
||
29 | Werner | 99 | // solar-rad-factor = 1/(sum rad > 45°) |
100 | return solar_sum * m_solarrad_factor; |
||
27 | Werner | 101 | //double ratio = c_hit / double(c_test); |
102 | //qDebug() << "tested"<< c_test<<"hit count:" << c_hit<<"ratio"<<c_hit/double(c_test)<<"total sum"<<m_shadowGrid.getSum(); |
||
103 | //return ratio; // TODO: the global radiation is not calculated!!!!! |
||
104 | |||
24 | Werner | 105 | } |
22 | Werner | 106 | |
25 | Werner | 107 | void LightRoom::calculateFullGrid() |
108 | { |
||
27 | Werner | 109 | float *v = m_2dvalues.begin(); |
110 | float *vend = m_2dvalues.end(); |
||
25 | Werner | 111 | int z; |
112 | QPoint pindex; |
||
113 | QPointF coord; |
||
114 | double hit_ratio; |
||
115 | DebugTimer t("calculate full grid"); |
||
116 | int c=0; |
||
27 | Werner | 117 | float maxh = m_roomObject->maxHeight(); |
28 | Werner | 118 | |
27 | Werner | 119 | float *values = new float[m_countZ]; |
68 | Werner | 120 | |
400 | werner | 121 | //double h_realized; |
27 | Werner | 122 | double sum; |
68 | Werner | 123 | |
25 | Werner | 124 | while (v!=vend) { |
27 | Werner | 125 | pindex = m_2dvalues.indexOf(v); |
105 | Werner | 126 | coord = m_2dvalues.cellCenterPoint(pindex); |
62 | Werner | 127 | double hor_distance = sqrt(coord.x()*coord.x() + coord.y()*coord.y()); |
128 | |||
27 | Werner | 129 | for (z=0;z<m_countZ && z*m_cellsize <= maxh;z++) { |
28 | Werner | 130 | // only calculate values up to the 45° line |
131 | // tan(45°)=1 -> so this is the case when the distance p->tree > height of the tree |
||
62 | Werner | 132 | if (hor_distance > maxh-z*m_cellsize) |
28 | Werner | 133 | break; |
27 | Werner | 134 | hit_ratio = calculateGridAtPoint(coord.x(), coord.y(), // coords x,y |
135 | z*m_cellsize,false); // heigth (z), false: do not clear and fill shadow grid structure |
||
31 | Werner | 136 | // stop calculating when return = -1 (e.g. when inside the crown) |
137 | if (hit_ratio==-1) |
||
138 | break; |
||
44 | Werner | 139 | values[z]=hit_ratio; // this value * height of cells |
25 | Werner | 140 | } |
27 | Werner | 141 | // calculate average |
142 | sum = 0; |
||
38 | Werner | 143 | // 20090708: do not average, but keep the sum |
28 | Werner | 144 | // aggregate mean for all cells with angles>45° to the tree-top! |
43 | Werner | 145 | // 20090711: again average it, but now include the tree top. |
52 | Werner | 146 | // 20090713: go back to sum... |
68 | Werner | 147 | // 20090812: remove the weighting for the 10m cells again. |
400 | werner | 148 | // 20100520: again, use the average value (per meter) |
27 | Werner | 149 | for(int i=0;i<z;i++) |
150 | sum+=values[i]; |
||
404 | werner | 151 | if (m_aggregationMode==0) { |
152 | // average shadow / meter height (within the 45° cone) |
||
153 | if (z) |
||
154 | sum/=float(z); |
||
155 | } else { |
||
156 | // aggregation: sum |
||
157 | sum *= m_cellsize; // multiply with height (shadow * meter) |
||
158 | } |
||
159 | *v = sum; // store in matrix |
||
400 | werner | 160 | // sum |
62 | Werner | 161 | |
25 | Werner | 162 | v++; |
163 | c++; |
||
164 | if (c%1000==0) { |
||
72 | Werner | 165 | qDebug() << c << "processed...time: ms: " << t.elapsed(); |
25 | Werner | 166 | QCoreApplication::processEvents(); |
167 | } |
||
43 | Werner | 168 | // save value of the middle column... |
169 | m_centervalue = m_2dvalues(0.f, 0.f); |
||
62 | Werner | 170 | } // while(v!=vend) |
25 | Werner | 171 | } |
172 | |||
22 | Werner | 173 | ////////////////////////////////////////////////////////// |
174 | // Lightroom Object |
||
175 | ////////////////////////////////////////////////////////// |
||
176 | |||
23 | Werner | 177 | LightRoomObject::~LightRoomObject() |
178 | { |
||
179 | if (m_radiusFormula) |
||
180 | delete m_radiusFormula; |
||
181 | } |
||
182 | |||
22 | Werner | 183 | void LightRoomObject::setuptree(const double height, const double crownheight, const QString &formula) |
184 | { |
||
23 | Werner | 185 | if (m_radiusFormula) |
186 | delete m_radiusFormula; |
||
22 | Werner | 187 | |
23 | Werner | 188 | m_radiusFormula = new Expression(formula); |
30 | Werner | 189 | |
23 | Werner | 190 | m_baseradius = m_radiusFormula->calculate(crownheight/height); |
191 | m_height = height; |
||
192 | m_crownheight = crownheight; |
||
27 | Werner | 193 | double h=0., r; |
194 | m_treeHeights.clear(); |
||
195 | // preprocess crown radii for each meter step |
||
196 | while (h<=m_height) { |
||
197 | if (h<m_crownheight) |
||
198 | r=0.; |
||
199 | else |
||
200 | r = m_radiusFormula->calculate(h/m_height); |
||
201 | m_treeHeights.push_back(r); |
||
202 | h++; |
||
203 | } |
||
22 | Werner | 204 | } |
32 | Werner | 205 | |
206 | |||
207 | // Angle-function. return the difference between |
||
208 | // two angles as a radian value between -pi..pi [-180..180°] |
||
209 | // >0: directionB is "left" (ccw) of directionA, <0: "right", clockwise |
||
210 | // e.g.: result=-10: 10° cw, result=10°: 10° ccw |
||
211 | // result:-180/+180: antiparallel. |
||
212 | double DiffOfAngles(double DirectionA, double DirectionB) |
||
213 | { |
||
214 | DirectionA = fmod(DirectionA, PI2); // -> -2pi .. 2pi |
||
215 | if (DirectionA < 0) DirectionA+=PI2; // -> 0..2pi (e.g.: AngleA = -30 -> 330) |
||
216 | DirectionB = fmod(DirectionB, PI2); |
||
217 | if (DirectionB < 0) DirectionB+=PI2; |
||
218 | double Delta = DirectionB - DirectionA; |
||
219 | if (Delta<0) { |
||
220 | if (Delta<-M_PI) |
||
221 | return Delta + PI2; // ccw |
||
222 | else |
||
223 | return Delta; // cw |
||
224 | } else { |
||
225 | if (Delta>M_PI) |
||
226 | return Delta - PI2; // cw |
||
227 | else |
||
228 | return Delta; // ccw |
||
229 | } |
||
230 | |||
231 | } |
||
232 | |||
233 | |||
234 | // Angle-function. return the absolute difference between |
||
235 | // two angles as a radian value between 0..pi [0..180°] |
||
236 | // e.g. 10° = +10° or -10°; maximum value is 180° |
||
237 | double AbsDiffOfAngles(double AngleA, double AngleB) |
||
238 | { |
||
239 | return fabs(DiffOfAngles(AngleA, AngleB)); |
||
240 | } |
||
241 | |||
242 | |||
23 | Werner | 243 | /** The tree is located in x/y=0/0. |
244 | */ |
||
31 | Werner | 245 | int LightRoomObject::hittest(const double p_x, const double p_y, const double p_z, |
22 | Werner | 246 | const double azimuth_rad, const double elevation_rad) |
247 | { |
||
27 | Werner | 248 | bool inside_crown=false; |
23 | Werner | 249 | if (p_z > m_height) |
31 | Werner | 250 | return 0; |
25 | Werner | 251 | // Test 1: does the ray (azimuth) direction hit the crown? |
23 | Werner | 252 | double phi = atan2(-p_y, -p_x); // angle between P and the tree center |
253 | double dist2d = sqrt(p_x*p_x + p_y*p_y); // distance at ground |
||
28 | Werner | 254 | //if (dist2d==0) |
255 | // return true; |
||
22 | Werner | 256 | |
32 | Werner | 257 | double alpha = DiffOfAngles(phi, azimuth_rad); // phi - azimuth_rad; // angle between the ray and the center of the tree |
25 | Werner | 258 | if (dist2d>m_baseradius) { // test only, if p not the crown |
23 | Werner | 259 | double half_max_angle = asin(m_baseradius / dist2d); // maximum deviation angle from direct connection p - tree where ray hits maxradius |
260 | if (fabs(alpha) > half_max_angle) |
||
31 | Werner | 261 | return 0; |
23 | Werner | 262 | } else { |
27 | Werner | 263 | inside_crown = true; |
23 | Werner | 264 | // test if p is inside the crown |
265 | if (p_z<=m_height && p_z>=m_crownheight) { |
||
266 | double radius_hit = m_radiusFormula->calculate(p_z / m_height); |
||
31 | Werner | 267 | if (dist2d <= radius_hit) |
268 | return -1; |
||
23 | Werner | 269 | } |
270 | } |
||
271 | |||
272 | // Test 2: test if the crown-"plate" at the bottom of the crown is hit. |
||
25 | Werner | 273 | if (elevation_rad>0. && p_z<m_crownheight) { |
23 | Werner | 274 | // calc. base distance between p and the point where the height of the ray reaches the bottom of the crown: |
275 | double r_hitbottom = dist2d; // for 90° |
||
276 | if (elevation_rad < M_PI_2) { |
||
277 | double d_hitbottom = (m_crownheight - p_z) / tan(elevation_rad); |
||
278 | // calc. position (projected) of that hit point |
||
27 | Werner | 279 | double rx = p_x + cos(azimuth_rad)*d_hitbottom; |
280 | double ry = p_y + sin(azimuth_rad)*d_hitbottom; |
||
23 | Werner | 281 | r_hitbottom = sqrt(rx*rx + ry*ry); |
282 | } |
||
283 | if (r_hitbottom <= m_baseradius) |
||
31 | Werner | 284 | return 1; |
23 | Werner | 285 | } |
27 | Werner | 286 | // Test 3: test for height steps... |
287 | // distance from p to the plane normal to p-vector through the center of the tree |
||
31 | Werner | 288 | // do only when p is |
27 | Werner | 289 | double rx,ry,rhit; |
290 | double d_center = cos(alpha)*dist2d; |
||
31 | Werner | 291 | if (d_center>=0) { |
292 | double h_center = p_z + d_center*tan(elevation_rad); |
||
293 | if (h_center<=m_height && h_center>=m_crownheight) { |
||
294 | rx = p_x + cos(azimuth_rad)*d_center; |
||
295 | ry = p_y + sin(azimuth_rad)*d_center; |
||
296 | rhit = sqrt(rx*rx + ry*ry); |
||
297 | double r_h = m_radiusFormula->calculate(h_center / m_height); |
||
298 | if (rhit < r_h) |
||
299 | return 1; |
||
300 | } |
||
27 | Werner | 301 | } |
302 | |||
303 | // Test 4: "walk" through crown using 1m steps. |
||
304 | double h=floor(p_z); |
||
305 | double d_1m = 1 / tan(elevation_rad); //projected ground distance equivalent of 1m height difference |
||
306 | double d_cur = 0; |
||
307 | if (h!=p_z) { |
||
308 | d_cur += ((h+1)-p_z)*d_1m; |
||
309 | } |
||
310 | |||
311 | while (h<=m_height) { |
||
312 | double r_tree = m_treeHeights[int(h)]; |
||
313 | rx = p_x + cos(azimuth_rad)*d_cur; |
||
314 | ry = p_y + sin(azimuth_rad)*d_cur; |
||
315 | rhit = rx*rx + ry*ry; |
||
31 | Werner | 316 | // hit if inside of radius |
28 | Werner | 317 | if (rhit < r_tree*r_tree) |
31 | Werner | 318 | return 1; |
319 | // no hit: if formerly was inside crown and now left |
||
27 | Werner | 320 | if (inside_crown && rhit > m_baseradius*m_baseradius) |
31 | Werner | 321 | return 0; |
27 | Werner | 322 | |
31 | Werner | 323 | // enter crown |
27 | Werner | 324 | if (!inside_crown && rhit <= m_baseradius*m_baseradius) |
325 | inside_crown = true; |
||
326 | d_cur+=d_1m; |
||
327 | h++; |
||
328 | } |
||
31 | Werner | 329 | return 0; |
23 | Werner | 330 | |
331 | |||
22 | Werner | 332 | } |
25 | Werner | 333 | |
334 | bool LightRoomObject::noHitGuaranteed(const double p_x, const double p_y, const double p_z) |
||
335 | { |
||
336 | // 1. simple: compare height... |
||
337 | if (p_z > m_height) |
||
338 | return true; |
||
339 | // 2. 45° test: |
||
340 | if (p_z > m_height - sqrt(p_x*p_x + p_y*p_y)) // 45°: height = distance from tree center |
||
341 | return true; |
||
342 | |||
343 | return false; |
||
344 | } |
||
345 |