aipp 0.2.4 → 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +26 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +9 -0
- data/README.md +59 -7
- data/aipp.gemspec +3 -2
- data/lib/aipp.rb +2 -0
- data/lib/aipp/aip.rb +4 -12
- data/lib/aipp/downloader.rb +21 -21
- data/lib/aipp/executable.rb +4 -1
- data/lib/aipp/parser.rb +58 -5
- data/lib/aipp/regions/LF/AD-1.3.rb +27 -13
- data/lib/aipp/regions/LF/AD-1.6.rb +2 -2
- data/lib/aipp/regions/LF/AD-2.rb +30 -6
- data/lib/aipp/regions/LF/AD-3.1.rb +2 -2
- data/lib/aipp/regions/LF/ENR-2.1.rb +1 -1
- data/lib/aipp/regions/LF/ENR-4.1.rb +20 -78
- data/lib/aipp/regions/LF/ENR-4.3.rb +1 -1
- data/lib/aipp/regions/LF/ENR-5.1.rb +44 -26
- data/lib/aipp/regions/LF/ENR-5.5.rb +1 -1
- data/lib/aipp/regions/LF/helpers/{common.rb → base.rb} +2 -2
- data/lib/aipp/regions/LF/helpers/navigational_aid.rb +104 -0
- data/lib/aipp/regions/LF/helpers/{AD_radio.rb → radio_AD.rb} +24 -12
- data/lib/aipp/version.rb +1 -1
- data/lib/core_ext/object.rb +1 -1
- data/spec/fixtures/{archive.zip → source.zip} +0 -0
- data/spec/lib/aipp/airac_spec.rb +18 -18
- data/spec/lib/aipp/border_spec.rb +19 -19
- data/spec/lib/aipp/downloader_spec.rb +25 -25
- data/spec/lib/aipp/patcher_spec.rb +4 -4
- data/spec/lib/aipp/pdf_spec.rb +23 -23
- data/spec/lib/aipp/t_hash_spec.rb +6 -6
- data/spec/lib/aipp/version_spec.rb +1 -1
- data/spec/lib/core_ext/enumberable_spec.rb +15 -15
- data/spec/lib/core_ext/hash_spec.rb +4 -4
- data/spec/lib/core_ext/integer_spec.rb +2 -2
- data/spec/lib/core_ext/nil_class_spec.rb +1 -1
- data/spec/lib/core_ext/string_spec.rb +28 -28
- data/spec/spec_helper.rb +1 -0
- metadata +27 -12
- data/.travis.yml +0 -8
@@ -4,7 +4,7 @@ module AIPP
|
|
4
4
|
# Aerodromes
|
5
5
|
class AD13 < AIP
|
6
6
|
|
7
|
-
include AIPP::LF::Helpers::
|
7
|
+
include AIPP::LF::Helpers::Base
|
8
8
|
|
9
9
|
DEPENDS = %w(AD-2)
|
10
10
|
|
@@ -25,7 +25,13 @@ module AIPP
|
|
25
25
|
"SAINT CYR LA CAMPAGNE" => 'LF9013',
|
26
26
|
"SEPTFONDS" => 'LF9014',
|
27
27
|
"TALMONT VENDEE AIR PARK" => 'LF9015'
|
28
|
-
}
|
28
|
+
}.freeze
|
29
|
+
|
30
|
+
PURPOSES = {
|
31
|
+
"s" => :scheduled,
|
32
|
+
"ns" => :not_scheduled,
|
33
|
+
"p" => :private
|
34
|
+
}.freeze
|
29
35
|
|
30
36
|
def parse
|
31
37
|
ad2_exists = false
|
@@ -76,15 +82,23 @@ module AIPP
|
|
76
82
|
when /usa.+restr|priv/ then :reservation_required
|
77
83
|
end
|
78
84
|
@airport.add_usage_limitation(limitation) do |l|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
85
|
+
(%w(s ns p) & raw_conditions).each do |raw_purpose|
|
86
|
+
l.add_condition do |c|
|
87
|
+
c.realm = raw_limitation.match?(/milit/) ? :military : :civilian
|
88
|
+
if (%w(intl ntl) - raw_conditions).empty?
|
89
|
+
c.origin = :any
|
90
|
+
else
|
91
|
+
c.origin = :national if raw_conditions.include?('ntl')
|
92
|
+
c.origin = :international if raw_conditions.include?('intl')
|
93
|
+
end
|
94
|
+
if (%w(ifr vfr) - raw_conditions).empty?
|
95
|
+
c.rule = :ifr_and_vfr
|
96
|
+
else
|
97
|
+
c.rule = :ifr if raw_conditions.include?('ifr')
|
98
|
+
c.rule = :vfr if raw_conditions.include?('vfr')
|
99
|
+
end
|
100
|
+
c.purpose = PURPOSES[raw_purpose]
|
101
|
+
end
|
88
102
|
end
|
89
103
|
l.remarks = "Usage restreint (voir VAC) / restricted use (see VAC)" if raw_limitation.match?(/usa.+restr/)
|
90
104
|
l.remarks = "Propriété privée / privately owned" if raw_limitation.match?(/priv/)
|
@@ -99,7 +113,7 @@ module AIPP
|
|
99
113
|
@runway = runway # TODO: needed for now for surface composition patches to work
|
100
114
|
runway.length = AIXM.d(tds[1].css('span[id$="VAL_LEN"]').text.to_i, :m)
|
101
115
|
runway.width = AIXM.d(tds[1].css('span[id$="VAL_WID"]').text.to_i, :m)
|
102
|
-
unless (text = tds[1].css('span[id*="SURFACE"]').text).blank?
|
116
|
+
unless (text = tds[1].css('span[id*="SURFACE"]').text.compact).blank?
|
103
117
|
surface = SURFACES.metch(text)
|
104
118
|
runway.surface.composition = surface[:composition]
|
105
119
|
runway.surface.preparation = surface[:preparation]
|
@@ -109,7 +123,7 @@ module AIPP
|
|
109
123
|
values = tds[2].text.remove('°').strip.split
|
110
124
|
runway.forth.geographic_orientation = AIXM.a(values.first.to_i)
|
111
125
|
runway.back.geographic_orientation = AIXM.a(values.last.to_i)
|
112
|
-
parts = tds[3].text.strip.split(/\n\s+\n\s
|
126
|
+
parts = tds[3].text.strip.split(/\n\s+\n\s+/, 2)
|
113
127
|
runway.forth.xy = (xy_from(parts[0]) unless parts[0].blank?)
|
114
128
|
runway.back.xy = (xy_from(parts[1]) unless parts[1].blank?)
|
115
129
|
values = tds[4].text.strip.split
|
data/lib/aipp/regions/LF/AD-2.rb
CHANGED
@@ -4,8 +4,9 @@ module AIPP
|
|
4
4
|
# Airports (IFR capable) and their CTR, AD navigational aids etc
|
5
5
|
class AD2 < AIP
|
6
6
|
|
7
|
-
include AIPP::LF::Helpers::
|
8
|
-
include AIPP::LF::Helpers::
|
7
|
+
include AIPP::LF::Helpers::Base
|
8
|
+
include AIPP::LF::Helpers::NavigationalAid
|
9
|
+
include AIPP::LF::Helpers::RadioAD
|
9
10
|
using AIXM::Refinements
|
10
11
|
|
11
12
|
# Map source types to type and optional local type
|
@@ -21,7 +22,7 @@ module AIPP
|
|
21
22
|
|
22
23
|
# Airports without VFR reporting points
|
23
24
|
# TODO: designated points on map but no list (LFLD LFSN LFBS) or no AD info (LFRL)
|
24
|
-
NO_DESIGNATED_POINTS = %w(LFAB LFAC LFAV LFAY LFBK LFBN LFBX LFCC LFCI LFCK LFCY LFDH LFDJ LFDN LFEC LFEY LFGA LFHP LFHV LFHY LFJR LFJY LFLA LFLH LFLO LFLV LFLW LFMQ LFMQ LFNB LFOH LFOQ LFOU LFOV LFOZ LFPO LFQA LFQB LFQG LFQM LFRC LFRI LFRM LFRT LFRU LFSD LFSG LFSM LFLD LFSN LFBS LFRL).freeze
|
25
|
+
NO_DESIGNATED_POINTS = %w(LFAB LFAC LFAV LFAY LFBK LFBN LFBX LFCC LFCI LFCK LFCY LFDH LFDJ LFDN LFEC LFFK LFEV LFEY LFGA LFHP LFHV LFHY LFJR LFJY LFLA LFLH LFLO LFLV LFLW LFMQ LFMQ LFNB LFOH LFOQ LFOU LFOV LFOZ LFPO LFQA LFQB LFQG LFQM LFRC LFRI LFRM LFRT LFRU LFSD LFSG LFSM LFLD LFSN LFBS LFRL).freeze
|
25
26
|
|
26
27
|
# Map synonyms for +correlate+
|
27
28
|
SYNONYMS = [
|
@@ -68,8 +69,8 @@ module AIPP
|
|
68
69
|
trs = html.css('div[id*="-AD-2\.18"] tbody tr')
|
69
70
|
addresses_from(trs).each { |a| @airport.add_address(a) }
|
70
71
|
units_from(trs).each(&method(:add))
|
71
|
-
#
|
72
|
-
|
72
|
+
# Navigational aids
|
73
|
+
navigational_aids_from(html.css('div[id*="-AD-2\.19"] tbody')).each(&method(:add))
|
73
74
|
# Designated points
|
74
75
|
unless NO_VAC.include?(@id) || NO_DESIGNATED_POINTS.include?(@id)
|
75
76
|
pdf = read("VAC-#{@id}")
|
@@ -121,7 +122,7 @@ module AIPP
|
|
121
122
|
length, width = tr.css('td:nth-of-type(3)').text.strip.split('x')
|
122
123
|
runway.length = AIXM.d(length.strip.to_i, :m)
|
123
124
|
runway.width = AIXM.d(width.strip.to_i, :m)
|
124
|
-
unless (text = tr.css('td:nth-of-type(5)').text.strip.split(%r<\W+/\W+>).first).blank?
|
125
|
+
unless (text = tr.css('td:nth-of-type(5)').text.strip.split(%r<\W+/\W+>).first.compact).blank?
|
125
126
|
surface = SURFACES.metch(text)
|
126
127
|
runway.surface.composition = surface[:composition]
|
127
128
|
runway.surface.preparation = surface[:preparation]
|
@@ -204,6 +205,28 @@ module AIPP
|
|
204
205
|
end
|
205
206
|
end
|
206
207
|
|
208
|
+
def navigational_aids_from(tbody)
|
209
|
+
tbody.css('tr').to_enum.with_object([]) do |tr, array|
|
210
|
+
tds = tr.css('td')
|
211
|
+
array << navigational_aid_from(
|
212
|
+
{
|
213
|
+
name: OpenStruct.new(text: @airport.name), # simulate td
|
214
|
+
type: tds[0],
|
215
|
+
id: tds[1],
|
216
|
+
f: tds[2],
|
217
|
+
schedule: tds[3],
|
218
|
+
xy: tds[4],
|
219
|
+
z: tds[5]
|
220
|
+
},
|
221
|
+
source: source(position: tr.line),
|
222
|
+
sections: {
|
223
|
+
range: tds[6],
|
224
|
+
situation: tds[8]
|
225
|
+
}
|
226
|
+
)
|
227
|
+
end.compact
|
228
|
+
end
|
229
|
+
|
207
230
|
def designated_points_from(pdf, recursive=false)
|
208
231
|
from = (pdf.text =~ /^(.*?coordinates.*?names?)/i)
|
209
232
|
return [] if recursive && !from
|
@@ -304,6 +327,7 @@ module AIPP
|
|
304
327
|
end
|
305
328
|
|
306
329
|
patch AIXM::Feature::NavigationalAid, :remarks do |parser, object, value|
|
330
|
+
throw :abort unless object.is_a? AIXM::Feature::NavigationalAid::DesignatedPoint
|
307
331
|
airport_id, designated_point_id = object.airport.id, object.id
|
308
332
|
parser.fixture.dig('designated_points', airport_id, designated_point_id, 'remarks') || throw(:abort)
|
309
333
|
end
|
@@ -4,7 +4,7 @@ module AIPP
|
|
4
4
|
# Helipads
|
5
5
|
class AD31 < AIP
|
6
6
|
|
7
|
-
include AIPP::LF::Helpers::
|
7
|
+
include AIPP::LF::Helpers::Base
|
8
8
|
using AIXM::Refinements
|
9
9
|
|
10
10
|
DEPENDS = %w(AD-2)
|
@@ -52,7 +52,7 @@ module AIPP
|
|
52
52
|
end
|
53
53
|
end
|
54
54
|
# FATOs and helipads
|
55
|
-
text = trs[2].css('span[id*="ADHP.REVETEMENT"]').text.remove(/tlof\s*|\s*\(.*?\)/i).downcase
|
55
|
+
text = trs[2].css('span[id*="ADHP.REVETEMENT"]').text.remove(/tlof\s*|\s*\(.*?\)/i).downcase.compact
|
56
56
|
surface = text.blank? ? {} : SURFACES.metch(text)
|
57
57
|
lighting = lighting_from(trs[1].css('span[id*="ADHP.BALISAGE"]').text.cleanup)
|
58
58
|
fatos_from(trs[1].css('span[id*="ADHP.DIM_FATO"]').text).each { |f| @airport.add_fato f }
|
@@ -4,94 +4,36 @@ module AIPP
|
|
4
4
|
# ENR Navaids
|
5
5
|
class ENR41 < AIP
|
6
6
|
|
7
|
-
include AIPP::LF::Helpers::
|
7
|
+
include AIPP::LF::Helpers::Base
|
8
|
+
include AIPP::LF::Helpers::NavigationalAid
|
8
9
|
|
9
10
|
def parse
|
10
11
|
prepare(html: read).css('tbody').each do |tbody|
|
11
12
|
tbody.css('tr').to_enum.with_index(1).each do |tr, index|
|
12
13
|
tds = tr.css('td')
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
14
|
+
navigational_aid = navigational_aid_from(
|
15
|
+
{
|
16
|
+
name: tds[0],
|
17
|
+
type: tds[1],
|
18
|
+
id: tds[2],
|
19
|
+
f: tds[3],
|
20
|
+
schedule: tds[4],
|
21
|
+
xy: tds[5],
|
22
|
+
z: tds[6]
|
23
|
+
},
|
24
|
+
source: source(position: tr.line),
|
25
|
+
sections: {
|
26
|
+
range: tds[5].css('span[id*="PORTEE"], span[id*="COUVERTURE"]'),
|
27
|
+
situation: tds[7],
|
28
|
+
observations: tds[9]
|
29
|
+
}
|
30
|
+
)
|
31
|
+
add navigational_aid if navigational_aid
|
20
32
|
rescue => error
|
21
33
|
warn("error parsing navigational aid at ##{index}: #{error.message}", pry: error)
|
22
34
|
end
|
23
35
|
end
|
24
36
|
end
|
25
|
-
|
26
|
-
private
|
27
|
-
|
28
|
-
def base_from(tds)
|
29
|
-
{
|
30
|
-
organisation: organisation_lf,
|
31
|
-
id: tds[2].text.strip,
|
32
|
-
name: tds[0].text.strip,
|
33
|
-
xy: xy_from(tds[5].text),
|
34
|
-
z: z_from(tds[6].text)
|
35
|
-
}
|
36
|
-
end
|
37
|
-
|
38
|
-
def vor_from(tds)
|
39
|
-
{
|
40
|
-
type: :conventional,
|
41
|
-
f: f_from(tds[3].text),
|
42
|
-
north: :magnetic,
|
43
|
-
}
|
44
|
-
end
|
45
|
-
|
46
|
-
def dme_from(tds)
|
47
|
-
{
|
48
|
-
channel: channel_from(tds[3].text)
|
49
|
-
}
|
50
|
-
end
|
51
|
-
|
52
|
-
def ndb_from(tds)
|
53
|
-
{
|
54
|
-
type: :en_route,
|
55
|
-
f: f_from(tds[3].text)
|
56
|
-
}
|
57
|
-
end
|
58
|
-
|
59
|
-
def tacan_from(tds)
|
60
|
-
{
|
61
|
-
channel: channel_from(tds[3].text)
|
62
|
-
}
|
63
|
-
end
|
64
|
-
|
65
|
-
def z_from(text)
|
66
|
-
parts = text.strip.split(/\s+/)
|
67
|
-
AIXM.z(parts[0].to_i, :qnh) if parts[1] == 'ft'
|
68
|
-
end
|
69
|
-
|
70
|
-
def f_from(text)
|
71
|
-
parts = text.strip.split(/\s+/)
|
72
|
-
AIXM.f(parts[0].to_f, parts[1]) if parts[1] =~ /hz$/i
|
73
|
-
end
|
74
|
-
|
75
|
-
def channel_from(text)
|
76
|
-
parts = text.strip.split(/\s+/)
|
77
|
-
parts.last if parts[-2].downcase == 'ch'
|
78
|
-
end
|
79
|
-
|
80
|
-
def remarks_from(*parts)
|
81
|
-
part_titles = ['RANGE', 'SITUATION', 'OBSERVATIONS']
|
82
|
-
[].tap do |remarks|
|
83
|
-
parts.each.with_index do |part, index|
|
84
|
-
text = if index == 0
|
85
|
-
part = part.text.strip.split(/\s+/)
|
86
|
-
part.shift(2)
|
87
|
-
part.join(' ').blank_to_nil
|
88
|
-
else
|
89
|
-
part.text.strip.blank_to_nil
|
90
|
-
end
|
91
|
-
remarks << "**#{part_titles[index]}**\n#{text}" if text
|
92
|
-
end
|
93
|
-
end.join("\n\n").blank_to_nil
|
94
|
-
end
|
95
37
|
end
|
96
38
|
end
|
97
39
|
end
|
@@ -4,7 +4,17 @@ module AIPP
|
|
4
4
|
# D/P/R Zones
|
5
5
|
class ENR51 < AIP
|
6
6
|
|
7
|
-
include AIPP::LF::Helpers::
|
7
|
+
include AIPP::LF::Helpers::Base
|
8
|
+
|
9
|
+
# Map sections to whether to parse them
|
10
|
+
SECTIONS = {
|
11
|
+
'5.1-1': true,
|
12
|
+
'5.1-2': false,
|
13
|
+
'5.1-3': true,
|
14
|
+
'5.1-4': true,
|
15
|
+
'5.1-5-1': false,
|
16
|
+
'5.1-5-2': true
|
17
|
+
}
|
8
18
|
|
9
19
|
# Map source types to type and optional local type
|
10
20
|
SOURCE_TYPES = {
|
@@ -15,28 +25,37 @@ module AIPP
|
|
15
25
|
}.freeze
|
16
26
|
|
17
27
|
def parse
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
airspace
|
33
|
-
|
34
|
-
airspace.layers
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
28
|
+
skip = false
|
29
|
+
prepare(html: read).css('h4, thead ~ tbody').each do |tag|
|
30
|
+
case tag.name
|
31
|
+
when 'h4'
|
32
|
+
section = tag.text.match(/^ENR ([\d.-]+)/).captures.first
|
33
|
+
skip = !SECTIONS.fetch(section.to_sym)
|
34
|
+
verbose_info "#{skip ? :Skipping : :Parsing} section #{section}"
|
35
|
+
when 'tbody'
|
36
|
+
next if skip
|
37
|
+
airspace = nil
|
38
|
+
tag.css('tr').to_enum.with_index(1).each do |tr, index|
|
39
|
+
tds = tr.css('td')
|
40
|
+
case
|
41
|
+
when tr.attr(:id).match?(/TXT_NAME/) # airspace
|
42
|
+
airspace = airspace_from tr
|
43
|
+
when tds.count == 1 # big comment on separate row
|
44
|
+
airspace.layers.first.remarks.
|
45
|
+
concat("\n", tds.text.cleanup).
|
46
|
+
remove!(/\((\d)\)\s*\(\1\)\W*/)
|
47
|
+
else # layer
|
48
|
+
begin
|
49
|
+
tds = tr.css('td')
|
50
|
+
airspace.geometry = geometry_from tds[0].text
|
51
|
+
fail("geometry is not closed") unless airspace.geometry.closed?
|
52
|
+
airspace.layers << layer_from(tds[1].text)
|
53
|
+
airspace.layers.first.timetable = timetable_from! tds[2].text
|
54
|
+
airspace.layers.first.remarks = remarks_from(tds[2], tds[3], tds[4])
|
55
|
+
add airspace
|
56
|
+
rescue => error
|
57
|
+
warn("error parsing airspace `#{airspace.name}' at ##{index}: #{error.message}", pry: error)
|
58
|
+
end
|
40
59
|
end
|
41
60
|
end
|
42
61
|
end
|
@@ -46,11 +65,10 @@ module AIPP
|
|
46
65
|
private
|
47
66
|
|
48
67
|
def airspace_from(tr)
|
49
|
-
|
50
|
-
source_type = spans[1].text.blank_to_nil
|
68
|
+
region, source_type, name = tr.text.cleanup.gsub(/\s/, ' ').split(nil, 3)
|
51
69
|
fail "unknown type `#{source_type}'" unless SOURCE_TYPES.has_key? source_type
|
52
70
|
AIXM.airspace(
|
53
|
-
name:
|
71
|
+
name: [region, source_type, name].join(' '),
|
54
72
|
type: SOURCE_TYPES.dig(source_type, :type),
|
55
73
|
local_type: SOURCE_TYPES.dig(source_type, :local_type)
|
56
74
|
).tap do |airspace|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module AIPP
|
2
2
|
module LF
|
3
3
|
module Helpers
|
4
|
-
module
|
4
|
+
module Base
|
5
5
|
|
6
6
|
using AIXM::Refinements
|
7
7
|
|
@@ -39,7 +39,7 @@ module AIPP
|
|
39
39
|
'macadam' => { composition: :macadam },
|
40
40
|
/^bitume ?(traité|psp)?$/ => { composition: :bitumen },
|
41
41
|
'ciment' => { composition: :concrete, preparation: :paved },
|
42
|
-
/^b[
|
42
|
+
/^b[eéè]ton ?(armé|bitume|bitumineux)?$/ => { composition: :concrete, preparation: :paved },
|
43
43
|
/^béton( de)? ciment$/ => { composition: :concrete, preparation: :paved },
|
44
44
|
'béton herbe' => { composition: :concrete_and_grass },
|
45
45
|
'béton avec résine' => { composition: :concrete, preparation: :paved, remarks: 'Avec résine / with resin' },
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module AIPP
|
2
|
+
module LF
|
3
|
+
module Helpers
|
4
|
+
module NavigationalAid
|
5
|
+
|
6
|
+
def navigational_aid_from(tds, source:, sections:)
|
7
|
+
NavigationalAid.new(tds, source: source, sections: sections).build
|
8
|
+
end
|
9
|
+
|
10
|
+
class NavigationalAid
|
11
|
+
include AIPP::LF::Helpers::Base
|
12
|
+
|
13
|
+
# Map atypical navigational aid denominations
|
14
|
+
NAVIGATIONAL_AIDS = {
|
15
|
+
'vor' => 'vor',
|
16
|
+
'dme' => 'dme',
|
17
|
+
'ndb' => 'ndb',
|
18
|
+
'tacan' => 'tacan',
|
19
|
+
'l' => 'ndb' # L denominates a NDB of class locator
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
def initialize(tds, source:, sections:)
|
23
|
+
@tds, @source, @sections = tds, source, sections
|
24
|
+
end
|
25
|
+
|
26
|
+
def build
|
27
|
+
master, slave = @tds[:type].text.strip.gsub(/[^\w-]/, '').downcase.split('-')
|
28
|
+
master = NAVIGATIONAL_AIDS.fetch(master, master)
|
29
|
+
slave = NAVIGATIONAL_AIDS.fetch(slave, slave)
|
30
|
+
return nil unless NAVIGATIONAL_AIDS.keys.include? master
|
31
|
+
AIXM.send(master, common.merge(send(master))).tap do |navigational_aid|
|
32
|
+
navigational_aid.source = @source
|
33
|
+
navigational_aid.remarks = remarks
|
34
|
+
navigational_aid.timetable = timetable_from!(@tds[:schedule].text)
|
35
|
+
navigational_aid.send("associate_#{slave}", channel: channel_from(@tds[:f].text)) if slave
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def common
|
42
|
+
{
|
43
|
+
organisation: organisation_lf,
|
44
|
+
id: @tds[:id].text.strip,
|
45
|
+
name: @tds[:name].text.strip,
|
46
|
+
xy: xy_from(@tds[:xy].text),
|
47
|
+
z: z_from(@tds[:z].text)
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def vor
|
52
|
+
{
|
53
|
+
type: :conventional,
|
54
|
+
f: f_from(@tds[:f].text),
|
55
|
+
north: :magnetic,
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
def dme
|
60
|
+
{
|
61
|
+
channel: channel_from(@tds[:f].text)
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
def ndb
|
66
|
+
{
|
67
|
+
type: @tds[:type].text.strip == 'L' ? :locator : :en_route,
|
68
|
+
f: f_from(@tds[:f].text)
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
def tacan
|
73
|
+
{
|
74
|
+
channel: channel_from(@tds[:f].text)
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
def remarks
|
79
|
+
@sections.map do |section, td|
|
80
|
+
if text = td.text.strip.blank_to_nil
|
81
|
+
"**#{section.upcase}**\n#{text}"
|
82
|
+
end
|
83
|
+
end.compact.join("\n\n").blank_to_nil
|
84
|
+
end
|
85
|
+
|
86
|
+
def z_from(text)
|
87
|
+
parts = text.strip.split(/\s+/)
|
88
|
+
AIXM.z(parts[0].to_i, :qnh) if parts[1] == 'ft'
|
89
|
+
end
|
90
|
+
|
91
|
+
def f_from(text)
|
92
|
+
parts = text.strip.split(/\s+/)
|
93
|
+
AIXM.f(parts[0].to_f, parts[1]) if parts[1] =~ /hz$/i
|
94
|
+
end
|
95
|
+
|
96
|
+
def channel_from(text)
|
97
|
+
parts = text.strip.split(/\s+/)
|
98
|
+
parts.last if parts[-2].downcase == 'ch'
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|