astropanel 1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/bin/astropanel.rb +1148 -0
  3. 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>&lt;p&gt;(.*?).&lt;\/p&gt;<\/description>/,1].decoder
770
+ event.gsub!(/&amp;deg;/, "°")
771
+ event.gsub!(/&amp;#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: []