Module: AstroHelper

Defined in:
lib/astro_helper.rb

Constant Summary collapse

PLANETS =
%i[SO MO ME VE MA JU SA UR NE PL].freeze
EXTRAS =
%i[AC MC NN CH VX].freeze
URANIAN =
%i[CU HA ZE KR AN AD VU PO].freeze
ASTEROIDS =
%i[CE PA JU VA].freeze
LOTS =
%i[POF POS POH].freeze
LUNAR_PHASES =
%i[new crescent first_quarter gibbous full disseminating last_quarter balsamic].freeze
PHASE_GLYPHS =

northern hemisphere

%w[πŸŒ‘ πŸŒ’ πŸŒ“ πŸŒ” πŸŒ• πŸŒ– πŸŒ— 🌘].freeze
HOUSE_THEMES =
%w[self possessions communication home creativity/pleasure health/service partnerships
sex/death/rebirth philosophy/travel career friendships dissolution/karma].unshift(nil)
SYMBOLS =
{ # http://en.wikipedia.org/wiki/Astrological_symbols
  sun: ["\u2609", "SO"], # "β˜‰"
  earth: ["\u2295", "EA"], # "βŠ•"
  # :earth      => ["\u2641", 'EA'], #"♁"
  moon: ["\u263d", "MO"], # "☾"
  mercury: ["\u263f", "ME"], # "☿"
  venus: ["\u2640", "VE"], # "♀"
  mars: ["\u2642", "MA"], # "β™‚"
  jupiter: ["\u2643", "JU"], # "♃"
  saturn: ["\u2644", "SA"], # "β™„"
  uranus: ["\u2645", "UR"], # "β™…"
  neptune: ["\u2646", "NE"], # "♆"
  pluto: ["\u2647", "PL"], # "♇"
  n_node: ["\u260a", "NN"], # "☊"
  s_node: ["\u260b", "SN"], # "β˜‹"
  chiron: ["\u26b7", "CH"],
  ceres: %w[Κ‘ CE], # "Κ‘"
  pallas: ["\u26b4", "PA"],
  juno: ["\u26b5", "JN"],
  vesta: ["\u26b6", "VA"],
  ascendant: %w[AC AC],
  descendant: %w[DC DC],
  midheaven: %w[MC MC],
  nadir: %w[IC IC],
  aries: ["β™ˆ", "AP"],
  vertex: ["🜊", "VX"], # TODO: "We surmise that it is possible to introduce another 10 points, complementing the Vertex or Antivertex to a 12-fold division along the prime vertical" # https://web.archive.org/web/20150926042925/http://www.levante.org/svarogich/en/principia_en/part01.html
  av: ["A🜊", "AV"],
  retrograde: "\u211e", # "β„ž",
  zodiac: ("\u2648".."\u2653").to_a # %w[β™ˆ ♉ β™Š β™‹ β™Œ ♍ β™Ž ♏ ♐ β™‘ β™’ β™“ ]
}.freeze

Class Method Summary collapse

Class Method Details

.abbr(key) ⇒ Object



333
334
335
# File 'lib/astro_helper.rb', line 333

def abbr(key)
  SYMBOLS[key].last.to_sym
end

.abbr_to_key(abbr) ⇒ Object



337
338
339
# File 'lib/astro_helper.rb', line 337

def abbr_to_key(abbr)
  SYMBOLS.find{|_k, v| v.last == abbr.to_s }.first
end

.aspects(planets, _opts = {}) ⇒ Object



252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/astro_helper.rb', line 252

def aspects(planets, _opts = {})
  opts = { clear_cache: true }.merge!(_opts)
  if @aspects.nil? || opts[:clear_cache]
    @aspects = []
    # $logger.debug "Aspects: opts = #{opts.inspect}"
    calc_all_aspects(planets.reverse, opts)
  end
  [%i[NN SN], %i[MC IC], %i[VX AV], %i[AC DC]].each do |anti|
    @aspects.reject!{|a| (a.bodies.map(&:abbr) & anti) == anti }
  end
  # $logger.debug("DONE! (#{@aspects.count})")
  @aspects
end

.calc_all_aspects(planets, opts = {}) ⇒ Object



231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/astro_helper.rb', line 231

def calc_all_aspects(planets, opts = {})
  return if planets.size < 2

  p1 = planets.pop
  # $logger.debug( "**** Aspects for #{p1.name}" )
  # $logger.debug( "  with #{planets.map(&:abbr)}" )
  planets.reverse.each do |p2|
    aspects = p1.%(p2, opts)
    next unless aspects.any?

    $logger.debug(aspects.map(&:to_s))
    @aspects += aspects
    # @aspects << [p1.to_sym, p2.to_sym, aspects.inject({}){|m,e| m.merge(e.to_a.last) } ]
  end
  calc_all_aspects(planets, opts)
end

.calculate_aspects(degree1, degree2, orb, max, min_orb = 0) ⇒ Object



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/astro_helper.rb', line 204

def calculate_aspects(degree1, degree2, orb, max, min_orb = 0)
  # Create an empty array to store the harmonics
  harmonics = {}
  delta = degree1 - degree2
  # Iterate over the range of harmonics
  (1..max).each do |harmonic|
    # Calculate the difference based on the harmonic and default orb value
    difference = delta * harmonic % 360
    difference = 360 - difference if difference > 300
    # Check if the difference is within the orb range
    next unless difference <= orb && difference > min_orb

    multiple = false
    if harmonics.any?
      first = harmonics.first[0]
      multiple = if first > 1
                   harmonic.gcd(first) > 1
                 else
                   (difference - (harmonics.first[1] * harmonic)) < 0.001
                 end
    end
    # If the difference is within the orb range, add the harmonic to the array
    harmonics[harmonic] = difference unless multiple
  end
  harmonics.transform_values{|v| v.round(3) }
end

.clear_aspects!Object



248
249
250
# File 'lib/astro_helper.rb', line 248

def clear_aspects!
  @aspects = nil
end

.converse(calc, natal_calc) ⇒ Object

this should work for a calc or a chart



342
343
344
345
346
347
348
# File 'lib/astro_helper.rb', line 342

def converse(calc, natal_calc)
  diff = (natal_calc.jd - calc.jd).abs
  output = calc.dup
  output.jd = natal_calc.jd - diff
  output.recalc!
  output
end

.datetime_to_jd(time = DateTime.now) ⇒ Object



350
351
352
353
# File 'lib/astro_helper.rb', line 350

def datetime_to_jd(time = DateTime.now)
  time = time.utc.to_datetime if time.is_a?(ActiveSupport::TimeWithZone)
  Swe4r.swe_julday(time.year, time.month, time.day, time.hour + (time.min / 60.0) + (0.1 / 3600.0))
end

.deg_to_gate(deg) ⇒ Object



156
157
158
159
# File 'lib/astro_helper.rb', line 156

def deg_to_gate(deg)
  gate, line, * = HumanDesign.deg_to_gate(deg)
  format("%2d.%1d%1s", gate, line, print_hexagram(gate))
end

.deg_to_s(angle, opts = {}) ⇒ Object



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/astro_helper.rb', line 161

def deg_to_s(angle, opts = {})
  opts = opts.dup
  opts = { padding: 12, tabs: true, angle: true, glyph: false, prefix: false, seconds: false, gate: false,
           base: false, house: false, spectrum: false }.merge!(opts)
  degree, minute, seconds = dms(angle, true)
  sign = deg_to_sign(angle)
  output = ""
  if opts[:angle]
    if opts[:glyph]
      # output += sprintf("%s %2dΒΊ%2d'", sign_to_str(sign, :glyph), degree, minute.round)
      output += format("%02d%s%02d", degree, sign_to_str(sign, :glyph), minute.round)
    else
      output += format("%2dΒΊ %s ", degree, sign_to_str(sign, :short))
      output += opts[:seconds] ? format("%2d'%2d", minute.floor, seconds.round) : format("%2d'", minute.round)
    end
  end
  output = format("%#{opts[:padding]}s %s", opts[:prefix], output) if opts[:prefix]
  output += opts[:postfix].to_s if opts[:postfix]

  if (val = opts[:gate] || opts[:spectrum])
    if val.is_a?(Numeric) # adjust for ayanamsha
      angle = (angle + val) % 360
      # $logger.debug("adjusting ayanamsha by #{val}")
    end
    gate, line, color, tone, base = HumanDesign.deg_to_gate(angle)
    if opts[:gate]
      output += format(" %1s %2d.%1d", print_hexagram(gate), gate, line)
      output += ".#{color}.#{tone}.#{base}" if opts[:base]
    end
    output += " (#{GeneKeys.gate_spectrum(gate)})" if opts[:spectrum]
  end
  output = output.gsub("\t", " ") unless opts[:tabs]
  output.strip if opts[:strip]
  output
end

.deg_to_sign(deg) ⇒ Object



152
153
154
# File 'lib/astro_helper.rb', line 152

def deg_to_sign(deg)
  SwissEphemeris::SIGNS[((deg % 360) / 30).floor]
end

.digital_root(num, max = 9) ⇒ Object



270
271
272
273
# File 'lib/astro_helper.rb', line 270

def digital_root(num, max = 9)
  num = num.to_s.chars.map(&:to_i).inject(:+) until num <= max
  num
end

.dms(degree, zodiac = true) ⇒ Object



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/astro_helper.rb', line 69

def dms(degree, zodiac = true)
  d = degree.abs.floor
  m = (degree.abs - d) * 60.0
  if m.round == 60
    m = 0
    d += 1
  end
  s = (m - m.floor) * 60.0
  if s.round == 60
    s = 0
    m += 1
  end
  d %= 30 if zodiac
  [d, m, s]
end

.dms_to_deg(d, m, s) ⇒ Object



85
86
87
# File 'lib/astro_helper.rb', line 85

def dms_to_deg(d, m, s)
  d + (m * 60) + (s * 3600)
end

.get_timezone_from_latlon(latitude, longitude) ⇒ Object



197
198
199
200
201
202
# File 'lib/astro_helper.rb', line 197

def get_timezone_from_latlon(latitude, longitude)
  response = HTTParty.get("https://api.geoapify.com/v1/geocode/reverse?lat=#{latitude}&lon=#{longitude}&format=json&apiKey=#{API_KEY}")
  return JSON.parse(response.body)["results"].first["timezone"] if response.success?

  puts "Request failed: #{response.code}"
end

.init_calc(calc, *args) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/astro_helper.rb', line 38

def init_calc(calc, *args)
  case args.size
  when 1
    ary = args.first.dup
    tz = ary.shift
    tz = tz["name"] if tz.respond_to?(:[])
    tz = tz.name if tz.respond_to?(:name)
    calc.tz = tz
    time = ary.shift
    if time.respond_to?(:jd)
      time = time.jd
    else
      time = datetime_to_jd(time) unless time.is_a?(Numeric)
    end
    calc.jd = time
    calc.set_topo(*ary[0...3])
    calc.datetime = DateTime.now if calc.jd.zero?
    calc
  else
    year, month, day, hour, minute, location, name = *args
    second = ((minute - minute.floor) * 60).round
    minute = minute.floor
    results = Geocoder.search(location)
    calc.set_topo(results.first.latitude, results.first.longitude)
    tz = results.first.send(:properties)["timezone"]
    calc.tz = tz["name"]
    calc.datetime = TZInfo::Timezone.get(tz["name"]).local_time(year, month, day, hour, minute, second, 10/600r).utc
    calc.to_a + [location, name]
  end
end

.name_to_sym(name) ⇒ Object



290
291
292
# File 'lib/astro_helper.rb', line 290

def name_to_sym(name)
  name.to_s.delete(" ").sub(".", "_").downcase.to_sym
end


89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/astro_helper.rb', line 89

def print_dms(deg, opts = {})
  if opts[:flavor] == :astrolog
    opts[:zodiac] = true
    opts[:glyph] = false
    opts[:minutes] = ""
  end
  opts[:glyph] = true if opts[:glyph].nil?
  d = deg.to_f
  neg = d.negative?
  d, m, s = dms(d, false)
  if opts[:zodiac]
    sign, d = d.divmod(30)
    opts[:separator] = opts[:glyph] ? SwissEphemeris::ZODIAC_SYM[sign] : SwissEphemeris::SIGNS[sign][0...3].capitalize
  end
  opts[:separator] ||= "Β°"
  if opts[:latitude] || opts[:longitude]
    opts[:seconds] ||= false
    opts[:separator] = neg ? "S" : "N" if opts[:latitude]
    opts[:separator] = neg ? "E" : "W" if opts[:longitude]
    opts[:separator] = "ΒΊ#{opts[:separator]}" if opts[:degree]
    neg = false
    d = d.abs
    output = if opts[:seconds] == false
               format("%02d%s%02d'", d, opts[:separator], m.round)
             else
               format("%02d%s%02d'%02d\"", d, opts[:separator], m.floor, s.round)
             end
  elsif opts[:full]
    output = format("%2d#{opts[:separator]}%2d'%7.4f", d, m.floor, s)
  elsif opts[:seconds] || (d.zero? && m.floor.zero? && !s.round.zero?)
    output = format("%02d#{opts[:separator]}%02d'%02d\"", d, m.floor, s.round)
  else # default to rounded minutes
    opts[:minutes] ||= "'"
    output = format("%02d#{opts[:separator]}%02d#{opts[:minutes]}", d, m.round)
  end
  if neg
    output = if output[0] == " "
               "-#{output[1..]}"
             else
               "-#{output}"
             end
  elsif opts[:plus]
    output = if output[0] == " "
               "+#{output[1..]}"
             else
               "+#{output}"
             end
  end
  output
end


266
267
268
# File 'lib/astro_helper.rb', line 266

def print_hexagram(idx)
  (0x4DC0 + idx - 1).to_s(16).to_i(16).chr("UTF-8")
end


277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/astro_helper.rb', line 277

def print_houses(calc, calc_method = nil)
  output = calc.house_cusps(calc_method).map.with_index do |deg, i|
    i += 1
    prefix = format("%-31s", "House #{SwissEphemeris::HOUSES[i]} (#{HOUSE_THEMES[i]}):")
    ruler = calc.get_body(degree_rulership(deg))
    tone = ruler.house - i + 1
    tone += 12 if tone.negative?
    postfix = " [Lord #{ruler.symbol} in #{format('%3s:%-2s', SwissEphemeris::HOUSES[ruler.house], tone)}] "
    AstroHelper.deg_to_s(deg, gate: calc.ayanamsha, prefix: prefix, spectrum: true, postfix: postfix)
  end
  puts output
end


20
21
22
23
24
25
26
27
# File 'lib/astro_helper.rb', line 20

def print_symbol(x)
  mp = x.to_s.split("/")
  if mp.size > 1
    mp.each{|p| print_symbol(p) }.join("/")
  else
    SwissEphemeris::BODIES[x.to_sym][:symbol]
  end
end

.sign_to_str(sign, kind = :full) ⇒ Object



140
141
142
143
144
145
146
147
148
149
150
# File 'lib/astro_helper.rb', line 140

def sign_to_str(sign, kind = :full)
  output = sign.capitalize
  case kind
  when :glyph
    SwissEphemeris::ZODIAC_SYM[sign_to_i(sign)]
  when :short
    output[0...3]
  else
    output
  end
end

.symbol(key) ⇒ Object



329
330
331
# File 'lib/astro_helper.rb', line 329

def symbol(key)
  SYMBOLS[key].first
end

.symbolize(s) ⇒ Object



29
30
31
# File 'lib/astro_helper.rb', line 29

def symbolize(s)
  s.to_s.delete(" ").sub(".", "_").downcase.to_sym
end

.titleize(str) ⇒ Object



33
34
35
36
# File 'lib/astro_helper.rb', line 33

def titleize(str)
  str = str.to_s
  str.split("_").map(&:capitalize).join(" ").gsub("Of", "of")
end

.transits(location, t = DateTime.now, **opts) ⇒ Object



355
356
357
358
359
# File 'lib/astro_helper.rb', line 355

def transits(location, t = DateTime.now, **opts)
  calc = Astroscript::Calculator.new(opts)
  date = [t.year, t.month, t.day, t.hour, t.min]
  calc.init(*date, location)
end

.zodiac_symbol(sign) ⇒ Object



325
326
327
# File 'lib/astro_helper.rb', line 325

def zodiac_symbol(sign)
  SYMBOLS[:zodiac][sign_to_i(sign)]
end