aipp 0.2.6 → 1.0.0
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 +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
|