@@ -173,6 +173,32 @@ def vf_ground_sky_2d(rotation, gcr, x, pitch, height, max_rows=10):
173173 return vf
174174
175175
176+ def _dist (p1 , p2 ):
177+ return ((p1 [0 ] - p2 [0 ])** 2 + (p1 [1 ] - p2 [1 ])** 2 )** 0.5
178+
179+
180+ def _angle (p1 , p2 ):
181+ return np .arctan2 (p2 [1 ] - p1 [1 ], p2 [0 ] - p1 [0 ])
182+
183+
184+ def _obstructed_string_length (p1 , p2 , ob_left , ob_right ):
185+ # string length calculations for Hottel's crossed strings method,
186+ # considering view obstructions from the left and right.
187+ # all inputs are (x, y) points.
188+
189+ # unobstructed length
190+ d = _dist (p1 , p2 )
191+ # obstructed on the left
192+ d = np .where (_angle (p1 , p2 ) > _angle (p1 , ob_left ),
193+ _dist (p1 , ob_left ) + _dist (ob_left , p2 ),
194+ d )
195+ # obstructed on the right
196+ d = np .where (_angle (p1 , p2 ) < _angle (p1 , ob_right ),
197+ _dist (p1 , ob_right ) + _dist (ob_right , p2 ),
198+ d )
199+ return d
200+
201+
176202def vf_ground_sky_2d_integ (surface_tilt , gcr , height , pitch , max_rows = 10 ,
177203 npoints = None , vectorize = None ):
178204 """
@@ -208,50 +234,40 @@ def vf_ground_sky_2d_integ(surface_tilt, gcr, height, pitch, max_rows=10,
208234 if npoints is not None or vectorize is not None :
209235 msg = (
210236 "The `npoints` and `vectorize` parameters have no effect and will "
211- "be removed in a future version."
237+ "be removed in a future version." # TODO make this better
212238 )
213239 warnings .warn (msg , pvlibDeprecationWarning )
214240
215241 input_is_scalar = np .isscalar (surface_tilt )
242+
216243 collector_width = pitch * gcr
217244 surface_tilt = np .atleast_2d (surface_tilt )
245+
218246 k = np .arange (- max_rows , max_rows + 1 )[:, np .newaxis ]
219247
248+ # primary crossed string points:
249+ # a, b: boundaries of ground segment
250+ # c, d: upper module edges
220251 a = (0 , 0 )
221252 b = (pitch , 0 )
222253 c = ((k + 1 )* pitch - 0.5 * collector_width * cosd (surface_tilt ),
223254 height + 0.5 * collector_width * sind (surface_tilt ))
224255 d = (c [0 ] - pitch , c [1 ])
225-
226- o1 = (d [0 ] + collector_width * cosd (surface_tilt ), d [1 ] - collector_width * sind (surface_tilt ))
227- o2 = (o1 [0 ] + pitch , o1 [1 ])
228-
229- def dist (p1 , p2 ):
230- return ((p1 [0 ] - p2 [0 ])** 2 + (p1 [1 ] - p2 [1 ])** 2 )** 0.5
231-
232- def angle (p1 , p2 ):
233- return np .arctan2 (p2 [1 ] - p1 [1 ], p2 [0 ] - p1 [0 ])
234-
235- def obstructed_string_length (p1 , p2 , ob_left , ob_right ):
236- # unobstructed length
237- d = dist (p1 , p2 )
238- # obstructed on the left
239- d = np .where (angle (p1 , p2 ) > angle (p1 , ob_left ),
240- dist (p1 , ob_left ) + dist (ob_left , p2 ),
241- d )
242- # obstructed on the right
243- d = np .where (angle (p1 , p2 ) < angle (p1 , ob_right ),
244- dist (p1 , ob_right ) + dist (ob_right , p2 ),
245- d )
246- return d
247-
248- ac = obstructed_string_length (a , c , o1 , o2 )
249- ad = obstructed_string_length (a , d , o1 , o2 )
250- bc = obstructed_string_length (b , c , o1 , o2 )
251- bd = obstructed_string_length (b , d , o1 , o2 )
252-
253- vf_per_slat = 0.5 * (1 / pitch ) * ((ac + bd ) - (bc + ad ))
254- vf_total = np .sum (np .clip (vf_per_slat , a_min = 0 , a_max = None ), axis = 0 )
256+
257+ # view obstruction points (lower module edges)
258+ obs_left = (d [0 ] + collector_width * cosd (surface_tilt ),
259+ d [1 ] - collector_width * sind (surface_tilt ))
260+ obs_right = (obs_left [0 ] + pitch , obs_left [1 ])
261+
262+ # hottel string lengths, considering obstructions
263+ ac = _obstructed_string_length (a , c , obs_left , obs_right )
264+ ad = _obstructed_string_length (a , d , obs_left , obs_right )
265+ bc = _obstructed_string_length (b , c , obs_left , obs_right )
266+ bd = _obstructed_string_length (b , d , obs_left , obs_right )
267+
268+ # crossed string formula for VF
269+ vf_per_slat = np .maximum (0.5 * (1 / pitch ) * ((ac + bd ) - (bc + ad )), 0 )
270+ vf_total = np .sum (vf_per_slat , axis = 0 )
255271
256272 if input_is_scalar :
257273 vf_total = vf_total .item ()
0 commit comments