Skip to content

Commit 8275155

Browse files
authored
Calculate Z array and account for tracker height in shading (#7)
* Calculate Z array and account for tracker height in shading * Correct y_0 calculation and implement relative_slope * Update documentation and layout plot, fix code style * Separate original PR code into modules * Delete two_axis_tracker_shading.py * Update doc * Add horizon line for tilted ground
1 parent c4b3996 commit 8275155

File tree

3 files changed

+66
-22
lines changed

3 files changed

+66
-22
lines changed

twoaxistracking/layout.py

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ def _rotate_origin(x, y, rotation_deg):
1414

1515
def generate_field_layout(gcr, collector_area, L_min, neighbor_order,
1616
aspect_ratio=None, offset=None, rotation=None,
17-
layout_type=None, plot=False):
17+
layout_type=None, slope_azimuth=0,
18+
slope_tilt=0, plot=False):
1819
"""
1920
Generate a regularly-spaced collector field layout.
2021
@@ -51,19 +52,31 @@ def generate_field_layout(gcr, collector_area, L_min, neighbor_order,
5152
Counterclockwise rotation of the field in degrees. 0 <= rotation < 180
5253
layout_type: {square, square_rotated, hexagon_e_w, hexagon_n_s}, optional
5354
Specification of the special layout type (only depend on gcr).
54-
plot: bool, default: True
55+
slope_azimuth : float, optional
56+
Direction of normal to slope on horizontal [degrees]
57+
slope_tilt : float, optional
58+
Tilt of slope relative to horizontal [degrees]
59+
plot: bool, default: False
5560
Whether to plot the field layout.
5661
5762
Returns
5863
-------
5964
X: array of floats
60-
x coordinates of neighboring trackers.
65+
Distance of neighboring trackers to the reference tracker in the east-
66+
west direction. East is positive.
6167
Y: array of floats
62-
y coordinates of neighboring trackers.
68+
Distance of neighboring trackers to the reference tracker in the north-
69+
south direction. North is positive.
70+
Z: array of floats
71+
Relative heights of neighboring trackers.
6372
tracker_distance: array of floats
64-
Distances between neighboring trackers and reference tracker.
73+
Distances between neighboring trackers and the reference tracker.
6574
relative_azimuth: array of floats
66-
Relative azimuth between neigboring trackers and reference tracker.
75+
Relative azimuth of neighboring trackers - measured clockwise from
76+
north [degrees].
77+
relative_slope: array of floats
78+
Slope between neighboring trackers and reference tracker. A positive
79+
slope means neighboring collector is higher than reference collector.
6780
6881
References
6982
----------
@@ -128,6 +141,11 @@ def generate_field_layout(gcr, collector_area, L_min, neighbor_order,
128141
X = X * aspect_ratio
129142
# Apply field rotation
130143
X, Y = _rotate_origin(X, Y, rotation)
144+
# Calculate relative tracker height based on surface slope
145+
Z = - X * np.sin(np.deg2rad(slope_azimuth)) * \
146+
np.tan(np.deg2rad(slope_tilt)) \
147+
- Y * np.cos(np.deg2rad(slope_azimuth)) * \
148+
np.tan(np.deg2rad(slope_tilt))
131149
# Calculate and apply the scaling factor based on GCR
132150
scaling = np.sqrt(collector_area / (gcr * aspect_ratio))
133151
X, Y = X*scaling, Y*scaling
@@ -136,9 +154,12 @@ def generate_field_layout(gcr, collector_area, L_min, neighbor_order,
136154
tracker_distance = np.sqrt(X**2 + Y**2)
137155
# The relative azimuth is defined clockwise eastwards from north
138156
relative_azimuth = np.mod(450-np.rad2deg(np.arctan2(Y, X)), 360)
157+
# Relative slope of collectors
158+
# positive means collector is higher than reference collector
159+
relative_slope = -np.cos(np.deg2rad(slope_azimuth - relative_azimuth)) * slope_tilt # noqa: E501
139160

140161
# Visualize layout
141162
if plot:
142-
plotting._plot_field_layout(X, Y, L_min)
163+
plotting._plot_field_layout(X, Y, Z, L_min)
143164

144-
return X, Y, tracker_distance, relative_azimuth
165+
return X, Y, Z, tracker_distance, relative_azimuth, relative_slope

twoaxistracking/plotting.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,40 @@
22
from matplotlib.collections import EllipseCollection
33
from matplotlib.collections import PatchCollection
44
from matplotlib.patches import Polygon
5+
import matplotlib.colors as mcolors
6+
from matplotlib import cm
57

68

7-
def _plot_field_layout(X, Y, L_min):
9+
def _plot_field_layout(X, Y, Z, L_min):
810
"""Plot field layout."""
9-
fig, ax = plt.subplots(figsize=(6, 6))
11+
# 0.000001 is added/subtracted for the limits in order for the colormap
12+
# to correctly display the middle color when all tracker Z coords are zero
13+
norm = mcolors.Normalize(vmin=min(Z)-0.000001, vmax=max(Z)+0.000001)
14+
cmap = cm.viridis_r
15+
colors = cmap(norm(Z))
16+
fig, ax = plt.subplots(figsize=(6, 6), subplot_kw={'aspect': 'equal'})
1017
# Plot a circle with a diameter equal to L_min
1118
ax.add_collection(EllipseCollection(widths=L_min, heights=L_min,
1219
angles=0, units='xy',
13-
facecolors='white',
20+
facecolors=colors,
1421
edgecolors=("black",),
1522
linewidths=(1,),
1623
offsets=list(zip(X, Y)),
1724
transOffset=ax.transData))
18-
# Add a circle for the origin
25+
# Similarly, add a circle for the origin
1926
ax.add_collection(EllipseCollection(widths=L_min, heights=L_min,
2027
angles=0, units='xy',
2128
facecolors='red',
2229
edgecolors=("black",),
2330
linewidths=(1,), offsets=[0, 0],
2431
transOffset=ax.transData))
2532
plt.axis('equal')
26-
ax.grid()
27-
ax.set_ylim(min(Y)-L_min, max(Y)+L_min)
28-
ax.set_xlim(min(X)-L_min, max(X)+L_min)
33+
fig.colorbar(cm.ScalarMappable(norm=norm, cmap=cmap), ax=ax, shrink=0.8,
34+
label='Relative tracker height (vertical)')
35+
lower_lim = min(min(X), min(Y)) - L_min
36+
upper_lim = max(max(X), max(Y)) + L_min
37+
ax.set_ylim(lower_lim, upper_lim)
38+
ax.set_xlim(lower_lim, upper_lim)
2939

3040

3141
def _plot_shading(collector_geometry, unshaded_geomtry, shade_geometries):

twoaxistracking/shading.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,18 @@ def _rotate_origin(x, y, rotation_deg):
1212
return xx, yy
1313

1414

15-
def shaded_fraction(solar_azimuth, solar_elevation,
15+
def shaded_fraction(solar_elevation, solar_azimuth,
1616
collector_geometry, L_min, tracker_distance,
17-
relative_azimuth, plot=False):
17+
relative_azimuth, relative_slope,
18+
slope_azimuth=0, slope_tilt=0, plot=False):
1819
"""Calculate the shaded fraction for any layout of two-axis tracking collectors.
1920
2021
Parameters
2122
----------
22-
solar_azimuth: float
23-
Solar azimuth angle in degrees.
2423
solar_elevation: float
2524
Solar elevation angle in degrees.
25+
solar_azimuth: float
26+
Solar azimuth angle in degrees.
2627
collector_geometry: Shapely geometry object
2728
The collector aperture geometry.
2829
L_min: float
@@ -32,6 +33,13 @@ def shaded_fraction(solar_azimuth, solar_elevation,
3233
Distances between neighboring trackers and reference tracker.
3334
relative_azimuth: array of floats
3435
Relative azimuth between neigboring trackers and reference tracker.
36+
relative_slope: array of floats
37+
Slope between neighboring trackers and reference tracker. A positive
38+
slope means neighboring collector is higher than reference collector.
39+
slope_azimuth : float
40+
Direction of normal to slope on horizontal [degrees].
41+
slope_tilt : float
42+
Tilt of slope relative to horizontal [degrees].
3543
plot: bool, default: True
3644
Whether to plot the projected shadows.
3745
@@ -43,16 +51,21 @@ def shaded_fraction(solar_azimuth, solar_elevation,
4351
# If the sun is below the horizon, set the shaded fraction to nan
4452
if solar_elevation < 0:
4553
return np.nan
54+
# Set shading fraction to 1 (fully shaded) if the solar elevation is below
55+
# the horizon line caused by the tilted ground
56+
elif solar_elevation < - np.cos(np.deg2rad(slope_azimuth-solar_azimuth)) * slope_tilt:
57+
return 1
4658

4759
azimuth_difference = solar_azimuth - relative_azimuth
4860

4961
# Create mask to only calculate shading for collectors within +/-90° view
5062
mask = np.where(np.cos(np.deg2rad(azimuth_difference)) > 0)
5163

5264
xoff = tracker_distance[mask]*np.sin(np.deg2rad(azimuth_difference[mask]))
53-
yoff = -tracker_distance[mask]\
54-
* np.cos(np.deg2rad(azimuth_difference[mask]))\
55-
* np.sin(np.deg2rad(solar_elevation))
65+
yoff = - tracker_distance[mask] *\
66+
np.cos(np.deg2rad(azimuth_difference[mask])) * \
67+
np.sin(np.deg2rad(solar_elevation-relative_slope[mask])) / \
68+
np.cos(np.deg2rad(relative_slope[mask]))
5669

5770
# Initialize the unshaded area as the collector area
5871
unshaded_geomtry = collector_geometry

0 commit comments

Comments
 (0)