class AIPP::LF::AD2
Airports (IFR capable) and their CTR, AD navigational aids etc
Constants
- NO_DESIGNATED_POINTS
Airports without VFR reporting points TODO: designated points on map but no list (LFLD LFSN LFBS) or no AD info (LFRL)
- NO_VAC
Airports without VAC (e.g. military installations)
- SOURCE_TYPES
Map source types to type and optional local type
- SYNONYMS
Map synonyms for
correlate
Public Instance Methods
parse()
click to toggle source
# File lib/aipp/regions/LF/AD-2.rb 40 def parse 41 index_html = prepare(html: read("AD-0.6")) # index for AD-2.xxxx files 42 index_html.css('#AD-0\.6\.eAIP > .toc-block:nth-of-type(3) .toc-block a').each do |a| 43 @id = a.attribute('href').value[-4,4] 44 begin 45 aip_file = "AD-2.#{@id}" 46 html = prepare(html: read(aip_file)) 47 # Airport 48 @remarks = [] 49 @airport = AIXM.airport( 50 source: source(position: html.css('tr[id*="CODE_ICAO"]').first.line, aip_file: aip_file), 51 organisation: organisation_lf, # TODO: not yet implemented 52 id: @id, 53 name: html.css('tr[id*="CODE_ICAO"] td span:nth-of-type(2)').text.strip.uptrans, 54 xy: xy_from(html.css('#AD-2\.2-Position_Geo_Arp td:nth-of-type(3)').text) 55 ).tap do |airport| 56 airport.z = elevation_from(html.css('#AD-2\.2-Altitude_Reference td:nth-of-type(3)').text) 57 airport.declination = declination_from(html.css('#AD-2\.2-Declinaison_Magnetique td:nth-of-type(3)').text) 58 # airport.transition_z = AIXM.z(5000, :qnh) # TODO: default - exceptions may exist 59 airport.timetable = timetable_from!(html.css('#AD-2\.3-Gestionnaire_AD td:nth-of-type(3)').text) 60 end 61 runways_from(html.css('div[id*="-AD-2\.12"] tbody')).each { @airport.add_runway(_1) if _1 } 62 helipads_from(html.css('div[id*="-AD-2\.16"] tbody')).each { @airport.add_helipad(_1) if _1 } 63 text = html.css('#AD-2\.2-Observations td:nth-of-type(3)').text 64 @airport.remarks = ([remarks_from(text)] + @remarks).compact.join("\n\n").blank_to_nil 65 add @airport 66 # Airspaces 67 airspaces_from(html.css('div[id*="-AD-2\.17"] tbody')). 68 reject { aixm.features.find_by(_1.class, type: _1.type, id: _1.id).any? }. 69 each(&method(:add)) 70 # Radio 71 trs = html.css('div[id*="-AD-2\.18"] tbody tr') 72 addresses_from(trs).each { @airport.add_address(_1) } 73 units_from(trs, airport: @airport).each(&method(:add)) 74 # Navigational aids 75 navigational_aids_from(html.css('div[id*="-AD-2\.19"] tbody')). 76 reject { aixm.features.find_by(_1.class, id: _1.id, xy: _1.xy).any? }. 77 each(&method(:add)) 78 # Designated points 79 unless NO_VAC.include?(@id) || NO_DESIGNATED_POINTS.include?(@id) 80 pdf = read("VAC-#{@id}") 81 designated_points_from(pdf).tap do |designated_points| 82 fix_designated_point_remarks(designated_points) 83 # debug(designated_points) 84 designated_points. 85 uniq(&:to_uid). 86 reject { aixm.features.find_by(_1.class, id: _1.id, xy: _1.xy).any? }. 87 each(&method(:add)) 88 end 89 end 90 rescue => error 91 warn("error parsing airport #{@id}: #{error.message}", pry: error) 92 end 93 end 94 end
Private Instance Methods
airspace_from(tr)
click to toggle source
# File lib/aipp/regions/LF/AD-2.rb 202 def airspace_from(tr) 203 spans = tr.css(:span) 204 source_type = spans[1].text.blank_to_nil 205 fail "unknown type `#{source_type}'" unless SOURCE_TYPES.has_key? source_type 206 AIXM.airspace( 207 name: [spans[2].text, anglicise(name: spans[3]&.text)].compact.join(' '), 208 type: SOURCE_TYPES.dig(source_type, :type), 209 local_type: SOURCE_TYPES.dig(source_type, :local_type) 210 ).tap do |airspace| 211 airspace.source = source(position: tr.line) 212 end 213 end
airspaces_from(tbody)
click to toggle source
# File lib/aipp/regions/LF/AD-2.rb 184 def airspaces_from(tbody) 185 return [] if tbody.text.blank? 186 airspace = nil 187 tbody.css('tr').to_enum.with_object([]) do |tr, array| 188 if tr.attr(:class) =~ /keep-with-next-row/ 189 airspace = airspace_from tr 190 else 191 tds = tr.css('td') 192 airspace.geometry = geometry_from tds[0].text 193 fail("geometry is not closed") unless airspace.geometry.closed? 194 airspace.add_layer layer_from(tds[2].text, tds[1].text.strip) 195 airspace.layers.first.timetable = timetable_from! tds[4].text 196 airspace.layers.first.remarks = remarks_from(tds[4].text) 197 array << airspace 198 end 199 end 200 end
declination_from(text)
click to toggle source
# File lib/aipp/regions/LF/AD-2.rb 98 def declination_from(text) 99 value, direction = text.strip.split('°') 100 value = value.to_f * (direction == 'W' ? -1 : 1) 101 end
designated_point_from(buffer, pdf)
click to toggle source
# File lib/aipp/regions/LF/AD-2.rb 264 def designated_point_from(buffer, pdf) 265 if buffer[:id] && buffer[:xy]&.size == 2 266 buffer[:remarks].gsub!(/ {20}/, "\n") # recognize empty column space 267 buffer[:remarks].remove!(/\(\d+\)/) # remove footnotes 268 buffer[:remarks] = buffer[:remarks].unglue # separate glued words 269 AIXM.designated_point( 270 source: source(position: buffer[:page], aip_file: pdf.file.basename('.*').to_s), 271 type: :vfr_mandatory_reporting_point, 272 id: buffer[:id].remove(/\W/), 273 xy: AIXM.xy(lat: buffer[:xy].first, long: buffer[:xy].last) 274 ).tap do |designated_point| 275 designated_point.airport = @airport 276 designated_point.remarks = buffer[:remarks].compact.blank_to_nil 277 buffer.clear 278 end 279 end 280 end
designated_points_from(pdf, recursive=false)
click to toggle source
# File lib/aipp/regions/LF/AD-2.rb 237 def designated_points_from(pdf, recursive=false) 238 from = (pdf.text =~ /^(.*?coordinates.*?names?)/i) 239 return [] if recursive && !from 240 warn("no designated points section begin found for #{@id}", pry: binding) unless from 241 from += $1.length 242 to = from + (pdf.text.from(from) =~ /\n\s*\n\s*\n|^.*(?:ifr|vfr|ad\s*equipment|special\s*activities|training\s*flights|mto\s*minima)/i) 243 warn("no designated points section end found for #{@id}", pry: binding) unless to 244 from, to = from + pdf.range.min, to + pdf.range.min # offset when recursive 245 buffer = {} 246 pdf.from(from).to(to).each_line.with_object([]) do |(line, page, last), designated_points| 247 line.remove!(/\u2190/) # remove arrow symbols 248 has_id = $1 if line.sub!(/^\s{,20}([A-Z][A-Z\d ]{1,3})(?=\W)/, '') 249 has_xy = line.match?(AIXM::DMS_RE) 250 designated_points << designated_point_from(buffer, pdf) if has_id || has_xy 251 if has_xy 252 2.times { (buffer[:xy] ||= []) << $1 if line.sub!(AIXM::DMS_RE, '') } 253 buffer[:xy]&.compact! 254 line.remove!(/\d{3,4}\D.+?MTG/) # remove extra columns (e.g. LFML) 255 line.remove!(/[\s#{AIXM::MIN}#{AIXM::SEC}]*[-\u2013]/) # remove dash between coordinates 256 end 257 buffer[:page] = page 258 buffer[:id] = has_id if has_id 259 buffer[:remarks] = [buffer[:remarks], line].join("\n") 260 designated_points << designated_point_from(buffer, pdf) if last 261 end.compact + designated_points_from(pdf.from(to).to(:end), true) 262 end
fix_designated_point_remarks(designated_points)
click to toggle source
Assign scattered similar remarks to one and the same designated point
# File lib/aipp/regions/LF/AD-2.rb 283 def fix_designated_point_remarks(designated_points) 284 one = nil 285 designated_points.map do |two| 286 if one 287 one_lines, two_lines = one.remarks&.lines, two.remarks&.lines 288 if one_lines && two_lines 289 if one_lines.count > 1 && (line = one_lines.last) !~ %r(\s/\s) 290 # Move up 291 if line.correlate(remainder = one_lines[0..-2].join, SYNONYMS) < line.correlate(two.remarks) 292 two.remarks = [line, two.remarks].join("\n").compact 293 one.remarks = remainder.compact 294 end 295 elsif two_lines.count > 1 && (line = two_lines.first) !~ %r(\s/\s) 296 # Move down 297 line = two_lines.first 298 if line.correlate(remainder = two_lines[1..-1].join, SYNONYMS) < line.correlate(one.remarks) 299 one.remarks = [one.remarks, line].join("\n").compact 300 two.remarks = remainder.compact 301 end 302 end 303 end 304 end 305 one = two 306 end.map do |designated_point| 307 designated_point.remarks = designated_point.remarks&.cleanup.blank_to_nil 308 end 309 end
helipads_from(tbody)
click to toggle source
# File lib/aipp/regions/LF/AD-2.rb 162 def helipads_from(tbody) 163 text_fr = tbody.css('td:nth-of-type(3)').text.compact 164 text_en = tbody.css('td:nth-of-type(4)').text.compact 165 case text_fr 166 when /NIL/, /\A\W*\z/ 167 [] 168 when /instructions?\s+twr/i 169 @remarks << "HELICOPTER:\nSur instructions TWR.\nOn TWR clearance." 170 [] 171 when AIXM::DMS_RE 172 text_fr.scan(AIXM::DMS_RE).each_slice(2).with_index(1).map do |(lat, long), index| 173 AIXM.helipad( 174 name: "H#{index}", 175 xy: AIXM.xy(lat: lat.first, long: long.first) 176 ) 177 end 178 else 179 @remarks << ['HELICOPTER:', text_fr.blank_to_nil, text_en.blank_to_nil].compact.join("\n") 180 [] 181 end 182 end
remarks_from(text)
click to toggle source
# File lib/aipp/regions/LF/AD-2.rb 103 def remarks_from(text) 104 text.sub(/NIL|\(\*\)\s+/, '').strip.gsub(/(\s)\s+/, '\1').blank_to_nil 105 end
runways_from(tbody)
click to toggle source
# File lib/aipp/regions/LF/AD-2.rb 107 def runways_from(tbody) 108 directions_map = tbody.css('tr[id*="TXT_DESIG"]').map do |tr| 109 [AIXM.a(tr.css('td:first-of-type').text.strip), tr] 110 end.to_h 111 remarks_map = tbody.css('tr[id*="TXT_RMK_NAT"]').map do |tr| 112 [tr.text.strip[/\A\((\d+)\)/, 1].to_i, tr.css('span')] 113 end.to_h 114 directions = directions_map.keys 115 grouped_directions = directions.map do |direction| 116 inverted_direction = direction.invert 117 if directions.include? inverted_direction 118 [direction, inverted_direction].map(&:to_s).sort.join('/') 119 else 120 direction.to_s 121 end 122 end.uniq 123 grouped_directions.map do |runway_name| 124 AIXM.runway(name: runway_name).tap do |runway| 125 %i(forth back).each do |direction_attr| 126 if direction = runway.send(direction_attr) 127 tr = directions_map[direction.name] 128 if direction_attr == :forth 129 length, width = tr.css('td:nth-of-type(3)').text.strip.split('x') 130 runway.length = AIXM.d(length.strip.to_i, :m) 131 runway.width = AIXM.d(width.strip.to_i, :m) 132 unless (text = tr.css('td:nth-of-type(5)').text.strip.split(%r<\W+/\W+>).first.compact).blank? 133 surface = SURFACES.metch(text) 134 runway.surface.composition = surface[:composition] 135 runway.surface.preparation = surface[:preparation] 136 runway.surface.remarks = surface[:remarks] 137 end 138 if (text = tr.css('td:nth-of-type(4)').text).match?(AIXM::PCN_RE) 139 runway.surface.pcn = text 140 end 141 end 142 text = tr.css('td:nth-of-type(6)').text.strip 143 direction.xy = (xy_from(text) unless text.match?(/\A(\(.*)?\z/m)) 144 if (text = tr.css('td:nth-of-type(7)').text.strip[/thr:\s+(\d+\s+\w+)/i, 1]).present? 145 direction.z = elevation_from(text) 146 end 147 if (text = tr.css('td:nth-of-type(2)').text.strip.sub(/\A(\d+).*$/m, '\1')).present? 148 direction.geographic_orientation = AIXM.a(text.to_i) 149 end 150 if (text = tr.css('td:nth-of-type(6)').text[/\((.+)\)/m, 1]).present? 151 direction.displaced_threshold = xy_from(text) 152 end 153 if (text = tr.css('td:nth-of-type(10)').text.strip[/\A\((\d+)\)/, 1]).present? 154 direction.remarks = remarks_from(remarks_map.fetch(text.to_i).text) 155 end 156 end 157 end 158 end 159 end 160 end