astroscript 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|