-
-
Notifications
You must be signed in to change notification settings - Fork 3
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
Refactor Angle as a single class #13
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,7 +15,9 @@ jobs: | |
strategy: | ||
matrix: | ||
ruby: | ||
- 3.1.4 | ||
- 3.2.0 | ||
- 3.2.1 | ||
- 3.2.2 | ||
- 3.2.3 | ||
- 3.3.0 | ||
steps: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,27 +22,22 @@ def apply | |
delta_longitude = Astronoby::Angle.as_degrees( | ||
-20.5 * | ||
Math.cos( | ||
@sun_longitude.to_radians.value - @coordinates.longitude.to_radians.value | ||
) / Math.cos(@coordinates.latitude.to_radians.value) / 3600 | ||
@sun_longitude.radians - @coordinates.longitude.radians | ||
) / Math.cos(@coordinates.latitude.radians) / 3600 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Out of scope for this PR but I'm wondering with the 3600 number comes from? I'd love to see this as a named constant. Given that I associate the number 360 with degrees in a circle, I'm wondering if this might be a number that needs to be owned by |
||
) | ||
|
||
delta_latitude = Astronoby::Angle.as_degrees( | ||
-20.5 * | ||
Math.sin( | ||
@sun_longitude.to_radians.value - | ||
@coordinates.longitude.to_radians.value | ||
) * | ||
Math.sin(@coordinates.latitude.to_radians.value) / 3600 | ||
Math.sin(@sun_longitude.radians - @coordinates.longitude.radians) * | ||
Math.sin(@coordinates.latitude.radians) / 3600 | ||
Comment on lines
+31
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similarly here, if you had (@sun_longitude - @coordinates.logitude).sin * @coordinates.lattitude.sin / 3600 Note that you don't need to implement |
||
) | ||
|
||
Astronoby::Coordinates::Ecliptic.new( | ||
latitude: Astronoby::Angle.as_degrees( | ||
@coordinates.latitude.to_degrees.value + | ||
delta_latitude.value | ||
@coordinates.latitude.degrees + delta_latitude.degrees | ||
), | ||
longitude: Astronoby::Angle.as_degrees( | ||
@coordinates.longitude.to_degrees.value + | ||
delta_longitude.value | ||
@coordinates.longitude.degrees + delta_longitude.degrees | ||
) | ||
) | ||
end | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,57 +4,109 @@ | |
|
||
module Astronoby | ||
class Angle | ||
UNITS = [ | ||
DEGREES = :degrees, | ||
HOURS = :hours, | ||
RADIANS = :radians | ||
].freeze | ||
|
||
UNIT_CLASS_NAMES = { | ||
DEGREES => "Astronoby::Degree", | ||
HOURS => "Astronoby::Hour", | ||
RADIANS => "Astronoby::Radian" | ||
} | ||
|
||
PRECISION = 14 | ||
PI = BigMath.PI(10) | ||
PI = BigMath.PI(PRECISION) | ||
PI_IN_DEGREES = BigDecimal("180") | ||
|
||
RADIAN_PER_HOUR = PI / BigDecimal("12") | ||
MINUTES_PER_DEGREE = BigDecimal("60") | ||
MINUTES_PER_HOUR = BigDecimal("60") | ||
SECONDS_PER_MINUTE = BigDecimal("60") | ||
SECONDS_PER_HOUR = MINUTES_PER_HOUR * SECONDS_PER_MINUTE | ||
|
||
FORMATS = %i[dms hms].freeze | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Freeze all the things! ❄️ |
||
|
||
class << self | ||
UNIT_CLASS_NAMES.each do |unit, class_name| | ||
define_method("as_#{unit}") do |angle| | ||
Kernel.const_get(class_name).new(angle) | ||
end | ||
def zero | ||
new(0) | ||
end | ||
|
||
def as_radians(radians) | ||
new(radians) | ||
end | ||
|
||
def as_degrees(degrees) | ||
radians = degrees / PI_IN_DEGREES * PI | ||
new(radians) | ||
end | ||
|
||
def as_hours(hours) | ||
radians = hours * RADIAN_PER_HOUR | ||
new(radians) | ||
end | ||
|
||
def as_hms(hour, minute, second) | ||
angle = hour + minute / 60.0 + second / 3600.0 | ||
Kernel.const_get(UNIT_CLASS_NAMES[HOURS]).new(angle) | ||
hours = hour + minute / MINUTES_PER_HOUR + second / SECONDS_PER_HOUR | ||
as_hours(hours) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's clever! Nice reuse |
||
end | ||
|
||
def as_dms(degree, minute, second) | ||
sign = degree.negative? ? -1 : 1 | ||
angle = degree.abs + minute / 60.0 + second / 3600.0 | ||
Kernel.const_get(UNIT_CLASS_NAMES[DEGREES]).new(sign * angle) | ||
degrees = degree.abs + minute / MINUTES_PER_HOUR + second / SECONDS_PER_HOUR | ||
as_degrees(sign * degrees) | ||
end | ||
end | ||
|
||
UNITS.each do |unit| | ||
define_method("to_#{unit}") do | ||
raise NotImplementedError, "#{self.class} must implement #to_#{unit} method." | ||
Comment on lines
-41
to
-43
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yay to cleaning up some metaprogramming! |
||
def radians | ||
@angle | ||
end | ||
|
||
def degrees | ||
@angle * PI_IN_DEGREES / PI | ||
end | ||
|
||
def hours | ||
@angle / RADIAN_PER_HOUR | ||
end | ||
|
||
def initialize(angle) | ||
@angle = if angle.is_a?(Integer) || angle.is_a?(BigDecimal) | ||
BigDecimal(angle) | ||
else | ||
BigDecimal(angle, PRECISION) | ||
end | ||
end | ||
|
||
def initialize(angle, unit:) | ||
@angle = BigDecimal(angle, PRECISION).ceil(PRECISION) | ||
@unit = unit | ||
def str(format) | ||
case format | ||
when :dms then to_dms(degrees).format | ||
when :hms then to_hms(hours).format | ||
else | ||
raise UnsupportedFormatError.new( | ||
"Expected a format between #{FORMATS.join(", ")}, got #{format}" | ||
) | ||
end | ||
end | ||
|
||
def value | ||
@angle | ||
def to_dms(deg) | ||
sign = deg.negative? ? "-" : "+" | ||
absolute_degrees = deg.abs | ||
degrees = absolute_degrees.floor | ||
decimal_minutes = MINUTES_PER_DEGREE * (absolute_degrees - degrees) | ||
absolute_decimal_minutes = ( | ||
MINUTES_PER_DEGREE * (absolute_degrees - degrees) | ||
).abs | ||
minutes = decimal_minutes.floor | ||
seconds = SECONDS_PER_MINUTE * ( | ||
absolute_decimal_minutes - absolute_decimal_minutes.floor | ||
) | ||
|
||
Dms.new(sign, degrees, minutes, seconds.to_f.floor(4)) | ||
end | ||
|
||
def ==(other) | ||
value == other.value && self.class == other.class | ||
def to_hms(hrs) | ||
absolute_hours = hrs.abs | ||
hours = absolute_hours.floor | ||
decimal_minutes = MINUTES_PER_HOUR * (absolute_hours - hours) | ||
absolute_decimal_minutes = ( | ||
MINUTES_PER_HOUR * (absolute_hours - hours) | ||
).abs | ||
minutes = decimal_minutes.floor | ||
seconds = SECONDS_PER_MINUTE * ( | ||
absolute_decimal_minutes - absolute_decimal_minutes.floor | ||
) | ||
|
||
Hms.new(hours, minutes, seconds.to_f.floor(4)) | ||
end | ||
end | ||
end |
This file was deleted.
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,9 +14,9 @@ def initialize(epoch:) | |
|
||
def ecliptic_coordinates | ||
Coordinates::Ecliptic.new( | ||
latitude: Angle.as_degrees(0), | ||
latitude: Angle.zero, | ||
longitude: Angle.as_degrees( | ||
(true_anomaly.value + longitude_at_perigee.to_degrees.value) % 360 | ||
(true_anomaly.degrees + longitude_at_perigee.degrees) % 360 | ||
) | ||
) | ||
Comment on lines
18
to
21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Out of scope for this first PR but a future refactor you could do would be to implement some math operators on longitude: (true_anomaly + logitude_at_perigee) % 360 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The same goes for a lot of the math in this file. By implementing a few arithmetic operators on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You will need to pay attention to the types when implementing math operations though. Keep in mind:
For example There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd love to see a future world where this file doesn't do any unwrapping or re-wrapping of angles |
||
end | ||
|
@@ -33,29 +33,24 @@ def horizontal_coordinates(latitude:, longitude:) | |
|
||
def mean_anomaly | ||
Angle.as_degrees( | ||
( | ||
longitude_at_base_epoch.to_degrees.value - | ||
longitude_at_perigee.to_degrees.value | ||
) % 360 | ||
(longitude_at_base_epoch.degrees - longitude_at_perigee.degrees) % 360 | ||
) | ||
end | ||
|
||
def true_anomaly | ||
eccentric_anomaly = Astronoby::Util::Astrodynamics.eccentric_anomaly_newton_raphson( | ||
mean_anomaly, | ||
orbital_eccentricity.to_degrees.value, | ||
orbital_eccentricity.degrees, | ||
2e-06, | ||
10 | ||
) | ||
|
||
tan = Math.sqrt( | ||
(1 + orbital_eccentricity.to_degrees.value)./( | ||
1 - orbital_eccentricity.to_degrees.value | ||
) | ||
) * Math.tan(eccentric_anomaly.to_radians.value / 2) | ||
(1 + orbital_eccentricity.degrees) / (1 - orbital_eccentricity.degrees) | ||
) * Math.tan(eccentric_anomaly.radians / 2) | ||
|
||
Astronoby::Angle.as_degrees( | ||
(Astronoby::Angle.as_radians(Math.atan(tan)).to_degrees.value * 2) % 360 | ||
(Astronoby::Angle.as_radians(Math.atan(tan)).degrees * 2) % 360 | ||
) | ||
end | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of scope for this PR but for the future, I wonder if it would be useful to implement some math operators directly on
Angle
? Then you don't need to unwrap the angle values. Ideally you get to treatAngle
instances like numbers, not just wrappers of numbers.With
Angle#cos
andAngle#-
this method would look like: