class AIPP::LF::ENR21

FIR, TMA etc

Constants

LOCATION_INDICATORS

Map airspace “<type> <name>” to location indicator

NAME_BLACKLIST_RE

Airspaces to be ignored

SERVICE_FIXES

Fix incomplete SIV service columns

SOURCE_TYPES

Map source types to type and optional local type

Public Instance Methods

parse() click to toggle source
   # File lib/aipp/regions/LF/ENR-2.1.rb
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

Private Instance Methods

airspace_from(td) click to toggle source
    # File lib/aipp/regions/LF/ENR-2.1.rb
 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
class_from(text) click to toggle source
    # File lib/aipp/regions/LF/ENR-2.1.rb
106 def class_from(text)
107   text.strip
108 end
remarks_from(text) click to toggle source
    # File lib/aipp/regions/LF/ENR-2.1.rb
162 def remarks_from(text)
163   text.strip.gsub(/(\s)\s+/, '\1').blank_to_nil
164 end
services_from(td, remarks) click to toggle source
    # File lib/aipp/regions/LF/ENR-2.1.rb
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