astroscript 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rubocop.defaults.yml +5818 -0
- data/.rubocop.yml +25 -0
- data/.ruby-version +1 -0
- data/ARCH.md +13 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/Rakefile +12 -0
- data/exe/astroscript +4 -0
- data/lib/astro_helper.rb +360 -0
- data/lib/astroscript/aspect.rb +188 -0
- data/lib/astroscript/body/const_body.rb +55 -0
- data/lib/astroscript/body/house_cusp.rb +34 -0
- data/lib/astroscript/body/method_body.rb +27 -0
- data/lib/astroscript/body/midpoint.rb +47 -0
- data/lib/astroscript/body.rb +495 -0
- data/lib/astroscript/calculator.rb +257 -0
- data/lib/astroscript/chart.rb +239 -0
- data/lib/astroscript/version.rb +5 -0
- data/lib/astroscript.rb +39 -0
- data/lib/ruby_extensions.rb +83 -0
- data/lib/swiss_ephemeris.rb +106 -0
- data/sig/astroscript.rbs +4 -0
- data/vendor/swe_data/houses.txt +33 -0
- data/vendor/swe_data/s136199s.se1 +0 -0
- data/vendor/swe_data/se00010s.se1 +0 -0
- data/vendor/swe_data/se90377s.se1 +0 -0
- data/vendor/swe_data/se90482s.se1 +0 -0
- data/vendor/swe_data/seas_18.se1 +0 -0
- data/vendor/swe_data/semo_18.se1 +0 -0
- data/vendor/swe_data/sepl_18.se1 +0 -0
- metadata +318 -0
@@ -0,0 +1,257 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Astroscript
|
4
|
+
class Calculator
|
5
|
+
attr_accessor :name, :latitude, :longitude, :altitude, :jd, :house_method, :tz, :arc, :prefix
|
6
|
+
attr_reader :flags, :asc, :mc, :vertex, :transformer, :equasc
|
7
|
+
|
8
|
+
def initialize *args
|
9
|
+
@arc = 0
|
10
|
+
params = args.last.is_a?(Hash) ? args.pop : {}
|
11
|
+
init_params(params)
|
12
|
+
# defaults
|
13
|
+
set_topo(51.476852, -0.000500) # Royal Observatory Greenwich, London, UK
|
14
|
+
self.datetime = DateTime.now.new_offset # Time.now.utc
|
15
|
+
end
|
16
|
+
|
17
|
+
def transformer=(method)
|
18
|
+
return unless method
|
19
|
+
raise ArgumentError, "must be a method or proc" unless method.respond_to?(:call)
|
20
|
+
|
21
|
+
@transformer = method
|
22
|
+
end
|
23
|
+
|
24
|
+
def options
|
25
|
+
opts = {}
|
26
|
+
opts[:house_method] = house_method
|
27
|
+
opts[:latitude] = AstroHelper.print_dms(latitude, lat: true)
|
28
|
+
opts[:longitude] = AstroHelper.print_dms(longitude, lon: true)
|
29
|
+
opts[:jd] = @jd
|
30
|
+
opts[:tz] = @tz.to_s
|
31
|
+
# date = @tz.to_local(DateTime.new(*Swe4r::swe_revjul(@jd))).to_s
|
32
|
+
date = DateTime.new(*Swe4r.swe_revjul(@jd)).to_s
|
33
|
+
opts[:date] = DateTime.parse(date).strftime("%Y-%m-%d %I:%M %p")
|
34
|
+
opts[:ayanamsha] = AstroHelper.print_dms ayanamsha, seconds: true
|
35
|
+
opts[:flags] = @flags
|
36
|
+
opts
|
37
|
+
end
|
38
|
+
|
39
|
+
def timezone
|
40
|
+
TZInfo::Timezone.get(@tz)
|
41
|
+
end
|
42
|
+
|
43
|
+
def utc
|
44
|
+
DateTime.new(*Swe4r.swe_revjul(@jd))
|
45
|
+
end
|
46
|
+
|
47
|
+
def datetime
|
48
|
+
@tz ? timezone.to_local(utc) : utc
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_a
|
52
|
+
[@tz, @jd, @latitude, @longitude, @altitude]
|
53
|
+
end
|
54
|
+
|
55
|
+
def init *args
|
56
|
+
AstroHelper.init_calc(self, *args)
|
57
|
+
end
|
58
|
+
|
59
|
+
def update(params)
|
60
|
+
init_params(params)
|
61
|
+
end
|
62
|
+
|
63
|
+
def ayanamsha
|
64
|
+
@ayanamsha ? Swe4r.swe_get_ayanamsa_ex_ut(@jd, @flags) : 0
|
65
|
+
end
|
66
|
+
|
67
|
+
def svp
|
68
|
+
-ayanamsha
|
69
|
+
end
|
70
|
+
|
71
|
+
def true_node?
|
72
|
+
!!@true_node
|
73
|
+
end
|
74
|
+
|
75
|
+
def oe # obliquity of the ecliptic
|
76
|
+
@oe ||= Swe4r.swe_calc_ut(@jd, -1, 0).first
|
77
|
+
end
|
78
|
+
|
79
|
+
def datetime=(time)
|
80
|
+
@oe = nil
|
81
|
+
@ra = nil
|
82
|
+
@planetary_hours = nil
|
83
|
+
@jd = AstroHelper.datetime_to_jd(time)
|
84
|
+
end
|
85
|
+
|
86
|
+
def set_topo(latitude, longitude, altitude = nil)
|
87
|
+
@latitude = latitude
|
88
|
+
@longitude = longitude
|
89
|
+
@altitude = altitude.to_f
|
90
|
+
Swe4r.swe_set_topo(@longitude, @latitude, @altitude)
|
91
|
+
end
|
92
|
+
|
93
|
+
def calc(id, equatorial: false, heliocentric: false)
|
94
|
+
flags = self.flags
|
95
|
+
flags |= Swe4r::SEFLG_EQUATORIAL if equatorial
|
96
|
+
flags |= Swe4r::SEFLG_HELCTR if heliocentric
|
97
|
+
|
98
|
+
Swe4r.swe_calc_ut(jd, id, flags)
|
99
|
+
end
|
100
|
+
|
101
|
+
def get_body(abbr, jd = nil)
|
102
|
+
@jd = jd if jd
|
103
|
+
$logger.warn "use :NN with @true_node configuration instead of #{abbr}" if %i[MN TN].include?(abbr)
|
104
|
+
if abbr =~ /^C(\d+)$/
|
105
|
+
HouseCusp.new(::Regexp.last_match(1).to_i, self)
|
106
|
+
else
|
107
|
+
Body.new(abbr, self)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def [](abbr)
|
112
|
+
abbr = abbr.upcase if abbr.size == 2
|
113
|
+
get_body(abbr)
|
114
|
+
end
|
115
|
+
|
116
|
+
def ascendant
|
117
|
+
@asc || (get_houses && @asc)
|
118
|
+
end
|
119
|
+
|
120
|
+
def midheaven
|
121
|
+
@mc
|
122
|
+
end
|
123
|
+
|
124
|
+
def house_cusps(calc_method = nil)
|
125
|
+
case calc_method
|
126
|
+
when :asc
|
127
|
+
AstroHelper.harmonic_cusps(get_position(:asc).lon)
|
128
|
+
when :mc
|
129
|
+
AstroHelper.harmonic_cusps(get_position(:mc).lon, 10)
|
130
|
+
else
|
131
|
+
get_houses(calc_method) unless @house_cusps && calc_method.nil?
|
132
|
+
@house_cusps[1..12]
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def get_houses(calc_method = nil)
|
137
|
+
if calc_method
|
138
|
+
@house_method = calc_method
|
139
|
+
else
|
140
|
+
@house_method ||= "P" # 'C'
|
141
|
+
end
|
142
|
+
raise "you must call set_topo with latitude and longitude before get_houses" unless @latitude && @longitude
|
143
|
+
|
144
|
+
flag = @ayanamsha ? Swe4r::SEFLG_SIDEREAL : 0
|
145
|
+
@house_cusps, ascmc, *, ascmc_speeds = Swe4r.swe_houses_ex2(jd, flag, @latitude, @longitude,
|
146
|
+
@house_method)
|
147
|
+
# @house_cusps.pop if @house_cusps.first.zero? # FIXME bug
|
148
|
+
@asc, @mc, @ramc, @vertex = *ascmc[0..3]
|
149
|
+
@equasc, = *ascmc[4..7]
|
150
|
+
# * ascmc[4] = equasc * "equatorial ascendant" *
|
151
|
+
# * ascmc[5] = coasc1 * "co-ascendant" (W. Koch) *
|
152
|
+
# * ascmc[6] = coasc2 * "co-ascendant" (M. Munkasey) *
|
153
|
+
# * ascmc[7] = polasc * "polar ascendant" (M. Munkasey) *
|
154
|
+
@asc_speed, @mc_speed, *, @vertex_speed = *ascmc_speeds[0..3]
|
155
|
+
true
|
156
|
+
end
|
157
|
+
|
158
|
+
def ramc
|
159
|
+
@ramc || (get_houses && @ramc)
|
160
|
+
end
|
161
|
+
|
162
|
+
def which_house(degree)
|
163
|
+
get_houses unless @house_cusps
|
164
|
+
min_cusp = house_cusps.min
|
165
|
+
degree += 360 if degree < min_cusp
|
166
|
+
# Iterate through the house cusps
|
167
|
+
house_cusps.each_with_index do |cusp, i|
|
168
|
+
# Check if the degree is within the current house
|
169
|
+
next_cusp = house_cusps[i + 1]
|
170
|
+
return 12 if next_cusp.nil?
|
171
|
+
|
172
|
+
next_cusp += 360 if next_cusp == min_cusp
|
173
|
+
# $logger.debug("#{degree.round(2)}.between? #{cusp},#{next_cusp}")
|
174
|
+
return i + 1 if degree.between?(cusp, next_cusp)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def dc
|
179
|
+
flip(asc)
|
180
|
+
end
|
181
|
+
|
182
|
+
def ic
|
183
|
+
flip(mc)
|
184
|
+
end
|
185
|
+
|
186
|
+
def av
|
187
|
+
flip(vertex)
|
188
|
+
end
|
189
|
+
alias avx av
|
190
|
+
|
191
|
+
def flip(d)
|
192
|
+
(d + 180) % 360
|
193
|
+
end
|
194
|
+
|
195
|
+
def before_sunrise?
|
196
|
+
ac = house_cusps[0] # sunrise
|
197
|
+
ic = house_cusps[3] # midnight
|
198
|
+
ic += 360 if ic < ac
|
199
|
+
get_body(:SO).to_f.between?(ac, ic) # before sunrise?
|
200
|
+
end
|
201
|
+
|
202
|
+
def solunar_phase
|
203
|
+
(get_body(:MO).to_f - get_body(:SO).to_f + 360) % 360
|
204
|
+
end
|
205
|
+
|
206
|
+
private
|
207
|
+
|
208
|
+
def init_params(params)
|
209
|
+
params = { ephemeris: :moshier, center: :geo, light_correction: true }.merge(params)
|
210
|
+
@house_method = params[:house_method] ||= "P" # Placidus as default
|
211
|
+
# default to true node
|
212
|
+
@true_node = true unless params[:true_node] == false || params[:mean_node] || params[:node] == :mean
|
213
|
+
# https://www.astro.com/swisseph/swephprg.htm
|
214
|
+
@flags = Swe4r::SEFLG_SPEED
|
215
|
+
if params[:astrometric]
|
216
|
+
@flags |= Swe4r::SEFLG_NOABERR | Swe4r::SEFLG_NOGDEFL
|
217
|
+
else
|
218
|
+
# no light time correction - return true positions, not apparent
|
219
|
+
@flags |= Swe4r::SEFLG_TRUEPOS unless params[:light_correction]
|
220
|
+
end
|
221
|
+
if (@ayanamsha = params[:sidereal])
|
222
|
+
@flags |= Swe4r::SEFLG_SIDEREAL if params[:sidereal]
|
223
|
+
if @ayanamsha.is_a? Array
|
224
|
+
Swe4r.swe_set_sid_mode(*@ayanamsha[0..2])
|
225
|
+
else
|
226
|
+
Swe4r.swe_set_sid_mode(@ayanamsha, 0, 0)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
if params[:heliocentric]
|
230
|
+
@flags |= Swe4r::SEFLG_HELCTR
|
231
|
+
elsif params[:topocentric]
|
232
|
+
@flags |= Swe4r::SEFLG_TOPOCTR
|
233
|
+
elsif params[:center]
|
234
|
+
case params[:center]
|
235
|
+
when :helio
|
236
|
+
@flags |= Swe4r::SEFLG_HELCTR
|
237
|
+
when :topo
|
238
|
+
@flags |= Swe4r::SEFLG_TOPOCTR
|
239
|
+
when :geo
|
240
|
+
else
|
241
|
+
raise "bad option! center: #{params[:center]}"
|
242
|
+
end
|
243
|
+
end
|
244
|
+
case params[:ephemeris]
|
245
|
+
when :moshier
|
246
|
+
@flags |= Swe4r::SEFLG_MOSEPH
|
247
|
+
when :jpl
|
248
|
+
@flags |= Swe4r::SEFLG_JPLEPH
|
249
|
+
when :swiss
|
250
|
+
@flags |= Swe4r::SEFLG_SWIEPH
|
251
|
+
else
|
252
|
+
raise "bad option! ephemeris: #{params[:ephemeris]}"
|
253
|
+
end
|
254
|
+
@flags |= Swe4r::SEFLG_EQUATORIAL if params[:equatorial] # right ascension and declination instead of lat/lon
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
@@ -0,0 +1,239 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Astroscript
|
4
|
+
class Chart
|
5
|
+
attr_reader :calc
|
6
|
+
attr_accessor :harmonic, :location
|
7
|
+
|
8
|
+
DEFAULT_BODIES = AstroHelper::PLANETS + AstroHelper::EXTRAS - [:VX]
|
9
|
+
|
10
|
+
# TODO: consider renaming opts to calc(_opts) ?
|
11
|
+
def initialize(birth_data, bodies: DEFAULT_BODIES, opts: {})
|
12
|
+
@opts = opts || {}
|
13
|
+
@opts[:true_node] ||= true
|
14
|
+
case birth_data
|
15
|
+
when Astroscript::Chart, Astroscript::Calculator
|
16
|
+
@calc = birth_data.dup
|
17
|
+
@calc.house_method = opts[:house_method]
|
18
|
+
@calc = @calc.calc if @calc.is_a?(Chart)
|
19
|
+
when Array
|
20
|
+
@calc = Astroscript::Calculator.new(opts).init(birth_data)
|
21
|
+
@location, name = birth_data[5..6]
|
22
|
+
when nil
|
23
|
+
raise ArgumentError, "no birth data provided!"
|
24
|
+
end
|
25
|
+
self.transformer = opts[:transformer]
|
26
|
+
@chart = {}
|
27
|
+
@harmonic = opts[:harmonic] || 1
|
28
|
+
@calc.arc = opts[:arc] if opts[:arc]
|
29
|
+
@calc.name = name || opts[:name] || ""
|
30
|
+
@calc.prefix = opts[:prefix]
|
31
|
+
get_bodies! bodies
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize_copy(orig)
|
35
|
+
super
|
36
|
+
# puts "duplicating @calc"
|
37
|
+
@calc = @calc.dup # attempt to prevent weirdness with transformers
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
# def dup
|
42
|
+
# Chart.new( @calc.dup, @chart.values.map(&:abbr), @opts)
|
43
|
+
# end
|
44
|
+
|
45
|
+
def transformer=(method)
|
46
|
+
@calc.transformer = method
|
47
|
+
end
|
48
|
+
|
49
|
+
def bodies
|
50
|
+
@chart.values
|
51
|
+
end
|
52
|
+
|
53
|
+
def recalc!(opts = {})
|
54
|
+
@calc.update(opts) unless opts.empty?
|
55
|
+
bodies.each(&:calculate!)
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
def asc=(deg)
|
60
|
+
@asc = deg.to_f
|
61
|
+
end
|
62
|
+
|
63
|
+
def asc
|
64
|
+
return @asc if @asc
|
65
|
+
|
66
|
+
if (a = find_body(:AC) || find_body(:C1))
|
67
|
+
a.degree
|
68
|
+
else
|
69
|
+
@calc.asc
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def deg(body, method = :to_f)
|
74
|
+
body.send(method)
|
75
|
+
end
|
76
|
+
|
77
|
+
def degree(body, asc = nil) # helper for drawing chart
|
78
|
+
((asc || self.asc) - 180 - deg(body)) % 360
|
79
|
+
end
|
80
|
+
|
81
|
+
def get_bodies!(planets)
|
82
|
+
planets.each do |abbr|
|
83
|
+
body = find_body!(abbr)
|
84
|
+
body.harmonize!(@harmonic) if body.is_a?(Astroscript::Body)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def find_body!(abbr)
|
89
|
+
case abbr
|
90
|
+
when *SwissEphemeris::BODIES.keys
|
91
|
+
key = AstroHelper.symbolize(SwissEphemeris::BODIES[abbr][:name])
|
92
|
+
self[key] = get_body(abbr)
|
93
|
+
else
|
94
|
+
raise ArgumentError, "unknown body: #{abbr}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def phase_angle(b1, b2)
|
99
|
+
b1 = find_body(b1) if b1.is_a?(Symbol) && SwissEphemeris::BODIES.keys.include?(b1)
|
100
|
+
b2 = find_body(b2) if b2.is_a?(Symbol) && SwissEphemeris::BODIES.keys.include?(b2)
|
101
|
+
# (b1.lon - b2.lon) % 360
|
102
|
+
b1 - b2
|
103
|
+
end
|
104
|
+
|
105
|
+
def []=(key, body)
|
106
|
+
@chart[key] = body
|
107
|
+
# body.chart = self
|
108
|
+
end
|
109
|
+
|
110
|
+
def [](abbr)
|
111
|
+
abbr = abbr.upcase if abbr.size == 2
|
112
|
+
get_body(abbr)
|
113
|
+
end
|
114
|
+
|
115
|
+
def find_body(abbr)
|
116
|
+
if (result = @chart.find{|_k, v| v.abbr == abbr })
|
117
|
+
result[1]
|
118
|
+
else
|
119
|
+
# calc.get_body(abbr)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def get_body(abbr)
|
124
|
+
body = SwissEphemeris::BODIES[abbr]
|
125
|
+
if (method = body[:method])
|
126
|
+
method = body[:name] if method == true
|
127
|
+
output = MethodBody.new(send(method), method, chart: self)
|
128
|
+
else
|
129
|
+
output = find_body(abbr) || calc.get_body(abbr)
|
130
|
+
end
|
131
|
+
output.harmonize(@harmonic)
|
132
|
+
end
|
133
|
+
|
134
|
+
def find_bodies(ary)
|
135
|
+
ary.map{|a| find_body(a) }
|
136
|
+
end
|
137
|
+
|
138
|
+
def aries_point
|
139
|
+
# ConstBody.new( 0, 'AP' )
|
140
|
+
0
|
141
|
+
end
|
142
|
+
|
143
|
+
def method_missing(method_name, ...)
|
144
|
+
if @calc.respond_to?(method_name)
|
145
|
+
@calc.send(method_name, ...)
|
146
|
+
elsif @chart.respond_to?(method_name)
|
147
|
+
@chart.send(method_name, ...)
|
148
|
+
else
|
149
|
+
raise "unknown method! :#{method_name}"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def night_birth?
|
154
|
+
sun = get_body(:SO).lon
|
155
|
+
desc = asc + 180
|
156
|
+
if desc > 360
|
157
|
+
sun.between?(desc % 360, asc)
|
158
|
+
else
|
159
|
+
sun.between?(asc, asc + 180)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def harmonic?
|
164
|
+
@harmonic > 1
|
165
|
+
end
|
166
|
+
|
167
|
+
def harmonize(h)
|
168
|
+
# output = Marshal.load( Marshal.dump(self) ) # deep copy
|
169
|
+
output = initialize_copy(self)
|
170
|
+
output.harmonize!(h)
|
171
|
+
end
|
172
|
+
|
173
|
+
def harmonize!(h)
|
174
|
+
@harmonic = h
|
175
|
+
bodies.each{|b| b.harmonize!(h) }
|
176
|
+
self
|
177
|
+
end
|
178
|
+
|
179
|
+
alias * harmonize
|
180
|
+
|
181
|
+
def print **opts # prefix: true, seconds: true, gate: true, house: true, spectrum: true
|
182
|
+
opts[:seconds] ||= true
|
183
|
+
puts @chart.map{|_k, v| v.to_s(opts) }.join("\n")
|
184
|
+
end
|
185
|
+
|
186
|
+
def to_a
|
187
|
+
calc.to_a << location
|
188
|
+
end
|
189
|
+
|
190
|
+
def to_json(*_args)
|
191
|
+
{
|
192
|
+
bodies: bodies.map(&:to_json)
|
193
|
+
}.to_json
|
194
|
+
end
|
195
|
+
|
196
|
+
def aspects(opts = {})
|
197
|
+
_bodies = bodies + opts[:with].to_a
|
198
|
+
if (orbs = opts[:orbs])
|
199
|
+
orbs.map do |flavor, orb|
|
200
|
+
_bodies.combination(2).map do |b1, b2|
|
201
|
+
Aspect.new(b1, b2, orb: orb, ratio: Aspect::FLAVORS.key(flavor))
|
202
|
+
end
|
203
|
+
end.flatten.uniq
|
204
|
+
else
|
205
|
+
_bodies.combination(2).map do |b1, b2|
|
206
|
+
Aspect.new(b1, b2, opts)
|
207
|
+
end.reject(&:invalid?)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def conjunctions(orb: 8)
|
212
|
+
aspects(orb: orb, harmonic: 1)
|
213
|
+
end
|
214
|
+
|
215
|
+
def trines(orb: 8)
|
216
|
+
aspects(orb: orb, harmonic: 3)
|
217
|
+
end
|
218
|
+
|
219
|
+
def midpoints
|
220
|
+
bodies.combination(2).map do |b1, b2|
|
221
|
+
Midpoint.new(b1, b2)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def midpoint_aspects(opts = {})
|
226
|
+
opts[:max_harmonic] ||= 2
|
227
|
+
opts[:orb] ||= 1.5
|
228
|
+
aspects(opts.merge(with: midpoints))
|
229
|
+
.select(&:midpoint?)
|
230
|
+
.reject(&:overlap?).reject(&:isotrap?)
|
231
|
+
.sort_by(&:orb)
|
232
|
+
end
|
233
|
+
|
234
|
+
# def isotraps( opts={} )
|
235
|
+
# opts[:orb] ||= 1.5
|
236
|
+
# aspects( opts.merge( with: midpoints ) ).select(&:isotrap?).reject(&:conjunction)
|
237
|
+
# end
|
238
|
+
end
|
239
|
+
end
|
data/lib/astroscript.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# $LOAD_PATH.unshift File.expand_path(".", __dir__)
|
4
|
+
|
5
|
+
require "dotenv/load" if Gem.loaded_specs.key?("dotenv") # do this first
|
6
|
+
require "byebug" if Gem.loaded_specs.key?("byebug")
|
7
|
+
|
8
|
+
require "active_support"
|
9
|
+
require "active_support/core_ext/string"
|
10
|
+
require "forwardable"
|
11
|
+
require "geocoder"
|
12
|
+
require "logger"
|
13
|
+
require "ostruct"
|
14
|
+
|
15
|
+
require_relative "ruby_extensions"
|
16
|
+
require_relative "swiss_ephemeris"
|
17
|
+
|
18
|
+
require_relative "astroscript/version"
|
19
|
+
require_relative "astro_helper"
|
20
|
+
require_relative "astroscript/calculator"
|
21
|
+
require_relative "astroscript/body"
|
22
|
+
require_relative "astroscript/body/midpoint"
|
23
|
+
require_relative "astroscript/body/const_body"
|
24
|
+
require_relative "astroscript/body/method_body"
|
25
|
+
# require_relative './astro/body/house_cusp'
|
26
|
+
require_relative "astroscript/chart"
|
27
|
+
require_relative "astroscript/aspect"
|
28
|
+
|
29
|
+
$logger = Logger.new($stdout)
|
30
|
+
$logger.level = Logger::WARN
|
31
|
+
# $logger.level = Logger::DEBUG
|
32
|
+
|
33
|
+
require 'dotenv/load'
|
34
|
+
Geocoder.configure(lookup: :geoapify, api_key: ENV.fetch("GEOCODER_API_KEY", nil))
|
35
|
+
SwissEphemeris.path = ENV.fetch("SE_EPHE_PATH", File.expand_path("../vendor/swe_data", __dir__))
|
36
|
+
|
37
|
+
module Astroscript
|
38
|
+
class Error < StandardError; end
|
39
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "date"
|
4
|
+
|
5
|
+
module ArrayExtensions
|
6
|
+
def diff
|
7
|
+
diffop{|x, y| y - x }
|
8
|
+
end
|
9
|
+
|
10
|
+
def diffop
|
11
|
+
d1 = dup.unshift(0)
|
12
|
+
d2 = dup.push(0)
|
13
|
+
(0..size).map{|i| yield(d1[i], d2[i]) }[1...-1]
|
14
|
+
end
|
15
|
+
|
16
|
+
def avg
|
17
|
+
sum / size.to_f
|
18
|
+
end
|
19
|
+
|
20
|
+
def gmean
|
21
|
+
reduce(:*).to_f**(1.0 / size)
|
22
|
+
end
|
23
|
+
|
24
|
+
def **(other)
|
25
|
+
map{|x| x * other }
|
26
|
+
end
|
27
|
+
|
28
|
+
# harmonic operator
|
29
|
+
def >>(other)
|
30
|
+
map{|x| (x * other) % 360.0 }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
Array.include ArrayExtensions
|
34
|
+
|
35
|
+
module NumericExtensions
|
36
|
+
def sgn
|
37
|
+
negative? ? -1 : 1
|
38
|
+
end
|
39
|
+
|
40
|
+
def mod360
|
41
|
+
self % 360
|
42
|
+
end
|
43
|
+
end
|
44
|
+
Numeric.include NumericExtensions
|
45
|
+
|
46
|
+
module IntegerExtensions
|
47
|
+
def ordinalize
|
48
|
+
n = self
|
49
|
+
return "#{n}th" if (11..13).include?(n % 100)
|
50
|
+
|
51
|
+
case n % 10
|
52
|
+
when 1 then "#{n}st"
|
53
|
+
when 2 then "#{n}nd"
|
54
|
+
when 3 then "#{n}rd"
|
55
|
+
else "#{n}th"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
PRIMES = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103,
|
60
|
+
107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349].freeze
|
61
|
+
|
62
|
+
def hph
|
63
|
+
n = self
|
64
|
+
return 1 if n == 1
|
65
|
+
|
66
|
+
PRIMES.reverse.find{|p| n.gcd(p) != 1 }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
Integer.include IntegerExtensions
|
70
|
+
|
71
|
+
module DateTimeExtensions
|
72
|
+
def round(granularity = 1.hour)
|
73
|
+
Time.at((to_time.to_i / granularity).round * granularity).to_datetime
|
74
|
+
end
|
75
|
+
end
|
76
|
+
DateTime.include DateTimeExtensions
|
77
|
+
|
78
|
+
module FloatExtensions
|
79
|
+
def to_dms(opts = {})
|
80
|
+
AstroHelper.print_dms(self, opts)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
Float.include FloatExtensions
|