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 ADDED
@@ -0,0 +1,10 @@
1
+ import math
2
+
3
+ J2000 = 2451545.0
4
+ EARTH_OBLIQUITY = (math.pi / 180.0) * 23.4397
5
+
6
+
7
+ class CelestialLocation:
8
+ def __init__(self, rightAscension, declination):
9
+ self.rightascension = rightAscension
10
+ self.declination = declination
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)