@@ -0,0 +1 @@ | |||||
/cache |
@@ -6,7 +6,7 @@ verify_ssl = true | |||||
[dev-packages] | [dev-packages] | ||||
[packages] | [packages] | ||||
ephem = "*" | |||||
skyfield = "*" | |||||
[requires] | [requires] | ||||
python_version = "3.7" | python_version = "3.7" |
@@ -1,7 +1,7 @@ | |||||
{ | { | ||||
"_meta": { | "_meta": { | ||||
"hash": { | "hash": { | ||||
"sha256": "522d4a9be67c6920ba1cc2d46f4ac93658a70bf4f0c51611fe4425b6785f27c0" | |||||
"sha256": "a4606ffb65d1c5bf46146e7c9cd8add1593934c7536c007bfd84a9f10427258e" | |||||
}, | }, | ||||
"pipfile-spec": 6, | "pipfile-spec": 6, | ||||
"requires": { | "requires": { | ||||
@@ -16,20 +16,52 @@ | |||||
] | ] | ||||
}, | }, | ||||
"default": { | "default": { | ||||
"ephem": { | |||||
"jplephem": { | |||||
"hashes": [ | "hashes": [ | ||||
"sha256:3884de133045d2f12784ef456c0f5557139a247b88d2c26097f7bd420803ed7f", | |||||
"sha256:4bcd9899863ef04f4e75d894a6973dce4b4d16baeb8c2e96fb66bd3c677491a2", | |||||
"sha256:6a2e445ba3a1e6bd9d6dedcafa4dda83957f4f9b0efac3d642974c55faebcfa4", | |||||
"sha256:7a4c82b1def2893e02aec0394f108d24adb17bd7b0ca6f4bc78eb7120c0212ac", | |||||
"sha256:7af6d726c3d903087c284e3dd72c5cda2b5438e84f2d564314469f0fb7494fab", | |||||
"sha256:9ea5c8d9b407fe151cece238d13e3ca12114ac5c73269ef6541bf65b208048a3", | |||||
"sha256:bb3e04e981352ab8c6049325533944b882d9be1bf13c19be5d85b918ba75723f", | |||||
"sha256:f19a380f83f36e56e6e499bf673a43c42ed28c766c9cafb4326b5defcca0a116", | |||||
"sha256:fd15421938cac27cd87c3b73c81e4695ab7a22cd37d43e05090140f5d48392d8" | |||||
"sha256:9dffb9f3d3f6d996ade875102431fe385e8ea422da25c8ba17b0508d9ca1282b" | |||||
], | |||||
"version": "==2.9" | |||||
}, | |||||
"numpy": { | |||||
"hashes": [ | |||||
"sha256:0778076e764e146d3078b17c24c4d89e0ecd4ac5401beff8e1c87879043a0633", | |||||
"sha256:141c7102f20abe6cf0d54c4ced8d565b86df4d3077ba2343b61a6db996cefec7", | |||||
"sha256:14270a1ee8917d11e7753fb54fc7ffd1934f4d529235beec0b275e2ccf00333b", | |||||
"sha256:27e11c7a8ec9d5838bc59f809bfa86efc8a4fd02e58960fa9c49d998e14332d5", | |||||
"sha256:2a04dda79606f3d2f760384c38ccd3d5b9bb79d4c8126b67aff5eb09a253763e", | |||||
"sha256:3c26010c1b51e1224a3ca6b8df807de6e95128b0908c7e34f190e7775455b0ca", | |||||
"sha256:52c40f1a4262c896420c6ea1c6fda62cf67070e3947e3307f5562bd783a90336", | |||||
"sha256:6e4f8d9e8aa79321657079b9ac03f3cf3fd067bf31c1cca4f56d49543f4356a5", | |||||
"sha256:7242be12a58fec245ee9734e625964b97cf7e3f2f7d016603f9e56660ce479c7", | |||||
"sha256:7dc253b542bfd4b4eb88d9dbae4ca079e7bf2e2afd819ee18891a43db66c60c7", | |||||
"sha256:94f5bd885f67bbb25c82d80184abbf7ce4f6c3c3a41fbaa4182f034bba803e69", | |||||
"sha256:a89e188daa119ffa0d03ce5123dee3f8ffd5115c896c2a9d4f0dbb3d8b95bfa3", | |||||
"sha256:ad3399da9b0ca36e2f24de72f67ab2854a62e623274607e37e0ce5f5d5fa9166", | |||||
"sha256:b0348be89275fd1d4c44ffa39530c41a21062f52299b1e3ee7d1c61f060044b8", | |||||
"sha256:b5554368e4ede1856121b0dfa35ce71768102e4aa55e526cb8de7f374ff78722", | |||||
"sha256:cbddc56b2502d3f87fda4f98d948eb5b11f36ff3902e17cb6cc44727f2200525", | |||||
"sha256:d79f18f41751725c56eceab2a886f021d70fd70a6188fd386e29a045945ffc10", | |||||
"sha256:dc2ca26a19ab32dc475dbad9dfe723d3a64c835f4c23f625c2b6566ca32b9f29", | |||||
"sha256:dd9bcd4f294eb0633bb33d1a74febdd2b9018b8b8ed325f861fffcd2c7660bb8", | |||||
"sha256:e8baab1bc7c9152715844f1faca6744f2416929de10d7639ed49555a85549f52", | |||||
"sha256:ec31fe12668af687b99acf1567399632a7c47b0e17cfb9ae47c098644ef36797", | |||||
"sha256:f12b4f7e2d8f9da3141564e6737d79016fe5336cc92de6814eba579744f65b0a", | |||||
"sha256:f58ac38d5ca045a377b3b377c84df8175ab992c970a53332fa8ac2373df44ff7" | |||||
], | |||||
"version": "==1.16.4" | |||||
}, | |||||
"sgp4": { | |||||
"hashes": [ | |||||
"sha256:1fb3cdbc11981a9ff34a032169f83c1f4a2877d1b6c295aed044e1d890b73892" | |||||
], | |||||
"version": "==1.4" | |||||
}, | |||||
"skyfield": { | |||||
"hashes": [ | |||||
"sha256:7711838214a23ba09bec0bc0c8040ba18dab58f4d496f5be66cf00b56e63ec34" | |||||
], | ], | ||||
"index": "pypi", | "index": "pypi", | ||||
"version": "==3.7.6.0" | |||||
"version": "==1.10" | |||||
} | } | ||||
}, | }, | ||||
"develop": {} | "develop": {} | ||||
@@ -0,0 +1,100 @@ | |||||
from skyfield.api import Loader, Topos | |||||
from skyfield import almanac | |||||
class Ephemeris: | |||||
position = None | |||||
timescale = None | |||||
planets = None | |||||
def __init__(self, position): | |||||
self.MONTH = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'] | |||||
self.PLANETS = ['mercury', 'venus', 'mars', 'jupiter', 'saturn', 'uranus', 'neptune', 'pluto'] | |||||
load = Loader('./cache') | |||||
self.timescale = load.timescale() | |||||
self.planets = load('de421.bsp') | |||||
self.position = Topos(latitude_degrees=position['lat'], longitude_degrees=position['lon']) | |||||
def compute_ephemeris_for_day(self, year: int, month: int, day: int) -> dict: | |||||
ephemeris = {} | |||||
time1 = self.timescale.utc(year, month, day, 2) | |||||
time2 = self.timescale.utc(year, month, day + 1, 2) | |||||
# Compute sunrise and sunset | |||||
ephemeris['sun'] = self.get_sun(time1, time2) | |||||
ephemeris['moon'] = self.get_moon(year, month, day) | |||||
return ephemeris | |||||
def get_sun(self, time1, time2) -> dict: | |||||
t, y = almanac.find_discrete(time1, time2, 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] | |||||
return {'rise': sunrise.utc_iso(), 'set': sunset.utc_iso()} | |||||
def get_moon(self, year, month, day) -> dict: | |||||
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)) | |||||
return {'phase': y[-1]} | |||||
def compute_ephemeris_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 | |||||
elif month < 8: | |||||
max_day = 30 if month % 2 == 0 else 31 | |||||
else: | |||||
max_day = 31 if month % 2 == 0 else 30 | |||||
e = [] | |||||
for day in range(1, max_day + 1): | |||||
e.append(self.compute_ephemeris_for_day(year, month, day)) | |||||
return e | |||||
def compute_ephemeris_for_year(self, year: int) -> dict: | |||||
e = {} | |||||
for month in range(0, 12): | |||||
e[self.MONTH[month]] = self.compute_ephemeris_for_month(year, month + 1) | |||||
e['seasons'] = self.get_seasons(year) | |||||
return e | |||||
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)) | |||||
seasons = {} | |||||
for ti, yi in zip(t, y): | |||||
if yi == 0: | |||||
season = 'MARCH' | |||||
elif yi == 1: | |||||
season = 'JUNE' | |||||
elif yi == 2: | |||||
season = 'SEPTEMBER' | |||||
elif yi == 3: | |||||
season = 'DECEMBER' | |||||
else: | |||||
raise AssertionError | |||||
seasons[season] = ti.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) |
@@ -1,8 +1,54 @@ | |||||
import sys | |||||
import ephem | |||||
import argparse | |||||
import numpy | |||||
from datetime import date | |||||
from ephemeris import Ephemeris | |||||
import json | |||||
# 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 main(): | |||||
args = get_args() | |||||
year = args.year | |||||
month = args.month | |||||
day = args.date | |||||
position = {'lat': args.latitude, 'lon': args.longitude, 'altitude': args.altitude} | |||||
if day is not None and month is None: | |||||
month = date.today().month | |||||
ephemeris = Ephemeris(position) | |||||
e = ephemeris.compute_ephemeris(year, month, day) | |||||
print(json.dumps(e, default=json_default, indent=4, separators=(',', ': '))) | |||||
def get_args(): | |||||
parser = argparse.ArgumentParser(description='Compute the ephemeris for a given day/month/year, by default for' | |||||
' Paris, France.', epilog='By default, the observer will be set at' | |||||
' position (0,0) with an altitude of 0.' | |||||
' You will more likely want to change that.') | |||||
parser.add_argument('--latitude', '-lat', type=float, default=0., help="The observer's position on Earth" | |||||
" (latitude)") | |||||
parser.add_argument('--longitude', '-lon', type=float, default=0., help="The observer's position on Earth" | |||||
" (longitude)") | |||||
parser.add_argument('--altitude', '-alt', type=float, default=0., help="The observer's position on Earth" | |||||
" (altitude)") | |||||
parser.add_argument('--date', '-d', type=int, help='A number between 1 and 28, 29, 30 or 31 (depending on the' | |||||
' month). The date you want to compute the ephemeris for') | |||||
parser.add_argument('--month', '-m', type=int, help='A number between 1 and 12. The month you want to compute the' | |||||
' ephemeris for (defaults to the current month if the day is' | |||||
' defined)') | |||||
parser.add_argument('year', type=int, help='The year you want to compute the ephemeris for') | |||||
return parser.parse_args() | |||||
def main(argv): | |||||
print(argv) | |||||
if '__main__' == __name__: | if '__main__' == __name__: | ||||
main(sys.argv) | |||||
main() |