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