astropanel 1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|