aipp 0.2.6 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +21 -0
- data/README.md +147 -91
- data/exe/aip2aixm +2 -2
- data/exe/aip2ofmx +2 -2
- data/lib/aipp/aip.rb +96 -11
- data/lib/aipp/border.rb +77 -46
- data/lib/aipp/debugger.rb +101 -0
- data/lib/aipp/downloader.rb +18 -5
- data/lib/aipp/executable.rb +33 -20
- data/lib/aipp/parser.rb +42 -37
- data/lib/aipp/patcher.rb +5 -2
- data/lib/aipp/regions/LF/README.md +49 -0
- data/lib/aipp/regions/LF/aerodromes.rb +223 -0
- data/lib/aipp/regions/LF/d_p_r_airspaces.rb +56 -0
- data/lib/aipp/regions/LF/dangerous_activities.rb +49 -0
- data/lib/aipp/regions/LF/designated_points.rb +47 -0
- data/lib/aipp/regions/LF/fixtures/aerodromes.yml +608 -0
- data/lib/aipp/regions/LF/helipads.rb +122 -0
- data/lib/aipp/regions/LF/helpers/base.rb +167 -174
- data/lib/aipp/regions/LF/helpers/surface.rb +49 -0
- data/lib/aipp/regions/LF/helpers/usage_limitation.rb +20 -0
- data/lib/aipp/regions/LF/navigational_aids.rb +85 -0
- data/lib/aipp/regions/LF/obstacles.rb +153 -0
- data/lib/aipp/regions/LF/serviced_airspaces.rb +70 -0
- data/lib/aipp/regions/LF/services.rb +172 -0
- data/lib/aipp/t_hash.rb +3 -4
- data/lib/aipp/version.rb +1 -1
- data/lib/aipp.rb +7 -5
- data/lib/core_ext/enumerable.rb +2 -2
- data/lib/core_ext/hash.rb +21 -5
- data/lib/core_ext/nokogiri.rb +54 -0
- data/lib/core_ext/string.rb +32 -65
- data.tar.gz.sig +0 -0
- metadata +70 -81
- metadata.gz.sig +0 -0
- data/lib/aipp/airac.rb +0 -55
- data/lib/aipp/regions/LF/AD-1.3.rb +0 -177
- data/lib/aipp/regions/LF/AD-1.6.rb +0 -33
- data/lib/aipp/regions/LF/AD-2.rb +0 -344
- data/lib/aipp/regions/LF/AD-3.1.rb +0 -185
- data/lib/aipp/regions/LF/ENR-2.1.rb +0 -167
- data/lib/aipp/regions/LF/ENR-4.1.rb +0 -41
- data/lib/aipp/regions/LF/ENR-4.3.rb +0 -27
- data/lib/aipp/regions/LF/ENR-5.1.rb +0 -106
- data/lib/aipp/regions/LF/ENR-5.4.rb +0 -90
- data/lib/aipp/regions/LF/ENR-5.5.rb +0 -55
- data/lib/aipp/regions/LF/fixtures/AD-1.3.yml +0 -511
- data/lib/aipp/regions/LF/fixtures/AD-2.yml +0 -185
- data/lib/aipp/regions/LF/fixtures/AD-3.1.yml +0 -10
- data/lib/aipp/regions/LF/helpers/URL.rb +0 -26
- data/lib/aipp/regions/LF/helpers/navigational_aid.rb +0 -104
- data/lib/aipp/regions/LF/helpers/radio_AD.rb +0 -110
- data/lib/core_ext/object.rb +0 -43
@@ -1,167 +0,0 @@
|
|
1
|
-
module AIPP
|
2
|
-
module LF
|
3
|
-
|
4
|
-
# FIR, TMA etc
|
5
|
-
class ENR21 < AIP
|
6
|
-
|
7
|
-
include AIPP::LF::Helpers::Base
|
8
|
-
|
9
|
-
# Airspaces to be ignored
|
10
|
-
NAME_BLACKLIST_RE = /deleg/i.freeze
|
11
|
-
|
12
|
-
# Map source types to type and optional local type
|
13
|
-
SOURCE_TYPES = {
|
14
|
-
'FIR' => { type: 'FIR' },
|
15
|
-
'UIR' => { type: 'UIR' },
|
16
|
-
'UTA' => { type: 'UTA' },
|
17
|
-
'CTA' => { type: 'CTA' },
|
18
|
-
'LTA' => { type: 'CTA', local_type: 'LTA' },
|
19
|
-
'TMA' => { type: 'TMA' },
|
20
|
-
'SIV' => { type: 'SECTOR', local_type: 'SIV' } # providing FIS
|
21
|
-
}.freeze
|
22
|
-
|
23
|
-
# Map airspace "<type> <name>" to location indicator
|
24
|
-
LOCATION_INDICATORS = {
|
25
|
-
'FIR BORDEAUX' => 'LFBB',
|
26
|
-
'FIR BREST' => 'LFRR',
|
27
|
-
'FIR MARSEILLE' => 'LFMM',
|
28
|
-
'FIR PARIS' => 'LFFF',
|
29
|
-
'FIR REIMS' => 'LFRR'
|
30
|
-
}.freeze
|
31
|
-
|
32
|
-
# Fix incomplete SIV service columns
|
33
|
-
SERVICE_FIXES = {
|
34
|
-
"IROISE INFO 135.825 / 119.575 (1)" => "APP IROISE\nIROISE INFO 135.825 / 119.575 (1)",
|
35
|
-
"APP TOULOUSE\nTOULOUSE INFO" => "APP TOULOUSE\nTOULOUSE INFO 121.250"
|
36
|
-
}.freeze
|
37
|
-
|
38
|
-
def parse
|
39
|
-
prepare(html: read).css('tbody').each do |tbody|
|
40
|
-
airspace = nil
|
41
|
-
tbody.css('tr').to_enum.with_index(1).each do |tr, index|
|
42
|
-
if tr.attr(:id).match?(/--TXT_NAME/)
|
43
|
-
if airspace
|
44
|
-
if airspace.name.match? NAME_BLACKLIST_RE
|
45
|
-
verbose_info "Ignoring #{airspace.type} #{airspace.name}" unless airspace.type == :terminal_control_area
|
46
|
-
else
|
47
|
-
add airspace
|
48
|
-
end
|
49
|
-
end
|
50
|
-
airspace = airspace_from tr.css(:td).first
|
51
|
-
verbose_info "Parsing #{airspace.type} #{airspace.name}" unless airspace.type == :terminal_control_area
|
52
|
-
next
|
53
|
-
end
|
54
|
-
begin
|
55
|
-
tds = tr.css('td')
|
56
|
-
if airspace.type == :terminal_control_area && tds[0].text.blank_to_nil
|
57
|
-
if airspace.layers.any?
|
58
|
-
if airspace.name.match? NAME_BLACKLIST_RE
|
59
|
-
verbose_info "Ignoring #{airspace.type} #{airspace.name}"
|
60
|
-
else
|
61
|
-
add airspace
|
62
|
-
end
|
63
|
-
end
|
64
|
-
airspace = airspace_from tds[0]
|
65
|
-
verbose_info "Parsing #{airspace.type} #{airspace.name}"
|
66
|
-
end
|
67
|
-
if airspace
|
68
|
-
remarks = tds[-1].text
|
69
|
-
if tds[0].text.blank_to_nil
|
70
|
-
airspace.geometry = geometry_from tds[0].text
|
71
|
-
fail("geometry is not closed") unless airspace.geometry.closed?
|
72
|
-
end
|
73
|
-
layer = layer_from(tds[-3].text)
|
74
|
-
layer.class = class_from(tds[1].text) if tds.count == 5
|
75
|
-
layer.location_indicator = LOCATION_INDICATORS.fetch("#{airspace.type} #{airspace.name}", nil)
|
76
|
-
if airspace.local_type == 'SIV' # services parsed for SIV only
|
77
|
-
layer.add_services services_from(tds[-2], remarks)
|
78
|
-
end
|
79
|
-
layer.timetable = timetable_from! remarks
|
80
|
-
layer.remarks = remarks_from remarks
|
81
|
-
airspace.add_layer layer
|
82
|
-
end
|
83
|
-
rescue => error
|
84
|
-
warn("error parsing #{airspace.type} `#{airspace.name}' at ##{index}: #{error.message}", pry: error)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
add airspace if airspace
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
private
|
92
|
-
|
93
|
-
def airspace_from(td)
|
94
|
-
spans = td.children.split { _1.name == 'br' }.first.css(:span).drop_while { _1.text.match? '\s' }
|
95
|
-
source_type = spans[0].text.blank_to_nil
|
96
|
-
fail "unknown type `#{source_type}'" unless SOURCE_TYPES.has_key? source_type
|
97
|
-
AIXM.airspace(
|
98
|
-
name: anglicise(name: spans[1..-1].join(' ')),
|
99
|
-
type: SOURCE_TYPES.dig(source_type, :type),
|
100
|
-
local_type: SOURCE_TYPES.dig(source_type, :local_type)
|
101
|
-
).tap do |airspace|
|
102
|
-
airspace.source = source(position: td.line)
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
def class_from(text)
|
107
|
-
text.strip
|
108
|
-
end
|
109
|
-
|
110
|
-
def services_from(td, remarks)
|
111
|
-
text = td.text.cleanup
|
112
|
-
text = SERVICE_FIXES.fetch(text, text) # fix incomplete service columns
|
113
|
-
text.gsub!(/(info|app)\s+([\d.]{3,})/i, "\\1\n\\2") # put frequencies on separate line
|
114
|
-
text.gsub!(/(\d)\s*\/\s*(\d)/, "\\1\n\\2") # split frequencies onto separate lines
|
115
|
-
units, services = [], []
|
116
|
-
text.split("\n").each do |line|
|
117
|
-
case line
|
118
|
-
when /^(.+(?:info|app))$/i # service
|
119
|
-
callsign = $1
|
120
|
-
service = AIXM.service(
|
121
|
-
# TODO: add source as soon as it is supported by components
|
122
|
-
# source: source(position: td.line),
|
123
|
-
type: :flight_information_service
|
124
|
-
).tap do |service|
|
125
|
-
service.timetable = AIXM::H24 if remarks.match? /h\s?24/i
|
126
|
-
end
|
127
|
-
services << [service, callsign]
|
128
|
-
units.shift.add_service service
|
129
|
-
when /^(.*?)(\d{3}[.\d]*)(.*)$/ # frequency
|
130
|
-
label, freq, footnote = $1, $2, $3
|
131
|
-
service, callsign = services.last
|
132
|
-
frequency = AIXM.frequency(
|
133
|
-
transmission_f: AIXM.f(freq.to_f, :mhz),
|
134
|
-
callsigns: { en: callsign, fr: callsign }
|
135
|
-
).tap do |frequency|
|
136
|
-
frequency.type = :standard
|
137
|
-
frequency.timetable = AIXM::H24 if remarks.match? /h\s?24/i
|
138
|
-
frequency.remarks = [
|
139
|
-
(remarks.extract(/#{Regexp.escape(footnote.strip)}\s*([^\n]+)/).join(' / ') unless footnote.empty?),
|
140
|
-
label.strip
|
141
|
-
].map(&:blank_to_nil).compact.join(' / ').blank_to_nil
|
142
|
-
end
|
143
|
-
service.add_frequency frequency
|
144
|
-
when /.*(?<!info|app|\d{3}|\))$/i # unit
|
145
|
-
unit = AIXM.unit(
|
146
|
-
source: source(position: td.line),
|
147
|
-
organisation: organisation_lf, # TODO: not yet implemented
|
148
|
-
name: line,
|
149
|
-
type: :flight_information_centre,
|
150
|
-
class: :icao
|
151
|
-
)
|
152
|
-
units << ((u = find(unit).first) ? (unit = u) : (add unit))
|
153
|
-
else
|
154
|
-
fail("cannot parse `#{text}'")
|
155
|
-
end
|
156
|
-
end
|
157
|
-
services = services.map(&:first)
|
158
|
-
fail("at least one service has no frequency") if services.any? { _1.frequencies.none? }
|
159
|
-
services
|
160
|
-
end
|
161
|
-
|
162
|
-
def remarks_from(text)
|
163
|
-
text.strip.gsub(/(\s)\s+/, '\1').blank_to_nil
|
164
|
-
end
|
165
|
-
end
|
166
|
-
end
|
167
|
-
end
|
@@ -1,41 +0,0 @@
|
|
1
|
-
module AIPP
|
2
|
-
module LF
|
3
|
-
|
4
|
-
# ENR Navaids
|
5
|
-
class ENR41 < AIP
|
6
|
-
|
7
|
-
include AIPP::LF::Helpers::Base
|
8
|
-
include AIPP::LF::Helpers::NavigationalAid
|
9
|
-
|
10
|
-
def parse
|
11
|
-
prepare(html: read).css('tbody').each do |tbody|
|
12
|
-
tbody.css('tr').to_enum.with_index(1).each do |tr, index|
|
13
|
-
tds = tr.css('td')
|
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
|
-
if navigational_aid && aixm.features.find_by(navigational_aid.class, id: navigational_aid.id, xy: navigational_aid.xy).none?
|
32
|
-
add navigational_aid
|
33
|
-
end
|
34
|
-
rescue => error
|
35
|
-
warn("error parsing navigational aid at ##{index}: #{error.message}", pry: error)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
@@ -1,27 +0,0 @@
|
|
1
|
-
module AIPP
|
2
|
-
module LF
|
3
|
-
|
4
|
-
# Designated Points
|
5
|
-
class ENR43 < AIP
|
6
|
-
|
7
|
-
include AIPP::LF::Helpers::Base
|
8
|
-
|
9
|
-
def parse
|
10
|
-
prepare(html: read).css('tbody').each do |tbody|
|
11
|
-
tbody.css('tr').to_enum.with_index(1).each do |tr, index|
|
12
|
-
tds = tr.css('td')
|
13
|
-
add AIXM.designated_point(
|
14
|
-
source: source(position: tr.line),
|
15
|
-
type: :icao,
|
16
|
-
id: tds[0].text.strip,
|
17
|
-
xy: xy_from(tds[1].text)
|
18
|
-
)
|
19
|
-
rescue => error
|
20
|
-
warn("error parsing designated point at ##{index}: #{error.message}", pry: error)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
@@ -1,106 +0,0 @@
|
|
1
|
-
module AIPP
|
2
|
-
module LF
|
3
|
-
|
4
|
-
# D/P/R Zones
|
5
|
-
class ENR51 < AIP
|
6
|
-
|
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
|
-
}
|
18
|
-
|
19
|
-
# Map source types to type and optional local type
|
20
|
-
SOURCE_TYPES = {
|
21
|
-
'D' => { type: 'D' },
|
22
|
-
'P' => { type: 'P' },
|
23
|
-
'R' => { type: 'R' },
|
24
|
-
'ZIT' => { type: 'P', local_type: 'ZIT' }
|
25
|
-
}.freeze
|
26
|
-
|
27
|
-
# Radius to use for zones consisting of one point only
|
28
|
-
POINT_RADIUS = AIXM.d(1, :km).freeze
|
29
|
-
|
30
|
-
def parse
|
31
|
-
skip = false
|
32
|
-
prepare(html: read).css('h4, thead ~ tbody').each do |tag|
|
33
|
-
case tag.name
|
34
|
-
when 'h4'
|
35
|
-
section = tag.text.match(/^ENR ([\d.-]+)/).captures.first
|
36
|
-
skip = !SECTIONS.fetch(section.to_sym)
|
37
|
-
verbose_info "#{skip ? :Skipping : :Parsing} section #{section}"
|
38
|
-
when 'tbody'
|
39
|
-
next if skip
|
40
|
-
airspace = nil
|
41
|
-
tag.css('tr').to_enum.with_index(1).each do |tr, index|
|
42
|
-
tds = tr.css('td')
|
43
|
-
case
|
44
|
-
when tr.attr(:id).match?(/TXT_NAME/) # airspace
|
45
|
-
airspace = airspace_from tr
|
46
|
-
when tds.count == 1 # big comment on separate row
|
47
|
-
airspace.layers.first.remarks.
|
48
|
-
concat("\n", tds.text.cleanup).
|
49
|
-
remove!(/\((\d)\)\s*\(\1\)\W*/)
|
50
|
-
else # layer
|
51
|
-
begin
|
52
|
-
tds = tr.css('td')
|
53
|
-
airspace.geometry = geometry_from tds[0].text
|
54
|
-
if airspace.geometry.point? # convert point to circle
|
55
|
-
airspace.geometry = AIXM.geometry(
|
56
|
-
AIXM.circle(
|
57
|
-
center_xy: airspace.geometry.segments.first.xy,
|
58
|
-
radius: POINT_RADIUS
|
59
|
-
)
|
60
|
-
)
|
61
|
-
end
|
62
|
-
fail("geometry is not closed") unless airspace.geometry.closed?
|
63
|
-
airspace.add_layer layer_from(tds[1].text)
|
64
|
-
airspace.layers.first.timetable = timetable_from! tds[2].text
|
65
|
-
airspace.layers.first.remarks = remarks_from(tds[2], tds[3], tds[4])
|
66
|
-
if aixm.features.find_by(:airspace, type: airspace.type, id: airspace.id).none?
|
67
|
-
add airspace
|
68
|
-
end
|
69
|
-
rescue => error
|
70
|
-
warn("error parsing airspace `#{airspace.name}' at ##{index}: #{error.message}", pry: error)
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
private
|
79
|
-
|
80
|
-
def airspace_from(tr)
|
81
|
-
region, source_type, name = tr.text.cleanup.gsub(/\s/, ' ').split(nil, 3)
|
82
|
-
fail "unknown type `#{source_type}'" unless SOURCE_TYPES.has_key? source_type
|
83
|
-
AIXM.airspace(
|
84
|
-
name: [region, source_type, name].join(' '),
|
85
|
-
type: SOURCE_TYPES.dig(source_type, :type),
|
86
|
-
local_type: SOURCE_TYPES.dig(source_type, :local_type)
|
87
|
-
).tap do |airspace|
|
88
|
-
airspace.source = source(position: tr.line)
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def remarks_from(*parts)
|
93
|
-
part_titles = ['TIMETABLE', 'RESTRICTION', 'AUTHORITY/CONDITIONS']
|
94
|
-
[].tap do |remarks|
|
95
|
-
parts.each.with_index do |part, index|
|
96
|
-
if part = part.text.gsub(/ +/, ' ').gsub(/(\n ?)+/, "\n").strip.blank_to_nil
|
97
|
-
unless index.zero? && part == 'H24'
|
98
|
-
remarks << "**#{part_titles[index]}**\n#{part}"
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end.join("\n\n").blank_to_nil
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
@@ -1,90 +0,0 @@
|
|
1
|
-
module AIPP
|
2
|
-
module LF
|
3
|
-
|
4
|
-
# Obstacles
|
5
|
-
class ENR54 < AIP
|
6
|
-
|
7
|
-
include AIPP::LF::Helpers::Base
|
8
|
-
|
9
|
-
# Obstacles to be ignored
|
10
|
-
NAME_BLACKLIST = %w(51076 52055 59000 72039).freeze # all duplicates
|
11
|
-
|
12
|
-
# Map type descriptions to AIXM types and remarks
|
13
|
-
TYPES = {
|
14
|
-
'Antenne' => [:antenna],
|
15
|
-
'Bâtiment' => [:building],
|
16
|
-
'Câble' => [:other, 'Cable / Câble'],
|
17
|
-
'Centrale thermique' => [:building, 'Thermal power plant / Centrale thermique'],
|
18
|
-
"Château d'eau" => [:tower, "Water tower / Château d'eau"],
|
19
|
-
'Cheminée' => [:chimney],
|
20
|
-
'Derrick' => [:tower, 'Derrick'],
|
21
|
-
'Eglise' => [:tower, 'Church / Eglise'],
|
22
|
-
'Eolienne(s)' => [:wind_turbine],
|
23
|
-
'Grue' => [:tower, 'Crane / Grue'],
|
24
|
-
'Mât' => [:mast],
|
25
|
-
'Phare marin' => [:tower, 'Lighthouse / Phare marin'],
|
26
|
-
'Pile de pont' => [:other, 'Bridge piers / Pile de pont'],
|
27
|
-
'Portique' => [:building, 'Arch / Portique'],
|
28
|
-
'Pylône' => [:mast, 'Pylon / Pylône'],
|
29
|
-
'Silo' => [:tower, 'Silo'],
|
30
|
-
'Terril' => [:other, 'Spoil heap / Teril'],
|
31
|
-
'Torchère' => [:chimney, 'Flare / Torchère'],
|
32
|
-
'Tour' => [:tower],
|
33
|
-
'Treillis métallique' => [:other, 'Metallic grid / Treillis métallique']
|
34
|
-
}.freeze
|
35
|
-
|
36
|
-
def parse
|
37
|
-
tbody = prepare(html: read).css('tbody').last
|
38
|
-
tbody.css('tr').to_enum.with_index(1).each do |tr, index|
|
39
|
-
tds = tr.css('td')
|
40
|
-
name = tds[0].text.cleanup
|
41
|
-
next if NAME_BLACKLIST.include? name
|
42
|
-
elevation, height = tds[4].text.cleanup.split(/[()]/).map { _1.cleanup.remove("\n") }
|
43
|
-
type, type_remarks = TYPES.fetch(tds[2].text.cleanup)
|
44
|
-
count = tds[3].text.cleanup.to_i
|
45
|
-
visibility = tds[5].text.cleanup
|
46
|
-
obstacle = AIXM.obstacle(
|
47
|
-
source: source(position: tr.line),
|
48
|
-
name: name,
|
49
|
-
type: type,
|
50
|
-
xy: xy_from(tds[1].text),
|
51
|
-
z: z_from(elevation + 'AMSL')
|
52
|
-
).tap do |obstacle|
|
53
|
-
obstacle.height = d_from(height)
|
54
|
-
obstacle.marking = visibility.match?(/jour/i)
|
55
|
-
obstacle.lighting = visibility.match?(/nuit/i)
|
56
|
-
obstacle.remarks = remarks_from(type_remarks, (count if count > 1), tds[6].text)
|
57
|
-
end
|
58
|
-
if count > 1
|
59
|
-
obstacle_group = AIXM.obstacle_group(
|
60
|
-
source: obstacle.source,
|
61
|
-
name: obstacle.name
|
62
|
-
).tap do |obstacle_group|
|
63
|
-
obstacle_group.remarks = "#{count} obstacles"
|
64
|
-
end
|
65
|
-
obstacle_group.add_obstacle obstacle
|
66
|
-
add obstacle_group
|
67
|
-
else
|
68
|
-
add obstacle
|
69
|
-
end
|
70
|
-
rescue => error
|
71
|
-
warn("error parsing obstacle at ##{index}: #{error.message}", pry: error)
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
private
|
76
|
-
|
77
|
-
def remarks_from(*parts)
|
78
|
-
part_titles = ['TYPE', 'NUMBER/NOMBRE', 'DETAILS']
|
79
|
-
[].tap do |remarks|
|
80
|
-
parts.each.with_index do |part, index|
|
81
|
-
if part
|
82
|
-
part = part.to_s.cleanup.blank_to_nil
|
83
|
-
remarks << "**#{part_titles[index]}**\n#{part}"
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end.join("\n\n").blank_to_nil
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
@@ -1,55 +0,0 @@
|
|
1
|
-
module AIPP
|
2
|
-
module LF
|
3
|
-
|
4
|
-
# Sporting and Recreational Activities
|
5
|
-
class ENR55 < AIP
|
6
|
-
|
7
|
-
include AIPP::LF::Helpers::Base
|
8
|
-
|
9
|
-
# Map raw activities to activity and airspace type
|
10
|
-
ACTIVITIES = {
|
11
|
-
'activité particulière' => { activity: :other, airspace_type: :dangerous_activities_area },
|
12
|
-
'aéromodélisme' => { activity: :aeromodelling, airspace_type: :dangerous_activities_area },
|
13
|
-
'parachutage' => { activity: :parachuting, airspace_type: :dangerous_activities_area },
|
14
|
-
'treuillage' => { activity: :glider_winch, airspace_type: :dangerous_activities_area },
|
15
|
-
'voltige' => { activity: :acrobatics, airspace_type: :dangerous_activities_area }
|
16
|
-
}.freeze
|
17
|
-
|
18
|
-
def parse
|
19
|
-
prepare(html: read).css('tbody').each do |tbody|
|
20
|
-
tbody.css('tr').to_enum.each_slice(2).with_index(1) do |trs, index|
|
21
|
-
begin
|
22
|
-
id, activity_and_name, upper_limit, timetable = trs.first.css('td')
|
23
|
-
activity, name = activity_and_name.css('span')
|
24
|
-
lateral_limit, lower_limit, remarks = trs.last.css('td')
|
25
|
-
lateral_limit.search('br').each { _1.replace("|||") }
|
26
|
-
geometry, lateral_limit = lateral_limit.text.split('|||', 2)
|
27
|
-
lateral_limit&.gsub!('|||', "\n")
|
28
|
-
remarks = [remarks&.text&.cleanup&.blank_to_nil]
|
29
|
-
s = timetable&.text&.cleanup and remarks.prepend('**SCHEDULE**', s, '')
|
30
|
-
s = lateral_limit&.cleanup and remarks.prepend('**LATERAL LIMIT**', s, '')
|
31
|
-
airspace = AIXM.airspace(
|
32
|
-
source: source(position: trs.first.line),
|
33
|
-
id: id.text.strip,
|
34
|
-
type: ACTIVITIES.fetch(activity.text.downcase).fetch(:airspace_type),
|
35
|
-
name: [id.text.strip, name.text.cleanup].join(' ')
|
36
|
-
).tap do |airspace|
|
37
|
-
airspace.geometry = geometry_from(geometry)
|
38
|
-
airspace.add_layer(
|
39
|
-
layer_from([upper_limit.text, lower_limit.text].join('---').cleanup).tap do |layer|
|
40
|
-
layer.activity = ACTIVITIES.fetch(activity.text.downcase).fetch(:activity)
|
41
|
-
layer.remarks = remarks.compact.join("\n")
|
42
|
-
end
|
43
|
-
)
|
44
|
-
end
|
45
|
-
rescue => error
|
46
|
-
warn("error parsing #{airspace.type} `#{airspace.name}' at ##{index}: #{error.message}", pry: error)
|
47
|
-
end
|
48
|
-
add airspace
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|