python-astro 1.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- Astro.py +10 -0
- Geo.py +283 -0
- Moon.py +356 -0
- Sun.py +154 -0
- python_astro-1.10.dist-info/METADATA +12 -0
- python_astro-1.10.dist-info/RECORD +9 -0
- python_astro-1.10.dist-info/WHEEL +5 -0
- python_astro-1.10.dist-info/licenses/LICENSE +674 -0
- python_astro-1.10.dist-info/top_level.txt +4 -0
Astro.py
ADDED
Geo.py
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import math
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
EARTH_WGS84_RADIUS = 6371000.0
|
|
5
|
+
EARTH_RADIUS = 6371000.0
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Location:
|
|
9
|
+
def __init__(self, latitude, longitude):
|
|
10
|
+
self.latitude = latitude
|
|
11
|
+
self.longitude = longitude
|
|
12
|
+
|
|
13
|
+
def distance(self, other):
|
|
14
|
+
dLat = (other.latitude - self.latitude) * math.pi / 180.0
|
|
15
|
+
dLon = (other.longitude - self.longitude) * math.pi / 180.0
|
|
16
|
+
sinDLat = math.sin(dLat / 2)
|
|
17
|
+
sinDLon = math.sin(dLon / 2)
|
|
18
|
+
a = sinDLat * sinDLat + \
|
|
19
|
+
math.cos(self.latitude * math.pi / 180.0) * math.cos(other.latitude * math.pi / 180.0) * \
|
|
20
|
+
sinDLon * sinDLon
|
|
21
|
+
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
|
|
22
|
+
return EARTH_RADIUS * c
|
|
23
|
+
|
|
24
|
+
def direction(self, other):
|
|
25
|
+
lat1 = self.latitude * math.pi / 180.0
|
|
26
|
+
lat2 = other.latitude * math.pi / 180.0
|
|
27
|
+
dLon = (other.longitude - self.longitude) * math.pi / 180.0
|
|
28
|
+
y = math.sin(dLon) * math.cos(lat2)
|
|
29
|
+
x = math.cos(lat1) * math.sin(lat2) - \
|
|
30
|
+
math.sin(lat1) * math.cos(lat2) * math.cos(dLon)
|
|
31
|
+
bearing = math.atan2(y, x) * 180.0 / math.pi
|
|
32
|
+
return (bearing + 360.0) % 360.0
|
|
33
|
+
|
|
34
|
+
def __repr__(self):
|
|
35
|
+
return f"Location({self.latitude}, {self.longitude})"
|
|
36
|
+
|
|
37
|
+
def __eq__(self, other):
|
|
38
|
+
return isinstance(other, Location) and \
|
|
39
|
+
self.latitude == other.latitude and self.longitude == other.longitude
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
DIASPORA_NORTH = Location(33.317, 35.767)
|
|
43
|
+
DIASPORA_SOUTH = Location(29.483, 34.900)
|
|
44
|
+
DIASPORA_EAST = Location(32.933, 35.883)
|
|
45
|
+
DIASPORA_WEST = Location(31.220, 34.268)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Place:
|
|
49
|
+
def __init__(self, id, latitude, longitude, area):
|
|
50
|
+
self.id = id
|
|
51
|
+
self.latitude = latitude
|
|
52
|
+
self.longitude = longitude
|
|
53
|
+
self.area = area
|
|
54
|
+
|
|
55
|
+
def location(self):
|
|
56
|
+
return Location(self.latitude, self.longitude)
|
|
57
|
+
|
|
58
|
+
def isDiaspora(self):
|
|
59
|
+
centerLat = (DIASPORA_NORTH.latitude + DIASPORA_SOUTH.latitude) / 2.0
|
|
60
|
+
centerLon = (DIASPORA_EAST.longitude + DIASPORA_WEST.longitude) / 2.0
|
|
61
|
+
semiMajor = (DIASPORA_NORTH.latitude - DIASPORA_SOUTH.latitude) / 2.0
|
|
62
|
+
semiMinor = (DIASPORA_EAST.longitude - DIASPORA_WEST.longitude) / 2.0
|
|
63
|
+
x = self.latitude - centerLat
|
|
64
|
+
y = self.longitude - centerLon
|
|
65
|
+
return ((x * x) / (semiMajor * semiMajor) + (y * y) / (semiMinor * semiMinor)) > 1.0
|
|
66
|
+
|
|
67
|
+
def __repr__(self):
|
|
68
|
+
return f"Place({self.id})"
|
|
69
|
+
|
|
70
|
+
def __eq__(self, other):
|
|
71
|
+
return isinstance(other, Place) and self.id == other.id
|
|
72
|
+
|
|
73
|
+
def __hash__(self):
|
|
74
|
+
return hash(self.id)
|
|
75
|
+
|
|
76
|
+
# Static methods operating on all known places
|
|
77
|
+
@staticmethod
|
|
78
|
+
def fromCoordinates(latitude, longitude):
|
|
79
|
+
for place in Place.all():
|
|
80
|
+
if abs(place.latitude - latitude) < 1e-6 and abs(place.longitude - longitude) < 1e-6:
|
|
81
|
+
return place
|
|
82
|
+
return Place.NONE
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def closest(loc):
|
|
86
|
+
closest = None
|
|
87
|
+
minDist = math.inf
|
|
88
|
+
for p in Place.all():
|
|
89
|
+
d = loc.distance(p.location())
|
|
90
|
+
if d < minDist:
|
|
91
|
+
minDist = d
|
|
92
|
+
closest = p
|
|
93
|
+
return closest
|
|
94
|
+
|
|
95
|
+
# Ordered list (matches original enum ordinal order)
|
|
96
|
+
@staticmethod
|
|
97
|
+
def all():
|
|
98
|
+
return [
|
|
99
|
+
Place.OFAKIM,
|
|
100
|
+
Place.OR_HAGANUZ,
|
|
101
|
+
Place.EILAT,
|
|
102
|
+
Place.ELAD,
|
|
103
|
+
Place.AMIRIM,
|
|
104
|
+
Place.ARIEL,
|
|
105
|
+
Place.ASHDOD,
|
|
106
|
+
Place.ASHKELON,
|
|
107
|
+
Place.BEER_YAAKOV,
|
|
108
|
+
Place.BEER_SHEVA,
|
|
109
|
+
Place.BEIT_SHEAN,
|
|
110
|
+
Place.BEIT_SHEMESH,
|
|
111
|
+
Place.BNEI_BRAK,
|
|
112
|
+
Place.BAR_YOCHAI,
|
|
113
|
+
Place.BAT_YAM,
|
|
114
|
+
Place.GIVAT_SHMUEL,
|
|
115
|
+
Place.JISH,
|
|
116
|
+
Place.GAN_YAVNE,
|
|
117
|
+
Place.GANEI_TIKVA,
|
|
118
|
+
Place.GIVATAYIM,
|
|
119
|
+
Place.DIMONA,
|
|
120
|
+
Place.DALTON,
|
|
121
|
+
Place.HOD_HASHARON,
|
|
122
|
+
Place.HERZLIYA,
|
|
123
|
+
Place.HADERA,
|
|
124
|
+
Place.HOLON,
|
|
125
|
+
Place.HAIFA,
|
|
126
|
+
Place.HARISH,
|
|
127
|
+
Place.TIBERIAS,
|
|
128
|
+
Place.TIRAT_CARMEL,
|
|
129
|
+
Place.YAVNE,
|
|
130
|
+
Place.YOKNEAM_ILLIT,
|
|
131
|
+
Place.YEHUD_MONOSSON,
|
|
132
|
+
Place.JERUSALEM,
|
|
133
|
+
Place.KARMIEL,
|
|
134
|
+
Place.KFAR_SABA,
|
|
135
|
+
Place.KFAR_YONA,
|
|
136
|
+
Place.KIRYAT_ATA,
|
|
137
|
+
Place.KIRYAT_BIALIK,
|
|
138
|
+
Place.KIRYAT_GAT,
|
|
139
|
+
Place.KIRYAT_MALAKHI,
|
|
140
|
+
Place.KIRYAT_MOTZKIN,
|
|
141
|
+
Place.KIRYAT_ONO,
|
|
142
|
+
Place.KIRYAT_SHMONA,
|
|
143
|
+
Place.KIRYAT_YAM,
|
|
144
|
+
Place.KATZRIN,
|
|
145
|
+
Place.LOD,
|
|
146
|
+
Place.MAALOT_TARSHIHA,
|
|
147
|
+
Place.MODIIN,
|
|
148
|
+
Place.MODIIN_ILLIT,
|
|
149
|
+
Place.MIGDAL_HAEMEK,
|
|
150
|
+
Place.MAALE_ADUMIM,
|
|
151
|
+
Place.MERON,
|
|
152
|
+
Place.NAHARIYA,
|
|
153
|
+
Place.NESS_ZIONA,
|
|
154
|
+
Place.NETANYA,
|
|
155
|
+
Place.NETIVOT,
|
|
156
|
+
Place.NESHER,
|
|
157
|
+
Place.NOF_HAGALIL,
|
|
158
|
+
Place.SIFSUFA,
|
|
159
|
+
Place.AKKO,
|
|
160
|
+
Place.AFULA,
|
|
161
|
+
Place.ARAD,
|
|
162
|
+
Place.OR_AKIVA,
|
|
163
|
+
Place.OR_YEHUDA,
|
|
164
|
+
Place.FAROD,
|
|
165
|
+
Place.PETAH_TIKVA,
|
|
166
|
+
Place.SAFED,
|
|
167
|
+
Place.KADITA,
|
|
168
|
+
Place.RAHAT,
|
|
169
|
+
Place.ROSH_HAAYIN,
|
|
170
|
+
Place.RISHON_LEZION,
|
|
171
|
+
Place.REHOVOT,
|
|
172
|
+
Place.RAMLA,
|
|
173
|
+
Place.RAMAT_GAN,
|
|
174
|
+
Place.RAMAT_HASHARON,
|
|
175
|
+
Place.RAANANA,
|
|
176
|
+
Place.SDEROT,
|
|
177
|
+
Place.SHEFER,
|
|
178
|
+
Place.TEL_AVIV,
|
|
179
|
+
Place.UMAN,
|
|
180
|
+
Place.AMSTERDAM,
|
|
181
|
+
Place.BANGKOK,
|
|
182
|
+
Place.BUDAPEST,
|
|
183
|
+
Place.DUBAI,
|
|
184
|
+
Place.LONDON,
|
|
185
|
+
Place.NEW_YORK,
|
|
186
|
+
Place.PARIS,
|
|
187
|
+
Place.KYIV,
|
|
188
|
+
Place.ROME,
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# Static instances
|
|
193
|
+
Place.NONE = Place('NONE', 0.0, 0.0, 0.0)
|
|
194
|
+
Place.OFAKIM = Place('OFAKIM', 31.3167, 34.6167, 16.35)
|
|
195
|
+
Place.OR_HAGANUZ = Place('OR_HAGANUZ', 33.0000, 35.4400, 0.32)
|
|
196
|
+
Place.EILAT = Place('EILAT', 29.5577, 34.9519, 101.49)
|
|
197
|
+
Place.ELAD = Place('ELAD', 32.0523, 34.9510, 3.49)
|
|
198
|
+
Place.AMIRIM = Place('AMIRIM', 32.9370, 35.4500, 0.76)
|
|
199
|
+
Place.ARIEL = Place('ARIEL', 32.1037, 35.1774, 14.48)
|
|
200
|
+
Place.ASHDOD = Place('ASHDOD', 31.8044, 34.6553, 63.92)
|
|
201
|
+
Place.ASHKELON = Place('ASHKELON', 31.6688, 34.5743, 52.32)
|
|
202
|
+
Place.BEER_YAAKOV = Place('BEER_YAAKOV', 31.94, 34.84, 9.46)
|
|
203
|
+
Place.BEER_SHEVA = Place('BEER_SHEVA', 31.2530, 34.7915, 117.39)
|
|
204
|
+
Place.BEIT_SHEAN = Place('BEIT_SHEAN', 32.4970, 35.4980, 10.97)
|
|
205
|
+
Place.BEIT_SHEMESH = Place('BEIT_SHEMESH', 31.7473, 34.9876, 38.29)
|
|
206
|
+
Place.BNEI_BRAK = Place('BNEI_BRAK', 32.0834, 34.8331, 7.35)
|
|
207
|
+
Place.BAR_YOCHAI = Place('BAR_YOCHAI', 32.9980, 35.4460, 0.78)
|
|
208
|
+
Place.BAT_YAM = Place('BAT_YAM', 32.0170, 34.7500, 9.41)
|
|
209
|
+
Place.GIVAT_SHMUEL = Place('GIVAT_SHMUEL', 32.0770, 34.8490, 2.58)
|
|
210
|
+
Place.JISH = Place('JISH', 33.0240, 35.4420, 6.92)
|
|
211
|
+
Place.GAN_YAVNE = Place('GAN_YAVNE', 31.7870, 34.7060, 4.47)
|
|
212
|
+
Place.GANEI_TIKVA = Place('GANEI_TIKVA', 32.08, 34.92, 2.16)
|
|
213
|
+
Place.GIVATAYIM = Place('GIVATAYIM', 32.072, 34.812, 3.24)
|
|
214
|
+
Place.DIMONA = Place('DIMONA', 31.0694, 35.0330, 220.46)
|
|
215
|
+
Place.DALTON = Place('DALTON', 33.0150, 35.4850, 1.31)
|
|
216
|
+
Place.HOD_HASHARON = Place('HOD_HASHARON', 32.15, 34.89, 19.26)
|
|
217
|
+
Place.HERZLIYA = Place('HERZLIYA', 32.1627, 34.8446, 24.07)
|
|
218
|
+
Place.HADERA = Place('HADERA', 32.4360, 34.9160, 56.28)
|
|
219
|
+
Place.HOLON = Place('HOLON', 32.0114, 34.7794, 19.04)
|
|
220
|
+
Place.HAIFA = Place('HAIFA', 32.7940, 34.9896, 72.93)
|
|
221
|
+
Place.HARISH = Place('HARISH', 32.4620, 35.0480, 9.4)
|
|
222
|
+
Place.TIBERIAS = Place('TIBERIAS', 32.7922, 35.5312, 16.19)
|
|
223
|
+
Place.TIRAT_CARMEL = Place('TIRAT_CARMEL', 32.77, 34.97, 6.71)
|
|
224
|
+
Place.YAVNE = Place('YAVNE', 31.8770, 34.7440, 29.18)
|
|
225
|
+
Place.YOKNEAM_ILLIT = Place('YOKNEAM_ILLIT', 32.66, 35.11, 8.31)
|
|
226
|
+
Place.YEHUD_MONOSSON = Place('YEHUD_MONOSSON', 32.03, 34.89, 5.73)
|
|
227
|
+
Place.JERUSALEM = Place('JERUSALEM', 31.7683, 35.2137, 125.55)
|
|
228
|
+
Place.KARMIEL = Place('KARMIEL', 32.9190, 35.3020, 22.03)
|
|
229
|
+
Place.KFAR_SABA = Place('KFAR_SABA', 32.1750, 34.9060, 14.48)
|
|
230
|
+
Place.KFAR_YONA = Place('KFAR_YONA', 32.32, 34.93, 11.51)
|
|
231
|
+
Place.KIRYAT_ATA = Place('KIRYAT_ATA', 32.81, 35.11, 24.16)
|
|
232
|
+
Place.KIRYAT_BIALIK = Place('KIRYAT_BIALIK', 32.83, 35.08, 8.48)
|
|
233
|
+
Place.KIRYAT_GAT = Place('KIRYAT_GAT', 31.6100, 34.7710, 15.85)
|
|
234
|
+
Place.KIRYAT_MALAKHI = Place('KIRYAT_MALAKHI', 31.73, 34.75, 4.58)
|
|
235
|
+
Place.KIRYAT_MOTZKIN = Place('KIRYAT_MOTZKIN', 32.83, 35.08, 3.84)
|
|
236
|
+
Place.KIRYAT_ONO = Place('KIRYAT_ONO', 32.06, 34.85, 4.60)
|
|
237
|
+
Place.KIRYAT_SHMONA = Place('KIRYAT_SHMONA', 33.2070, 35.5710, 14.38)
|
|
238
|
+
Place.KIRYAT_YAM = Place('KIRYAT_YAM', 32.85, 35.07, 11.34)
|
|
239
|
+
Place.KATZRIN = Place('KATZRIN', 32.9920, 35.6900, 4.0)
|
|
240
|
+
Place.LOD = Place('LOD', 31.9530, 34.8930, 14.79)
|
|
241
|
+
Place.MAALOT_TARSHIHA = Place('MAALOT_TARSHIHA', 33.02, 35.29, 9.26)
|
|
242
|
+
Place.MODIIN = Place('MODIIN', 31.8815, 35.0106, 48.33)
|
|
243
|
+
Place.MODIIN_ILLIT = Place('MODIIN_ILLIT', 31.9290, 35.0430, 6.0)
|
|
244
|
+
Place.MIGDAL_HAEMEK = Place('MIGDAL_HAEMEK', 32.67, 35.24, 8.71)
|
|
245
|
+
Place.MAALE_ADUMIM = Place('MAALE_ADUMIM', 31.7770, 35.2980, 8.0)
|
|
246
|
+
Place.MERON = Place('MERON', 32.9837, 35.4508, 1.17)
|
|
247
|
+
Place.NAHARIYA = Place('NAHARIYA', 33.0119, 35.0980, 13.83)
|
|
248
|
+
Place.NESS_ZIONA = Place('NESS_ZIONA', 31.93, 34.80, 15.68)
|
|
249
|
+
Place.NETANYA = Place('NETANYA', 32.3323, 34.8571, 34.75)
|
|
250
|
+
Place.NETIVOT = Place('NETIVOT', 31.4210, 34.5860, 16.22)
|
|
251
|
+
Place.NESHER = Place('NESHER', 32.76, 35.04, 12.94)
|
|
252
|
+
Place.NOF_HAGALIL = Place('NOF_HAGALIL', 32.71, 35.33, 32.86)
|
|
253
|
+
Place.SIFSUFA = Place('SIFSUFA', 33.0120, 35.4300, 1.09)
|
|
254
|
+
Place.AKKO = Place('AKKO', 32.9270, 35.0830, 18.09)
|
|
255
|
+
Place.AFULA = Place('AFULA', 32.6083, 35.2880, 29.17)
|
|
256
|
+
Place.ARAD = Place('ARAD', 31.2583, 35.2140, 126.13)
|
|
257
|
+
Place.OR_AKIVA = Place('OR_AKIVA', 32.51, 34.92, 5.55)
|
|
258
|
+
Place.OR_YEHUDA = Place('OR_YEHUDA', 32.03, 34.86, 6.73)
|
|
259
|
+
Place.FAROD = Place('FAROD', 32.9320, 35.4300, 1.2)
|
|
260
|
+
Place.PETAH_TIKVA = Place('PETAH_TIKVA', 32.0849, 34.8877, 35.77)
|
|
261
|
+
Place.SAFED = Place('SAFED', 32.9646, 35.4969, 29.94)
|
|
262
|
+
Place.KADITA = Place('KADITA', 33.0020, 35.4700, 0.90)
|
|
263
|
+
Place.RAHAT = Place('RAHAT', 31.39, 34.75, 33.48)
|
|
264
|
+
Place.ROSH_HAAYIN = Place('ROSH_HAAYIN', 32.0950, 34.9520, 15.86)
|
|
265
|
+
Place.RISHON_LEZION = Place('RISHON_LEZION', 31.9730, 34.7925, 61.91)
|
|
266
|
+
Place.REHOVOT = Place('REHOVOT', 31.8948, 34.8096, 23.76)
|
|
267
|
+
Place.RAMLA = Place('RAMLA', 31.9290, 34.8660, 13.39)
|
|
268
|
+
Place.RAMAT_GAN = Place('RAMAT_GAN', 32.0834, 34.8106, 16.39)
|
|
269
|
+
Place.RAMAT_HASHARON = Place('RAMAT_HASHARON', 32.15, 34.84, 16.73)
|
|
270
|
+
Place.RAANANA = Place('RAANANA', 32.1833, 34.8700, 14.86)
|
|
271
|
+
Place.SDEROT = Place('SDEROT', 31.5250, 34.5930, 10.66)
|
|
272
|
+
Place.SHEFER = Place('SHEFER', 32.9370, 35.4400, 0.35)
|
|
273
|
+
Place.TEL_AVIV = Place('TEL_AVIV', 32.0853, 34.7818, 57.14)
|
|
274
|
+
Place.UMAN = Place('UMAN', 48.7519, 30.2193, 10.0)
|
|
275
|
+
Place.AMSTERDAM = Place('AMSTERDAM', 52.3676, 4.9041, 219.0)
|
|
276
|
+
Place.BANGKOK = Place('BANGKOK', 13.7563, 100.5018, 1569.0)
|
|
277
|
+
Place.BUDAPEST = Place('BUDAPEST', 47.4979, 19.0402, 525.0)
|
|
278
|
+
Place.DUBAI = Place('DUBAI', 25.2048, 55.2708, 4114.0)
|
|
279
|
+
Place.LONDON = Place('LONDON', 51.5074, -0.1278, 1572.0)
|
|
280
|
+
Place.NEW_YORK = Place('NEW_YORK', 40.7128, -74.0060, 1214.0)
|
|
281
|
+
Place.PARIS = Place('PARIS', 48.8566, 2.3522, 105.4)
|
|
282
|
+
Place.KYIV = Place('KYIV', 50.4501, 30.5234, 839.0)
|
|
283
|
+
Place.ROME = Place('ROME', 41.9028, 12.4964, 1285.0)
|
Moon.py
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
from enum import IntEnum
|
|
2
|
+
import math
|
|
3
|
+
|
|
4
|
+
import Astro
|
|
5
|
+
from Astro import CelestialLocation
|
|
6
|
+
from Date import DateTime, Date, Time
|
|
7
|
+
from Sun import Sun
|
|
8
|
+
|
|
9
|
+
RAD = math.pi / 180.0
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MoonPhaseColor(IntEnum):
|
|
14
|
+
NEW_MOON = 0xB35900
|
|
15
|
+
WAXING_CRESCENT = 0xC6650A
|
|
16
|
+
FIRST_QUARTER = 0xD97614
|
|
17
|
+
WAXING_GIBBOUS = 0xEC8A1E
|
|
18
|
+
FULL_MOON = 0xFF9800
|
|
19
|
+
WANING_GIBBOUS = 0xEC8A1E
|
|
20
|
+
LAST_QUARTER = 0xD97614
|
|
21
|
+
WANING_CRESCENT = 0xC6650A
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class MoonPhaseType(IntEnum):
|
|
25
|
+
NEW_MOON = 1
|
|
26
|
+
WAXING_CRESCENT = 2
|
|
27
|
+
FIRST_QUARTER = 3
|
|
28
|
+
WAXING_GIBBOUS = 4
|
|
29
|
+
FULL_MOON = 5
|
|
30
|
+
WANING_GIBBOUS = 6
|
|
31
|
+
LAST_QUARTER = 7
|
|
32
|
+
WANING_CRESCENT = 8
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class MoonIllumination:
|
|
36
|
+
def __init__(self, moon):
|
|
37
|
+
self.moon = moon
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def phi(self):
|
|
41
|
+
sunLocation = self.moon.sun.celestialLocation
|
|
42
|
+
moonLocation = self.moon.celestialLocation
|
|
43
|
+
return math.acos(
|
|
44
|
+
math.sin(sunLocation.declination) * math.sin(moonLocation.declination) +
|
|
45
|
+
math.cos(sunLocation.declination) * math.cos(moonLocation.declination) *
|
|
46
|
+
math.cos(sunLocation.rightascension - moonLocation.rightascension)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def inc(self):
|
|
51
|
+
return math.atan2(
|
|
52
|
+
self.moon.sun.distance * math.sin(self.phi),
|
|
53
|
+
self.moon.distance - self.moon.sun.distance * math.cos(self.phi)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def fraction(self):
|
|
58
|
+
return (1.0 + math.cos(self.inc)) / 2.0
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def angle(self):
|
|
62
|
+
sunLocation = self.moon.sun.celestialLocation
|
|
63
|
+
moonLocation = self.moon.celestialLocation
|
|
64
|
+
return math.atan2(
|
|
65
|
+
math.cos(sunLocation.declination) * math.sin(sunLocation.rightascension - moonLocation.rightascension),
|
|
66
|
+
math.sin(sunLocation.declination) * math.cos(moonLocation.declination) -
|
|
67
|
+
math.cos(sunLocation.declination) * math.sin(moonLocation.declination) *
|
|
68
|
+
math.cos(sunLocation.rightascension - moonLocation.rightascension)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def phase(self):
|
|
73
|
+
return 0.5 + 0.5 * self.inc * (-1.0 if self.angle < 0.0 else 1.0) / math.pi
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def phaseType(self):
|
|
77
|
+
p = self.phase
|
|
78
|
+
if p < 0.0625 or p >= 0.9375:
|
|
79
|
+
return MoonPhaseType.NEW_MOON
|
|
80
|
+
if p < 0.3125:
|
|
81
|
+
return MoonPhaseType.WAXING_CRESCENT
|
|
82
|
+
if p < 0.4375:
|
|
83
|
+
return MoonPhaseType.FIRST_QUARTER
|
|
84
|
+
if p < 0.5625:
|
|
85
|
+
return MoonPhaseType.FULL_MOON
|
|
86
|
+
if p < 0.6875:
|
|
87
|
+
return MoonPhaseType.WANING_GIBBOUS
|
|
88
|
+
if p < 0.8125:
|
|
89
|
+
return MoonPhaseType.LAST_QUARTER
|
|
90
|
+
return MoonPhaseType.WANING_CRESCENT
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def phaseEmoji(self):
|
|
94
|
+
north = self.moon.location.latitude >= 0
|
|
95
|
+
t = self.phaseType
|
|
96
|
+
if t == MoonPhaseType.NEW_MOON:
|
|
97
|
+
return "🌑"
|
|
98
|
+
if t == MoonPhaseType.WAXING_CRESCENT:
|
|
99
|
+
return "🌒" if north else "🌘"
|
|
100
|
+
if t == MoonPhaseType.FIRST_QUARTER:
|
|
101
|
+
return "🌓" if north else "🌗"
|
|
102
|
+
if t == MoonPhaseType.FULL_MOON:
|
|
103
|
+
return "🌕"
|
|
104
|
+
if t == MoonPhaseType.WANING_GIBBOUS:
|
|
105
|
+
return "🌖" if north else "🌔"
|
|
106
|
+
if t == MoonPhaseType.LAST_QUARTER:
|
|
107
|
+
return "🌗" if north else "🌓"
|
|
108
|
+
if t == MoonPhaseType.WANING_CRESCENT:
|
|
109
|
+
return "🌘" if north else "🌒"
|
|
110
|
+
return "🌑"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class Moon:
|
|
114
|
+
def __init__(self, location, datetime):
|
|
115
|
+
self.location = location
|
|
116
|
+
self.datetime = datetime
|
|
117
|
+
self.sun = Sun(location, datetime)
|
|
118
|
+
|
|
119
|
+
def updateDateTime(self, datetime):
|
|
120
|
+
self.datetime = datetime
|
|
121
|
+
self.sun.updateDateTime(datetime)
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def celestialLocation(self):
|
|
125
|
+
days = self.datetime.julianDay2K
|
|
126
|
+
L = RAD * (218.316 + 13.176396 * days)
|
|
127
|
+
M = RAD * (134.963 + 13.064993 * days)
|
|
128
|
+
F = RAD * (93.272 + 13.229350 * days)
|
|
129
|
+
l = L + RAD * 6.289 * math.sin(M)
|
|
130
|
+
b = RAD * 5.128 * math.sin(F)
|
|
131
|
+
ra = math.atan2(
|
|
132
|
+
math.sin(l) * math.cos(Astro.EARTH_OBLIQUITY) - math.tan(b) * math.sin(Astro.EARTH_OBLIQUITY),
|
|
133
|
+
math.cos(l)
|
|
134
|
+
)
|
|
135
|
+
dec = math.asin(
|
|
136
|
+
math.sin(b) * math.cos(Astro.EARTH_OBLIQUITY) +
|
|
137
|
+
math.cos(b) * math.sin(Astro.EARTH_OBLIQUITY) * math.sin(l)
|
|
138
|
+
)
|
|
139
|
+
return CelestialLocation(ra, dec)
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def distance(self):
|
|
143
|
+
days = self.datetime.julianDay2K
|
|
144
|
+
M = RAD * (134.963 + 13.064993 * days)
|
|
145
|
+
return 385001.0 - 20905.0 * math.cos(M)
|
|
146
|
+
|
|
147
|
+
# @property
|
|
148
|
+
# def altitude(self):
|
|
149
|
+
# lw = RAD * -self.location.longitude
|
|
150
|
+
# phi = RAD * self.location.latitude
|
|
151
|
+
# moonDir = self.celestialLocation
|
|
152
|
+
# H = Moon.siderealTime(self.datetime, lw) - moonDir.rightascension
|
|
153
|
+
# h = math.asin(math.sin(phi) * math.sin(moonDir.declination) +
|
|
154
|
+
# math.cos(phi) * math.cos(moonDir.declination) * math.cos(H))
|
|
155
|
+
# return h + Moon.astroRefraction(h)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def altitude(self):
|
|
160
|
+
lw = RAD * -self.location.longitude
|
|
161
|
+
phi = RAD * self.location.latitude
|
|
162
|
+
moonDir = self.celestialLocation
|
|
163
|
+
H = Moon.siderealTime(self.datetime, lw) - moonDir.rightascension
|
|
164
|
+
h = math.asin(math.sin(phi) * math.sin(moonDir.declination) +
|
|
165
|
+
math.cos(phi) * math.cos(moonDir.declination) * math.cos(H))
|
|
166
|
+
|
|
167
|
+
# Parallax correction (essential for the moon!)
|
|
168
|
+
parallax = math.asin(6371.0 / self.distance) * math.cos(h)
|
|
169
|
+
h = h - parallax # Moon appears lower due to parallax
|
|
170
|
+
|
|
171
|
+
return h + Moon.astroRefraction(h)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@property
|
|
175
|
+
def altitudes(self):
|
|
176
|
+
alts = []
|
|
177
|
+
for h in range(25):
|
|
178
|
+
if h == 24:
|
|
179
|
+
t = DateTime(self.datetime.date.next, Time(0, 0, 0))
|
|
180
|
+
else:
|
|
181
|
+
t = DateTime(self.datetime.date, Time(h, 0, 0))
|
|
182
|
+
m = Moon(self.location, t)
|
|
183
|
+
alts.append(m.altitude)
|
|
184
|
+
return alts
|
|
185
|
+
|
|
186
|
+
def _calculateTimes(self):
|
|
187
|
+
alts = self.altitudes
|
|
188
|
+
riseHour = None
|
|
189
|
+
setHour = None
|
|
190
|
+
|
|
191
|
+
for i in range(23):
|
|
192
|
+
h0 = alts[i]
|
|
193
|
+
h1 = alts[i + 1]
|
|
194
|
+
h2 = alts[i + 2]
|
|
195
|
+
|
|
196
|
+
a = (h0 + h2) / 2.0 - h1
|
|
197
|
+
b = (h2 - h0) / 2.0
|
|
198
|
+
d = b * b - 4.0 * a * h1
|
|
199
|
+
roots = 0
|
|
200
|
+
x1 = 0.0
|
|
201
|
+
x2 = 0.0
|
|
202
|
+
|
|
203
|
+
if a != 0.0:
|
|
204
|
+
xe = -b / (2.0 * a)
|
|
205
|
+
ye = (a * xe + b) * xe + h1
|
|
206
|
+
if d >= 0.0:
|
|
207
|
+
dx = math.sqrt(d) / (abs(a) * 2.0)
|
|
208
|
+
x1 = xe - dx
|
|
209
|
+
x2 = xe + dx
|
|
210
|
+
if abs(x1) <= 1.0:
|
|
211
|
+
roots += 1
|
|
212
|
+
if abs(x2) <= 1.0:
|
|
213
|
+
roots += 1
|
|
214
|
+
if x1 < -1.0:
|
|
215
|
+
x1 = x2
|
|
216
|
+
|
|
217
|
+
if roots == 1:
|
|
218
|
+
if h0 < 0.0:
|
|
219
|
+
if riseHour is None:
|
|
220
|
+
riseHour = (i + 1) + x1
|
|
221
|
+
else:
|
|
222
|
+
if setHour is None:
|
|
223
|
+
setHour = (i + 1) + x1
|
|
224
|
+
elif roots == 2:
|
|
225
|
+
if riseHour is None:
|
|
226
|
+
riseHour = (i + 1) + (x2 if ye < 0.0 else x1)
|
|
227
|
+
if setHour is None:
|
|
228
|
+
setHour = (i + 1) + (x1 if ye < 0.0 else x2)
|
|
229
|
+
|
|
230
|
+
if riseHour is not None and setHour is not None:
|
|
231
|
+
break
|
|
232
|
+
|
|
233
|
+
return riseHour, setHour
|
|
234
|
+
|
|
235
|
+
@property
|
|
236
|
+
def rise(self):
|
|
237
|
+
riseHour, _ = self._calculateTimes()
|
|
238
|
+
if riseHour is not None:
|
|
239
|
+
midnight = DateTime(self.datetime.date, Time(0, 0, 0))
|
|
240
|
+
return midnight + int(riseHour * 3600)
|
|
241
|
+
return None
|
|
242
|
+
|
|
243
|
+
@property
|
|
244
|
+
def set(self):
|
|
245
|
+
_, setHour = self._calculateTimes()
|
|
246
|
+
if setHour is not None:
|
|
247
|
+
midnight = DateTime(self.datetime.date, Time(0, 0, 0))
|
|
248
|
+
return midnight + int(setHour * 3600)
|
|
249
|
+
return None
|
|
250
|
+
|
|
251
|
+
@property
|
|
252
|
+
def isAlwaysUp(self):
|
|
253
|
+
riseHour, setHour = self._calculateTimes()
|
|
254
|
+
if riseHour is None and setHour is None:
|
|
255
|
+
noon = DateTime(self.datetime.date, Time(12, 0, 0))
|
|
256
|
+
return Moon(self.location, noon).altitude > 0.0
|
|
257
|
+
return False
|
|
258
|
+
|
|
259
|
+
@property
|
|
260
|
+
def illumination(self):
|
|
261
|
+
return MoonIllumination(self)
|
|
262
|
+
|
|
263
|
+
@property
|
|
264
|
+
def emoji(self):
|
|
265
|
+
return self.illumination.phaseEmoji
|
|
266
|
+
|
|
267
|
+
@staticmethod
|
|
268
|
+
def eclipticLongitude(M):
|
|
269
|
+
C = RAD * (1.9148 * math.sin(M) + 0.02 * math.sin(2.0 * M) + 0.0003 * math.sin(3.0 * M))
|
|
270
|
+
P = RAD * 102.9372
|
|
271
|
+
return M + C + P + math.pi
|
|
272
|
+
|
|
273
|
+
@staticmethod
|
|
274
|
+
def declination(L, b):
|
|
275
|
+
return math.asin(
|
|
276
|
+
math.sin(b) * math.cos(Astro.EARTH_OBLIQUITY) +
|
|
277
|
+
math.cos(b) * math.sin(Astro.EARTH_OBLIQUITY) * math.sin(L)
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
@staticmethod
|
|
281
|
+
def rightAscension(L, b):
|
|
282
|
+
return math.atan2(
|
|
283
|
+
math.sin(L) * math.cos(Astro.EARTH_OBLIQUITY) - math.tan(b) * math.sin(Astro.EARTH_OBLIQUITY),
|
|
284
|
+
math.cos(L)
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
@staticmethod
|
|
288
|
+
def siderealTime(dt, lw):
|
|
289
|
+
days = dt.julianDay2K
|
|
290
|
+
return RAD * (280.16 + 360.9856235 * days) - lw
|
|
291
|
+
|
|
292
|
+
@staticmethod
|
|
293
|
+
def astroRefraction(h):
|
|
294
|
+
if h < 0.0:
|
|
295
|
+
h = 0.0
|
|
296
|
+
return 0.0002967 / math.tan(h + 0.00312536 / (h + 0.08901179))
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class MoonPhaseTypeNames:
|
|
301
|
+
NAMES_EN = [
|
|
302
|
+
"",
|
|
303
|
+
"New Moon",
|
|
304
|
+
"Waxing Crescent",
|
|
305
|
+
"First Quarter",
|
|
306
|
+
"Waxing Gibbous",
|
|
307
|
+
"Full Moon",
|
|
308
|
+
"Waning Gibbous",
|
|
309
|
+
"Last Quarter",
|
|
310
|
+
"Waning Crescent",
|
|
311
|
+
]
|
|
312
|
+
NAMES_HE = [
|
|
313
|
+
"",
|
|
314
|
+
"מולד",
|
|
315
|
+
"סהר עולה",
|
|
316
|
+
"רבע ראשון",
|
|
317
|
+
"גבנון עולה",
|
|
318
|
+
"ירח מלא",
|
|
319
|
+
"גבנון יורד",
|
|
320
|
+
"רבע אחרון",
|
|
321
|
+
"סהר יורד",
|
|
322
|
+
]
|
|
323
|
+
|
|
324
|
+
def __init__(self, lang, phaseType):
|
|
325
|
+
self.lang = lang
|
|
326
|
+
self.phaseType = phaseType
|
|
327
|
+
|
|
328
|
+
def name(self):
|
|
329
|
+
if self.lang == 'en':
|
|
330
|
+
return self.NAMES_EN[self.phaseType]
|
|
331
|
+
return self.NAMES_HE[self.phaseType]
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
if __name__ == "__main__":
|
|
339
|
+
import time as _systime
|
|
340
|
+
from Date import DateTime, Date, Time
|
|
341
|
+
from Geo import Place
|
|
342
|
+
|
|
343
|
+
now = _systime.localtime()
|
|
344
|
+
today = Date(now.tm_year, now.tm_mon, now.tm_mday)
|
|
345
|
+
midnight = DateTime(today, Time(0, 0, 0))
|
|
346
|
+
location = Place.JERUSALEM.location()
|
|
347
|
+
|
|
348
|
+
moon = Moon(location, midnight)
|
|
349
|
+
|
|
350
|
+
rise = moon.rise
|
|
351
|
+
set_time = moon.set
|
|
352
|
+
|
|
353
|
+
print("Moon rise / set for", today)
|
|
354
|
+
print(" Rise:", rise if rise else "None")
|
|
355
|
+
print(" Set :", set_time if set_time else "None")
|
|
356
|
+
print(" Always up?", moon.isAlwaysUp)
|