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.
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: []