| @@ -15,8 +15,8 @@ jobs: | |||
| python-version: 3.7 | |||
| - name: Install dependencies | |||
| run: | | |||
| python -m pip install --upgrade pip pipenv pylint-fail-under pylintfileheader | |||
| pip install --upgrade pipenv | |||
| pipenv sync -d | |||
| - name: Lint | |||
| run: | | |||
| pylint-fail-under --fail_under 7.5 *.py | |||
| pipenv run pylint-fail-under --fail_under 9.0 *.py | |||
| @@ -5,10 +5,13 @@ verify_ssl = true | |||
| [dev-packages] | |||
| pylintfileheader = "*" | |||
| pylint = "*" | |||
| pylint-fail-under = "*" | |||
| [packages] | |||
| skyfield = ">=1.13.0,<2.0.0" | |||
| tabulate = "*" | |||
| numpy = ">=1.17.0,<2.0.0" | |||
| [requires] | |||
| python_version = "3.7" | |||
| @@ -1,7 +1,7 @@ | |||
| { | |||
| "_meta": { | |||
| "hash": { | |||
| "sha256": "2567979765f3ac22ecdbf4c6fbaf6e20c75bc83173db4f43d961e7a405804685" | |||
| "sha256": "e2479c38bc67f14f5ac49d82e76f3bd4e4dd3608049c62d5c7b78747a4d967f0" | |||
| }, | |||
| "pipfile-spec": 6, | |||
| "requires": { | |||
| @@ -46,6 +46,7 @@ | |||
| "sha256:f1df7b2b7740dd777571c732f98adb5aad5450aee32772f1b39249c8a50386f6", | |||
| "sha256:ffca69e29079f7880c5392bf675eb8b4146479d976ae1924d01cd92b04cccbcc" | |||
| ], | |||
| "index": "pypi", | |||
| "version": "==1.17.3" | |||
| }, | |||
| "sgp4": { | |||
| @@ -70,6 +71,69 @@ | |||
| } | |||
| }, | |||
| "develop": { | |||
| "astroid": { | |||
| "hashes": [ | |||
| "sha256:09a3fba616519311f1af8a461f804b68f0370e100c9264a035aa7846d7852e33", | |||
| "sha256:5a79c9b4bd6c4be777424593f957c996e20beb5f74e0bc332f47713c6f675efe" | |||
| ], | |||
| "version": "==2.3.2" | |||
| }, | |||
| "isort": { | |||
| "hashes": [ | |||
| "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1", | |||
| "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd" | |||
| ], | |||
| "version": "==4.3.21" | |||
| }, | |||
| "lazy-object-proxy": { | |||
| "hashes": [ | |||
| "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d", | |||
| "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449", | |||
| "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08", | |||
| "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a", | |||
| "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50", | |||
| "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd", | |||
| "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239", | |||
| "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb", | |||
| "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea", | |||
| "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e", | |||
| "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156", | |||
| "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142", | |||
| "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442", | |||
| "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62", | |||
| "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db", | |||
| "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531", | |||
| "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383", | |||
| "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a", | |||
| "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357", | |||
| "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4", | |||
| "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0" | |||
| ], | |||
| "version": "==1.4.3" | |||
| }, | |||
| "mccabe": { | |||
| "hashes": [ | |||
| "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", | |||
| "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" | |||
| ], | |||
| "version": "==0.6.1" | |||
| }, | |||
| "pylint": { | |||
| "hashes": [ | |||
| "sha256:7b76045426c650d2b0f02fc47c14d7934d17898779da95288a74c2a7ec440702", | |||
| "sha256:856476331f3e26598017290fd65bebe81c960e806776f324093a46b76fb2d1c0" | |||
| ], | |||
| "index": "pypi", | |||
| "version": "==2.4.3" | |||
| }, | |||
| "pylint-fail-under": { | |||
| "hashes": [ | |||
| "sha256:4584c0eb6bf2c3dcf9402c74734a49d3a7a86fbe3590276b517fbcbdcba1333a", | |||
| "sha256:d372e3a698634b48550c4e4af10dc145a6d2c2d9ee26a40690ae635661c85de2" | |||
| ], | |||
| "index": "pypi", | |||
| "version": "==0.3.0" | |||
| }, | |||
| "pylintfileheader": { | |||
| "hashes": [ | |||
| "sha256:214a445432b56c177b65bd1b8f10f2b3dafbb5bc002cea9309c9309bf66e7588", | |||
| @@ -77,6 +141,45 @@ | |||
| ], | |||
| "index": "pypi", | |||
| "version": "==0.0.2" | |||
| }, | |||
| "six": { | |||
| "hashes": [ | |||
| "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", | |||
| "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" | |||
| ], | |||
| "version": "==1.12" | |||
| }, | |||
| "typed-ast": { | |||
| "hashes": [ | |||
| "sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161", | |||
| "sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", | |||
| "sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", | |||
| "sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", | |||
| "sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", | |||
| "sha256:48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47", | |||
| "sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", | |||
| "sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", | |||
| "sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", | |||
| "sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", | |||
| "sha256:7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2", | |||
| "sha256:838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e", | |||
| "sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", | |||
| "sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", | |||
| "sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", | |||
| "sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", | |||
| "sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", | |||
| "sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", | |||
| "sha256:fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66", | |||
| "sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12" | |||
| ], | |||
| "markers": "implementation_name == 'cpython' and python_version < '3.8'", | |||
| "version": "==1.4.0" | |||
| }, | |||
| "wrapt": { | |||
| "hashes": [ | |||
| "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1" | |||
| ], | |||
| "version": "==1.11.2" | |||
| } | |||
| } | |||
| } | |||
| @@ -30,13 +30,12 @@ class Dumper(ABC): | |||
| class TextDumper(Dumper): | |||
| def to_string(self): | |||
| s = '\n\n'.join([self.get_planets(self.ephemeris['planets'], self.ephemeris['sun']), | |||
| self.get_moon(self.ephemeris['moon'])]) | |||
| return s | |||
| return '\n\n'.join([self.get_planets(self.ephemeris['planets'], self.ephemeris['sun']), | |||
| self.get_moon(self.ephemeris['moon'])]) | |||
| @staticmethod | |||
| def get_planets(planets, sun): | |||
| s = [['SUN', sun['rise'].utc_strftime('%H:%M'), '-', sun['set'].utc_strftime('%H:%M')]] | |||
| data = [['SUN', sun['rise'].utc_strftime('%H:%M'), '-', sun['set'].utc_strftime('%H:%M')]] | |||
| for planet in planets: | |||
| name = planet | |||
| planet_data = planets[planet] | |||
| @@ -45,9 +44,9 @@ class TextDumper(Dumper): | |||
| else ' -' | |||
| planet_set = planet_data['set'].utc_strftime('%H:%M') if planet_data['set'] is not None else ' -' | |||
| s.append([name, planet_rise, planet_maximum, planet_set]) | |||
| data.append([name, planet_rise, planet_maximum, planet_set]) | |||
| return tabulate(s, headers=['Planet', 'Rise time', 'Maximum time', 'Set time'], tablefmt='simple', | |||
| return tabulate(data, headers=['Planet', 'Rise time', 'Maximum time', 'Set time'], tablefmt='simple', | |||
| stralign='center', colalign=('left',)) | |||
| @staticmethod | |||
| @@ -49,11 +49,13 @@ class Ephemeris: | |||
| return ephemeris | |||
| def get_sun(self, time1, time2) -> dict: | |||
| t, y = almanac.find_discrete(time1, time2, almanac.sunrise_sunset(self.planets, self.position)) | |||
| def get_sun(self, start_time, end_time) -> dict: | |||
| times, is_risen = almanac.find_discrete(start_time, | |||
| end_time, | |||
| almanac.sunrise_sunset(self.planets, self.position)) | |||
| sunrise = t[0] if y[0] else t[1] | |||
| sunset = t[1] if not y[1] else t[0] | |||
| sunrise = times[0] if is_risen[0] else times[1] | |||
| sunset = times[1] if not is_risen[1] else times[0] | |||
| return {'rise': sunrise, 'set': sunset} | |||
| @@ -61,9 +63,9 @@ class Ephemeris: | |||
| time1 = self.timescale.utc(year, month, day - 10) | |||
| time2 = self.timescale.utc(year, month, day) | |||
| _, y = almanac.find_discrete(time1, time2, almanac.moon_phases(self.planets)) | |||
| _, moon_phase = almanac.find_discrete(time1, time2, almanac.moon_phases(self.planets)) | |||
| return {'phase': y[-1]} | |||
| return {'phase': moon_phase[-1]} | |||
| def get_planets(self, year: int, month: int, day: int) -> dict: | |||
| args = [] | |||
| @@ -85,22 +87,22 @@ class Ephemeris: | |||
| return obj | |||
| @staticmethod | |||
| def get_planet(o) -> dict: | |||
| def get_planet(p_obj) -> dict: | |||
| load = Loader('./cache') | |||
| planets = load('de421.bsp') | |||
| timescale = load.timescale() | |||
| position = Topos(latitude_degrees=o['observer']['latitude'], longitude_degrees=o['observer']['longitude']) | |||
| position = Topos(latitude_degrees=p_obj['observer']['latitude'], | |||
| longitude_degrees=p_obj['observer']['longitude']) | |||
| observer = planets['earth'] + position | |||
| planet = planets[o['planet']] | |||
| planet = planets[p_obj['planet']] | |||
| rise_time = maximum_time = set_time = None | |||
| max_elevation = None | |||
| is_risen = None | |||
| is_elevating = None | |||
| last_is_elevating = None | |||
| last_position = None | |||
| for hours in range(0, 24): | |||
| time = timescale.utc(o['year'], o['month'], o['day'], hours) | |||
| time = timescale.utc(p_obj['year'], p_obj['month'], p_obj['day'], hours) | |||
| position = observer.at(time).observe(planet).apparent().altaz()[0].degrees | |||
| if is_risen is None: | |||
| @@ -111,7 +113,7 @@ class Ephemeris: | |||
| if rise_time is None and not is_risen and is_elevating and position > 0: | |||
| # Planet has risen in the last hour, let's look for a more precise time! | |||
| for minutes in range(0, 60): | |||
| time = timescale.utc(o['year'], o['month'], o['day'], hours - 1, minutes) | |||
| time = timescale.utc(p_obj['year'], p_obj['month'], p_obj['day'], hours - 1, minutes) | |||
| position = observer.at(time).observe(planet).apparent().altaz()[0].degrees | |||
| if position > 0: | |||
| @@ -123,7 +125,7 @@ class Ephemeris: | |||
| if set_time is None and is_risen and not is_elevating and position < 0: | |||
| # Planet has set in the last hour, let's look for a more precise time! | |||
| for minutes in range(0, 60): | |||
| time = timescale.utc(o['year'], o['month'], o['day'], hours - 1, minutes) | |||
| time = timescale.utc(p_obj['year'], p_obj['month'], p_obj['day'], hours - 1, minutes) | |||
| position = observer.at(time).observe(planet).apparent().altaz()[0].degrees | |||
| if position < 0: | |||
| @@ -135,10 +137,9 @@ class Ephemeris: | |||
| if not is_elevating and last_is_elevating: | |||
| # Planet has reached its azimuth in the last hour, let's look for a more precise time! | |||
| for minutes in range(0, 60): | |||
| time = timescale.utc(o['year'], o['month'], o['day'], hours - 1, minutes) | |||
| time = timescale.utc(p_obj['year'], p_obj['month'], p_obj['day'], hours - 1, minutes) | |||
| position = observer.at(time).observe(planet).apparent().altaz()[0].degrees | |||
| max_elevation = position | |||
| maximum_time = time | |||
| if last_position > position: | |||
| @@ -153,20 +154,20 @@ class Ephemeris: | |||
| if rise_time is not None and set_time is not None and maximum_time is not None: | |||
| return { | |||
| 'name': o['planet'], | |||
| 'name': p_obj['planet'], | |||
| 'rise': rise_time, | |||
| 'maximum': maximum_time, | |||
| 'set': set_time | |||
| } | |||
| return { | |||
| 'name': o['planet'], | |||
| 'name': p_obj['planet'], | |||
| 'rise': rise_time if rise_time is not None else None, | |||
| 'maximum': maximum_time if maximum_time is not None else None, | |||
| 'set': set_time if set_time is not None else None | |||
| } | |||
| def compute_ephemeris_for_month(self, year: int, month: int) -> list: | |||
| def compute_ephemerides_for_month(self, year: int, month: int) -> list: | |||
| if month == 2: | |||
| is_leap_year = (year % 4 == 0 and year % 100 > 0) or (year % 400 == 0) | |||
| max_day = 29 if is_leap_year else 28 | |||
| @@ -175,48 +176,49 @@ class Ephemeris: | |||
| else: | |||
| max_day = 31 if month % 2 == 0 else 30 | |||
| e = [] | |||
| ephemerides = [] | |||
| for day in range(1, max_day + 1): | |||
| e.append(self.compute_ephemeris_for_day(year, month, day)) | |||
| ephemerides.append(self.compute_ephemeris_for_day(year, month, day)) | |||
| return e | |||
| return ephemerides | |||
| def compute_ephemeris_for_year(self, year: int) -> dict: | |||
| e = {} | |||
| def compute_ephemerides_for_year(self, year: int) -> dict: | |||
| ephemerides = {} | |||
| for month in range(0, 12): | |||
| e[self.MONTH[month]] = self.compute_ephemeris_for_month(year, month + 1) | |||
| ephemerides[self.MONTH[month]] = self.compute_ephemerides_for_month(year, month + 1) | |||
| e['seasons'] = self.get_seasons(year) | |||
| ephemerides['seasons'] = self.get_seasons(year) | |||
| return e | |||
| return ephemerides | |||
| def get_seasons(self, year: int) -> dict: | |||
| t1 = self.timescale.utc(year, 1, 1) | |||
| t2 = self.timescale.utc(year, 12, 31) | |||
| t, y = almanac.find_discrete(t1, t2, almanac.seasons(self.planets)) | |||
| start_time = self.timescale.utc(year, 1, 1) | |||
| end_time = self.timescale.utc(year, 12, 31) | |||
| times, almanac_seasons = almanac.find_discrete(start_time, end_time, almanac.seasons(self.planets)) | |||
| seasons = {} | |||
| for ti, yi in zip(t, y): | |||
| if yi == 0: | |||
| for time, almanac_season in zip(times, almanac_seasons): | |||
| if almanac_season == 0: | |||
| season = 'MARCH' | |||
| elif yi == 1: | |||
| elif almanac_season == 1: | |||
| season = 'JUNE' | |||
| elif yi == 2: | |||
| elif almanac_season == 2: | |||
| season = 'SEPTEMBER' | |||
| elif yi == 3: | |||
| elif almanac_season == 3: | |||
| season = 'DECEMBER' | |||
| else: | |||
| raise AssertionError | |||
| seasons[season] = ti.utc_iso() | |||
| seasons[season] = time.utc_iso() | |||
| return seasons | |||
| def compute_ephemeris(self, year: int, month: int, day: int): | |||
| if day is not None: | |||
| return self.compute_ephemeris_for_day(year, month, day) | |||
| elif month is not None: | |||
| return self.compute_ephemeris_for_month(year, month) | |||
| else: | |||
| return self.compute_ephemeris_for_year(year) | |||
| if month is not None: | |||
| return self.compute_ephemerides_for_month(year, month) | |||
| return self.compute_ephemerides_for_year(year) | |||
| @@ -15,19 +15,18 @@ | |||
| # along with this program. If not, see <https://www.gnu.org/licenses/>. | |||
| import argparse | |||
| import numpy | |||
| from datetime import date | |||
| from ephemeris import Ephemeris | |||
| import numpy | |||
| import dumper | |||
| import json | |||
| from ephemeris import Ephemeris | |||
| # Fixes the "TypeError: Object of type int64 is not JSON serializable" | |||
| # See https://stackoverflow.com/a/50577730 | |||
| def json_default(o): | |||
| if isinstance(o, numpy.int64): | |||
| return int(o) | |||
| raise TypeError('Object of type ' + str(type(o)) + ' could not be integrated in the JSON') | |||
| def json_default(obj): | |||
| if isinstance(obj, numpy.int64): | |||
| return int(obj) | |||
| raise TypeError('Object of type ' + str(type(obj)) + ' could not be integrated in the JSON') | |||
| def main(): | |||
| @@ -41,10 +40,10 @@ def main(): | |||
| month = date.today().month | |||
| ephemeris = Ephemeris(position) | |||
| e = ephemeris.compute_ephemeris(year, month, day) | |||
| ephemerides = ephemeris.compute_ephemeris(year, month, day) | |||
| d = dumper.TextDumper(e) | |||
| print(d.to_string()) | |||
| dump = dumper.TextDumper(ephemerides) | |||
| print(dump.to_string()) | |||
| def get_args(): | |||
| @@ -67,5 +66,5 @@ def get_args(): | |||
| return parser.parse_args() | |||
| if '__main__' == __name__: | |||
| if __name__ == '__main__': | |||
| main() | |||