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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +21 -0
  4. data/README.md +147 -91
  5. data/exe/aip2aixm +2 -2
  6. data/exe/aip2ofmx +2 -2
  7. data/lib/aipp/aip.rb +96 -11
  8. data/lib/aipp/border.rb +77 -46
  9. data/lib/aipp/debugger.rb +101 -0
  10. data/lib/aipp/downloader.rb +18 -5
  11. data/lib/aipp/executable.rb +33 -20
  12. data/lib/aipp/parser.rb +42 -37
  13. data/lib/aipp/patcher.rb +5 -2
  14. data/lib/aipp/regions/LF/README.md +49 -0
  15. data/lib/aipp/regions/LF/aerodromes.rb +223 -0
  16. data/lib/aipp/regions/LF/d_p_r_airspaces.rb +56 -0
  17. data/lib/aipp/regions/LF/dangerous_activities.rb +49 -0
  18. data/lib/aipp/regions/LF/designated_points.rb +47 -0
  19. data/lib/aipp/regions/LF/fixtures/aerodromes.yml +608 -0
  20. data/lib/aipp/regions/LF/helipads.rb +122 -0
  21. data/lib/aipp/regions/LF/helpers/base.rb +167 -174
  22. data/lib/aipp/regions/LF/helpers/surface.rb +49 -0
  23. data/lib/aipp/regions/LF/helpers/usage_limitation.rb +20 -0
  24. data/lib/aipp/regions/LF/navigational_aids.rb +85 -0
  25. data/lib/aipp/regions/LF/obstacles.rb +153 -0
  26. data/lib/aipp/regions/LF/serviced_airspaces.rb +70 -0
  27. data/lib/aipp/regions/LF/services.rb +172 -0
  28. data/lib/aipp/t_hash.rb +3 -4
  29. data/lib/aipp/version.rb +1 -1
  30. data/lib/aipp.rb +7 -5
  31. data/lib/core_ext/enumerable.rb +2 -2
  32. data/lib/core_ext/hash.rb +21 -5
  33. data/lib/core_ext/nokogiri.rb +54 -0
  34. data/lib/core_ext/string.rb +32 -65
  35. data.tar.gz.sig +0 -0
  36. metadata +70 -81
  37. metadata.gz.sig +0 -0
  38. data/lib/aipp/airac.rb +0 -55
  39. data/lib/aipp/regions/LF/AD-1.3.rb +0 -177
  40. data/lib/aipp/regions/LF/AD-1.6.rb +0 -33
  41. data/lib/aipp/regions/LF/AD-2.rb +0 -344
  42. data/lib/aipp/regions/LF/AD-3.1.rb +0 -185
  43. data/lib/aipp/regions/LF/ENR-2.1.rb +0 -167
  44. data/lib/aipp/regions/LF/ENR-4.1.rb +0 -41
  45. data/lib/aipp/regions/LF/ENR-4.3.rb +0 -27
  46. data/lib/aipp/regions/LF/ENR-5.1.rb +0 -106
  47. data/lib/aipp/regions/LF/ENR-5.4.rb +0 -90
  48. data/lib/aipp/regions/LF/ENR-5.5.rb +0 -55
  49. data/lib/aipp/regions/LF/fixtures/AD-1.3.yml +0 -511
  50. data/lib/aipp/regions/LF/fixtures/AD-2.yml +0 -185
  51. data/lib/aipp/regions/LF/fixtures/AD-3.1.yml +0 -10
  52. data/lib/aipp/regions/LF/helpers/URL.rb +0 -26
  53. data/lib/aipp/regions/LF/helpers/navigational_aid.rb +0 -104
  54. data/lib/aipp/regions/LF/helpers/radio_AD.rb +0 -110
  55. 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