astropanel 1.1
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/bin/astropanel.rb +1148 -0
- metadata +47 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 78a081062ae991ded72d65794dc8340be72f020550c3a1d3e3b9390e82746884
|
4
|
+
data.tar.gz: '0119adc663554bfb4ded3b959452cb761cf3b5ad2026986f3288114036414fce'
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7695e2b8d7c85516fa257ac8b395832cdd6690872eac9e49f83f0d16f6275fba5311fd83a9104a99fb2b9c8d6290b6dc2be4590483e3d9b8488f3f26ca6abeb2
|
7
|
+
data.tar.gz: ca344667613ddf52a8798e19a7c6e2bafc02788d43714434cc6137076888051ac844b291e7284514f8b5dc2e3ce50e22725ef16791e3de71549e580290082c7d
|
data/bin/astropanel.rb
ADDED
@@ -0,0 +1,1148 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
# PROGRAM INFO
|
5
|
+
# Name: AstroPanel
|
6
|
+
# Language: Pure Ruby, best viewed in VIM
|
7
|
+
# Author: Geir Isene <g@isene.com>
|
8
|
+
# Web_site: http://isene.com/
|
9
|
+
# Github: https://github.com/isene/AstroPanel
|
10
|
+
# License: I release all copyright claims. This code is in the public domain.
|
11
|
+
# Permission is granted to use, copy modify, distribute, and sell
|
12
|
+
# this software for any purpose. I make no guarantee about the
|
13
|
+
# suitability of this software for any purpose and I am not liable
|
14
|
+
# for any damages resulting from its use. Further, I am under no
|
15
|
+
# obligation to maintain or extend this software. It is provided
|
16
|
+
# on an 'as is' basis without any expressed or implied warranty.
|
17
|
+
|
18
|
+
# PRELIMINARIES
|
19
|
+
@help = <<HELPTEXT
|
20
|
+
AstroPanel (https://github.com/isene/AstroPanel)
|
21
|
+
|
22
|
+
KEYS
|
23
|
+
? = Show this help text ENTER = Refresh starchart/image
|
24
|
+
l = Edit Location r = Refresh all data
|
25
|
+
a = Edit Latitude s = Get starchart for selected time
|
26
|
+
o = Edit Longitude S = Open starchart in image program
|
27
|
+
c = Edit Cloud limit A = Show Astronomy Picture Of the Day
|
28
|
+
h = Edit Humidity limit e = Show upcoming events
|
29
|
+
t = Edit Temperature limit W = Write to config file
|
30
|
+
w = Edit Wind limit q = Quit (write to config file)
|
31
|
+
b = Edit Bortle value Q = Quit (no config write)
|
32
|
+
|
33
|
+
COPYRIGHT: Geir Isene, 2020. No rights reserved. See http://isene.com for more.
|
34
|
+
HELPTEXT
|
35
|
+
begin # BASIC SETUP
|
36
|
+
require 'net/http'
|
37
|
+
require 'open-uri'
|
38
|
+
require 'json'
|
39
|
+
require 'date'
|
40
|
+
require 'time'
|
41
|
+
require 'readline'
|
42
|
+
require 'io/console'
|
43
|
+
require 'curses'
|
44
|
+
include Curses
|
45
|
+
|
46
|
+
def cmd?(command)
|
47
|
+
system("which #{command} > /dev/null 2>&1")
|
48
|
+
end
|
49
|
+
if cmd?('/usr/lib/w3m/w3mimgdisplay')
|
50
|
+
@w3mimgdisplay = "/usr/lib/w3m/w3mimgdisplay"
|
51
|
+
@showimage = true
|
52
|
+
else
|
53
|
+
@showimage = false
|
54
|
+
end
|
55
|
+
@showimage = false unless (cmd?('xwininfo') and cmd?('xdotool'))
|
56
|
+
|
57
|
+
begin # Check if network is available
|
58
|
+
URI.open("https://www.met.no/", :open_timeout=>5)
|
59
|
+
rescue
|
60
|
+
puts "\nUnable to get data from met.no\n\n"
|
61
|
+
exit
|
62
|
+
end
|
63
|
+
|
64
|
+
# INITIALIZE VARIABLES
|
65
|
+
@loc, @lat, @lon, @cloudlimit, @humiditylimit, @templimit, @windlimit = ""
|
66
|
+
@noimage = false
|
67
|
+
if File.exist?(Dir.home+'/.ap.conf')
|
68
|
+
load(Dir.home+'/.ap.conf')
|
69
|
+
else
|
70
|
+
until @loc.match(/\w+\/\w+/)
|
71
|
+
puts "\nEnter Location (format like Europe/Oslo): "
|
72
|
+
@loc = Readline.readline('> ', true).chomp.to_s
|
73
|
+
end
|
74
|
+
until (-90.0..90.0).include?(@lat)
|
75
|
+
puts "\nEnter Latitude (format like 59.4351 or -14.54):"
|
76
|
+
@lat = Readline.readline('> ', true).chomp.to_f
|
77
|
+
end
|
78
|
+
until (-180.0..180.0).include?(@lon)
|
79
|
+
puts "\nEnter Longitude (between -180 and 180):"
|
80
|
+
@lon = Readline.readline('> ', true).chomp.to_f
|
81
|
+
end
|
82
|
+
until (0..100.0).include?(@cloudlimit)
|
83
|
+
puts "\nLimit for Cloud Coverage (format like 35 for 35%):"
|
84
|
+
@cloudlimit = Readline.readline('> ', true).chomp.to_i
|
85
|
+
end
|
86
|
+
until (0..100.0).include?(@humiditylimit)
|
87
|
+
puts "\nLimit for Humidity (format 70 for 70%):"
|
88
|
+
@humiditylimit = Readline.readline('> ', true).chomp.to_i
|
89
|
+
end
|
90
|
+
until (-100.0..100.0).include?(@templimit)
|
91
|
+
puts "\nMinimum observation temperature in °C (format like -15):"
|
92
|
+
@templimit =Readline.readline('> ', true).chomp.to_i
|
93
|
+
end
|
94
|
+
until (0..50.0).include?(@windlimit)
|
95
|
+
puts "\nLimit for Wind in m/s (format like 6):"
|
96
|
+
@windlimit = Readline.readline('> ', true).chomp.to_i
|
97
|
+
end
|
98
|
+
conf = "@loc = \"#{@loc}\"\n"
|
99
|
+
conf += "@lat = #{@lat}\n"
|
100
|
+
conf += "@lon = #{@lon}\n"
|
101
|
+
conf += "@cloudlimit = #{@cloudlimit}\n"
|
102
|
+
conf += "@humiditylimit = #{@humiditylimit}\n"
|
103
|
+
conf += "@templimit = #{@templimit}\n"
|
104
|
+
conf += "@windlimit = #{@windlimit}\n"
|
105
|
+
File.write(Dir.home+'/.ap.conf', conf)
|
106
|
+
end
|
107
|
+
## Don't change these
|
108
|
+
@lat > 23 ? @image = "/tmp/starchart.jpg" : @image = "/tmp/apod.jpg"
|
109
|
+
@w_l_width = 70
|
110
|
+
@weather_point = []
|
111
|
+
@weather = []
|
112
|
+
@history = []
|
113
|
+
@index = 0
|
114
|
+
|
115
|
+
## Curses setup
|
116
|
+
Curses.init_screen
|
117
|
+
Curses.start_color
|
118
|
+
Curses.curs_set(0)
|
119
|
+
Curses.noecho
|
120
|
+
Curses.cbreak
|
121
|
+
Curses.stdscr.keypad = true
|
122
|
+
|
123
|
+
## Initialize colors
|
124
|
+
init_pair(1, 118, 0) # green bg
|
125
|
+
init_pair(2, 214, 0) # orange bg
|
126
|
+
init_pair(3, 160, 0) # red bg
|
127
|
+
end
|
128
|
+
# CLASSES
|
129
|
+
class Numeric # NUMERIC CLASS EXTENSION
|
130
|
+
def deg
|
131
|
+
self * Math::PI / 180
|
132
|
+
end
|
133
|
+
def hms
|
134
|
+
hrs = self.to_i
|
135
|
+
m = ((self - hrs)*60).abs
|
136
|
+
min = m.to_i
|
137
|
+
sec = ((m - min)*60).to_i.abs
|
138
|
+
return hrs, min, sec
|
139
|
+
end
|
140
|
+
def to_hms
|
141
|
+
hrs, min, sec = self.hms
|
142
|
+
return "#{hrs.to_s.rjust(2, "0")}:#{min.to_s.rjust(2, "0")}:#{sec.to_s.rjust(2, "0")}"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
class Ephemeris # THE CORE EPHEMERIS CLASS
|
146
|
+
# The repo for this class: https://github.com/isene/ephemeris
|
147
|
+
attr_reader :sun, :moon, :mphase, :mph_s, :mercury, :venus, :mars, :jupiter, :saturn, :uranus, :neptune
|
148
|
+
|
149
|
+
def body_data
|
150
|
+
@body = {
|
151
|
+
"sun" => {
|
152
|
+
"N" => 0.0,
|
153
|
+
"i" => 0.0,
|
154
|
+
"w" => 282.9404 + 4.70935e-5 * @d,
|
155
|
+
"a" => 1.000000,
|
156
|
+
"e" => 0.016709 - 1.151e-9 * @d,
|
157
|
+
"M" => 356.0470 + 0.98555 * @d},
|
158
|
+
#"M" => 356.0470 + 0.9856002585 * @d},
|
159
|
+
"moon" => {
|
160
|
+
"N" => 125.1228 - 0.0529538083 * @d,
|
161
|
+
"i" => 5.1454,
|
162
|
+
"w" => 318.0634 + 0.1643573223 * @d,
|
163
|
+
"a" => 60.2666,
|
164
|
+
"e" => 0.054900,
|
165
|
+
"M" => 115.3654 + 13.064886 * @d},
|
166
|
+
#"M" => 115.3654 + 13.0649929509 * @d},
|
167
|
+
"mercury" => {
|
168
|
+
"N" => 48.3313 + 3.24587e-5 * @d,
|
169
|
+
"i" => 7.0047 + 5.00e-8 * @d,
|
170
|
+
"w" => 29.1241 + 1.01444e-5 * @d,
|
171
|
+
"a" => 0.387098,
|
172
|
+
"e" => 0.205635 + 5.59e-10 * @d,
|
173
|
+
#"M" => 168.6562 + 4.09257 * @d},
|
174
|
+
"M" => 168.6562 + 4.0923344368 * @d},
|
175
|
+
"venus" => {
|
176
|
+
"N" => 76.6799 + 2.46590e-5 * @d,
|
177
|
+
"i" => 3.3946 + 2.75e-8 * @d,
|
178
|
+
"w" => 54.8910 + 1.38374e-5 * @d,
|
179
|
+
"a" => 0.723330,
|
180
|
+
"e" => 0.006773 - 1.302e-9 * @d,
|
181
|
+
#"M" => 48.0052 + 1.602206 * @d},
|
182
|
+
"M" => 48.0052 + 1.6021302244 * @d},
|
183
|
+
"mars" => {
|
184
|
+
"N" => 49.5574 + 2.11081e-5 * @d,
|
185
|
+
"i" => 1.8497 - 1.78e-8 * @d,
|
186
|
+
"w" => 286.5016 + 2.92961e-5 * @d,
|
187
|
+
"a" => 1.523688,
|
188
|
+
"e" => 0.093405 + 2.516e-9 * @d,
|
189
|
+
"M" => 18.6021 + 0.52398 * @d},
|
190
|
+
#"M" => 18.6021 + 0.5240207766 * @d},
|
191
|
+
"jupiter" => {
|
192
|
+
"N" => 100.4542 + 2.76854e-5 * @d,
|
193
|
+
"i" => 1.3030 - 1.557e-7 * @d,
|
194
|
+
"w" => 273.8777 + 1.64505e-5 * @d,
|
195
|
+
"a" => 5.20256,
|
196
|
+
"e" => 0.048498 + 4.469e-9 * @d,
|
197
|
+
"M" => 19.8950 + 0.083052 * @d},
|
198
|
+
#"M" => 19.8950 + 0.0830853001 * @d},
|
199
|
+
"saturn" => {
|
200
|
+
"N" => 113.6634 + 2.38980e-5 * @d,
|
201
|
+
"i" => 2.4886 - 1.081e-7 * @d,
|
202
|
+
"w" => 339.3939 + 2.97661e-5 * @d,
|
203
|
+
"a" => 9.55475,
|
204
|
+
"e" => 0.055546 - 9.499e-9 * @d,
|
205
|
+
"M" => 316.9670 + 0.03339 * @d},
|
206
|
+
#"M" => 316.9670 + 0.0334442282 * @d},
|
207
|
+
"uranus" => {
|
208
|
+
"N" => 74.0005 + 1.3978e-5 * @d,
|
209
|
+
"i" => 0.7733 + 1.9e-8 * @d,
|
210
|
+
"w" => 96.6612 + 3.0565e-5 * @d,
|
211
|
+
"a" => 19.18171 - 1.55e-8 * @d,
|
212
|
+
"e" => 0.047318 + 7.45e-9 * @d,
|
213
|
+
"M" => 142.5905 + 0.01168 * @d},
|
214
|
+
#"M" => 142.5905 + 0.011725806 * @d},
|
215
|
+
"neptune" => {
|
216
|
+
"N" => 131.7806 + 3.0173e-5 * @d,
|
217
|
+
"i" => 1.7700 - 2.55e-7 * @d,
|
218
|
+
"w" => 272.8461 - 6.027e-6 * @d,
|
219
|
+
"a" => 30.05826 + 3.313e-8 * @d,
|
220
|
+
"e" => 0.008606 + 2.15e-9 * @d,
|
221
|
+
"M" => 260.2471 + 0.005953 * @d}}
|
222
|
+
#"M" => 260.2471 + 0.005995147 * @d}}
|
223
|
+
end
|
224
|
+
|
225
|
+
def hms_dms(ra, dec) # Show HMS & DMS
|
226
|
+
h, m, s = (ra/15).hms
|
227
|
+
#ra_hms = "#{h.to_s.rjust(2)}h #{m.to_s.rjust(2)}m #{s.to_s.rjust(2)}s"
|
228
|
+
ra_hms = "#{h.to_s.rjust(2)}h #{m.to_s.rjust(2)}m"
|
229
|
+
d, m, s = dec.hms
|
230
|
+
#dec_dms = "#{d.to_s.rjust(3)}° #{m.to_s.rjust(2)}´ #{s.to_s.rjust(2)}˝"
|
231
|
+
dec_dms = "#{d.to_s.rjust(3)}° #{m.to_s.rjust(2)}´"
|
232
|
+
return ra_hms, dec_dms
|
233
|
+
end
|
234
|
+
|
235
|
+
def alt_az(ra, dec, time)
|
236
|
+
pi = Math::PI
|
237
|
+
ra_h = ra/15
|
238
|
+
#ha = (@sidtime - ra_h)*15
|
239
|
+
ha = (time - ra_h)*15
|
240
|
+
x = Math.cos(ha.deg) * Math.cos(dec.deg)
|
241
|
+
y = Math.sin(ha.deg) * Math.cos(dec.deg)
|
242
|
+
z = Math.sin(dec.deg)
|
243
|
+
xhor = x * Math.sin(@lat.deg) - z * Math.cos(@lat.deg)
|
244
|
+
yhor = y
|
245
|
+
zhor = x * Math.cos(@lat.deg) + z * Math.sin(@lat.deg)
|
246
|
+
az = Math.atan2(yhor, xhor)*180/pi + 180
|
247
|
+
alt = Math.asin(zhor)*180/pi
|
248
|
+
return alt, az
|
249
|
+
end
|
250
|
+
|
251
|
+
def body_alt_az(body, time)
|
252
|
+
self.alt_az(self.body_calc(body)[0], self.body_calc(body)[1], time)
|
253
|
+
end
|
254
|
+
|
255
|
+
def rts(ra, dec, h)
|
256
|
+
pi = Math::PI
|
257
|
+
transit = (ra - @ls - @lon)/15 + 12 + @tz
|
258
|
+
transit = (transit + 24) % 24
|
259
|
+
cos_lha = (Math.sin(h.deg) - (Math.sin(@lat.deg)*Math.sin(dec.deg))) / (Math.cos(@lat.deg) * Math.cos(dec.deg))
|
260
|
+
if cos_lha < -1
|
261
|
+
rise = "always"
|
262
|
+
set = "never"
|
263
|
+
elsif cos_lha > 1
|
264
|
+
rise = "never"
|
265
|
+
set = "always"
|
266
|
+
else
|
267
|
+
lha = Math.acos(cos_lha) * 180/pi
|
268
|
+
lha_h = lha/15
|
269
|
+
rise = ((transit - lha_h + 24) % 24).to_hms
|
270
|
+
set = ((transit + lha_h + 24) % 24).to_hms
|
271
|
+
end
|
272
|
+
trans = transit.to_hms
|
273
|
+
return rise, trans, set
|
274
|
+
end
|
275
|
+
|
276
|
+
def print
|
277
|
+
|
278
|
+
def distf(d)
|
279
|
+
int = d.to_i.to_s.rjust(2)
|
280
|
+
f = d % 1
|
281
|
+
frc = "%.4f" % f
|
282
|
+
return int + frc[1..5]
|
283
|
+
end
|
284
|
+
|
285
|
+
out = "Planet │ RA │ Dec │ d=AU │ Rise │ Trans │ Set \n"
|
286
|
+
out += "────────┼─────────┼──────────┼───────┼───────┼───────┼────── \n"
|
287
|
+
|
288
|
+
["sun", "moon", "mercury", "venus", "mars", "jupiter", "saturn", "uranus", "neptune"].each do |p|
|
289
|
+
o = self.body_calc(p)
|
290
|
+
n_o = (p[0].upcase + p[1..-1]).ljust(7)
|
291
|
+
ra_o = o[3].ljust(7)
|
292
|
+
dec_o = o[4].ljust(2)
|
293
|
+
o[2].class == Float ? d_o = distf(o[2])[0..-3] : d_o = o[2]
|
294
|
+
ris_o = o[5][0..-4].rjust(5)
|
295
|
+
tra_o = o[6][0..-4].rjust(5)
|
296
|
+
set_o = o[7][0..-4].rjust(5)
|
297
|
+
|
298
|
+
out += "#{n_o } │ #{ra_o } │ #{dec_o } │ #{d_o } │ #{ris_o} │ #{tra_o} │ #{set_o} \n"
|
299
|
+
end
|
300
|
+
return out
|
301
|
+
end
|
302
|
+
|
303
|
+
def initialize (date, lat, lon, tz)
|
304
|
+
pi = Math::PI
|
305
|
+
|
306
|
+
def get_vars(body) # GET VARIABLES FOR THE BODY
|
307
|
+
b = @body[body]
|
308
|
+
return b["N"], b["i"], b["w"], b["a"], b["e"], b["M"]
|
309
|
+
end
|
310
|
+
|
311
|
+
def body_calc(body) # CALCULATE FOR THE BODY
|
312
|
+
pi = Math::PI
|
313
|
+
n_b, i_b, w_b, a_b, e_b, m_b = self.get_vars(body)
|
314
|
+
w_b = (w_b + 360) % 360
|
315
|
+
m_b = m_b % 360
|
316
|
+
e1 = m_b + (180/pi) * e_b * Math.sin(m_b.deg) * (1 + e_b*Math.cos(m_b.deg))
|
317
|
+
e0 = 0
|
318
|
+
while (e1 - e0).abs > 0.0005
|
319
|
+
e0 = e1
|
320
|
+
e1 = e0 - (e0 - (180/pi) * e_b * Math.sin(e0.deg) - m_b) / (1 - e_b * Math.cos(e0.deg))
|
321
|
+
end
|
322
|
+
e = e1
|
323
|
+
x = a_b * (Math.cos(e.deg) - e_b)
|
324
|
+
y = a_b * Math.sqrt(1 - e_b*e_b) * Math.sin(e.deg)
|
325
|
+
r = Math.sqrt(x*x + y*y)
|
326
|
+
v = (Math.atan2(y, x)*180/pi + 360) % 360
|
327
|
+
xeclip = r * (Math.cos(n_b.deg) * Math.cos((v+w_b).deg) - Math.sin(n_b.deg) * Math.sin((v+w_b).deg) * Math.cos(i_b.deg))
|
328
|
+
yeclip = r * (Math.sin(n_b.deg) * Math.cos((v+w_b).deg) + Math.cos(n_b.deg) * Math.sin((v+w_b).deg) * Math.cos(i_b.deg))
|
329
|
+
zeclip = r * Math.sin((v+w_b).deg) * Math.sin(i_b.deg)
|
330
|
+
lon = (Math.atan2(yeclip, xeclip)*180/pi + 360) % 360
|
331
|
+
lat = Math.atan2(zeclip, Math.sqrt(xeclip*xeclip + yeclip*yeclip))*180/pi
|
332
|
+
r_b = Math.sqrt(xeclip*xeclip + yeclip*yeclip + zeclip*zeclip)
|
333
|
+
m_J = @body["jupiter"]["M"]
|
334
|
+
m_S = @body["saturn"]["M"]
|
335
|
+
m_U = @body["uranus"]["M"]
|
336
|
+
plon = 0
|
337
|
+
plat = 0
|
338
|
+
pdist = 0
|
339
|
+
case body
|
340
|
+
when "moon"
|
341
|
+
lb = (n_b + w_b + m_b) % 360
|
342
|
+
db = (lb - @ls + 360) % 360
|
343
|
+
fb = (lb - n_b + 360) % 360
|
344
|
+
plon += -1.274 * Math.sin((m_b - 2*db).deg)
|
345
|
+
plon += 0.658 * Math.sin((2*db).deg)
|
346
|
+
plon += -0.186 * Math.sin(@ms.deg)
|
347
|
+
plon += -0.059 * Math.sin((2*m_b - 2*db).deg)
|
348
|
+
plon += -0.057 * Math.sin((m_b - 2*db + @ms).deg)
|
349
|
+
plon += 0.053 * Math.sin((m_b + 2*db).deg)
|
350
|
+
plon += 0.046 * Math.sin((2*db - @ms).deg)
|
351
|
+
plon += 0.041 * Math.sin((m_b - @ms).deg)
|
352
|
+
plon += -0.035 * Math.sin(db.deg)
|
353
|
+
plon += -0.031 * Math.sin((m_b + @ms).deg)
|
354
|
+
plon += -0.015 * Math.sin((2*fb - 2*db).deg)
|
355
|
+
plon += 0.011 * Math.sin((m_b - 4*db).deg)
|
356
|
+
plat += -0.173 * Math.sin((fb - 2*db).deg)
|
357
|
+
plat += -0.055 * Math.sin((m_b - fb - 2*db).deg)
|
358
|
+
plat += -0.046 * Math.sin((m_b + fb - 2*db).deg)
|
359
|
+
plat += 0.033 * Math.sin((fb + 2*db).deg)
|
360
|
+
plat += 0.017 * Math.sin((2*m_b + fb).deg)
|
361
|
+
pdist += -0.58 * Math.cos((m_b - 2*db).deg)
|
362
|
+
pdist += -0.46 * Math.cos(2*db.deg)
|
363
|
+
when "jupiter"
|
364
|
+
plon += -0.332 * Math.sin((2*m_J - 5*m_S - 67.6).deg)
|
365
|
+
plon += -0.056 * Math.sin((2*m_J - 2*m_S + 21).deg)
|
366
|
+
plon += 0.042 * Math.sin((3*m_J - 5*m_S + 21).deg)
|
367
|
+
plon += -0.036 * Math.sin((m_J - 2*m_S).deg)
|
368
|
+
plon += 0.022 * Math.cos((m_J - m_S).deg)
|
369
|
+
plon += 0.023 * Math.sin((2*m_J - 3*m_S + 52).deg)
|
370
|
+
plon += -0.016 * Math.sin((m_J - 5*m_S - 69).deg)
|
371
|
+
when "saturn"
|
372
|
+
plon += 0.812 * Math.sin((2*m_J - 5*m_S - 67.6).deg)
|
373
|
+
plon += -0.229 * Math.cos((2*m_J - 4*m_S - 2).deg)
|
374
|
+
plon += 0.119 * Math.sin((m_J - 2*m_S - 3).deg)
|
375
|
+
plon += 0.046 * Math.sin((2*m_J - 6*m_S - 69).deg)
|
376
|
+
plon += 0.014 * Math.sin((m_J - 3*m_S + 32).deg)
|
377
|
+
plat += -0.020 * Math.cos((2*m_J - 4*m_S - 2).deg)
|
378
|
+
plat += 0.018 * Math.sin((2*m_J - 6*m_S - 49).deg)
|
379
|
+
when "uranus"
|
380
|
+
plon += 0.040 * Math.sin((m_S - 2*m_U + 6).deg)
|
381
|
+
plon += 0.035 * Math.sin((m_S - 3*m_U + 33).deg)
|
382
|
+
plon += -0.015 * Math.sin((m_J - m_U + 20).deg)
|
383
|
+
end
|
384
|
+
lon += plon
|
385
|
+
lat += plat
|
386
|
+
r_b += pdist
|
387
|
+
if body == "moon"
|
388
|
+
xeclip = Math.cos(lon.deg) * Math.cos(lat.deg)
|
389
|
+
yeclip = Math.sin(lon.deg) * Math.cos(lat.deg)
|
390
|
+
zeclip = Math.sin(lat.deg)
|
391
|
+
else
|
392
|
+
xeclip += @xs
|
393
|
+
yeclip += @ys
|
394
|
+
end
|
395
|
+
xequat = xeclip
|
396
|
+
yequat = yeclip * Math.cos(@ecl.deg) - zeclip * Math.sin(@ecl.deg)
|
397
|
+
zequat = yeclip * Math.sin(@ecl.deg) + zeclip * Math.cos(@ecl.deg)
|
398
|
+
ra = (Math.atan2(yequat, xequat)*180/pi + 360) % 360
|
399
|
+
dec = Math.atan2(zequat, Math.sqrt(xequat*xequat + yequat*yequat))*180/pi
|
400
|
+
body == "moon" ? par = Math.asin(1/r_b)*180/pi : par = (8.794/3600)/r_b
|
401
|
+
gclat = @lat - 0.1924 * Math.sin(2*@lat.deg)
|
402
|
+
rho = 0.99833 + 0.00167 * Math.cos(2*@lat.deg)
|
403
|
+
lst = @sidtime * 15
|
404
|
+
ha = (lst - ra + 360) % 360
|
405
|
+
g = Math.atan(Math.tan(gclat.deg) / Math.cos(ha.deg))*180/pi
|
406
|
+
topRA = ra - par * rho * Math.cos(gclat.deg) * Math.sin(ha.deg) / Math.cos(dec.deg)
|
407
|
+
topDecl = dec - par * rho * Math.sin(gclat.deg) * Math.sin((g - dec).deg) / Math.sin(g.deg)
|
408
|
+
ra = topRA.round(4)
|
409
|
+
dec = topDecl.round(4)
|
410
|
+
r = Math.sqrt(xequat*xequat + yequat*yequat + zequat*zequat).round(4)
|
411
|
+
h = 0
|
412
|
+
if body == "moon"
|
413
|
+
r = " - "
|
414
|
+
h = -0.833
|
415
|
+
elsif body == "sun"
|
416
|
+
ra = @ra_s
|
417
|
+
dec = @dec_s
|
418
|
+
r = 1.0
|
419
|
+
h = -0.833
|
420
|
+
end
|
421
|
+
ri, tr, se = self.rts(ra, dec, h)
|
422
|
+
object = [ra, dec, r, self.hms_dms(ra, dec), ri, tr, se].flatten
|
423
|
+
return object
|
424
|
+
end
|
425
|
+
|
426
|
+
# START OF INITIALIZE
|
427
|
+
@lat = lat
|
428
|
+
@lon = lon
|
429
|
+
@tz = tz
|
430
|
+
y = date[0..3].to_i
|
431
|
+
m = date[5..6].to_i
|
432
|
+
d = date[8..9].to_i
|
433
|
+
@d = 367*y - 7*(y + (m+9)/12) / 4 + 275*m/9 + d - 730530
|
434
|
+
@ecl = 23.4393 - 3.563E-7*@d
|
435
|
+
|
436
|
+
self.body_data
|
437
|
+
|
438
|
+
# SUN
|
439
|
+
n_s, i_s, w_s, a_s, e_s, m_s = self.get_vars("sun")
|
440
|
+
w_s = (w_s + 360) % 360
|
441
|
+
@ms = m_s % 360
|
442
|
+
es = @ms + (180/pi) * e_s * Math.sin(@ms.deg) * (1 + e_s*Math.cos(@ms.deg))
|
443
|
+
x = Math.cos(es.deg) - e_s
|
444
|
+
y = Math.sin(es.deg) * Math.sqrt(1 - e_s*e_s)
|
445
|
+
v = Math.atan2(y,x)*180/pi
|
446
|
+
r = Math.sqrt(x*x + y*y)
|
447
|
+
tlon = (v + w_s)%360
|
448
|
+
@xs = r * Math.cos(tlon.deg)
|
449
|
+
@ys = r * Math.sin(tlon.deg)
|
450
|
+
xe = @xs
|
451
|
+
ye = @ys * Math.cos(@ecl.deg)
|
452
|
+
ze = @ys * Math.sin(@ecl.deg)
|
453
|
+
r = Math.sqrt(xe*xe + ye*ye + ze*ze)
|
454
|
+
ra = Math.atan2(ye,xe)*180/pi
|
455
|
+
@ra_s = ((ra + 360)%360).round(4)
|
456
|
+
@dec_s = (Math.atan2(ze,Math.sqrt(xe*xe + ye*ye))*180/pi).round(4)
|
457
|
+
|
458
|
+
@ls = (w_s + @ms)%360
|
459
|
+
gmst0 = (@ls + 180)/15%24
|
460
|
+
@sidtime = gmst0 + @lon/15
|
461
|
+
|
462
|
+
@alt_s, @az_s = self.alt_az(@ra_s, @dec_s, @sidtime)
|
463
|
+
|
464
|
+
@sun = self.body_calc("sun").flatten
|
465
|
+
@moon = self.body_calc("moon").flatten
|
466
|
+
|
467
|
+
mp = 29.530588861
|
468
|
+
nm = 2459198.177777778
|
469
|
+
jd = Date.parse(date).ajd.to_f
|
470
|
+
@mphase = 100*((jd - nm) % mp) / mp
|
471
|
+
if @mphase < 2.5
|
472
|
+
@mph_s = "New moon"
|
473
|
+
elsif @mphase < 27.5
|
474
|
+
@mph_s = "Waxing crescent"
|
475
|
+
elsif @mphase < 32.5
|
476
|
+
@mph_s = "First quarter"
|
477
|
+
elsif @mphase < 47.5
|
478
|
+
@mph_s = "Waxing gibbous"
|
479
|
+
elsif @mphase < 52.5
|
480
|
+
@mph_s = "Full moon"
|
481
|
+
elsif @mphase < 72.5
|
482
|
+
@mph_s = "Waning gibbous"
|
483
|
+
elsif @mphase < 77.5
|
484
|
+
@mph_s = "Last quarter"
|
485
|
+
elsif @mphase < 97.5
|
486
|
+
@mph_s = "Waning crescent"
|
487
|
+
else
|
488
|
+
@mph_s = "New moon"
|
489
|
+
end
|
490
|
+
|
491
|
+
@mercury = self.body_calc("mercury").flatten
|
492
|
+
@venus = self.body_calc("venus").flatten
|
493
|
+
@mars = self.body_calc("mars").flatten
|
494
|
+
@jupiter = self.body_calc("jupiter").flatten
|
495
|
+
@saturn = self.body_calc("saturn").flatten
|
496
|
+
@uranus = self.body_calc("uranus").flatten
|
497
|
+
@neptune = self.body_calc("neptune").flatten
|
498
|
+
|
499
|
+
end
|
500
|
+
end
|
501
|
+
class String # CLASS EXTENSION
|
502
|
+
def decoder
|
503
|
+
self.gsub(/&#(\d+);/) { |m| $1.to_i(10).chr(Encoding::UTF_8) }
|
504
|
+
end
|
505
|
+
end
|
506
|
+
class Curses::Window # CLASS EXTENSION
|
507
|
+
attr_accessor :fg, :bg, :attr, :text, :update, :pager, :pager_more, :pager_cmd, :locate, :nohistory
|
508
|
+
# General extensions (see https://github.com/isene/Ruby-Curses-Class-Extension)
|
509
|
+
def clr
|
510
|
+
self.setpos(0, 0)
|
511
|
+
self.maxy.times {self.deleteln()}
|
512
|
+
self.refresh
|
513
|
+
self.setpos(0, 0)
|
514
|
+
end
|
515
|
+
def fill # Fill window with color as set by :bg
|
516
|
+
self.setpos(0, 0)
|
517
|
+
self.bg = 0 if self.bg == nil
|
518
|
+
self.fg = 255 if self.fg == nil
|
519
|
+
init_pair(self.fg, self.fg, self.bg)
|
520
|
+
blank = " " * self.maxx
|
521
|
+
self.maxy.times {self.attron(color_pair(self.fg)) {self << blank}}
|
522
|
+
self.refresh
|
523
|
+
self.setpos(0, 0)
|
524
|
+
end
|
525
|
+
def write # Write context of :text to window with attributes :attr
|
526
|
+
self.bg = 0 if self.bg == nil
|
527
|
+
self.fg = 255 if self.fg == nil
|
528
|
+
init_pair(self.fg, self.fg, self.bg)
|
529
|
+
self.attr = 0 if self.attr == nil
|
530
|
+
self.attron(color_pair(self.fg) | self.attr) { self << self.text }
|
531
|
+
self.refresh
|
532
|
+
self.text = ""
|
533
|
+
end
|
534
|
+
def p(fg, bg, attr, text)
|
535
|
+
init_pair(fg, fg, bg)
|
536
|
+
self.attron(color_pair(fg) | attr) { self << text }
|
537
|
+
self.refresh
|
538
|
+
end
|
539
|
+
end
|
540
|
+
# GENERIC FUNCTIONS
|
541
|
+
def getchr # PROCESS KEY PRESSES
|
542
|
+
# Note: Curses.getch blanks out @w_t
|
543
|
+
# @w_l.getch makes Curses::KEY_DOWN etc not work
|
544
|
+
# Therefore resorting to the generic method
|
545
|
+
c = STDIN.getc
|
546
|
+
#c = STDIN.getch(min: 0, time: 5) # Non-blocking for future needs
|
547
|
+
case c
|
548
|
+
when "\e" # ANSI escape sequences
|
549
|
+
case $stdin.getc
|
550
|
+
when '[' # CSI
|
551
|
+
case $stdin.getc
|
552
|
+
when 'A' then chr = "UP"
|
553
|
+
when 'B' then chr = "DOWN"
|
554
|
+
when 'C' then chr = "RIGHT"
|
555
|
+
when 'D' then chr = "LEFT"
|
556
|
+
when 'Z' then chr = "S-TAB"
|
557
|
+
when '2' then chr = "INS" ; STDIN.getc
|
558
|
+
when '3' then chr = "DEL" ; STDIN.getc
|
559
|
+
when '5' then chr = "PgUP" ; STDIN.getc
|
560
|
+
when '6' then chr = "PgDOWN" ; STDIN.getc
|
561
|
+
when '7' then chr = "HOME" ; STDIN.getc
|
562
|
+
when '8' then chr = "END" ; STDIN.getc
|
563
|
+
end
|
564
|
+
end
|
565
|
+
when "", "" then chr = "BACK"
|
566
|
+
when "" then chr = "WBACK"
|
567
|
+
when "" then chr = "LDEL"
|
568
|
+
when "" then chr = "C-T"
|
569
|
+
when "\r" then chr = "ENTER"
|
570
|
+
when "\t" then chr = "TAB"
|
571
|
+
when /./ then chr = c
|
572
|
+
end
|
573
|
+
return chr
|
574
|
+
end
|
575
|
+
def main_getkey # GET KEY FROM USER
|
576
|
+
chr = getchr
|
577
|
+
case chr
|
578
|
+
when '?' # Show helptext in right window
|
579
|
+
w_u_msg(@help)
|
580
|
+
when 'UP'
|
581
|
+
@index = @index <= @min_index ? @max_index : @index - 1
|
582
|
+
@w_u.update = true
|
583
|
+
when 'DOWN'
|
584
|
+
@index = @index >= @max_index ? @min_index : @index + 1
|
585
|
+
@w_u.update = true
|
586
|
+
when 'PgUP'
|
587
|
+
@index -= @w_l.maxy - 2
|
588
|
+
@index = @min_index if @index < @min_index
|
589
|
+
@w_u.update = true
|
590
|
+
when 'PgDOWN'
|
591
|
+
@index += @w_l.maxy - 2
|
592
|
+
@index = @max_index if @index > @max_index
|
593
|
+
@w_u.update = true
|
594
|
+
when 'HOME'
|
595
|
+
@index = @min_index
|
596
|
+
@w_u.update = true
|
597
|
+
when 'END'
|
598
|
+
@index = @max_index
|
599
|
+
@w_u.update = true
|
600
|
+
when 'l'
|
601
|
+
@loc = w_b_getstr("Loc: ", @loc)
|
602
|
+
@w_u.update = true
|
603
|
+
when 'a'
|
604
|
+
@lat = w_b_getstr("Lat: ", @lat.to_s).to_f
|
605
|
+
@w_u.update = true
|
606
|
+
when 'o'
|
607
|
+
@lon = w_b_getstr("Lon: ", @lon.to_s).to_f
|
608
|
+
@w_u.update = true
|
609
|
+
when 'c'
|
610
|
+
@cloudlimit = w_b_getstr("Cloudlimit: ", @cloudlimit.to_s).to_i
|
611
|
+
@w_u.update = true
|
612
|
+
when 'h'
|
613
|
+
@humiditylimit = w_b_getstr("Humiditylimit: ", @humiditylimit.to_s).to_i
|
614
|
+
@w_u.update = true
|
615
|
+
when 't'
|
616
|
+
@templimit = w_b_getstr("Templimit: ", @templimit.to_s).to_i
|
617
|
+
@w_u.update = true
|
618
|
+
when 'w'
|
619
|
+
@windlimit = w_b_getstr("Windlimit: ", @windlimit.to_s).to_i
|
620
|
+
@w_u.update = true
|
621
|
+
when 'b'
|
622
|
+
@bortle = w_b_getstr("Bortle: ", @bortle.to_s).to_f.round(1)
|
623
|
+
@w_u.update = true
|
624
|
+
when 'e'
|
625
|
+
ev = "\nUPCOMING EVENTS:\n\n"
|
626
|
+
@events.each do |key, val|
|
627
|
+
ev += key + " " + val["time"] + " " + val["event"] + "\n"
|
628
|
+
end
|
629
|
+
w_u_msg(ev)
|
630
|
+
when 's'
|
631
|
+
starchart
|
632
|
+
@image = "/tmp/starchart.jpg"
|
633
|
+
image_show("clear")
|
634
|
+
image_show(@image)
|
635
|
+
when 'S'
|
636
|
+
begin
|
637
|
+
Thread.new { system("xdg-open '/tmp/starchart.jpg'") }
|
638
|
+
rescue
|
639
|
+
end
|
640
|
+
@break = true
|
641
|
+
when 'A'
|
642
|
+
image_show("clear")
|
643
|
+
@image = "/tmp/apod.jpg"
|
644
|
+
image_show(@image)
|
645
|
+
when 'ENTER' # Refresh image
|
646
|
+
image_show(@image)
|
647
|
+
@w_u.update = true
|
648
|
+
when 'r' # Refresh all windows
|
649
|
+
@break = true
|
650
|
+
when '@' # Enter "Ruby debug"
|
651
|
+
@w_b.nohistory = false
|
652
|
+
cmd = w_b_getstr("◆ ", "")
|
653
|
+
begin
|
654
|
+
@w_b.text = eval(cmd)
|
655
|
+
@w_b.fill
|
656
|
+
@w_b.write
|
657
|
+
rescue StandardError => e
|
658
|
+
w_b_info("Error: #{e.inspect}")
|
659
|
+
end
|
660
|
+
@w_b.update = false
|
661
|
+
when 'R' # Reload .ap.conf
|
662
|
+
if File.exist?(Dir.home+'/.ap.conf')
|
663
|
+
load(Dir.home+'/.ap.conf')
|
664
|
+
end
|
665
|
+
w_b_info(" Config reloaded")
|
666
|
+
@w_b.update = false
|
667
|
+
when 'W' # Write all parameters to .ap.conf
|
668
|
+
@write_conf_all = true
|
669
|
+
conf_write
|
670
|
+
when 'q' # Exit
|
671
|
+
@write_conf = true
|
672
|
+
exit 0
|
673
|
+
when 'Q' # Exit without writing to .ap.conf
|
674
|
+
@write_conf = false
|
675
|
+
exit 0
|
676
|
+
end
|
677
|
+
end
|
678
|
+
def get_weather # WEATHER FORECAST FROM MET.NO
|
679
|
+
begin
|
680
|
+
uri = URI.parse("https://api.met.no/weatherapi/locationforecast/2.0/complete?lat=#{@lat}&lon=#{@lon}")
|
681
|
+
req = Net::HTTP::Get.new(uri)
|
682
|
+
req["User-Agent"] = "astropanel/1.0 g@isene.com"
|
683
|
+
json = Net::HTTP.start(uri.hostname, uri.port, :use_ssl => true) do |http|
|
684
|
+
http.request(req)
|
685
|
+
end
|
686
|
+
weather_data = JSON.parse(json.body)
|
687
|
+
@weather_point = weather_data["properties"]["timeseries"]
|
688
|
+
weather_size = @weather_point.size
|
689
|
+
@weather = []
|
690
|
+
weather_size.times do |t|
|
691
|
+
details = @weather_point[t]["data"]["instant"]["details"]
|
692
|
+
time = @weather_point[t]["time"]
|
693
|
+
date = time[0..9]
|
694
|
+
hour = time[11..12]
|
695
|
+
wthr = details["cloud_area_fraction"].to_i.to_s.rjust(5) + "%"
|
696
|
+
wthr += details["relative_humidity"].to_s.rjust(7)
|
697
|
+
wthr += details["air_temperature"].to_s.rjust(6)
|
698
|
+
wind = details["wind_speed"].to_s + " ("
|
699
|
+
case details["wind_from_direction"].to_i
|
700
|
+
when 0..22
|
701
|
+
wdir = "N"
|
702
|
+
when 23..67
|
703
|
+
wdir = "NE"
|
704
|
+
when 68..112
|
705
|
+
wdir = "E"
|
706
|
+
when 113..158
|
707
|
+
wdir = "SE"
|
708
|
+
when 159..203
|
709
|
+
wdir = "S"
|
710
|
+
when 204..249
|
711
|
+
wdir = "SW"
|
712
|
+
when 250..294
|
713
|
+
wdir = "W"
|
714
|
+
when 295..340
|
715
|
+
wdir = "NW"
|
716
|
+
else
|
717
|
+
wdir = "N"
|
718
|
+
end
|
719
|
+
wind += wdir.rjust(2)
|
720
|
+
wthr += wind.rjust(10) + ")"
|
721
|
+
info = date + " (" + Date.parse(date).strftime("%A") + ") #{hour}:00\n\n"
|
722
|
+
cld = "Clouds (-/+) " + details["cloud_area_fraction"].to_i.to_s + "% ("
|
723
|
+
cld += details["cloud_area_fraction_low"].to_i.to_s + "% " + details["cloud_area_fraction_high"].to_i.to_s + "%)"
|
724
|
+
info += cld.ljust(34)
|
725
|
+
details["fog_area_fraction"] == 0 ? fog = "-" : fog = (details["fog_area_fraction"].to_f.round(1)).to_s + "%"
|
726
|
+
info += "Humidity (fog) " + details["relative_humidity"].to_s + "% (" + fog + ")\n"
|
727
|
+
wnd = "Wind [gusts] " + details["wind_speed"].to_s + " m/s (" + wdir + ") [" + details["wind_speed_of_gust"].to_s + "]"
|
728
|
+
info += wnd.ljust(34)
|
729
|
+
info += "Temp (dew) " + details["air_temperature"].to_s + "°C ("
|
730
|
+
info += details["dew_point_temperature"].to_s + "°C)\n"
|
731
|
+
air = "Air pressure " + details["air_pressure_at_sea_level"].to_i.to_s + " hPa "
|
732
|
+
info += air.ljust(34)
|
733
|
+
uv = details["ultraviolet_index_clear_sky"].to_s
|
734
|
+
uv = "-" if uv == ""
|
735
|
+
info += "UV index " + uv + "\n"
|
736
|
+
@weather.push([date, hour, wthr, info])
|
737
|
+
rescue
|
738
|
+
w_b_info("Not able to retrieve weather data from met.no")
|
739
|
+
end
|
740
|
+
end
|
741
|
+
end
|
742
|
+
def get_planets # PLANET EPHEMERIS DATA
|
743
|
+
planets = {}
|
744
|
+
12.times do |x|
|
745
|
+
date = (Time.now + 86400 * (x - 1)).strftime("%F")
|
746
|
+
p = Ephemeris.new(date, @lat, @lon, @tz.to_i)
|
747
|
+
planets[date] = {"table" => p.print,
|
748
|
+
"srise" => p.sun[5], "sset" => p.sun[7],
|
749
|
+
"mrise" => p.moon[5], "mset" => p.moon[7],
|
750
|
+
"phase" => p.mphase, "ph_s" => p.mph_s,
|
751
|
+
"Mrise" => p.mercury[5], "Mset" => p.mercury[7],
|
752
|
+
"Vrise" => p.venus[5], "Vset" => p.venus[7],
|
753
|
+
"Arise" => p.mars[5], "Aset" => p.mars[7],
|
754
|
+
"Jrise" => p.jupiter[5], "Jset" => p.jupiter[7],
|
755
|
+
"Srise" => p.saturn[5], "Sset" => p.saturn[7],
|
756
|
+
"Urise" => p.uranus[5], "Uset" => p.uranus[7],
|
757
|
+
"Nrise" => p.neptune[5], "Nset" => p.neptune[7]}
|
758
|
+
end
|
759
|
+
return planets
|
760
|
+
end
|
761
|
+
def get_events # ASTRONOMICAL EVENTS
|
762
|
+
events = {}
|
763
|
+
eventsURI = "https://in-the-sky.org//rss.php?feed=dfan&latitude=#{@lat}&longitude=#{@lon}&timezone=#{@loc}"
|
764
|
+
events_rss = Net::HTTP.get(URI(eventsURI))
|
765
|
+
events_data = events_rss.scan(/<item>.*?<\/item>/m)
|
766
|
+
events_data.each do |e|
|
767
|
+
date = Time.parse(e[/<title>(.{11})/,1]).strftime("%F")
|
768
|
+
time = e[/\d\d:\d\d:\d\d/]
|
769
|
+
event = e[/<description><p>(.*?).<\/p><\/description>/,1].decoder
|
770
|
+
event.gsub!(/&deg;/, "°")
|
771
|
+
event.gsub!(/&#39;/, "'")
|
772
|
+
link = e[/<link>(.*?)<\/link>/,1]
|
773
|
+
events[date] = {"time" => time, "event" => event, "link" => link} if date >= @today
|
774
|
+
end
|
775
|
+
return events
|
776
|
+
end
|
777
|
+
def get_cond(t) # GREEN/YELLOW/RED FROM CONDITIONS
|
778
|
+
details = @weather_point[t]["data"]["instant"]["details"]
|
779
|
+
cond = 0
|
780
|
+
cond += 1 if details["cloud_area_fraction"].to_i > @cloudlimit
|
781
|
+
cond += 1 if details["cloud_area_fraction"].to_i > @cloudlimit + (100 - @cloudlimit)/2
|
782
|
+
cond += 2 if details["cloud_area_fraction"].to_i > 90
|
783
|
+
cond += 1 if details["relative_humidity"].to_i > @humiditylimit
|
784
|
+
cond += 1 if details["air_temperature"].to_i < @templimit
|
785
|
+
cond += 1 if details["air_temperature"].to_i + 7 < @templimit
|
786
|
+
cond += 1 if details["wind_speed"].to_i > @windlimit
|
787
|
+
cond += 1 if details["wind_speed"].to_i > @windlimit * 2
|
788
|
+
case cond
|
789
|
+
when 0..1
|
790
|
+
return 1
|
791
|
+
when 2..3
|
792
|
+
return 2
|
793
|
+
else
|
794
|
+
return 3
|
795
|
+
end
|
796
|
+
end
|
797
|
+
def conf_write # WRITE TO .AP.CONF
|
798
|
+
if File.exist?(Dir.home+'/.ap.conf')
|
799
|
+
conf = File.read(Dir.home+'/.ap.conf')
|
800
|
+
else
|
801
|
+
conf = ""
|
802
|
+
end
|
803
|
+
if @write_conf_all
|
804
|
+
conf.sub!(/^@loc.*/, "@loc = \"#{@loc}\"")
|
805
|
+
conf.sub!(/^@lat.*/, "@lat = #{@lat}")
|
806
|
+
conf.sub!(/^@lon.*/, "@lon = #{@lon}")
|
807
|
+
conf.sub!(/^@cloudlimit.*/, "@cloudlimit = #{@cloudlimit}")
|
808
|
+
conf.sub!(/^@humiditylimit.*/, "@humiditylimit = #{@humiditylimit}")
|
809
|
+
conf.sub!(/^@templimit.*/, "@templimit = #{@templimit}")
|
810
|
+
conf.sub!(/^@windlimit.*/, "@windlimit = #{@windlimit}")
|
811
|
+
w_u_msg("Press W again to write this to .ap.conf:\n\n" + conf)
|
812
|
+
if getchr == 'W'
|
813
|
+
w_b_info(" Parameters written to .ap.conf")
|
814
|
+
@w_b.update = false
|
815
|
+
else
|
816
|
+
w_b_info(" Config NOT updated")
|
817
|
+
@w_b.update = false
|
818
|
+
return
|
819
|
+
end
|
820
|
+
end
|
821
|
+
File.write(Dir.home+'/.ap.conf', conf)
|
822
|
+
end
|
823
|
+
# TOP WINDOW FUNCTIONS
|
824
|
+
def w_t_info # SHOW INFO IN @w_t
|
825
|
+
@w_t.clr
|
826
|
+
text = " #{@loc} (tz=#{'%02d' % @tz}) Lat: #{@lat}, Lon: #{@lon} "
|
827
|
+
text += "(Bortle #{@bortle}) "
|
828
|
+
text += "Updated: #{@time} (JD: 24#{DateTime.now.amjd().to_f.round(4) + 0.5})"
|
829
|
+
text += " " * (@w_t.maxx - text.length) if text.length < @w_t.maxx
|
830
|
+
@w_t.text = text
|
831
|
+
@w_t.write
|
832
|
+
end
|
833
|
+
# LEFT WINDOW FUNCTIONS
|
834
|
+
def print_p(ix, date, rise, set, c)
|
835
|
+
@w_l << " "
|
836
|
+
m = "┃"
|
837
|
+
m = "█" if rise == "srise" or rise == "mrise"
|
838
|
+
if @planets[date][set] == "never"
|
839
|
+
@w_l.p(c,0,0,m)
|
840
|
+
elsif @planets[date][set][0..1] < @planets[date][rise][0..1] and @weather[ix][1] <= @planets[date][set][0..1]
|
841
|
+
@w_l.p(c,0,0,m)
|
842
|
+
elsif @planets[date][set][0..1] < @planets[date][rise][0..1] and @weather[ix][1] >= @planets[date][rise][0..1]
|
843
|
+
@w_l.p(c,0,0,m)
|
844
|
+
elsif @weather[ix][1] >= @planets[date][rise][0..1] and @weather[ix][1] <= @planets[date][set][0..1]
|
845
|
+
@w_l.p(c,0,0,m)
|
846
|
+
else
|
847
|
+
@w_l << " "
|
848
|
+
end
|
849
|
+
end
|
850
|
+
def w_l_info # SHOW WEATHER CONDITION AND RISE/SET IN @w_l
|
851
|
+
@w_l.clr
|
852
|
+
@w_l.p(254, 238, 0, "YYYY-MM-DD HH Cld% Hum% °C Wind m/s * ● ○ m V M J S U N\n")
|
853
|
+
ix = 0; t = 1; prev_date = ""
|
854
|
+
ix = @index - @w_l.maxy/2 if @index > @w_l.maxy/2 and @weather.size > @w_l.maxy
|
855
|
+
while ix < @weather.size and t < @w_l.maxy do
|
856
|
+
marker = 0
|
857
|
+
color = color_pair(get_cond(ix))
|
858
|
+
date = @weather[ix][0]
|
859
|
+
date == prev_date ? line = " " : line = date + " "
|
860
|
+
@w_l.attron(color) { @w_l << line }
|
861
|
+
marker = Curses::A_UNDERLINE if ix == @index
|
862
|
+
line = @weather[ix][1] + @weather[ix][2]
|
863
|
+
@w_l.attron(color | marker) { @w_l << line }
|
864
|
+
if @events.has_key?(date)
|
865
|
+
@events[date]["time"][0..1] == @weather[ix][1] ? line = " !" : line = " "
|
866
|
+
else
|
867
|
+
line = " "
|
868
|
+
end
|
869
|
+
@w_l.attron(color) { @w_l << line }
|
870
|
+
begin
|
871
|
+
print_p(ix, date, "srise", "sset", 226)
|
872
|
+
c0 = ((50 - (@planets[date]["phase"] - 50).abs)/2.7 + 237).to_i
|
873
|
+
print_p(ix, date, "mrise", "mset", c0)
|
874
|
+
print_p(ix, date, "Mrise", "Mset", 130)
|
875
|
+
print_p(ix, date, "Vrise", "Vset", 153)
|
876
|
+
print_p(ix, date, "Arise", "Aset", 124)
|
877
|
+
print_p(ix, date, "Jrise", "Jset", 108)
|
878
|
+
print_p(ix, date, "Srise", "Sset", 142)
|
879
|
+
print_p(ix, date, "Urise", "Uset", 24)
|
880
|
+
print_p(ix, date, "Nrise", "Nset", 27)
|
881
|
+
rescue
|
882
|
+
end
|
883
|
+
clrtoeol
|
884
|
+
@w_l << "\n"
|
885
|
+
prev_date = date unless date == prev_date
|
886
|
+
ix += 1
|
887
|
+
t += 1
|
888
|
+
end
|
889
|
+
@w_l.refresh
|
890
|
+
end
|
891
|
+
# RIGHT UPPER WINDOW FUNCTIONS
|
892
|
+
def w_u_msg(msg) # MESSAGES IN @w_u
|
893
|
+
@w_u.clr
|
894
|
+
@w_u.text = msg
|
895
|
+
@w_u.write
|
896
|
+
@w_u.update = false
|
897
|
+
end
|
898
|
+
def w_u_info # ASTRO INFO IN @w_u
|
899
|
+
@w_u.clr
|
900
|
+
color = color_pair(get_cond(@index))
|
901
|
+
info = @weather[@index][3].split("\n")
|
902
|
+
# Moon phase
|
903
|
+
mp = 29.530588861
|
904
|
+
nm = 2459198.177777778
|
905
|
+
fm = nm + mp/2
|
906
|
+
y = @weather[@index][0][0..3].to_i
|
907
|
+
m = @weather[@index][0][5..6].to_i
|
908
|
+
d = @weather[@index][0][8..9].to_i
|
909
|
+
h = @weather[@index][1].to_i
|
910
|
+
jd = DateTime.new(y, m, d, h, 0, 0, @tz).ajd.to_f
|
911
|
+
mp_n = (100*((jd - nm) % mp) / mp).round(1)
|
912
|
+
ph_a = ((jd - fm) % mp) / mp * 360
|
913
|
+
mp_ip = ((1 + Math.cos(ph_a.deg))*50).to_i
|
914
|
+
mp_s = @planets[@weather[@index][0]]["ph_s"]
|
915
|
+
title = info[0] + " (Moon: #{mp_n}/#{mp_ip}% #{mp_s})"
|
916
|
+
@w_u.attron(color) { @w_u << title }
|
917
|
+
@w_u.write
|
918
|
+
info.shift
|
919
|
+
@w_u.maxx < Curses.cols ? maxx = @w_u.maxx : maxx = Curses.cols
|
920
|
+
info.each_with_index do |line, index|
|
921
|
+
line += " "*(maxx - line.length - 1)
|
922
|
+
info[index] = line
|
923
|
+
end
|
924
|
+
@w_u.text = info.join("\n")
|
925
|
+
@w_u.text += "\n\n"
|
926
|
+
@w_u.text += @planets[@weather[@index][0]]["table"]
|
927
|
+
@w_u.write
|
928
|
+
date = @weather[@index][0]
|
929
|
+
|
930
|
+
@w_u << "\n"
|
931
|
+
if @events.has_key?(date)
|
932
|
+
text = "@ " + @events[date]["time"] + ": "
|
933
|
+
text += @events[date]["event"] + "\n"
|
934
|
+
text += @events[date]["link"] + "\n"
|
935
|
+
if @events[date]["time"][0..1] == @weather[@index][1]
|
936
|
+
@w_u.p(111,0,Curses::A_BOLD,text)
|
937
|
+
else
|
938
|
+
@w_u.text = text
|
939
|
+
@w_u.write
|
940
|
+
end
|
941
|
+
end
|
942
|
+
end
|
943
|
+
# RIGHT LOWER WINDOW FUNCTIONS
|
944
|
+
def image_show(image)# SHOW THE SELECTED IMAGE IN TOP RIGHT WINDOW
|
945
|
+
return unless @showimage
|
946
|
+
return if @noimage
|
947
|
+
# Pass "clear" to clear the window for previous image
|
948
|
+
begin
|
949
|
+
terminfo = `xwininfo -id $(xdotool getactivewindow)`
|
950
|
+
term_w = terminfo.match(/Width: (\d+)/)[1].to_i
|
951
|
+
term_h = terminfo.match(/Height: (\d+)/)[1].to_i
|
952
|
+
char_w = term_w / Curses.cols
|
953
|
+
char_h = term_h / Curses.lines
|
954
|
+
img_x = char_w * (@w_l_width + 1)
|
955
|
+
img_y = char_h * 23
|
956
|
+
img_max_w = char_w * (Curses.cols - @w_l_width - 2)
|
957
|
+
img_max_h = char_h * (@w_d.maxy - 2)
|
958
|
+
if image == "clear"
|
959
|
+
img_max_w += 2
|
960
|
+
img_max_h += 2
|
961
|
+
`echo "6;#{img_x};#{img_y};#{img_max_w};#{img_max_h};\n4;\n3;" | #{@w3mimgdisplay} 2>/dev/null`
|
962
|
+
else
|
963
|
+
img_w,img_h = `identify -format "%[fx:w]x%[fx:h]" #{image} 2>/dev/null`.split('x')
|
964
|
+
img_w = img_w.to_i
|
965
|
+
img_h = img_h.to_i
|
966
|
+
if img_w > img_max_w
|
967
|
+
img_h = img_h * img_max_w / img_w
|
968
|
+
img_w = img_max_w
|
969
|
+
end
|
970
|
+
if img_h > img_max_h
|
971
|
+
img_w = img_w * img_max_h / img_h
|
972
|
+
img_h = img_max_h
|
973
|
+
end
|
974
|
+
`echo "0;1;#{img_x};#{img_y};#{img_w};#{img_h};;;;;\"#{image}\"\n4;\n3;" | #{@w3mimgdisplay} 2>/dev/null`
|
975
|
+
end
|
976
|
+
rescue
|
977
|
+
w_b_info("Error showing image")
|
978
|
+
end
|
979
|
+
end
|
980
|
+
def starchart # GET AND SHOW STARCHART FOR SELECTED TIME
|
981
|
+
d = Time.parse(@weather[@index][0]).strftime("%d").to_i
|
982
|
+
m = Time.parse(@weather[@index][0]).strftime("%m").to_i
|
983
|
+
y = Time.parse(@weather[@index][0]).strftime("%Y").to_i
|
984
|
+
starchartURI = "https://www.stelvision.com/carte-ciel/visu_carte.php?stelmarq=C&mode_affichage=normal&req=stel&date_j_carte=#{d}&date_m_carte=#{m}&date_a_carte=#{y}&heure_h=#{@weather[@index][1].to_i}&heure_m=00&longi=#{@lon}&lat=#{@lat}&tzone=#{@tz.to_i}.0&dst_offset=1&taille_carte=1200&fond_r=255&fond_v=255&fond_b=255&lang=en"
|
985
|
+
`curl -s "#{starchartURI}" > /tmp/stars.png`
|
986
|
+
`convert /tmp/stars.png /tmp/starchart.jpg`
|
987
|
+
end
|
988
|
+
def apod # GET ASTRONOMY PICTRUE OF THE DAY
|
989
|
+
apod = Net::HTTP.get(URI("https://apod.nasa.gov/apod/astropix.html"))
|
990
|
+
apod.sub!(/^.*IMG SRC=./m, "")
|
991
|
+
apod.sub!(/\".*/m, "")
|
992
|
+
apod = "https://apod.nasa.gov/apod/" + apod
|
993
|
+
`curl -s "#{apod}" > /tmp/apod.jpg`
|
994
|
+
end
|
995
|
+
# BOTTOM WINDOW FUNCTIONS
|
996
|
+
def w_b_info(info) # SHOW INFO IN @W_B
|
997
|
+
@w_b.clr
|
998
|
+
info = "?=Show Help | Edit: l=Loc a=Lat o=Lon | s=Starchart for selected time | ENTER=Refresh r=Redraw q=Quit Q=Quit (no config save)" if info == nil
|
999
|
+
info = info[1..(@w_b.maxx - 3)] + "…" if info.length + 3 > @w_b.maxx
|
1000
|
+
info += " " * (@w_b.maxx - info.length) if info.length < @w_b.maxx
|
1001
|
+
@w_b.text = info
|
1002
|
+
@w_b.write
|
1003
|
+
@w_b.update = false
|
1004
|
+
end
|
1005
|
+
def w_b_getstr(pretext, text) # A SIMPLE READLINE-LIKE ROUTINE
|
1006
|
+
Curses.curs_set(1)
|
1007
|
+
Curses.echo
|
1008
|
+
stk = 0
|
1009
|
+
@history.insert(stk, text)
|
1010
|
+
pos = @history[stk].length
|
1011
|
+
chr = ""
|
1012
|
+
while chr != "ENTER"
|
1013
|
+
@w_b.setpos(0,0)
|
1014
|
+
@w_b.text = pretext + @history[stk]
|
1015
|
+
@w_b.text += " " * (@w_b.maxx - text.length) if text.length < @w_b.maxx
|
1016
|
+
@w_b.write
|
1017
|
+
@w_b.setpos(0,pretext.length + pos)
|
1018
|
+
@w_b.refresh
|
1019
|
+
chr = getchr
|
1020
|
+
case chr
|
1021
|
+
when 'UP'
|
1022
|
+
unless @w_b.nohistory
|
1023
|
+
unless stk == @history.length - 1
|
1024
|
+
stk += 1
|
1025
|
+
pos = @history[stk].length
|
1026
|
+
end
|
1027
|
+
end
|
1028
|
+
when 'DOWN'
|
1029
|
+
unless @w_b.nohistory
|
1030
|
+
unless stk == 0
|
1031
|
+
stk -= 1
|
1032
|
+
pos = @history[stk].length
|
1033
|
+
end
|
1034
|
+
end
|
1035
|
+
when 'RIGHT'
|
1036
|
+
pos += 1 unless pos > @history[stk].length
|
1037
|
+
when 'LEFT'
|
1038
|
+
pos -= 1 unless pos == 0
|
1039
|
+
when 'HOME'
|
1040
|
+
pos = 0
|
1041
|
+
when 'END'
|
1042
|
+
pos = @history[stk].length
|
1043
|
+
when 'DEL'
|
1044
|
+
@history[stk][pos] = ""
|
1045
|
+
when 'BACK'
|
1046
|
+
unless pos == 0
|
1047
|
+
pos -= 1
|
1048
|
+
@history[stk][pos] = ""
|
1049
|
+
end
|
1050
|
+
when 'WBACK'
|
1051
|
+
unless pos == 0
|
1052
|
+
until @history[stk][pos - 1] == " " or pos == 0
|
1053
|
+
pos -= 1
|
1054
|
+
@history[stk][pos] = ""
|
1055
|
+
end
|
1056
|
+
if @history[stk][pos - 1] == " "
|
1057
|
+
pos -= 1
|
1058
|
+
@history[stk][pos] = ""
|
1059
|
+
end
|
1060
|
+
end
|
1061
|
+
when 'LDEL'
|
1062
|
+
@history[stk] = ""
|
1063
|
+
pos = 0
|
1064
|
+
when /^.$/
|
1065
|
+
@history[stk].insert(pos,chr)
|
1066
|
+
pos += 1
|
1067
|
+
end
|
1068
|
+
end
|
1069
|
+
curstr = @history[stk]
|
1070
|
+
@history.shift if @w_b.nohistory
|
1071
|
+
unless @w_b.nohistory
|
1072
|
+
@history.uniq!
|
1073
|
+
@history.compact!
|
1074
|
+
@history.delete("")
|
1075
|
+
end
|
1076
|
+
Curses.curs_set(0)
|
1077
|
+
Curses.noecho
|
1078
|
+
return curstr
|
1079
|
+
end
|
1080
|
+
|
1081
|
+
# MAIN PROGRAM
|
1082
|
+
loop do # OUTER LOOP - (catching refreshes via 'r')
|
1083
|
+
@break = false # Initialize @break variable (set if user hits 'r')
|
1084
|
+
@today = Time.now.strftime("%F")
|
1085
|
+
@tz = Time.now.strftime("%z")[0..2]
|
1086
|
+
@time = Time.now.strftime("%H:%M")
|
1087
|
+
get_weather
|
1088
|
+
@planets = get_planets
|
1089
|
+
@events = get_events
|
1090
|
+
Thread.new {apod}
|
1091
|
+
Thread.new {starchart} if @lat > 23
|
1092
|
+
begin # Create the four windows/panels
|
1093
|
+
Curses.stdscr.bg = 236 # Use for borders
|
1094
|
+
Curses.stdscr.fill
|
1095
|
+
maxx = Curses.cols
|
1096
|
+
exit if maxx < @w_l_width
|
1097
|
+
maxy = Curses.lines
|
1098
|
+
# Curses::Window.new(h,w,y,x)
|
1099
|
+
@w_t = Curses::Window.new(1, maxx, 0, 0)
|
1100
|
+
@w_b = Curses::Window.new(1, maxx, maxy - 1, 0)
|
1101
|
+
@w_l = Curses::Window.new(maxy - 2, @w_l_width - 1, 1, 0)
|
1102
|
+
@w_u = Curses::Window.new(21, maxx - @w_l_width, 1, @w_l_width)
|
1103
|
+
@w_d = Curses::Window.new(maxy - 23, maxx - @w_l_width, 23, @w_l_width)
|
1104
|
+
@w_t.fg, @w_t.bg = 7, 18
|
1105
|
+
@w_t.attr = Curses::A_BOLD
|
1106
|
+
@w_b.fg, @w_b.bg = 7, 18
|
1107
|
+
@w_d.fill
|
1108
|
+
@w_t.update = true
|
1109
|
+
@w_b.update = true
|
1110
|
+
@w_l.update = true
|
1111
|
+
@w_u.update = true
|
1112
|
+
@w_d.update = true
|
1113
|
+
loop do # INNER, CORE LOOP
|
1114
|
+
@min_index = 0
|
1115
|
+
@max_index = @weather.size - 1
|
1116
|
+
# Top window (info line)
|
1117
|
+
w_t_info
|
1118
|
+
# Bottom window (command line) Before @w_u to avoid image dropping out on startup
|
1119
|
+
w_b_info(nil) if @w_b.update
|
1120
|
+
@w_b.update = true
|
1121
|
+
# Left and right windows (browser & content viewer)
|
1122
|
+
w_l_info
|
1123
|
+
begin
|
1124
|
+
w_u_info if @w_u.update
|
1125
|
+
rescue
|
1126
|
+
w_u_msg("== No info for past time ==")
|
1127
|
+
end
|
1128
|
+
Curses.curs_set(1) # Clear residual cursor
|
1129
|
+
Curses.curs_set(0) # ...from editing files
|
1130
|
+
main_getkey # Get key from user
|
1131
|
+
@w_u.text = ""
|
1132
|
+
if @w_d.update
|
1133
|
+
image_show("clear")
|
1134
|
+
image_show(@image)
|
1135
|
+
end
|
1136
|
+
@w_d.update = false
|
1137
|
+
break if @break # Break to outer loop, redrawing windows, if user hit 'r'
|
1138
|
+
break if Curses.cols != maxx or Curses.lines != maxy # break on terminal resize
|
1139
|
+
end
|
1140
|
+
ensure # On exit: close curses, clear terminal
|
1141
|
+
@write_conf_all = false
|
1142
|
+
conf_write if @write_conf # Write marks to config file
|
1143
|
+
image_show("clear")
|
1144
|
+
close_screen
|
1145
|
+
end
|
1146
|
+
end
|
1147
|
+
|
1148
|
+
# vim: set sw=2 sts=2 et fdm=syntax fdn=2 fcs=fold\:\ :
|
metadata
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: astropanel
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '1.1'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Geir Isene
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-09-23 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: 'This program gives you essential data to plan your observations: 9 days
|
14
|
+
weatheer forecast, full ephemeris for Sun, Moon and all major planets with graphic
|
15
|
+
representation of rise/set times, detailed info for each day with important astronomical
|
16
|
+
events, star chart displayed in the terminal and more.'
|
17
|
+
email: g@isene.com
|
18
|
+
executables: []
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- bin/astropanel.rb
|
23
|
+
homepage: https://isene.com/
|
24
|
+
licenses:
|
25
|
+
- Unlicense
|
26
|
+
metadata:
|
27
|
+
source_code_uri: https://github.com/isene/astropanel
|
28
|
+
post_install_message:
|
29
|
+
rdoc_options: []
|
30
|
+
require_paths:
|
31
|
+
- lib
|
32
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '0'
|
37
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
requirements: []
|
43
|
+
rubygems_version: 3.1.2
|
44
|
+
signing_key:
|
45
|
+
specification_version: 4
|
46
|
+
summary: Terminal program for amateur astronomers with weather forecast.
|
47
|
+
test_files: []
|