vfr_utils 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ad057bae66be0a8f641c375a0ed7f0c4e6413b37
4
+ data.tar.gz: 2592fd98b47284abb45bfee57bf003bc69c832fa
5
+ SHA512:
6
+ metadata.gz: 7997372764a5dbb062fe6450860fb33fbfeceaeffea6ee34c8f9ef7d35b16edc9987109a92d981350e356847c67065dbf3b2e45480e2a6dac70c53bed6ecfd71
7
+ data.tar.gz: 2142d275478481899105ec253def50ffbb425c3650042a1776cfab9df6d61da356b91c725a9136ec4451da05e89cd3521be56c6a5c18fa96bfdb28be90b401ad
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/vfr_utils/cli'
4
+
5
+ VfrUtils::CLI.run ARGV
@@ -0,0 +1,23 @@
1
+ require_relative 'vfr_utils/version'
2
+ require_relative 'vfr_utils/configuration'
3
+ require_relative 'vfr_utils/master_configuration'
4
+ require_relative 'vfr_utils/cache'
5
+ require_relative 'vfr_utils/base_service'
6
+ require_relative 'vfr_utils/taf'
7
+ require_relative 'vfr_utils/notam'
8
+ require_relative 'vfr_utils/metar'
9
+
10
+ module VfrUtils
11
+ class << self
12
+ attr_accessor :master_configuration
13
+ end
14
+
15
+ self.master_configuration ||= MasterConfiguration.new
16
+
17
+ class << self
18
+ def configure
19
+ self.master_configuration ||= MasterConfiguration.new
20
+ yield master_configuration
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,38 @@
1
+ require_relative '../vfr_utils'
2
+ require 'faraday'
3
+ require 'nokogiri'
4
+ require 'date'
5
+
6
+ module VfrUtils
7
+ class BaseService
8
+
9
+ class << self
10
+
11
+ def inherited(child_class)
12
+ def child_class.cache
13
+ unless @cache
14
+ config_key = self.name.split('::').last.downcase.to_sym
15
+ @cache = Cache.new(VfrUtils.master_configuration.send(config_key))
16
+ end
17
+ @cache
18
+ end
19
+ end
20
+
21
+ def get(icao_codes)
22
+ icao_codes = [*icao_codes]
23
+ icao_codes.map!(&:upcase)
24
+
25
+ ret = {}
26
+ icao_codes.each do |icao_code|
27
+ ret[icao_code] = get_one(icao_code)
28
+ end
29
+ ret
30
+ end
31
+
32
+ def get_one(icao_code)
33
+ raise "Implement me!"
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,33 @@
1
+ module VfrUtils
2
+ class Cache
3
+
4
+ def initialize(config)
5
+ @config = config
6
+ end
7
+
8
+ def get(k, &blk)
9
+ return get_files(k, &blk) if config.cache_backend == :files
10
+ raise "Unsupported cache backend: #{config.cache_backend}"
11
+ end
12
+
13
+ private
14
+
15
+ def config
16
+ @config
17
+ end
18
+
19
+ def get_files(k)
20
+ cache_file_path = "#{config.cache_directory}/#{k}.tmp"
21
+ if File.exists?(cache_file_path)
22
+ if Time.now.to_i - File.ctime(cache_file_path).to_i > config.cache_lifetime
23
+ FileUtils.rm_rf(cache_file_path)
24
+ else
25
+ return Marshal.load(File.read(cache_file_path))
26
+ end
27
+ end
28
+ v = yield
29
+ File.write(cache_file_path, Marshal.dump(v))
30
+ v
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,49 @@
1
+ require_relative '../vfr_utils'
2
+
3
+ module VfrUtils
4
+ module CLI
5
+
6
+ ALLOWED_ACTIONS = [
7
+ 'metar',
8
+ 'taf',
9
+ 'notam'
10
+ ]
11
+
12
+ def self.run(argv)
13
+ return usage if argv.empty?
14
+ action = argv[0].downcase
15
+ params = argv[1..-1]
16
+ return usage unless ALLOWED_ACTIONS.include? action
17
+ send(action.to_sym, params)
18
+ 0
19
+ end
20
+
21
+ def self.usage
22
+ STDERR.puts "Version: #{VfrUtils::VERSION} Usage: vfr-utils <action> <params>"
23
+ STDERR.puts ""
24
+ STDERR.puts "Allowed actions:"
25
+ ALLOWED_ACTIONS.each { |action| STDERR.puts " #{action} <space delimited list of ICAO codes>" }
26
+ STDERR.puts ""
27
+ 1
28
+ end
29
+
30
+ def self.notam(icao_codes)
31
+ require_relative 'notam'
32
+ require_relative 'formatter/notam'
33
+ VfrUtils::Formatter::NOTAM.pretty_display(VfrUtils::NOTAM.get(icao_codes))
34
+ end
35
+
36
+ def self.metar(icao_codes)
37
+ require_relative 'metar'
38
+ require_relative 'formatter/weather'
39
+ VfrUtils::Formatter::Weather.pretty_display(VfrUtils::METAR.get(icao_codes))
40
+ end
41
+
42
+ def self.taf(icao_codes)
43
+ require_relative 'taf'
44
+ require_relative 'formatter/weather'
45
+ VfrUtils::Formatter::Weather.pretty_display(VfrUtils::TAF.get(icao_codes))
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,29 @@
1
+ require 'tmpdir'
2
+
3
+ module VfrUtils
4
+ class Configuration
5
+ DEFAULTS = {
6
+ cache_lifetime: 900, # 900 secs = 15 minutes
7
+ cache_directory: "#{Dir.tmpdir}/vfr-utils",
8
+ cache_backend: :files,
9
+ redis_url: nil,
10
+ }
11
+ attr_accessor :cache_lifetime
12
+ attr_accessor :cache_directory
13
+ attr_accessor :cache_backend
14
+ attr_accessor :redis_url
15
+
16
+ def initialize(defaults=DEFAULTS)
17
+ @cache_lifetime = defaults[:cache_lifetime] || DEFAULTS[:cache_lifetime]
18
+ @cache_directory = defaults[:cache_directory] || DEFAULTS[:cache_directory]
19
+ @cache_backend = defaults[:cache_backend] || DEFAULTS[:cache_backend]
20
+ @redis_url = defaults[:redis_url] || DEFAULTS[:redis_url]
21
+ apply
22
+ end
23
+
24
+ def apply
25
+ FileUtils.mkdir_p @cache_directory if @cache_backend == :files
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,25 @@
1
+ require 'pp'
2
+
3
+ module VfrUtils
4
+ module Formatter
5
+ module NOTAM
6
+ def self.pretty_display(notams)
7
+ notams.each_pair do |icao_code, notams_for_aerodrome|
8
+ puts "=================================================="
9
+ puts "===================== #{icao_code} ====================="
10
+ notams_for_aerodrome.each do |notam_data|
11
+ puts "=================================================="
12
+ puts "Signature: #{notam_data[:signature]}" if notam_data[:signature]
13
+ puts "Valid from: #{notam_data[:valid_from].strftime("%F %H:%M (%A)")}" if notam_data[:valid_from]
14
+ puts "Valid to: #{notam_data[:valid_to].strftime("%F %H:%M (%A)")}" if notam_data[:valid_to]
15
+ puts ""
16
+ puts notam_data[:message]
17
+ puts ""
18
+ puts "Created at #{notam_data[:created_at].strftime("%F %H:%M")} by #{notam_data[:source]}" if notam_data[:created_at]
19
+ puts "" if notam_data[:created_at]
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,17 @@
1
+ require 'pp'
2
+
3
+ module VfrUtils
4
+ module Formatter
5
+ module Weather
6
+ def self.pretty_display(data)
7
+ data.each_pair do |icao_code, data_for_aerodrome|
8
+ puts ""
9
+ #puts "=================================================="
10
+ puts "===================== #{icao_code} ====================="
11
+ puts data_for_aerodrome[:data]
12
+ puts "=================================================="
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,30 @@
1
+ module VfrUtils
2
+ class MasterConfiguration
3
+
4
+ def initialize(defaults={})
5
+ @defaults = defaults
6
+ @modules = {}
7
+ end
8
+
9
+ def method_missing(method_name, *args, &blk)
10
+ unless @modules[method_name]
11
+ @modules[method_name] = Configuration.new @defaults
12
+ self.class.class_exec do
13
+ define_method(method_name) { @modules[method_name] }
14
+ end
15
+ send(method_name, *args, &blk)
16
+ end
17
+ end
18
+
19
+ def global(values)
20
+ @defaults.merge! values
21
+ @modules.each_value do |m|
22
+ values.each_pair do |k,v|
23
+ key = "#{k}=".to_sym
24
+ m.send(key, v)
25
+ end
26
+ end
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,47 @@
1
+ require_relative '../vfr_utils'
2
+ require 'faraday'
3
+ require 'nokogiri'
4
+ require 'date'
5
+
6
+ module VfrUtils
7
+ class METAR < BaseService
8
+
9
+ URL='http://aviationweather.gov/metar/data'
10
+
11
+ class << self
12
+
13
+ def get_one(icao_code)
14
+ return cache.get("metar_#{icao_code}") do
15
+ html = fetch_from_web(icao_code)
16
+ parse(html)
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def fetch_from_web(icao_code)
23
+ request_params = {
24
+ ids: icao_code,
25
+ format: 'raw',
26
+ hours: 0,
27
+ taf: 'off',
28
+ layout: 'off',
29
+ date: 0
30
+ }
31
+ response = Faraday.get URL, request_params
32
+ response.body
33
+ end
34
+
35
+ def parse(html)
36
+ html_doc = Nokogiri::HTML(html)
37
+ begin
38
+ data = html_doc.xpath("//div[@id='awc_main_content']//p").first.next_sibling.next_sibling.next_sibling.text
39
+ rescue
40
+ data = nil
41
+ end
42
+ { data: data }
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,88 @@
1
+ require_relative '../vfr_utils'
2
+ require 'faraday'
3
+ require 'nokogiri'
4
+ require 'date'
5
+
6
+ module VfrUtils
7
+ class NOTAM < BaseService
8
+
9
+ URL='https://www.notams.faa.gov/dinsQueryWeb/queryRetrievalMapAction.do'
10
+
11
+ class << self
12
+
13
+ def get_one(icao_code)
14
+ ret = []
15
+ return cache.get("notam_#{icao_code}") do
16
+ html = fetch_from_web(icao_code)
17
+ html_doc = Nokogiri::HTML(html)
18
+ html_doc.xpath("//td[@class='textBlack12']/pre").map(&:text).each do |raw_notam|
19
+ ret << parse(raw_notam)
20
+ end
21
+ ret
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def fetch_from_web(icao_code)
28
+ request_params = {
29
+ reportType: 'Raw',
30
+ retrieveLocId: icao_code,
31
+ actionType: 'notamRetrievalByICAOs',
32
+ submit: 'View NOTAMs'
33
+ }
34
+ response = Faraday.post URL, request_params
35
+ response.body
36
+ end
37
+
38
+ def parse(raw_notam)
39
+ ret = {
40
+ raw: raw_notam,
41
+ header: nil,
42
+ signature: nil,
43
+ icao_code: nil,
44
+ valid_from: nil,
45
+ valid_to: nil,
46
+ created_at: nil,
47
+ source: nil,
48
+ message: nil
49
+ }
50
+
51
+ lines = raw_notam.split("\n")
52
+ ret[:header] = lines[0]
53
+ ret[:created_at] = lines[-2].split(':', 2).last.strip # "17 Nov 2016 10:03:00"
54
+ ret[:created_at] = DateTime.parse(ret[:created_at])
55
+ ret[:source] = lines[-1].split(':', 2).last.strip
56
+ body = lines[1..-3].join(' ').gsub("\n", ' ')
57
+
58
+ if matches = body.match(/\AQ\)\s+(?<signature>.+)\s+A\)\s+(?<icao_code>.+)\s+B\)\s+(?<valid_from>.+)\s+C\)\s+(?<valid_to>.+)\s+E\)\s+(?<message>.+)\s*\z/)
59
+ ret[:signature] = matches[:signature]
60
+ ret[:icao_code] = matches[:icao_code]
61
+ ret[:valid_from] = parse_validity_time(matches[:valid_from])
62
+ ret[:valid_to] = parse_validity_time(matches[:valid_to])
63
+ ret[:message] = matches[:message]
64
+ else
65
+ body = ret[:header]+body
66
+ if matches = body.match(/\D(?<valid_from>\d{10})-(?<valid_to>\d{10}([A-Z]{3-5})?|PERM)/)
67
+ ret[:valid_from] = parse_validity_time(matches[:valid_from])
68
+ ret[:valid_to] = parse_validity_time(matches[:valid_to])
69
+ body.gsub!(matches[0], '')
70
+ end
71
+ ret[:message] = body
72
+ end
73
+ ret
74
+ end
75
+
76
+ def parse_validity_time(text)
77
+ return DateTime.new(2100) if text == 'PERM'
78
+ year = text[0..1].to_i+2000
79
+ month = text[2..3].to_i
80
+ day = text[4..5].to_i
81
+ hour = text[6..7].to_i
82
+ minute = text[8..9].to_i
83
+ zone = text[10..15]
84
+ DateTime.parse("#{year}-#{month}-#{day} #{hour}:#{minute}:00#{zone}")
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,46 @@
1
+ require_relative '../vfr_utils'
2
+ require 'faraday'
3
+ require 'nokogiri'
4
+ require 'date'
5
+
6
+ module VfrUtils
7
+ class TAF < BaseService
8
+
9
+ URL='http://aviationweather.gov/taf/data'
10
+
11
+ class << self
12
+
13
+ def get_one(icao_code)
14
+ return cache.get("taf_#{icao_code}") do
15
+ html = fetch_from_web(icao_code)
16
+ parse(html)
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def fetch_from_web(icao_code)
23
+ # http://aviationweather.gov/taf/data?ids=EPWR&format=raw&metars=off&layout=off
24
+ request_params = {
25
+ ids: icao_code,
26
+ format: 'raw',
27
+ metars: 'off',
28
+ layout: 'off',
29
+ }
30
+ response = Faraday.get URL, request_params
31
+ response.body
32
+ end
33
+
34
+ def parse(html)
35
+ html_doc = Nokogiri::HTML(html)
36
+ begin
37
+ data = html_doc.xpath("//code").first.inner_html.gsub('<br>', "\n")
38
+ rescue
39
+ data = nil
40
+ end
41
+ { data: data }
42
+ end
43
+
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,3 @@
1
+ module VfrUtils
2
+ VERSION='0.0.3'
3
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vfr_utils
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Bartek Wilczek
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-12-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nokogiri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.9'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.9'
41
+ description: This gem provides set of classes and a CLI for obtaining information
42
+ like NOTAM, METAR and TAF
43
+ email:
44
+ - bwilczek@gmail.com
45
+ executables:
46
+ - vfr_utils
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - bin/vfr_utils
51
+ - lib/vfr_utils.rb
52
+ - lib/vfr_utils/base_service.rb
53
+ - lib/vfr_utils/cache.rb
54
+ - lib/vfr_utils/cli.rb
55
+ - lib/vfr_utils/configuration.rb
56
+ - lib/vfr_utils/formatter/notam.rb
57
+ - lib/vfr_utils/formatter/weather.rb
58
+ - lib/vfr_utils/master_configuration.rb
59
+ - lib/vfr_utils/metar.rb
60
+ - lib/vfr_utils/notam.rb
61
+ - lib/vfr_utils/taf.rb
62
+ - lib/vfr_utils/version.rb
63
+ homepage: https://github.com/bwilczek/vfr-utils
64
+ licenses:
65
+ - MIT
66
+ metadata: {}
67
+ post_install_message:
68
+ rdoc_options: []
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.0'
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ requirements: []
82
+ rubyforge_project:
83
+ rubygems_version: 2.5.1
84
+ signing_key:
85
+ specification_version: 4
86
+ summary: Basic toolkit for obtaining aeronautical data
87
+ test_files: []