Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generalize hour offset to float so can plot analemma at any time of day #11

Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Changed int to float and started on actual tests
russellgoyder committed Sep 15, 2024

Verified

This commit was signed with the committer’s verified signature.
umihico Umihiko Iwasa
commit 57783fe95c58b0b1155b5a47d06108f5116afe4d
57 changes: 13 additions & 44 deletions docs/sundial_plots.ipynb

Large diffs are not rendered by default.

30 changes: 23 additions & 7 deletions src/analemma/plot.py
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@ class DialParameters:
Parameters defining a sundial

Parameters:
theta: $90^\circ - \\theta$ is the latitude of the sundial
theta: $90^\\circ - \\theta$ is the latitude of the sundial
iota: Inclination of the gnomon
i: Inclination of the dial face
d: Declination of the dial face
@@ -217,7 +217,7 @@ def orbit_date_to_day(the_date: datetime.date, year=2024) -> int:


def sunray_dialface_angle_over_one_year(
planet: orbit.PlanetParameters, dial: DialParameters, hour_offset=0
planet: orbit.PlanetParameters, dial: DialParameters, hour_offset: float = 0.0
):
"""
Calculate daily the time since perihelion in seconds and the corresponding sin(sunray-dialface angle)
@@ -283,7 +283,10 @@ def _plot_analemma_segment(


def _analemma_plot_sampling_times(
season: Season, hour_offset, planet: orbit.PlanetParameters, dial: DialParameters
season: Season,
hour_offset: float,
planet: orbit.PlanetParameters,
dial: DialParameters,
):
# season lengths are [89, 91, 94, 91] (Winter Spring Summer Autumn)
# place equinoxes and solstices in the middle for plotting
@@ -319,12 +322,21 @@ def _analemma_plot_sampling_times(
def plot_analemma_season_segment(
ax,
season: Season,
hour_offset: int,
hour_offset: float,
planet: orbit.PlanetParameters,
dial: DialParameters,
**kwargs,
):
"Plot the analemma segment for the given season"
"""
Plot the analemma segment for the given season

Parameters:
ax: matplotlib axes
season: The given season
hour_offset: Number of hours relative to noon, eg -2.25 corresponds to 9:45am
planet: The planet on which the dial is located
dial: The orientation and location of the sundial
"""

times = _analemma_plot_sampling_times(season, hour_offset, planet, dial)
return _plot_analemma_segment(
@@ -395,7 +407,7 @@ def plot_special_sun_path(

def _analemma_point_coordinates(
days_since_perihelion: int,
hour_offset: int,
hour_offset: float,
planet: orbit.PlanetParameters,
dial: DialParameters,
):
@@ -444,7 +456,7 @@ def _furthest_point(p1, p2):


def _analemma_label_coordinates(
hour_offset: int, planet: orbit.PlanetParameters, dial: DialParameters
hour_offset: float, planet: orbit.PlanetParameters, dial: DialParameters
):
june_solstice_day, december_solstice_day = _solstice_days(planet, dial)

@@ -477,6 +489,8 @@ def falls_on_dial(x, y):
def hour_offset_to_oclock(hour_offset: int):
"""
Render an integer hour offset (eg +2) as the corresponding time (eg '2pm')

Note that any non-integer part of the hour offset will be truncated
"""
if hour_offset == 0:
return "12pm"
@@ -495,6 +509,8 @@ def annotate_analemma_with_hour(
):
"""
For the given hour, annotate with the time

Note that any non-integer part of the hour offset will be truncated
"""
if hour_offset % 3 == 0:
points = _analemma_label_coordinates(hour_offset, planet, dial)
16 changes: 16 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import pytest
import numpy as np
from analemma import orbit
from analemma import plot as ap


@pytest.fixture
def earth():
return orbit.PlanetParameters.earth()


@pytest.fixture
def camdial():
return ap.DialParameters(
theta=37.5 / 180 * np.pi, iota=37.5 / 180 * np.pi, i=0, d=0
) # Analemmatic dial in Cambridge, UK
54 changes: 54 additions & 0 deletions tests/test_plot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import datetime
import numpy as np
from analemma import plot as ap


def test_sun_rise_noon_set(earth, camdial):
st = ap.find_sun_rise_noon_set_relative_to_dial_face(
days_since_perihelion=50, planet=earth, dial=camdial
)
assert (
st.sunrise.hours_from_midnight
< st.noon.hours_from_midnight
< st.sunset.hours_from_midnight
)


def test_solstices(earth, camdial):
"""
The June and December solstices should occur on the longest and shortest day of the year as seen on a horizontal dial
"""

sun_times = [
ap.find_sun_rise_noon_set_relative_to_dial_face(
days_since_perihelion, earth, camdial
)
for days_since_perihelion in np.arange(0, 365)
]

day_lengths = [
st.sunset.hours_from_midnight - st.sunrise.hours_from_midnight
for st in sun_times
]
december_solstice = np.argmin(day_lengths)
june_solstice = np.argmax(day_lengths)

assert ap.orbit_day_to_date(0) == datetime.date.fromisoformat("2024-01-03")
assert ap.orbit_day_to_date(june_solstice) == datetime.date.fromisoformat(
"2024-06-21"
)
assert ap.orbit_day_to_date(december_solstice) == datetime.date.fromisoformat(
"2024-12-21"
)

assert ap.orbit_date_to_day(datetime.date.fromisoformat("2024-01-03")) == 0
assert (
ap.orbit_date_to_day(datetime.date.fromisoformat("2024-06-21")) == june_solstice
)
assert (
ap.orbit_date_to_day(datetime.date.fromisoformat("2024-12-21"))
== december_solstice
)

arbitrary_date = datetime.date.fromisoformat("2024-05-26")
assert ap.orbit_day_to_date(ap.orbit_date_to_day(arbitrary_date)) == arbitrary_date