Browse Source

Refactorization of the computation functions

tags/v0.1.0
Jérôme Deuchnord 4 years ago
parent
commit
392b2dd7dc
No known key found for this signature in database GPG Key ID: BC6F3C345B7D33B0
10 changed files with 381 additions and 276 deletions
  1. +1
    -1
      .github/workflows/pythonapp.yml
  2. +3
    -2
      .pylintrc
  3. +14
    -11
      README.md
  4. +0
    -226
      ephemeris.py
  5. +29
    -22
      kosmorro.py
  6. +17
    -0
      kosmorrolib/__init__.py
  7. +46
    -0
      kosmorrolib/core.py
  8. +92
    -0
      kosmorrolib/data.py
  9. +30
    -14
      kosmorrolib/dumper.py
  10. +149
    -0
      kosmorrolib/ephemerides.py

+ 1
- 1
.github/workflows/pythonapp.yml View File

@@ -19,4 +19,4 @@ jobs:
pipenv sync -d
- name: Lint
run: |
pipenv run pylint *.py
pipenv run pylint *.py kosmorrolib/*.py

+ 3
- 2
.pylintrc View File

@@ -30,7 +30,7 @@ limit-inference-results=100
# usually to register additional checkers.
load-plugins=pylintfileheader

file-header=#!/usr/bin/env python3\n\n# Kosmorro - Compute The Next Ephemeris\n# Copyright \(C\) 2019 Jérôme Deuchnord <jerome@deuchnord.fr>\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as\n# published by the Free Software Foundation, either version 3 of the\n# License, or \(at your option\) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Affero General Public License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with this program. If not, see <https://www.gnu.org/licenses/>.\n\n
file-header=#!/usr/bin/env python3\n\n# Kosmorro - Compute The Next Ephemerides\n# Copyright \(C\) 2019 Jérôme Deuchnord <jerome@deuchnord.fr>\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as\n# published by the Free Software Foundation, either version 3 of the\n# License, or \(at your option\) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n# GNU Affero General Public License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with this program. If not, see <https://www.gnu.org/licenses/>.\n

# Pickle collected data for later comparisons.
persistent=yes
@@ -144,7 +144,8 @@ disable=print-statement,
missing-docstring,
too-many-locals,
too-many-branches,
too-few-public-methods
too-few-public-methods,
protected-access

# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option


+ 14
- 11
README.md View File

@@ -57,17 +57,20 @@ For instance, if you want the ephemeris of October 31th, 2019 in Paris, France:

```console
$ python kosmorro.py --latitude 48.8032 --longitude 2.3511 -m 10 -d 31 2019
Planet Rise time Maximum time Set time
-------- ----------- -------------- ----------
SUN 06:35 - 16:32
MERCURY 08:44 13:01 16:59
VENUS 08:35 13:01 17:18
MARS 04:48 10:20 15:51
JUPITER 10:40 15:01 18:46
SATURN 12:12 16:20 20:26
URANUS 16:23 - 06:22
NEPTUNE 14:53 20:23 01:56
PLUTO 12:36 17:01 20:50
Planet Rise time Culmination time Set time
-------- ----------- ------------------ ----------
Sun 06:35 11:33 16:32
Moon 10:21 14:41 19:01
Mercury 08:37 12:51 17:04
Venus 08:28 12:56 17:23
Mars 04:42 10:19 15:55
Jupiter 10:33 14:42 18:52
Saturn 12:05 16:18 20:31
Uranus 16:17 - 06:27
Neptune 14:47 20:21 02:00
Pluto 12:29 16:42 20:55

Moon phase: New Moon

Note: All the hours are given in UTC.
```

+ 0
- 226
ephemeris.py View File

@@ -1,226 +0,0 @@
#!/usr/bin/env python3

# Kosmorro - Compute The Next Ephemeris
# Copyright (C) 2019 Jérôme Deuchnord <jerome@deuchnord.fr>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

from multiprocessing import Pool as ThreadPool
from skyfield.api import Loader, Topos
from skyfield import almanac


class Ephemeris:
MONTH = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']
PLANETS = ['MERCURY', 'VENUS', 'MARS', 'JUPITER BARYCENTER', 'SATURN BARYCENTER', 'URANUS BARYCENTER',
'NEPTUNE BARYCENTER', 'PLUTO BARYCENTER']
position = None
latitude = None
longitude = None
timescale = None
planets = None

def __init__(self, position):
load = Loader('./cache')
self.timescale = load.timescale()
self.planets = load('de421.bsp')
self.latitude = position['lat']
self.longitude = position['lon']
self.position = Topos(latitude_degrees=position['lat'], longitude_degrees=position['lon'])

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 = 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}

def get_moon(self, year, month, day) -> dict:
time1 = self.timescale.utc(year, month, day - 10)
time2 = self.timescale.utc(year, month, day)

_, moon_phase = almanac.find_discrete(time1, time2, almanac.moon_phases(self.planets))

return {'phase': moon_phase[-1]}

def get_planets(self, year: int, month: int, day: int) -> dict:
args = []

for planet_name in self.PLANETS:
args.append({'planet': planet_name,
'observer': {'latitude': self.latitude, 'longitude': self.longitude},
'year': year, 'month': month, 'day': day})

with ThreadPool() as pool:
planets = pool.map(Ephemeris.get_planet, args)

obj = {}

for planet in planets:
obj[planet['name'].split(' ')[0]] = {'rise': planet['rise'], 'maximum': planet['maximum'],
'set': planet['set']}

return obj

@staticmethod
def get_planet(p_obj) -> dict:
load = Loader('./cache')
planets = load('de421.bsp')
timescale = load.timescale()
position = Topos(latitude_degrees=p_obj['observer']['latitude'],
longitude_degrees=p_obj['observer']['longitude'])
observer = planets['earth'] + position
planet = planets[p_obj['planet']]
rise_time = maximum_time = set_time = None
is_risen = None
is_elevating = None
last_is_elevating = None
last_position = None

for hours in range(0, 24):
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:
is_risen = position > 0
if last_position is not None:
is_elevating = last_position < position

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(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:
# Planet has just risen!
rise_time = time
is_risen = True
break

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(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:
# Planet has just set!
set_time = time
is_risen = False
break

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(p_obj['year'], p_obj['month'], p_obj['day'], hours - 1, minutes)
position = observer.at(time).observe(planet).apparent().altaz()[0].degrees

maximum_time = time

if last_position > position:
# Planet has reached its azimuth!
is_elevating = False
break

last_position = position

last_position = position
last_is_elevating = is_elevating

if rise_time is not None and set_time is not None and maximum_time is not None:
return {
'name': p_obj['planet'],
'rise': rise_time,
'maximum': maximum_time,
'set': set_time
}

return {
'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_ephemerides_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)
ephemeris['planets'] = self.get_planets(year, month, day)

return ephemeris

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
elif month < 8:
max_day = 30 if month % 2 == 0 else 31
else:
max_day = 31 if month % 2 == 0 else 30

ephemerides = []

for day in range(1, max_day + 1):
ephemerides.append(self.compute_ephemerides_for_day(year, month, day))

return ephemerides

def compute_ephemerides_for_year(self, year: int) -> dict:
ephemerides = {}
for month in range(0, 12):
ephemerides[self.MONTH[month]] = self.compute_ephemerides_for_month(year, month + 1)

ephemerides['seasons'] = self.get_seasons(year)

return ephemerides

def get_seasons(self, year: int) -> dict:
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 time, almanac_season in zip(times, almanac_seasons):
if almanac_season == 0:
season = 'MARCH'
elif almanac_season == 1:
season = 'JUNE'
elif almanac_season == 2:
season = 'SEPTEMBER'
elif almanac_season == 3:
season = 'DECEMBER'
else:
raise AssertionError

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_ephemerides_for_day(year, month, day)

if month is not None:
return self.compute_ephemerides_for_month(year, month)

return self.compute_ephemerides_for_year(year)

+ 29
- 22
kosmorro.py View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python3

# Kosmorro - Compute The Next Ephemeris
# Kosmorro - Compute The Next Ephemerides
# Copyright (C) 2019 Jérôme Deuchnord <jerome@deuchnord.fr>
#
# This program is free software: you can redistribute it and/or modify
@@ -19,8 +19,8 @@
import argparse
from datetime import date
import numpy
import dumper
from ephemeris import Ephemeris
from kosmorrolib import dumper
from kosmorrolib.ephemerides import EphemeridesComputer, Position


# Fixes the "TypeError: Object of type int64 is not JSON serializable"
@@ -35,35 +35,42 @@ def main():
args = get_args()
year = args.year
month = args.month
day = args.date
position = {'lat': args.latitude, 'lon': args.longitude, 'alt': args.altitude}
day = args.day

if day is not None and month is None:
month = date.today().month

ephemeris = Ephemeris(position)
ephemerides = ephemeris.compute_ephemeris(year, month, day)
ephemeris = EphemeridesComputer(Position(args.latitude, args.longitude, altitude=args.altitude))
ephemerides = ephemeris.compute_ephemerides(year, month, day)

dump = dumper.TextDumper(ephemerides)
print(dump.to_string())


def get_args():
parser = argparse.ArgumentParser(description='Compute the ephemeris for a given day/month/year.',
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')
today = date.today()

parser = argparse.ArgumentParser(description='Compute the ephemerides for a given date, at a given position'
' on Earth.',
epilog='By default, the ephemerides will be computed for today (%s) for an'
' observer positioned at coordinates (0,0), with an altitude of 0.'
% today.strftime('%a %b %d, %Y'))

parser.add_argument('--latitude', '-lat', type=float, default=0.,
help="The observer's latitude on Earth")
parser.add_argument('--longitude', '-lon', type=float, default=0.,
help="The observer's longitude on Earth")
parser.add_argument('--altitude', '-alt', type=float, default=0.,
help="The observer's altitude on Earth")
parser.add_argument('--day', '-d', type=int, default=today.day,
help='A number between 1 and 28, 29, 30 or 31 (depending on the month). The day you want to '
' compute the ephemerides for. Defaults to %d (the current day).' % today.day)
parser.add_argument('--month', '-m', type=int, default=today.month,
help='A number between 1 and 12. The month you want to compute the ephemerides for. Defaults to'
' %d (the current month).' % today.month)
parser.add_argument('--year', '-y', type=int, default=today.year,
help='The year you want to compute the ephemerides for.'
' Defaults to %d (the current year).' % today.year)

return parser.parse_args()



+ 17
- 0
kosmorrolib/__init__.py View File

@@ -0,0 +1,17 @@
#!/usr/bin/env python3

# Kosmorro - Compute The Next Ephemerides
# Copyright (C) 2019 Jérôme Deuchnord <jerome@deuchnord.fr>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

+ 46
- 0
kosmorrolib/core.py View File

@@ -0,0 +1,46 @@
#!/usr/bin/env python3

# Kosmorro - Compute The Next Ephemerides
# Copyright (C) 2019 Jérôme Deuchnord <jerome@deuchnord.fr>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

from skyfield.api import Loader

from .data import Star, Planet, Satellite

MONTHS = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']

ASTERS = [Star('Sun', 'SUN'),
Satellite('Moon', 'MOON'),
Planet('Mercury', 'MERCURY'),
Planet('Venus', 'VENUS'),
Planet('Mars', 'MARS'),
Planet('Jupiter', 'JUPITER BARYCENTER'),
Planet('Saturn', 'SATURN BARYCENTER'),
Planet('Uranus', 'URANUS BARYCENTER'),
Planet('Neptune', 'NEPTUNE BARYCENTER'),
Planet('Pluto', 'PLUTO BARYCENTER')]


def get_loader():
return Loader('./cache')


def get_timescale():
return get_loader().timescale()


def get_skf_objects():
return get_loader()('de421.bsp')

+ 92
- 0
kosmorrolib/data.py View File

@@ -0,0 +1,92 @@
#!/usr/bin/env python3

# Kosmorro - Compute The Next Ephemerides
# Copyright (C) 2019 Jérôme Deuchnord <jerome@deuchnord.fr>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

from abc import ABC, abstractmethod
from typing import Union

from skyfield.api import Topos
from skyfield.timelib import Time


class Position:
def __init__(self, latitude: float, longitude: float, altitude: float = 0):
self.latitude = latitude
self.longitude = longitude
self.altitude = altitude
self.observation_planet = None

def get_planet_topos(self) -> Topos:
if self.observation_planet is None:
raise TypeError('Observation planet must be set.')

return self.observation_planet + Topos(latitude_degrees=self.latitude, longitude_degrees=self.longitude)


class AsterEphemerides:
def __init__(self,
rise_time: Union[Time, None],
culmination_time: Union[Time, None],
set_time: Union[Time, None]):
self.rise_time = rise_time
self.maximum_time = culmination_time
self.set_time = set_time


class Object(ABC):
"""
An astronomical object.
"""

def __init__(self,
name: str,
skyfield_name: str,
ephemerides: AsterEphemerides or None = None):
"""
Initialize an astronomical object

:param str name: the official name of the object (may be internationalized)
:param str skyfield_name: the internal name of the object in Skyfield library
:param AsterEphemerides ephemerides: the ephemerides associated to the object
"""
self.name = name
self.skyfield_name = skyfield_name
self.ephemerides = ephemerides

@abstractmethod
def get_type(self) -> str:
pass


class Star(Object):
def get_type(self) -> str:
return 'star'


class Planet(Object):
def get_type(self) -> str:
return 'planet'


class DwarfPlanet(Planet):
def get_type(self) -> str:
return 'dwarf_planet'


class Satellite(Object):
def get_type(self) -> str:
return 'satellite'

dumper.py → kosmorrolib/dumper.py View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python3

# Kosmorro - Compute The Next Ephemeris
# Kosmorro - Compute The Next Ephemerides
# Copyright (C) 2019 Jérôme Deuchnord <jerome@deuchnord.fr>
#
# This program is free software: you can redistribute it and/or modify
@@ -17,13 +17,16 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.

from abc import ABC, abstractmethod
import datetime
from tabulate import tabulate
from skyfield import almanac
from .data import Object


class Dumper(ABC):
def __init__(self, ephemeris):
def __init__(self, ephemeris, date: datetime.date = datetime.date.today()):
self.ephemeris = ephemeris
self.date = date

@abstractmethod
def to_string(self):
@@ -32,23 +35,36 @@ class Dumper(ABC):

class TextDumper(Dumper):
def to_string(self):
return '\n\n'.join([self.get_planets(self.ephemeris['planets'], self.ephemeris['sun']),
self.get_moon(self.ephemeris['moon'])])
return '\n\n'.join(['Ephemerides of %s' % self.date.strftime('%A %B %d, %Y'),
self.get_asters(self.ephemeris['planets']),
self.get_moon(self.ephemeris['moon_phase']),
'Note: All the hours are given in UTC.'])

@staticmethod
def get_planets(planets, sun):
data = [['SUN', sun['rise'].utc_strftime('%H:%M'), '-', sun['set'].utc_strftime('%H:%M')]]
for planet in planets:
name = planet
planet_data = planets[planet]
planet_rise = planet_data['rise'].utc_strftime('%H:%M') if planet_data['rise'] is not None else ' -'
planet_maximum = planet_data['maximum'].utc_strftime('%H:%M') if planet_data['maximum'] is not None\
else ' -'
planet_set = planet_data['set'].utc_strftime('%H:%M') if planet_data['set'] is not None else ' -'
def get_asters(asters: [Object]) -> str:
data = []

for aster in asters:
name = aster.name

if aster.ephemerides.rise_time is not None:
planet_rise = aster.ephemerides.rise_time.utc_strftime('%H:%M')
else:
planet_rise = '-'

if aster.ephemerides.maximum_time is not None:
planet_maximum = aster.ephemerides.maximum_time.utc_strftime('%H:%M')
else:
planet_maximum = '-'

if aster.ephemerides.set_time is not None:
planet_set = aster.ephemerides.set_time.utc_strftime('%H:%M')
else:
planet_set = '-'

data.append([name, planet_rise, planet_maximum, planet_set])

return tabulate(data, headers=['Planet', 'Rise time', 'Maximum time', 'Set time'], tablefmt='simple',
return tabulate(data, headers=['Planet', 'Rise time', 'Culmination time', 'Set time'], tablefmt='simple',
stralign='center', colalign=('left',))

@staticmethod

+ 149
- 0
kosmorrolib/ephemerides.py View File

@@ -0,0 +1,149 @@
#!/usr/bin/env python3

# Kosmorro - Compute The Next Ephemerides
# Copyright (C) 2019 Jérôme Deuchnord <jerome@deuchnord.fr>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

import datetime
from skyfield import almanac
from skyfield.timelib import Time

from .data import Object, Position, AsterEphemerides
from .core import get_skf_objects, get_timescale, ASTERS, MONTHS

RISEN_ANGLE = -0.8333


class EphemeridesComputer:
def __init__(self, position: Position):
position.observation_planet = get_skf_objects()['earth']
self.position = position

def get_sun(self, start_time, end_time) -> dict:
times, is_risen = almanac.find_discrete(start_time,
end_time,
almanac.sunrise_sunset(get_skf_objects(), self.position))

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}

@staticmethod
def get_moon(year, month, day) -> dict:
time1 = get_timescale().utc(year, month, day - 10)
time2 = get_timescale().utc(year, month, day)

_, moon_phase = almanac.find_discrete(time1, time2, almanac.moon_phases(get_skf_objects()))

return {'phase': moon_phase[-1]}

@staticmethod
def get_asters_ephemerides_for_aster(aster, date: datetime.date, position: Position) -> Object:
skyfield_aster = get_skf_objects()[aster.skyfield_name]

def get_angle(time: Time) -> float:
return position.get_planet_topos().at(time).observe(skyfield_aster).apparent().altaz()[0].degrees

def is_risen(time: Time) -> bool:
return get_angle(time) > RISEN_ANGLE

get_angle.rough_period = 1.0
is_risen.rough_period = 0.5

start_time = get_timescale().utc(date.year, date.month, date.day)
end_time = get_timescale().utc(date.year, date.month, date.day, 23, 59, 59)

rise_times, arr = almanac.find_discrete(start_time, end_time, is_risen)
try:
culmination_time, _ = almanac._find_maxima(start_time, end_time, get_angle, epsilon=1./3600/24)
except ValueError:
culmination_time = None

if len(rise_times) == 2:
rise_time = rise_times[0 if arr[0] else 1]
set_time = rise_times[0 if not arr[1] else 0]
else:
rise_time = rise_times[0] if arr[0] else None
set_time = rise_times[0] if not arr[0] else None

culmination_time = culmination_time[0] if culmination_time is not None else None

aster.ephemerides = AsterEphemerides(rise_time, culmination_time, set_time)
return aster

@staticmethod
def is_leap_year(year: int) -> bool:
return (year % 4 == 0 and year % 100 > 0) or (year % 400 == 0)

def compute_ephemerides_for_day(self, year: int, month: int, day: int) -> dict:
return {'moon_phase': self.get_moon(year, month, day),
'planets': [self.get_asters_ephemerides_for_aster(aster, datetime.date(year, month, day), self.position)
for aster in ASTERS]}

def compute_ephemerides_for_month(self, year: int, month: int) -> [dict]:
if month == 2:
max_day = 29 if self.is_leap_year(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

ephemerides = []

for day in range(1, max_day + 1):
ephemerides.append(self.compute_ephemerides_for_day(year, month, day))

return ephemerides

def compute_ephemerides_for_year(self, year: int) -> [dict]:
ephemerides = {'seasons': self.get_seasons(year)}

for month in range(0, 12):
ephemerides[MONTHS[month]] = self.compute_ephemerides_for_month(year, month + 1)

return ephemerides

@staticmethod
def get_seasons(year: int) -> dict:
start_time = get_timescale().utc(year, 1, 1)
end_time = get_timescale().utc(year, 12, 31)
times, almanac_seasons = almanac.find_discrete(start_time, end_time, almanac.seasons(get_skf_objects()))

seasons = {}
for time, almanac_season in zip(times, almanac_seasons):
if almanac_season == 0:
season = 'MARCH'
elif almanac_season == 1:
season = 'JUNE'
elif almanac_season == 2:
season = 'SEPTEMBER'
elif almanac_season == 3:
season = 'DECEMBER'
else:
raise AssertionError

seasons[season] = time.utc_iso()

return seasons

def compute_ephemerides(self, year: int, month: int, day: int):
if day is not None:
return self.compute_ephemerides_for_day(year, month, day)

if month is not None:
return self.compute_ephemerides_for_month(year, month)

return self.compute_ephemerides_for_year(year)

Loading…
Cancel
Save