himawari 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 62481fb8ecb232de59856604c05dea5b09f65ecc5b799892861548ff02d973dc
4
+ data.tar.gz: 1572d43032c289465c7af40f119a94fd6e2837394daeff5bdd26d2b5b362401e
5
+ SHA512:
6
+ metadata.gz: 4a8b27e15225991ee80a77117abc18b04939ee4870cf90b70b11a7160631097e00bfdf4dc4b61d6cc480e5743970f0e70c5d0634a5cce0d6c1cfd1a1ceb027af
7
+ data.tar.gz: 9b52bad56dfc83a3edb2a5e255d9bd6d009072c607435e21239c53de5c61ae77dfb3568294d553a357b2e3e2e47946d10e4a1549c9c217b607cd1378cd315779
@@ -0,0 +1,2 @@
1
+ **/data/*
2
+ *.gem
@@ -0,0 +1,52 @@
1
+ AllCops:
2
+ DisplayCopNames: true
3
+ StyleGuideCopsOnly: false
4
+ TargetRubyVersion: 2.5
5
+ Exclude:
6
+ - data/**/*
7
+
8
+ Layout/LineLength:
9
+ Enabled: true
10
+ Max: 130
11
+
12
+ Metrics/AbcSize:
13
+ Enabled: false
14
+
15
+ Metrics/MethodLength:
16
+ Enabled: true
17
+ Max: 25
18
+ Exclude:
19
+ - spec/**/*
20
+
21
+ Metrics/BlockLength:
22
+ Exclude:
23
+ - spec/**/*
24
+ - ./*.gemspec
25
+
26
+ Metrics/CyclomaticComplexity:
27
+ Max: 9
28
+ # Exclude:
29
+
30
+ Metrics/PerceivedComplexity:
31
+ Max: 11
32
+ # Exclude:
33
+
34
+ Lint/RaiseException:
35
+ Enabled: true
36
+
37
+ Lint/StructNewOverride:
38
+ Enabled: true
39
+
40
+ Style/FrozenStringLiteralComment:
41
+ Enabled: true
42
+ Exclude:
43
+ - exe/**/*
44
+
45
+ Style/HashEachMethods:
46
+ Enabled: true
47
+
48
+ Style/HashTransformKeys:
49
+ Enabled: true
50
+
51
+ Style/HashTransformValues:
52
+ Enabled: true
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen-string-literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in himawari.gemspec
6
+ gemspec
@@ -0,0 +1,36 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ himawari (0.1.0)
5
+ httparty (~> 0.17.3)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ coderay (1.1.2)
11
+ httparty (0.17.3)
12
+ mime-types (~> 3.0)
13
+ multi_xml (>= 0.5.2)
14
+ method_source (1.0.0)
15
+ mime-types (3.3.1)
16
+ mime-types-data (~> 3.2015)
17
+ mime-types-data (3.2019.1009)
18
+ minitest (5.14.0)
19
+ multi_xml (0.6.0)
20
+ pry (0.13.1)
21
+ coderay (~> 1.1)
22
+ method_source (~> 1.0)
23
+ rake (13.0.1)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ bundler (~> 2.1.4)
30
+ himawari!
31
+ minitest (~> 5.14.0)
32
+ pry (~> 0.13.1)
33
+ rake (~> 13.0.1)
34
+
35
+ BUNDLED WITH
36
+ 2.1.4
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 engura
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,73 @@
1
+ # himawari
2
+ Access images from the [himawari8 weather satellite](https://himawari8.nict.go.jp)] (courtesy of NICT) and compose near real-time desktop backgrounds of Earth or use the high-res images for other personal uses. For example....
3
+ ![full globe](doc/img/h_2020-06-01T0100.png)
4
+ ![high res section](doc/img/h_2020-11-29T0110.png)
5
+
6
+ # Installation
7
+ Add this line to your application's Gemfile:
8
+ ```
9
+ gem 'himawari'
10
+ ```
11
+ And then execute:
12
+ ```
13
+ bundle
14
+ ```
15
+ Or install it yourself as:
16
+ ```
17
+ gem install himawari
18
+ ```
19
+ ## Install CLI Dependencies
20
+ Mac:
21
+ ```
22
+ brew install imagemagick # https://github.com/minimagick/minimagick
23
+ brew install parallel # https://linux.die.net/man/1/parallel
24
+ ```
25
+ or Linux: run `bin/setup`
26
+
27
+ # Usage
28
+
29
+ You can use it as a CLI executable, or as a library.
30
+
31
+ *RUN EXAMPLE*: `himawari -w /home/User/himawari -d /home/User/Pictures/live -r 4 -f top -m day -v`
32
+
33
+ To set cron (with all the supplied arguments):
34
+ `himawari -w /home/User/himawari -d /home/User/Pictures/live -r 4 -f top -m day -v -c set`
35
+ and then to remove it from cron:
36
+ `himawari -w /home/User/himawari -d /home/User/Pictures/live -r 4 -f top -m day -v -c clear`
37
+
38
+ ### Details
39
+ - Grabs a tiled image from Himawari, (each photo of Earth is split into square tiles of 550x550px) reassembles it into one image, and optionally copies one of the downloaded images into a `destination` folder to use as a desktop background.
40
+ ![a tile](doc/img/t_2020-12-01T0410-1_0.png)
41
+ - Can change the resolution. Default is "2", smallest. Maximum is "20". It will produce an jpg of ~200MB in size for the full planet. The allowed steps are [2, 4, 8, 16, 20]. Resolution of 2 means that the image is composed of *2* tiles across, so the full image becomes 1100px wide/high. An image of resolution *factor 4* is then 550 * 4 = 2200px across. And the maximum one is 20 * 550 = 11000px X 11000px!!!!!
42
+ - Since the images can be kinda big, we can customize which part of the planet we want to see: `top`, `full` planet, or bottom (`low`).
43
+ - When using the `autorun` option (it's the method that command line utility uses), we save the last 48hrs in a folder (`working_dir`). We can then have 2 options: either show what is `now` outside, or! cycle the last complete 24 hours with a new background photo every 2 minutes? Because the images are all downloaded at once, and then an incremental download of one photo as needed, the internet traffic is relatively reasonable. Cycling the background photos doesn't need to access internet or download anything.
44
+
45
+ - 1. do NOT attempt to fetch data from black listed WiFis
46
+ - 2. if no internet, just show last available data
47
+ - 3. keep only 1 day's worth of images (~144 photos)
48
+ - 4. check for `No Image` images and skip those (until? if?) they become available
49
+
50
+ # Development
51
+ After checking out the repo, doing the steps in `installation` above and messing around with the code, run `rake test` and `rubocop` to use the tests and make sure everything is ok. To run a specific test, use `rake test TEST=spec/test_base.rb TESTOPTS="--name=test_bad_params --seed=1234"` and as for rubocop: `rubocop lib/himawari/base.rb`
52
+
53
+ ## Building the gem on local machine/from source
54
+ navigate to the gem's directory and...
55
+ Manually:
56
+ ```
57
+ gem build himawari.gemspec
58
+ gem install himawari-#.#.#.gem # replace the # with the most recent version
59
+ ```
60
+ Semi-manually (the end result is same as above):
61
+ ```
62
+ bundle exec rake install
63
+ ```
64
+
65
+ ## Releasing a new version of the Gem
66
+ - Update the version number in `himawari.gemspec`
67
+ - run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the .gem file to [rubygems.org](https://rubygems.org).
68
+
69
+ # Contributing
70
+ Bug reports and pull requests are welcome on GitHub at https://github.com/engura/himawari.
71
+
72
+ # License
73
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,13 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/testtask'
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << 'spec'
8
+ t.libs << 'lib'
9
+ t.test_files = FileList['spec/**/test_*.rb']
10
+ end
11
+
12
+ desc 'Run tests'
13
+ task default: :test
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bash
2
+ echo 'Installing CLI Dependencies'
3
+ sudo apt-get install -y parallel imagemagick
4
+
5
+ bundle install
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'himawari'
4
+
5
+ params = Himawari::OsUtils.parse_cli_args
6
+ Himawari.autorun(params)
@@ -0,0 +1,35 @@
1
+ # frozen-string-literal: true
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'himawari'
5
+ s.version = '0.1.0'
6
+ s.date = '2020-12-15'
7
+ s.summary = 'Grabs latest images from the himawari8 weather satellite'
8
+ s.description = 'Makes pretty, high-res backgrounds from the real-time photos of Earth by Himawari8,' \
9
+ 'or is intended for similar personal usage. Please see the readme for more!'
10
+ s.authors = ['engura']
11
+ s.email = ['engura@gmail.com']
12
+ s.homepage = 'https://github.com/engura/himawari'
13
+ s.license = 'MIT'
14
+
15
+ s.files = `git ls-files`.split
16
+ s.bindir = 'exe'
17
+ s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ s.test_files = s.files.grep(%r{^(tests|spec|features)/})
19
+ s.require_paths = ['lib']
20
+
21
+ s.add_runtime_dependency 'httparty', '~> 0.17.3'
22
+
23
+ s.add_development_dependency 'bundler', '~> 2.1.4'
24
+ s.add_development_dependency 'minitest', '~> 5.14.0'
25
+ s.add_development_dependency 'pry', '~> 0.13.1'
26
+ s.add_development_dependency 'rake', '~> 13.0.1'
27
+
28
+ s.requirements << 'Mac OSX or Linux... Windows `not supported yet` due to the lack of the following:'
29
+ s.requirements << 'parallel, ~>20161222 (Let\'s download in parallel)'
30
+ s.requirements << 'ImageMagick, ~>6.9.10-23 (Specifically, the `montage` utility)'
31
+ s.requirements << 'common utilities: find, touch, crontab, curl, iwgetid, rm, bash'
32
+
33
+ s.post_install_message = "Thanks for installing! Have fun with the amazing pics of our Home made\n" \
34
+ 'accessible to us for FREE by NICT. And please, don\'t break their website!!'
35
+ end
@@ -0,0 +1,33 @@
1
+ # frozen-string-literal: true
2
+
3
+ require_relative './himawari/base.rb'
4
+ require_relative './himawari/os_utils.rb'
5
+ require_relative './himawari/net_utils.rb'
6
+ require_relative './himawari/process.rb'
7
+ require_relative './himawari/download.rb'
8
+
9
+ # encapsulates all the functions pertaining to acquiring images from the himawari8 satellite in near real-time
10
+ # from HIMAWARI_URL (defined in the Base Class)
11
+ module Himawari
12
+ def self.autorun(params = {})
13
+ h = Download.new(params)
14
+ h.cron_action ? h.crontab : h.update_backgrnd ^ h.start
15
+ end
16
+
17
+ def self.get_pic(params = {})
18
+ t = validate(params[:datetime])
19
+ Download.new(params).pic(t, OsUtils.tenmin(t.min, t.min))
20
+ end
21
+
22
+ def self.get_pics(params = {})
23
+ Download.new(params).pics(validate(params[:from]), validate(params[:to]))
24
+ end
25
+
26
+ def self.validate(stamp)
27
+ return stamp if stamp.is_a? Time
28
+ return Time.parse("#{stamp}+00:00") if stamp.is_a? String
29
+
30
+ t = Time.now.utc - 600 # 600secs == 10.minutes ago
31
+ Time.new(t.year, t.month, t.day, t.hour, t.min / 10 * 10, 0, '+00:00')
32
+ end
33
+ end
@@ -0,0 +1,135 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'httparty'
4
+ require 'fileutils'
5
+ require 'pathname'
6
+ require 'optparse'
7
+ # require 'pry'
8
+
9
+ module Himawari
10
+ MONITOR_ASPECT = 16.0 / 9
11
+ UPDATE_RATE = 2 # int; update the pic once every `UPDATE_RATE` minutes
12
+ HIMAWARI_URL = 'https://himawari8-dl.nict.go.jp/himawari8/img/D531106'
13
+
14
+ # Local (JST) midnight happens @14:10 UTC, so we have to use pics in interval of [2.days.ago@14:40 .. 1.day.ago@14:40]
15
+ LOCAL_MIDNIGHT = '1410'
16
+
17
+ # sets up the methods for Himawari that do not have to do with accessing the internet. ie:
18
+ # - it sets up all the default and customized params, builds awareness of the locally stored pics
19
+ # - implements "setting" the background (copies a downloaded image into a different folder)
20
+ class Base
21
+ attr_reader :now, :app_root, :work_path, :data_path, :destination_path, :focus, :mode, :resolution,
22
+ :latest_local, :latest_remote, :verbose, :by_schedule, :blacklist_wifi, :cron_action
23
+
24
+ # relative to the location of the script: Pathname.new(File.expand_path('../', __FILE__))
25
+ # relative to the current working dir: Dir.pwd
26
+ def initialize(params = {})
27
+ init_paths(params[:workdir])
28
+ @destination_path = params[:destination]
29
+
30
+ @now = Time.now.utc
31
+ @focus = params[:focus] || :top
32
+ @mode = params[:mode] || :day
33
+ @resolution = params[:resolution] || 2
34
+ @latest_local = find_latest_local
35
+ @latest_remote = nil
36
+ @verbose = params[:verbose]
37
+ @by_schedule = params[:by_schedule]
38
+ @blacklist_wifi = params[:blacklist] || []
39
+ @cron_action = params[:cron]
40
+ end
41
+
42
+ def up_to_date?
43
+ puts "Latest local: #{latest_local[:timestamp]}" if verbose
44
+ if now - latest_local[:timestamp] < 10 * 60
45
+ puts 'Local pic is up to date; no need to go online.' if verbose
46
+ return true
47
+ end
48
+ false
49
+ end
50
+
51
+ def background(img)
52
+ return false unless img && params_valid?
53
+
54
+ cmd = "sleep 5 ; rm -f #{destination_path}/*.png ; cp #{img} #{destination_path}"
55
+ # "osascript -e 'tell application \"System Events\" to tell DESKTOP to set picture to \"#{img}\"'"
56
+ puts cmd if verbose
57
+ system(cmd)
58
+ end
59
+
60
+ def update_backgrnd
61
+ return false unless mode == :day && params_valid?
62
+
63
+ # only rotate the background if we have one complete rotation of the globe saved
64
+ sexy_pics = full_24hrs_available
65
+ # puts sexy_pics
66
+ return false unless sexy_pics
67
+
68
+ i = (now.hour * 60 + now.min) / UPDATE_RATE % sexy_pics.count
69
+ puts "#{i} :: #{(now.hour * 60 + now.min)} % #{sexy_pics.count} switching to #{sexy_pics[i]}" if verbose
70
+ background(sexy_pics[i])
71
+ end
72
+
73
+ def crontab(action = nil)
74
+ @cron_action = action if action
75
+ return false unless cron_action && params_valid?
76
+
77
+ cmd = '* * * * * PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin himawari ' \
78
+ "-s -m #{mode} -f #{focus} -r #{resolution} -d '#{destination_path}' -w '#{work_path}' -b #{blacklist_wifi.join(',')}"
79
+ OsUtils.crontab(cmd, cron_action)
80
+ cmd
81
+ end
82
+
83
+ def params_valid?
84
+ unless %i[full top mid low].include?(focus) && %i[live day].include?(mode) &&
85
+ [2, 4, 8, 16, 20].include?(resolution) && blacklist_wifi.is_a?(Array) &&
86
+ (!destination_path || (destination_path && File.directory?(destination_path) && destination_path != '/'))
87
+ puts 'Invalid params. Please double check them.'
88
+ return false
89
+ end
90
+ true
91
+ end
92
+
93
+ private
94
+
95
+ # returns array of pic_names to choose a background from, or false if we do not have enough pictures for a full 24hr rotation
96
+ def full_24hrs_available
97
+ prev_pic = ''
98
+ sexy_pics = []
99
+
100
+ Dir["#{data_path}/h_*.png"].sort { |x, y| y <=> x }.each do |pic|
101
+ pic_time = pic[-8, 4]
102
+ return sexy_pics if sexy_pics.count.positive? && pic_time < LOCAL_MIDNIGHT && LOCAL_MIDNIGHT <= prev_pic
103
+
104
+ sexy_pics.unshift(pic) if sexy_pics.count.positive? || pic_time < LOCAL_MIDNIGHT && LOCAL_MIDNIGHT <= prev_pic
105
+ prev_pic = pic_time
106
+ end
107
+ false
108
+ end
109
+
110
+ def init_paths(workdir)
111
+ @app_root = workdir && File.directory?(workdir) ? workdir : Dir.pwd
112
+ @work_path = @app_root
113
+ @data_path = "#{@app_root}/data"
114
+
115
+ @app_root = Pathname.new(File.expand_path(__dir__))
116
+ Dir.mkdir(@data_path) unless File.exist?(@data_path)
117
+ end
118
+
119
+ def find_latest_local
120
+ # we can't select by timestamp, because those could be messed up. Instead, just search for "latest" by filename
121
+ # latest_pic = `ls -t #{data_path}/h_* | head -1`
122
+ twodays_ago = now - 86_400 * 2
123
+ latest = { timestamp: nil }
124
+ failsafe = { filename: nil, timestamp: Time.new(twodays_ago.year, twodays_ago.month, twodays_ago.day, 0, 0, 0, '+00:00') }
125
+
126
+ Dir["#{data_path}/h_*.png"].each do |pic|
127
+ stamp = Time.parse(pic[0..-5].insert(-3, ':') + ':00+00:00')
128
+ `touch -t #{stamp.strftime('%Y%m%d%H%M.%S')} #{pic}`
129
+ latest = { filename: pic, timestamp: stamp } if !latest[:timestamp] || latest[:timestamp] < stamp
130
+ end
131
+
132
+ latest[:timestamp] && latest[:timestamp] > twodays_ago ? latest : failsafe
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,143 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Himawari
4
+ # Let's expand our basic Himawari class with the ability to actually download stuff
5
+ # in addition to the downloading, we will also need to:
6
+ # - parse CLI arguments / set crontab (OsUtils)
7
+ # - check network status (NetUtils)
8
+ # - check and try to fix downloaded picture Tiles (Process)
9
+ class Download < Base
10
+ extend OsUtils
11
+ include NetUtils
12
+ include Process
13
+
14
+ def start
15
+ return false unless everything_ok && pics_updated?
16
+
17
+ set_background(latest_local[:filename], destination_path) if mode == :live
18
+ # clean up: remove any files that are more than 2 days old
19
+ `find #{data_path} -name \"*.png\" -type f -mtime +2 -exec rm -f {} \\;`
20
+ end
21
+
22
+ def pics(from_datetime, to_datetime)
23
+ # returns true if any new pictures were downloaded from the web, false otherwise
24
+ pic_dwnlded = false
25
+ from_datetime += 10 * 60 # +10.minutes from our latest pic on local drive
26
+
27
+ while from_datetime <= to_datetime
28
+ tenmin = if last_iter?(from_datetime, to_datetime)
29
+ OsUtils.tenmin(from_datetime.min, to_datetime.min)
30
+ elsif !pic_dwnlded # i.e. it's while's first iteration. don't update the whole hour, only the remaining minutes
31
+ OsUtils.tenmin(from_datetime.min)
32
+ else
33
+ OsUtils.tenmin
34
+ end
35
+
36
+ pic_dwnlded = pic(from_datetime, tenmin)
37
+
38
+ break if last_iter?(from_datetime, to_datetime) || !pic_dwnlded
39
+
40
+ from_datetime = if to_datetime - from_datetime > 3600
41
+ from_datetime + 3600
42
+ else
43
+ Time.new(to_datetime.year, to_datetime.month, to_datetime.day, to_datetime.hour, 0, 0, '+00:00')
44
+ end
45
+ end
46
+
47
+ if verbose
48
+ puts pic_dwnlded ? "Latest Fetched: #{find_latest_local[:timestamp]}" : 'Nothing downloaded.'
49
+ end
50
+ pic_dwnlded
51
+ end
52
+
53
+ # `parallel --header : 'montage -mode concatenate -tile 2x {00,10,01,11}/{year}-{mo}-{dy}T{hr}{tenmin}000-*.png
54
+ # full/{year}-{mo}-{dy}T{hr}{tenmin}000.png' ::: year 2015 ::: mo 11 ::: dy {27..28} ::: hr {00..23} ::: tenmin {0..5}`
55
+ def pic(timestamp, tenmin = '{0..5}')
56
+ return false unless everything_ok
57
+
58
+ if timestamp > latest_remote
59
+ puts "Can't download #{timestamp} because it is newer than the most recent available (#{latest_remote})"
60
+ return false
61
+ end
62
+
63
+ yr = timestamp.year.to_s.rjust(2, '0')
64
+ mo = timestamp.month.to_s.rjust(2, '0')
65
+ dy = timestamp.day.to_s.rjust(2, '0')
66
+ hr = timestamp.hour.to_s.rjust(2, '0')
67
+ x, y = download_region
68
+
69
+ command1 = 'parallel -j5 --delay 0.1 --header : '\
70
+ "'curl -sC - \"#{HIMAWARI_URL}/"\
71
+ "#{resolution}d/550/{year}/{mo}/{dy}/{hr}{tenmin}000_{x}_{y}.png\" > "\
72
+ "#{data_path}/t_{year}-{mo}-{dy}T{hr}{tenmin}0-{y}_{x}.png'"\
73
+ " ::: year #{yr} ::: mo #{mo} ::: dy #{dy} ::: hr #{hr} ::: tenmin #{tenmin} ::: x #{x} ::: y #{y}"
74
+ command2 = 'parallel --header : '\
75
+ "'montage -mode concatenate -tile #{resolution}x #{data_path}/t_{year}-{mo}-{dy}T{hr}{tenmin}0-*.png "\
76
+ "#{data_path}/h_{year}-{mo}-{dy}T{hr}{tenmin}0.png'"\
77
+ " ::: year #{yr} ::: mo #{mo} ::: dy #{dy} ::: hr #{hr} ::: tenmin #{tenmin}"
78
+ # command3 = 'parallel convert -channel R -gamma 1.2 -channel G -gamma 1.1 +channel -sigmoidal-contrast 3,50% {} '\
79
+ # "pretty/{/} ::: #{data_path}/*.png"
80
+
81
+ # system(command1) # works totally fine on mac...
82
+ # OOOK>> I have NO idea why linux is not parsing {..} from parallel above and instead sticks it as a literal,
83
+ # but we need to make it work >> hack it this way for the time being...
84
+ script = "#{data_path}/script_#{yr}_#{yr}_#{mo}_#{dy}_#{hr}.sh"
85
+ OsUtils.scriptify_sys(script, command1)
86
+
87
+ check_tiles
88
+
89
+ # system(command2) # works totally fine on mac...
90
+ OsUtils.scriptify_sys(script, command2)
91
+ `rm #{data_path}/t_*`
92
+ true
93
+ end
94
+
95
+ private
96
+
97
+ def everything_ok
98
+ @everything_ok ||= params_valid? && checks_passed?
99
+ end
100
+
101
+ def checks_passed?
102
+ if blacklisted_wifi?
103
+ puts "Blacklisted Network; Won't go online"
104
+ return false
105
+ end
106
+
107
+ # we don't want to spam himawari's site more than once every 10 minutes while running on a schedule
108
+ return false if by_schedule && now.min % 10 != 1
109
+
110
+ if `find #{data_path} -name \"t_*.png\"`.length.positive?
111
+ puts 'Another himawari process is still downloading/processing files.\n' \
112
+ '(There are tiles (t_*.png) in the `data` folder.) Quitting w/o overlapping.'
113
+ return false
114
+ end
115
+
116
+ unless internet_connection?
117
+ puts "Not online? Can't reach #{HIMAWARI_URL}"
118
+ return false
119
+ end
120
+ true
121
+ end
122
+
123
+ def last_iter?(from, to)
124
+ from.day == to.day && from.hour == to.hour
125
+ end
126
+
127
+ def pics_updated?
128
+ pics(latest_local[:timestamp], latest_remote) unless up_to_date?
129
+ end
130
+
131
+ def download_region
132
+ x = "{0..#{resolution - 1}}"
133
+ y = if focus == :top # :full :mid :low
134
+ "{0..#{(resolution / MONITOR_ASPECT).ceil - 1}}"
135
+ elsif focus == :low
136
+ "{#{resolution - (resolution / MONITOR_ASPECT).ceil}..#{resolution - 1}}"
137
+ else # full / mid
138
+ "{0..#{resolution - 1}}"
139
+ end
140
+ [x, y]
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,31 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Himawari
4
+ # Network-related methods to help establish whether there is network access and
5
+ # figure out if we are on a possibly "metered" connection (via a hard-coded SSID blacklist)
6
+ module NetUtils
7
+ def internet_connection?
8
+ uid = now.to_i * 1_000
9
+ r = HTTParty.get("#{HIMAWARI_URL}/latest.json?uid=#{uid}", { timeout: 10 })
10
+ # puts "#{HIMAWARI_URL}/latest.json?uid=#{uid}" if verbose
11
+ if r.code == 200
12
+ puts "Latest Himawari: #{r['date']}" if verbose
13
+ @latest_remote = Time.parse(r['date'] + '+00:00')
14
+ return true
15
+ end
16
+ false
17
+ end
18
+
19
+ private
20
+
21
+ def blacklisted_wifi?
22
+ current_wifi = if OsUtils.os == :mac
23
+ `networksetup -getairportnetwork en0`
24
+ elsif OsUtils.os == :linux
25
+ `iwgetid -r`
26
+ end
27
+ puts current_wifi if verbose
28
+ current_wifi && blacklist_wifi.any? { |wifi| current_wifi.include?(wifi) }
29
+ end
30
+ end
31
+ end
Binary file
@@ -0,0 +1,128 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Himawari
4
+ # all the misc. functions for dealing with CLI and/or OS...
5
+ module OsUtils
6
+ def self.os
7
+ if RUBY_PLATFORM =~ /win32/
8
+ :win
9
+ elsif RUBY_PLATFORM =~ /linux/
10
+ :linux
11
+ elsif RUBY_PLATFORM =~ /darwin/
12
+ :mac
13
+ elsif RUBY_PLATFORM =~ /freebsd/
14
+ :freebsd
15
+ else
16
+ :unknown
17
+ end
18
+ end
19
+
20
+ # rubocop:disable Metrics/MethodLength, Metrics/BlockLength
21
+ def self.parse_cli_args
22
+ params = {}
23
+
24
+ OptionParser.new do |opts|
25
+ opts.banner = 'Usage: himawari [params]'
26
+
27
+ opts.on('-f', '--focus STRING', String, 'Which section of the planet to focus on? ' \
28
+ 'Valid values are `full`, `top`, `mid`, `low`. Default is `top`.') do |o|
29
+ params[:focus] = o.to_sym if %w[full top mid low].include? o
30
+ end
31
+
32
+ opts.on('-m', '--mode STRING', String, 'Valid values are `day` (cycles pics in the `destination` folder from the' \
33
+ 'most recent day) or `live` (copies the latest photo downloaded ' \
34
+ 'to `destination`) Default is `day`.') do |o|
35
+ params[:mode] = :live if o == 'live'
36
+ end
37
+
38
+ opts.on('-r', '--resolution INT', Integer, 'Adjust the resolution of the downloaded image. Valid numbers are ' \
39
+ '2, 4, 8, 16, 20. 20 is the highest resolution and 2 is the default. ' \
40
+ 'For a 4k-monitor a setting of 4 seems sufficient.') do |o|
41
+ params[:resolution] = o if o <= 20 && o.positive? && o.even?
42
+ end
43
+
44
+ opts.on('-d', '--destination PATH', String, 'The folder where to copy a background image. If left blank, images will ' \
45
+ 'just be downloaded, but won\'t be copied anywhere afterward.') do |o|
46
+ params[:destination] = o if File.directory?(o)
47
+ end
48
+
49
+ opts.on('-w', '--workdir PATH', String, 'The folder where to save all the downloaded pics. If left blank, ' \
50
+ 'images will be saved to the `./data` directory relative to your ' \
51
+ 'current path of working dir.') do |o|
52
+ params[:workdir] = o if File.directory?(o)
53
+ end
54
+
55
+ opts.on('-b', '--blacklist STRING,STRING...', Array, 'Blacklist SSIDs of networks from which we do not want to go ' \
56
+ 'online to download new images.') do |o|
57
+ params[:blacklist] = o
58
+ end
59
+
60
+ opts.on('-c', '--cron STRING', String, 'Can `set`/`clear` cron with the specified params, so we can update the images ' \
61
+ 'automatically') do |o|
62
+ params[:cron] = o.to_sym if %w[set clear].include? o
63
+ end
64
+
65
+ opts.on('-v', '--verbose', 'Increase verbosity: mostly for debugging') do |o|
66
+ params[:verbose] = o
67
+ end
68
+
69
+ opts.on('-s', '--schedule', 'Flag for determining when the script is run by schedule/automatically.') do |o|
70
+ params[:by_schedule] = o
71
+ end
72
+
73
+ opts.on('-h', '--help', 'Prints this help & exits') do
74
+ puts opts
75
+ exit
76
+ end
77
+ end.parse! # (into: params)
78
+
79
+ params
80
+ end
81
+ # rubocop:enable Metrics/MethodLength, Metrics/BlockLength
82
+
83
+ def self.scriptify_sys(script, command)
84
+ `echo "#!/bin/bash\n#{command}" > #{script}`
85
+ `chmod +x #{script} && #{script}`
86
+ `rm #{script}`
87
+ end
88
+
89
+ def self.tenmin(from = 0, to = 59)
90
+ "{#{from / 10}..#{to / 10}}"
91
+ end
92
+
93
+ # to force crossfade:
94
+ # https://apple.stackexchange.com/questions/141834/applescript-to-change-desktop-image-on-all-monitors
95
+
96
+ # set picture rotation to 1 -- turn on wallpaper cycling
97
+ # set change interval to -1 -- force a change to happen right now
98
+ # delay 1.5 -- wait a bit to allow for the fade transition - you may want to play w/ this #
99
+ # set picture of item N of theDesktops to POSIX file ("/Users/vladimir/chie/lib/space/data/h_2019-11-06T0220.png")
100
+ # -- set wallpaper to wallpaper you want
101
+ # set picture rotation to 0 -- turn off wallpaper cycling
102
+
103
+ # tell application "System Events"
104
+ # tell every desktop
105
+ # tell desktop 1
106
+ # set pictures folder to "/Library/Desktop Pictures"
107
+ # set picture rotation to 2 -- using interval
108
+ # set change interval to 1800
109
+ # set random order to true
110
+ # end tell
111
+ # tell desktop 2
112
+ # set pictures folder to "/Library/Desktop Pictures/Mine"
113
+ # set picture rotation to 2 -- using interval
114
+ # set change interval to 1800
115
+ # set random order to true
116
+ # end tell
117
+ # end tell
118
+
119
+ # to silence the status mails, add MAILTO='' at the top of the crontab manually
120
+ def self.crontab(cmd, action)
121
+ if action == :set
122
+ `(crontab -l ; echo \"#{cmd}\") 2>&1 | grep -v \"no crontab\" | sort | uniq | crontab -`
123
+ else
124
+ `(crontab -l ; echo \"#{cmd}\") 2>&1 | grep -v \"no crontab\" | grep -v himawari | sort | uniq | crontab -`
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,81 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Himawari
4
+ # these methods provide checking and (trying to) fix the Tiles that we downloaded from the web.
5
+ # sometimes, the connection might be bad, and instead of an actual PNG for a Tile, we get a blank empty file.
6
+ # in other times, we get a special "no_image" png. This one is annoying. It IS a real picture,
7
+ # but it most CERTAINLY is NOT a picture of the Earth. (These normally come back during 0240 and 1440 timestamps)
8
+ # -- Himawari does not take photos when the Sun is in the view of the Satellite. Well, we wouldn't be able to see
9
+ # the Earth at those times anyway due to the Sun's brightness.
10
+ module Process
11
+ private
12
+
13
+ def check_tiles
14
+ @control_size ||= File.size("#{app_root}/no_image.png")
15
+ bad_tiles = {}
16
+ Dir["#{data_path}/t_*.png"].each do |tile|
17
+ bad_tiles = process_bad_tiles(bad_tiles, tile) if bad_tile?(tile)
18
+ end
19
+ recover_bad_sectors(bad_tiles)
20
+ end
21
+
22
+ def process_bad_tiles(bad_tiles, tile)
23
+ i = File.basename(tile[0..-9])
24
+ ending = tile[-7, 3]
25
+ stamp = Time.parse(i.dup.insert(-3, ':') + ':00+00:00')
26
+ bad_tiles[i] = [] unless bad_tiles.dig(i)
27
+ bad_tiles[i] << [
28
+ himawari_format(stamp, ending),
29
+ himawari_format(stamp - 600, ending),
30
+ himawari_format(stamp - 1_200, ending)
31
+ ]
32
+ # `cp #{tile} #{tile.gsub('/t_', '/x_')}`
33
+ # no need to delete individual "bad tiles" because we will try to recover them.
34
+ # But upon failure, will delete the whole himawari image
35
+ # File.delete(tile)
36
+ bad_tiles
37
+ end
38
+
39
+ def himawari_format(stamp, tile_end)
40
+ "#{stamp.year}/#{stamp.month}/#{stamp.day}/#{stamp.hour}#{stamp.min}00_#{tile_end}"
41
+ end
42
+
43
+ def png?(file)
44
+ File.size(file).positive? && IO.read(file, 4).force_encoding('utf-8') == "\x89PNG"
45
+ end
46
+
47
+ def bad_tile?(tile)
48
+ !png?(tile) || File.size(tile) == @control_size && system("cmp #{tile} #{app_root}/no_image.png")
49
+ end
50
+
51
+ # bad_tiles structure:
52
+ # { "t_2019-11-11T0240": [ [ [tile_url_1, tile_url_2, tile_url_3] ], [array_of_tiles], [array_of_tiles] ] }
53
+ def recover_bad_sectors(bad_tiles)
54
+ bad_tiles.each do |bad_pic_name, sectors|
55
+ pic_not_good = true
56
+
57
+ if sectors.count >= resolution # don't try to fix/recover pics that have too many pieces missing
58
+ puts "#{bad_pic_name} has #{sectors.count} pieces missing. Too many. Can't recover & will delete the whole thing!"
59
+ else
60
+ puts "#{bad_pic_name} has #{sectors.count} pieces missing. Will try to recover/fill in with older data."
61
+ p sectors
62
+ tile = "#{data_path}/#{bad_pic_name}-#{sectors[0][-3, 3]}.png"
63
+ sectors.each do |bad_tile|
64
+ `curl -sC - "#{HIMAWARI_URL}/#{resolution}d/550/#{bad_tile}.png" > #{tile}`
65
+ if bad_tile?(tile) # yep, it's bad...
66
+ # File.delete(tile)
67
+ pic_not_good = true
68
+ else
69
+ puts "#{tile} was recovered using #{bad_tile}!"
70
+ pic_not_good = false
71
+ break
72
+ end
73
+ end
74
+ end
75
+ # either too many pieces missing, or couldn't recover the wholes. It will look UGLY after reassembly,
76
+ # so just bite the bullet and get rid of the whole thing
77
+ `rm #{data_path}/#{bad_pic_name}*` if pic_not_good
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,54 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class TestBase < HimawariTest
6
+ # Firstly, let's check our params sanity...
7
+ def test_bad_params
8
+ bad_params = [
9
+ { resolution: 5 },
10
+ { focus: :bad },
11
+ { mode: 'live' },
12
+ { blacklist: 'live' },
13
+ { destination: '/definitely/a/bad_path' }
14
+ ]
15
+ bad_combos = (1..bad_params.count).flat_map { |length| bad_params.combination(length).to_a }
16
+
17
+ bad_combos.each do |params|
18
+ params = (params + [{ workdir: @@workdir }]).reduce({}, :merge)
19
+
20
+ # out = \
21
+ capture_stdout do
22
+ puts params
23
+ assert_equal false, @himawari.new(params).params_valid?
24
+ end
25
+ # puts out
26
+ end
27
+ end
28
+
29
+ def test_up_to_date
30
+ h = @himawari.new({ workdir: @@workdir })
31
+ assert_equal false, h.up_to_date?
32
+
33
+ h.latest_local[:timestamp] = Time.now - 300 # 300 == 5.minutes
34
+ assert_equal true, h.up_to_date?
35
+ end
36
+
37
+ def test_set_background
38
+ destination = "#{@@workdir}/data/selected_background"
39
+ Dir.mkdir(destination) unless File.exist?(destination)
40
+
41
+ h = @himawari.new({ workdir: @@workdir, destination: destination })
42
+ sample = Dir["#{h.data_path}/h_*.png"].sample
43
+
44
+ assert_equal !sample.nil?, h.background(sample)
45
+ end
46
+
47
+ def test_cron
48
+ cmd = @himawari.new({ workdir: @@workdir }).crontab(:set)
49
+ assert_equal true, `crontab -l | grep \"#{cmd}\"`.size.positive?
50
+
51
+ @himawari.new({ workdir: @@workdir }).crontab(:clear)
52
+ assert_equal true, `crontab -l | grep \"#{cmd}\"`.empty?
53
+ end
54
+ end
@@ -0,0 +1,42 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'minitest/autorun'
4
+ require_relative '../lib/himawari.rb'
5
+
6
+ class HimawariTest < Minitest::Test
7
+ # rubocop:disable Style/ClassVars
8
+ @@workdir = Pathname.new(File.expand_path(__dir__))
9
+ # rubocop:enable Style/ClassVars
10
+
11
+ def setup
12
+ @himawari = Himawari::Download
13
+ date1 = Time.parse('2019-01-01 00:00:00 +00:00')
14
+ date2 = Time.parse('2020-06-01 00:00:00 +00:00')
15
+ @timestamp = Time.at((date2.to_f - date1.to_f) * rand + date1.to_f)
16
+
17
+ # 1200secs == 20.minutes 02:40 and 14:40 are "bad" timestamps because
18
+ # we know 100% that himawari does not take photos at those times
19
+ @timestamp -= 1200 if @timestamp.min == 40 && [2, 14].include?(@timestamp.hour)
20
+ end
21
+
22
+ # def teardown
23
+ # puts 'run after each test'
24
+ # end
25
+
26
+ def capture_stdout
27
+ original_stdout = $stdout # capture previous value of $stdout
28
+ $stdout = StringIO.new # assign a string buffer to $stdout
29
+ yield # perform the body of the user code
30
+ $stdout.string # return the contents of the string buffer
31
+ ensure
32
+ $stdout = original_stdout # restore $stdout to its previous value
33
+ end
34
+
35
+ def self.cleanup
36
+ puts "Cleanup: Removing #{@@workdir}/data"
37
+ `rm -r #{@@workdir}/data*` if File.directory?("#{@@workdir}/data")
38
+ end
39
+ end
40
+
41
+ HimawariTest.cleanup
42
+ Minitest.after_run { HimawariTest.cleanup }
@@ -0,0 +1,28 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class TestNet < HimawariTest
6
+ # Do the main methods function w/o crashing?
7
+ def test_get_pic
8
+ puts "Try to get a pic from #{@timestamp}"
9
+ assert_equal true, Himawari.get_pic(datetime: @timestamp, workdir: @@workdir)
10
+ end
11
+
12
+ def test_get_pic_blacklisted_wifi
13
+ current_wifi = if Himawari::OsUtils.os == :mac
14
+ `networksetup -getairportnetwork en0`
15
+ elsif Himawari::OsUtils.os == :linux
16
+ `iwgetid -r`
17
+ end
18
+
19
+ capture_stdout do
20
+ assert_equal false, Himawari.get_pic(datetime: @timestamp, workdir: @@workdir, blacklist: [current_wifi])
21
+ end
22
+ end
23
+
24
+ def test_get_pics
25
+ puts "Try to get pics in #{@timestamp} ~ #{@timestamp + 3600 * 2}"
26
+ assert_equal true, Himawari.get_pics(from: @timestamp, to: @timestamp + 3600 * 2, workdir: @@workdir)
27
+ end
28
+ end
metadata ADDED
@@ -0,0 +1,149 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: himawari
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - engura
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-12-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: httparty
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.17.3
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.17.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 2.1.4
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 2.1.4
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 5.14.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 5.14.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.13.1
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.13.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 13.0.1
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 13.0.1
83
+ description: Makes pretty, high-res backgrounds from the real-time photos of Earth
84
+ by Himawari8,or is intended for similar personal usage. Please see the readme for
85
+ more!
86
+ email:
87
+ - engura@gmail.com
88
+ executables:
89
+ - himawari
90
+ extensions: []
91
+ extra_rdoc_files: []
92
+ files:
93
+ - ".gitignore"
94
+ - ".rubocop.yml"
95
+ - Gemfile
96
+ - Gemfile.lock
97
+ - LICENSE
98
+ - README.md
99
+ - Rakefile
100
+ - bin/setup
101
+ - doc/img/h_2020-06-01T0100.png
102
+ - doc/img/h_2020-11-29T0110.png
103
+ - doc/img/t_2020-12-01T0410-1_0.png
104
+ - exe/himawari
105
+ - himawari.gemspec
106
+ - lib/himawari.rb
107
+ - lib/himawari/base.rb
108
+ - lib/himawari/download.rb
109
+ - lib/himawari/net_utils.rb
110
+ - lib/himawari/no_image.png
111
+ - lib/himawari/os_utils.rb
112
+ - lib/himawari/process.rb
113
+ - spec/test_base.rb
114
+ - spec/test_helper.rb
115
+ - spec/test_net.rb
116
+ homepage: https://github.com/engura/himawari
117
+ licenses:
118
+ - MIT
119
+ metadata: {}
120
+ post_install_message: |-
121
+ Thanks for installing! Have fun with the amazing pics of our Home made
122
+ accessible to us for FREE by NICT. And please, don't break their website!!
123
+ rdoc_options: []
124
+ require_paths:
125
+ - lib
126
+ required_ruby_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements:
137
+ - 'Mac OSX or Linux... Windows `not supported yet` due to the lack of the following:'
138
+ - parallel, ~>20161222 (Let's download in parallel)
139
+ - ImageMagick, ~>6.9.10-23 (Specifically, the `montage` utility)
140
+ - 'common utilities: find, touch, crontab, curl, iwgetid, rm, bash'
141
+ rubyforge_project:
142
+ rubygems_version: 2.7.6.2
143
+ signing_key:
144
+ specification_version: 4
145
+ summary: Grabs latest images from the himawari8 weather satellite
146
+ test_files:
147
+ - spec/test_base.rb
148
+ - spec/test_helper.rb
149
+ - spec/test_net.rb