vfr_utils 0.0.3
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.
- checksums.yaml +7 -0
- data/bin/vfr_utils +5 -0
- data/lib/vfr_utils.rb +23 -0
- data/lib/vfr_utils/base_service.rb +38 -0
- data/lib/vfr_utils/cache.rb +33 -0
- data/lib/vfr_utils/cli.rb +49 -0
- data/lib/vfr_utils/configuration.rb +29 -0
- data/lib/vfr_utils/formatter/notam.rb +25 -0
- data/lib/vfr_utils/formatter/weather.rb +17 -0
- data/lib/vfr_utils/master_configuration.rb +30 -0
- data/lib/vfr_utils/metar.rb +47 -0
- data/lib/vfr_utils/notam.rb +88 -0
- data/lib/vfr_utils/taf.rb +46 -0
- data/lib/vfr_utils/version.rb +3 -0
- metadata +87 -0
checksums.yaml
ADDED
@@ -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
|
data/bin/vfr_utils
ADDED
data/lib/vfr_utils.rb
ADDED
@@ -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
|
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: []
|