-
Notifications
You must be signed in to change notification settings - Fork 0
/
Field.py
266 lines (237 loc) · 10.5 KB
/
Field.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# -*- coding: utf-8 -*-
"""
Created on Tue Aug 05 19:40:41 2014
@author: Carlos R. Pascual
"""
############################### LIBRERÍAS ###############################
import numpy as np
import matplotlib.pyplot as plt
import Heliostat as ht
############################### FUNCIONES ###############################
def targetCoords(baseHeight):
'''
Para una superficie colectora dividida en 12 cuadros de 5 x 5 m, determina
las coordenadas de los centros de cada cuadro en función de la altura de
la base de la caldera solar.
baseHeight: Altura desde el suelo hasta la base de la superficie colectora
(metros).
'''
x = np.ones(12) * 2.5
for i in xrange(len(x)):
if i % 2 == 1:
x[i] *= -1
y = np.zeros(12)
z = np.array([2.5, 2.5, 7.5, 7.5, 12.5, 12.5, 17.5, 17.5,
22.5, 22.5, 27.5, 27.5]) + baseHeight
return np.transpose(np.vstack([x, y, z]))
############################### CLASES ###############################
class Field(object):
def __init__(self, baseHeight, apertureAngle, firstRowDist,
heightHelio=5, wideHelio=6.3, largeHelio=6.3):
'''
baseHeight: Altura desde el suelo hasta la base de la superficie
colectora (float, metros).
apertureAngle: Ángulo de apertura del campo solar (float, grados).
firstRowDist: Distancia desde el orígen hasta la primera fila de
heliostatos (float, metros).
heightHelio: Altura de la estructura de sujeción del heliostato
(float, metros).
wideHelio: Ancho de la superficie reflectante del heliostato
(float, metros).
largeHelio: Alto de la superficie reflectante del heliostato
(float, metros).
'''
self.baseHeight = float(baseHeight)
self.apertureAngle = apertureAngle * np.pi/180
self.heightHelio = float(heightHelio)
self.wideHelio = float(wideHelio)
self.medDiameter = (float(wideHelio)**2 + float(largeHelio)**2)**0.5
self.firstRowDist = float(firstRowDist)
self.heliostats = []
self.targetCoords = targetCoords(baseHeight)
def getHeliostatNumber(self):
'''
Devuelve el nº de heliostatos que tiene el campo solar.
'''
return len(self.heliostats)
def getTargetCoords(self):
'''
Devuelve las coordenadas del objetivo al que enfoca el heliostato.
'''
return self.targetCoords
def getHeliostats(self):
'''
Devuelve la lista con los heliostatos del campo.
'''
return self.heliostats
def deltaRadDist(self, radialDist, targetHeight, factor=0.25):
'''
Distancia radial entre la fila n y la n+2.
radialDist: Distancia de la fila de heliostatos a la caldera solar
(metros).
targetHeight: Altura a la que se encuentra el objetivo a enfocar
(metros).
factor: factor de corrección (float).
'''
theta = np.arctan((targetHeight - self.heightHelio*0.5) / radialDist)
deltaRadDist = self.medDiameter * ((1.009/theta) - 0.063 + 0.04803*theta)
return deltaRadDist * factor
def deltaAngDist(self, radialDist, targetHeight, factor=0.45):
'''
Distancia angular entre heliostatos de la misma fila.
radialDist: Distancia de la fila de heliostatos a la caldera solar
(metros).
targetHeight: Altura a la que se encuentra el objetivo a enfocar
(metros).
'''
theta = np.arctan((targetHeight - self.heightHelio*0.5) / radialDist)
deltaAngDist = self.medDiameter * (2.170 - 0.6589*theta + 1.247*theta**2)
return deltaAngDist * factor
def maxHeliosInRow(self, radialDist, targetHeight):
'''
Determina cuantos heliostatos caben en una fila situada a una
distancia radialDist de la caldera.
radialDist: Distancia de la fila a la caldera (metros).
targetHeight: Altura a la que se encuentra el objetivo a enfocar
(metros).
'''
arcLength = self.apertureAngle * radialDist
angularSeparation = self.deltaAngDist(radialDist, targetHeight)
return int(arcLength // angularSeparation)
def assignCartesianCoords(self, rowDist, targetHeight, position,
maxHeliosInRow):
'''
Asigna coordenadas cartesianas a un heliostato.
radialDist: Distancia a la fila donde se encuentra el heliostato (metros).
targetHeight: Altura a la que se encuentra el objetivo a enfocar
(metros).
maxHeliosInRow: nº máximo de heliostatos que cabe en la fila.
'''
heliosDist = self.deltaAngDist(rowDist, targetHeight)
# Si el numero max de heliostatos en la fila es impar:
if maxHeliosInRow % 2 == 1:
if position[1] % 2 == 1:
n = (position[1] - 1) * 0.5
angle = (heliosDist / rowDist) * n
else:
n = position[1] * 0.5
angle = -(heliosDist / rowDist) * n
# Si el número máximo no es impar:
else:
initAngle = heliosDist * 0.5 / rowDist
if position[1] % 2 == 1:
n = (position[1] - 1) * 0.5
angle = initAngle + (heliosDist / rowDist) * n
else:
n = position[1] * 0.5
angle = -initAngle - (heliosDist / rowDist) * (n - 1)
# Coordenadas polares a cartesianas:
x = rowDist * np.sin(angle)
y = rowDist * np.cos(angle)
return (x, y)
def placeHeliostat(self, targetPoint, solarVector):
'''
Asigna una nueva posición a un heliostato y calcula sus coordenadas
cartesianas.
targetPoint: Coordenadas XYZ del objetivo a enfocar (metros).
solarVector: Vector de dirección solar.
'''
targetHeight = targetPoint[2]
# Si se trata del primer heliostato del campo:
if len(self.heliostats) == 0:
rowDistance = self.firstRowDist
maxHeliosInRow = self.maxHeliosInRow(rowDistance, targetHeight)
position = (1, 1)
# Si ya hay al menos un heliostato:
else:
xLast, yLast = self.heliostats[-1].getCoordinates()[:2]
rowDistance = (xLast**2 + yLast**2)**0.5
maxHeliosInRow = self.maxHeliosInRow(rowDistance, targetHeight)
lastPosition = self.heliostats[-1].getPosition()
# Si la fila no está llena aun:
if lastPosition[1] < maxHeliosInRow:
position = (lastPosition[0], lastPosition[1] + 1)
# Si no caben más heliostatos en la fila:
else:
position = (lastPosition[0] + 1, 1)
rowDistance += 0.5 * self.deltaRadDist(rowDistance,
targetHeight)
maxHeliosInRow = self.maxHeliosInRow(rowDistance,
targetHeight)
# Crear y añadir un nuevo heliostato a la lista:
xyCoord = self.assignCartesianCoords(rowDistance, targetHeight,
position, maxHeliosInRow)
newHelio = ht.Heliostat(xyCoord, targetPoint, solarVector, position)
self.heliostats.append(newHelio)
def draw(self):
'''
Representa gráficamente el campo solar.
'''
X, Y = [], []
for helio in self.heliostats:
x, y = helio.getCoordinates()[:2]
X.append(x)
Y.append(y)
plt.xlabel('Coordenada X (m)')
plt.ylabel('Coordenada Y (m)')
plt.scatter(X, Y, s=5)
plt.show()
def isTowerShading(self, helioCoord, solarAngles):
'''
Determina si un heliostato está sombreado por la torre en el mediodía
solar.
helioCoord: Coordenadas cartesianas del heliostato (lista, metros)
solarAngles: Ángulos solares.
'''
towerHeight = self.baseHeight + 30
solarHeight = solarAngles[0]
if solarHeight >= np.pi/2:
return False
xShadow = 5
yShadow = towerHeight / np.tan(solarHeight)
xCoord, yCoord = helioCoord[0], helioCoord[1]
if abs(xCoord) <= xShadow and yCoord <= yShadow:
return True
return False
def surfaceShaded(self, heliostat2, solarVector):
'''
Calcula la superficie que proyecta un heliostato sobre el que tiene
detrás.
heliostat2: Heliostato sombreado, 1 es para el que proyecta sombra.
solarvector: Vector de posición solar.
'''
position2 = heliostat2.getPosition()
coordinates2 = heliostat2.getCoordinates()
heliostat1 = None
# Buscar el heliostato que sombrea a heliostat2:
for heliostat in self.heliostats:
position1 = heliostat.getPosition()
coordinates1 = heliostat.getCoordinates()
# Si heliostat está en la fila anterior a heliostat2:
if position1[0] == position2[0] - 1:
xDistance = abs(coordinates2[0] - coordinates1[0])
# Si existe solapamiento entre ellos:
if xDistance < self.wideHelio:
horizontalShadow = self.wideHelio - xDistance
heliostat1 = heliostat
break
# Si no recibe sombra de ninguno:
if heliostat1 == None:
return 0.0
# Cálculo de la sombra vertical
alpha1 = heliostat1.getOrientationAngles()[0]
alpha2 = heliostat2.getOrientationAngles()[0]
gamma = np.arcsin(solarVector[2])
Y1 = coordinates1[1] + 0.5*self.heightHelio * np.cos(alpha1)
Z1 = coordinates1[2] + 0.5*self.heightHelio * np.sin(alpha1)
Y2 = coordinates2[1] - 0.5*self.heightHelio * np.cos(alpha2)
Z2 = coordinates2[2] - 0.5*self.heightHelio * np.sin(alpha2)
f1 = Y1*np.tan(gamma) + Y2*np.tan(alpha2) + (Z1 - Z2)
f2 = np.tan(alpha2) + np.tan(gamma)
Ys = f1 / f2
# Si la sombra vertical es mas baja que la parte baja del heliostato:
if Ys <= Y2:
return 0.0
Zs = np.tan(alpha2)*(Ys - Y2) + Z2
verticalShadow = ((Ys - Y2)**2 + (Zs - Z2)**2)**0.5
return horizontalShadow * verticalShadow