aipp 0.2.4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +2 -0
  3. data/CHANGELOG.md +38 -0
  4. data/README.md +222 -88
  5. data/exe/aip2aixm +2 -2
  6. data/exe/aip2ofmx +2 -2
  7. data/lib/aipp/aip.rb +113 -31
  8. data/lib/aipp/border.rb +77 -46
  9. data/lib/aipp/debugger.rb +101 -0
  10. data/lib/aipp/downloader.rb +39 -26
  11. data/lib/aipp/executable.rb +41 -22
  12. data/lib/aipp/parser.rb +94 -21
  13. data/lib/aipp/patcher.rb +5 -2
  14. data/lib/aipp/pdf.rb +1 -1
  15. data/lib/aipp/regions/LF/README.md +49 -0
  16. data/lib/aipp/regions/LF/aerodromes.rb +223 -0
  17. data/lib/aipp/regions/LF/d_p_r_airspaces.rb +56 -0
  18. data/lib/aipp/regions/LF/dangerous_activities.rb +49 -0
  19. data/lib/aipp/regions/LF/designated_points.rb +47 -0
  20. data/lib/aipp/regions/LF/fixtures/aerodromes.yml +608 -0
  21. data/lib/aipp/regions/LF/helipads.rb +122 -0
  22. data/lib/aipp/regions/LF/helpers/base.rb +218 -0
  23. data/lib/aipp/regions/LF/helpers/surface.rb +49 -0
  24. data/lib/aipp/regions/LF/helpers/usage_limitation.rb +20 -0
  25. data/lib/aipp/regions/LF/navigational_aids.rb +85 -0
  26. data/lib/aipp/regions/LF/obstacles.rb +153 -0
  27. data/lib/aipp/regions/LF/serviced_airspaces.rb +70 -0
  28. data/lib/aipp/regions/LF/services.rb +172 -0
  29. data/lib/aipp/t_hash.rb +4 -5
  30. data/lib/aipp/version.rb +1 -1
  31. data/lib/aipp.rb +11 -5
  32. data/lib/core_ext/enumerable.rb +9 -9
  33. data/lib/core_ext/hash.rb +21 -5
  34. data/lib/core_ext/nokogiri.rb +54 -0
  35. data/lib/core_ext/string.rb +38 -66
  36. data.tar.gz.sig +2 -0
  37. metadata +180 -188
  38. metadata.gz.sig +0 -0
  39. data/.gitignore +0 -8
  40. data/.ruby-version +0 -1
  41. data/.travis.yml +0 -8
  42. data/.yardopts +0 -3
  43. data/Guardfile +0 -7
  44. data/TODO.md +0 -6
  45. data/aipp.gemspec +0 -44
  46. data/gems.rb +0 -3
  47. data/lib/aipp/airac.rb +0 -55
  48. data/lib/aipp/regions/LF/AD-1.3.rb +0 -162
  49. data/lib/aipp/regions/LF/AD-1.6.rb +0 -31
  50. data/lib/aipp/regions/LF/AD-2.rb +0 -313
  51. data/lib/aipp/regions/LF/AD-3.1.rb +0 -185
  52. data/lib/aipp/regions/LF/ENR-2.1.rb +0 -92
  53. data/lib/aipp/regions/LF/ENR-4.1.rb +0 -97
  54. data/lib/aipp/regions/LF/ENR-4.3.rb +0 -28
  55. data/lib/aipp/regions/LF/ENR-5.1.rb +0 -75
  56. data/lib/aipp/regions/LF/ENR-5.5.rb +0 -53
  57. data/lib/aipp/regions/LF/fixtures/AD-1.3.yml +0 -511
  58. data/lib/aipp/regions/LF/fixtures/AD-2.yml +0 -185
  59. data/lib/aipp/regions/LF/fixtures/AD-3.1.yml +0 -10
  60. data/lib/aipp/regions/LF/helpers/AD_radio.rb +0 -90
  61. data/lib/aipp/regions/LF/helpers/URL.rb +0 -26
  62. data/lib/aipp/regions/LF/helpers/common.rb +0 -217
  63. data/lib/core_ext/object.rb +0 -43
  64. data/rakefile.rb +0 -12
  65. data/spec/fixtures/archive.zip +0 -0
  66. data/spec/fixtures/border.geojson +0 -201
  67. data/spec/fixtures/document.pdf +0 -0
  68. data/spec/fixtures/document.pdf.json +0 -1
  69. data/spec/fixtures/new.html +0 -6
  70. data/spec/fixtures/new.pdf +0 -0
  71. data/spec/fixtures/new.txt +0 -1
  72. data/spec/lib/aipp/airac_spec.rb +0 -98
  73. data/spec/lib/aipp/border_spec.rb +0 -135
  74. data/spec/lib/aipp/downloader_spec.rb +0 -81
  75. data/spec/lib/aipp/patcher_spec.rb +0 -46
  76. data/spec/lib/aipp/pdf_spec.rb +0 -124
  77. data/spec/lib/aipp/t_hash_spec.rb +0 -44
  78. data/spec/lib/aipp/version_spec.rb +0 -7
  79. data/spec/lib/core_ext/enumberable_spec.rb +0 -76
  80. data/spec/lib/core_ext/hash_spec.rb +0 -27
  81. data/spec/lib/core_ext/integer_spec.rb +0 -15
  82. data/spec/lib/core_ext/nil_class_spec.rb +0 -11
  83. data/spec/lib/core_ext/string_spec.rb +0 -112
  84. data/spec/sounds/failure.mp3 +0 -0
  85. data/spec/sounds/success.mp3 +0 -0
  86. data/spec/spec_helper.rb +0 -28
@@ -1,185 +0,0 @@
1
- module AIPP
2
- module LF
3
-
4
- # Helipads
5
- class AD31 < AIP
6
-
7
- include AIPP::LF::Helpers::Common
8
- using AIXM::Refinements
9
-
10
- DEPENDS = %w(AD-2)
11
-
12
- HOSTILITIES = {
13
- 'zone hostile habitée' => 'Zone hostile habitée / hostile populated area',
14
- 'zone hostile non habitée' => 'Zone hostile non habitée / hostile unpopulated area',
15
- 'zone non hostile' => 'Zone non hostile / non-hostile area'
16
- }.freeze
17
-
18
- POSITIONINGS = {
19
- 'en terrasse' => 'En terrasse / on deck',
20
- 'en surface' => 'En surface / on ground'
21
- }.freeze
22
-
23
- DIMENSIONS_RE = /( diam.tre\s+\d+ | (?:\d[\s\d,.m]*x\s*){1,}[\s\d,.m]+ )/ix.freeze
24
-
25
- def parse
26
- prepare(html: read).css('tbody').each do |tbody|
27
- tbody.css('tr').to_enum.each_slice(3).with_index(1) do |trs, index|
28
- name = trs[0].css('span[id*="ADHP.TXT_NAME"]').text.cleanup.remove(/[^\w' ]/)
29
- if select(:airport, name: name).any?
30
- verbose_info "Skipping #{name} in favor of AD-2"
31
- next
32
- end
33
- # Airport
34
- @airport = AIXM.airport(
35
- source: source(position: trs[0].line),
36
- organisation: organisation_lf, # TODO: not yet implemented
37
- id: options[:region],
38
- name: name,
39
- xy: xy_from(trs[1].css('td:nth-of-type(1)').text.cleanup)
40
- ).tap do |airport|
41
- airport.z = elevation_from(trs[1].css('td:nth-of-type(2)').text)
42
- end
43
- # Usage restrictions
44
- if trs[0].css('span[id*="ADHP.STATUT"]').text.match?(/usage\s+restreint/i)
45
- @airport.add_usage_limitation(:reservation_required) do |reservation_required|
46
- reservation_required.remarks = "Usage restreint / restricted use"
47
- end
48
- end
49
- if trs[0].css('span[id*="ADHP.STATUT"]').text.match?(/r.serv.\s+aux\s+administrations/i)
50
- @airport.add_usage_limitation(:other) do |other|
51
- other.remarks = "Réservé aux administrations de l'État / reserved for State administrations"
52
- end
53
- end
54
- # FATOs and helipads
55
- text = trs[2].css('span[id*="ADHP.REVETEMENT"]').text.remove(/tlof\s*|\s*\(.*?\)/i).downcase
56
- surface = text.blank? ? {} : SURFACES.metch(text)
57
- lighting = lighting_from(trs[1].css('span[id*="ADHP.BALISAGE"]').text.cleanup)
58
- fatos_from(trs[1].css('span[id*="ADHP.DIM_FATO"]').text).each { |f| @airport.add_fato f }
59
- helipads_from(trs[1].css('span[id*="ADHP.DIM_TLOF"]').text).each do |helipad|
60
- helipad.surface.composition = surface[:composition]
61
- helipad.surface.preparation = surface[:preparation]
62
- helipad.surface.remarks = surface[:remarks]
63
- helipad.surface.auw_weight = auw_weight_from(trs[2].css('span[id*="ADHP.RESISTANCE"]').text)
64
- helipad.add_lighting(lighting) if lighting
65
- @airport.add_helipad helipad
66
- end
67
- # Operator and addresses
68
- operator = trs[0].css('span[id*="ADHP.EXPLOITANT"]')
69
- splitted = operator.text.split(/( (?<!\p{L})t[ée]l | fax | standard | [\d\s]{10,} | \.\s | \( )/ix, 2)
70
- @airport.operator = splitted[0].full_strip.truncate(60, omission: '…').blank_to_nil
71
- raw_addresses = splitted[1..].join.cleanup.full_strip
72
- addresses_from(splitted[1..].join, source(position: operator.first.line)).each { |a| @airport.add_address(a) }
73
- # Remarks
74
- @airport.remarks = [].tap do |remarks|
75
- hostility = trs[2].css('span[id*="ADHP.ZONE_HABITEE"]').text.cleanup.downcase.blank_to_nil
76
- hostility = HOSTILITIES.fetch(hostility) if hostility
77
- positioning = trs[2].css('span[id*="ADHP.EN_TERRASSE"]').text.cleanup.downcase.blank_to_nil
78
- positioning = POSITIONINGS.fetch(positioning) if positioning
79
- remarks << ('**SITUATION**' if hostility || positioning) << hostility << positioning << ''
80
- remarks << trs[2].css('td:nth-of-type(5)').text.cleanup
81
- remarks << raw_addresses unless raw_addresses.blank?
82
- end.compact.join("\n").strip
83
- add(@airport) if @airport.fatos.any? || @airport.helipads.any?
84
- end
85
- end
86
- end
87
-
88
- private
89
-
90
- def fatos_from(text)
91
- [
92
- if text.cleanup.match DIMENSIONS_RE
93
- AIXM.fato(name: 'FATO').tap do |fato|
94
- fato.length, fato.width = dimensions_from($1)
95
- end
96
- end
97
- ].compact
98
- end
99
-
100
- def helipads_from(text)
101
- [
102
- if text.cleanup.match DIMENSIONS_RE
103
- AIXM.helipad(name: 'TLOF', xy: @airport.xy).tap do |helipad|
104
- helipad.z = @airport.z
105
- helipad.length, helipad.width = dimensions_from($1)
106
- end
107
- end
108
- ].compact
109
- end
110
-
111
- def dimensions_from(text)
112
- dims = text.remove(/[^x\d.,]/i).split(/x/i).map { |s| s.to_ff.floor }
113
- case dims.size
114
- when 1
115
- [dim = AIXM.d(dims[0], :m), dim]
116
- when 2
117
- [AIXM.d(dims[0], :m), AIXM.d(dims[1], :m)]
118
- when 4
119
- [dim = AIXM.d(dims.min, :m), dim]
120
- else
121
- warn("bad dimensions for #{@airport.name}", pry: binding)
122
- end
123
- end
124
-
125
- def auw_weight_from(text)
126
- if wgt = text.match(/(\d+(?:[,.]\d+)?)\s*t/i)&.captures&.first
127
- AIXM.w(wgt.to_ff, :t)
128
- end
129
- end
130
-
131
- def lighting_from(text)
132
- return if text.blank? || text.match?(/nil|balisage\s*:\s*non/i)
133
- description = text.remove(/balisage\s*:|oui\.?\s*:?/i).compact.full_strip
134
- AIXM.lighting(position: :edge).tap do |lighting|
135
- lighting.description = description unless description.blank?
136
- end
137
- end
138
-
139
- def addresses_from(text, source)
140
- [].tap do |addresses|
141
- text.sub! /(?<!\p{L})t[ée]l\D*([\d\s.]{10,}(?:poste[\d\s.]{2,})?)[-\/]?/i do |m|
142
- addresses << AIXM.address(
143
- source: source,
144
- type: :phone,
145
- address: m.strip.sub(/poste/i, '-').remove(/[^\d-]|-$/)
146
- )
147
- end
148
- text.sub! /fax\D*([\d\s.]{10,}(?:poste[\d\s.]{2,})?)[-\/]?/i do |m|
149
- addresses << AIXM.address(
150
- source: source,
151
- type: :fax,
152
- address: m.strip.sub(/poste/i, '-').remove(/[^\d-]|-$/)
153
- )
154
- end
155
- text.sub! /e-mail\W*(\S+)[-\/]?/i do |m|
156
- addresses << AIXM.address(
157
- source: source,
158
- type: :email,
159
- address: m.strip
160
- )
161
- end
162
- text.sub! /(\d[\d\s]{9,}(?:poste[\d\s.]{2,})?)[-\/]?/i do |m|
163
- addresses << AIXM.address(
164
- source: source,
165
- type: :phone,
166
- address: m.strip.sub(/poste/i, '-').remove(/[^\d-]|-$/)
167
- )
168
- end
169
- end
170
- end
171
-
172
- patch AIXM::Feature::Airport, :xy do |parser, object, value|
173
- throw :abort if value.seconds?
174
- if xy = parser.fixture.dig(object.name, 'xy')
175
- lat, long = xy.split(/\s+/)
176
- AIXM.xy(lat: lat, long: long)
177
- else
178
- warn("coordinates for #{object.name} appear not to be exact", pry: binding)
179
- throw :abort
180
- end
181
- end
182
-
183
- end
184
- end
185
- end
@@ -1,92 +0,0 @@
1
- module AIPP
2
- module LF
3
-
4
- # FIR, TMA etc
5
- class ENR21 < AIP
6
-
7
- include AIPP::LF::Helpers::Common
8
-
9
- # Map source types to type and optional local type
10
- SOURCE_TYPES = {
11
- 'FIR' => { type: 'FIR' },
12
- 'UIR' => { type: 'UIR' },
13
- 'UTA' => { type: 'UTA' },
14
- 'CTA' => { type: 'CTA' },
15
- 'LTA' => { type: 'CTA', local_type: 'LTA' },
16
- 'TMA' => { type: 'TMA' },
17
- 'SIV' => { type: 'SECTOR', local_type: 'SIV' } # providing FIS
18
- }.freeze
19
-
20
- # Map airspace "<type> <name>" to location indicator
21
- LOCATION_INDICATORS = {
22
- 'FIR BORDEAUX' => 'LFBB',
23
- 'FIR BREST' => 'LFRR',
24
- 'FIR MARSEILLE' => 'LFMM',
25
- 'FIR PARIS' => 'LFFF',
26
- 'FIR REIMS' => 'LFRR'
27
- }.freeze
28
-
29
- def parse
30
- prepare(html: read).css('tbody').each do |tbody|
31
- airspace = nil
32
- tbody.css('tr').to_enum.with_index(1).each do |tr, index|
33
- if tr.attr(:id).match?(/--TXT_NAME/)
34
- add airspace if airspace
35
- airspace = airspace_from tr.css(:td).first
36
- verbose_info "Parsing #{airspace.type} #{airspace.name}" unless airspace.type == :terminal_control_area
37
- next
38
- end
39
- begin
40
- tds = tr.css('td')
41
- if airspace.type == :terminal_control_area && tds[0].text.blank_to_nil
42
- add airspace if airspace.layers.any?
43
- airspace = airspace_from tds[0]
44
- verbose_info "Parsing #{airspace.type} #{airspace.name}"
45
- end
46
- if airspace
47
- if tds[0].text.blank_to_nil
48
- airspace.geometry = geometry_from tds[0].text
49
- fail("geometry is not closed") unless airspace.geometry.closed?
50
- end
51
- layer = layer_from(tds[-3].text)
52
- layer.class = class_from(tds[1].text) if tds.count == 5
53
- layer.location_indicator = LOCATION_INDICATORS.fetch("#{airspace.type} #{airspace.name}", nil)
54
- # TODO: unit, call sign and frequency from tds[-2] and with extracted remarks such as (1), (2) etc
55
- remarks = tds[-1].text
56
- layer.timetable = timetable_from! remarks
57
- layer.remarks = remarks_from remarks
58
- airspace.layers << layer
59
- end
60
- rescue => error
61
- warn("error parsing #{airspace.type} `#{airspace.name}' at ##{index}: #{error.message}", pry: error)
62
- end
63
- end
64
- add airspace if airspace
65
- end
66
- end
67
-
68
- private
69
-
70
- def airspace_from(td)
71
- spans = td.children.split { |e| e.name == 'br' }.first.css(:span).drop_while { |e| e.text.match? '\s' }
72
- source_type = spans[0].text.blank_to_nil
73
- fail "unknown type `#{source_type}'" unless SOURCE_TYPES.has_key? source_type
74
- AIXM.airspace(
75
- name: anglicise(name: spans[1..-1].join(' ')),
76
- type: SOURCE_TYPES.dig(source_type, :type),
77
- local_type: SOURCE_TYPES.dig(source_type, :local_type)
78
- ).tap do |airspace|
79
- airspace.source = source(position: td.line)
80
- end
81
- end
82
-
83
- def class_from(text)
84
- text.strip
85
- end
86
-
87
- def remarks_from(text)
88
- text.strip.gsub(/(\s)\s+/, '\1').blank_to_nil
89
- end
90
- end
91
- end
92
- end
@@ -1,97 +0,0 @@
1
- module AIPP
2
- module LF
3
-
4
- # ENR Navaids
5
- class ENR41 < AIP
6
-
7
- include AIPP::LF::Helpers::Common
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
- master, slave = tds[1].text.strip.gsub(/[^\w-]/, '').downcase.split('-')
14
- navaid = AIXM.send(master, base_from(tds).merge(send("#{master}_from", tds)))
15
- navaid.source = source(position: tr.line)
16
- navaid.timetable = timetable_from! tds[4].text
17
- navaid.remarks = remarks_from(tds[5], tds[7], tds[9])
18
- navaid.send("associate_#{slave}", channel: channel_from(tds[3].text)) if slave
19
- add navaid
20
- rescue => error
21
- warn("error parsing navigational aid at ##{index}: #{error.message}", pry: error)
22
- end
23
- end
24
- end
25
-
26
- private
27
-
28
- def base_from(tds)
29
- {
30
- organisation: organisation_lf,
31
- id: tds[2].text.strip,
32
- name: tds[0].text.strip,
33
- xy: xy_from(tds[5].text),
34
- z: z_from(tds[6].text)
35
- }
36
- end
37
-
38
- def vor_from(tds)
39
- {
40
- type: :conventional,
41
- f: f_from(tds[3].text),
42
- north: :magnetic,
43
- }
44
- end
45
-
46
- def dme_from(tds)
47
- {
48
- channel: channel_from(tds[3].text)
49
- }
50
- end
51
-
52
- def ndb_from(tds)
53
- {
54
- type: :en_route,
55
- f: f_from(tds[3].text)
56
- }
57
- end
58
-
59
- def tacan_from(tds)
60
- {
61
- channel: channel_from(tds[3].text)
62
- }
63
- end
64
-
65
- def z_from(text)
66
- parts = text.strip.split(/\s+/)
67
- AIXM.z(parts[0].to_i, :qnh) if parts[1] == 'ft'
68
- end
69
-
70
- def f_from(text)
71
- parts = text.strip.split(/\s+/)
72
- AIXM.f(parts[0].to_f, parts[1]) if parts[1] =~ /hz$/i
73
- end
74
-
75
- def channel_from(text)
76
- parts = text.strip.split(/\s+/)
77
- parts.last if parts[-2].downcase == 'ch'
78
- end
79
-
80
- def remarks_from(*parts)
81
- part_titles = ['RANGE', 'SITUATION', 'OBSERVATIONS']
82
- [].tap do |remarks|
83
- parts.each.with_index do |part, index|
84
- text = if index == 0
85
- part = part.text.strip.split(/\s+/)
86
- part.shift(2)
87
- part.join(' ').blank_to_nil
88
- else
89
- part.text.strip.blank_to_nil
90
- end
91
- remarks << "**#{part_titles[index]}**\n#{text}" if text
92
- end
93
- end.join("\n\n").blank_to_nil
94
- end
95
- end
96
- end
97
- end
@@ -1,28 +0,0 @@
1
- module AIPP
2
- module LF
3
-
4
- # Designated Points
5
- class ENR43 < AIP
6
-
7
- include AIPP::LF::Helpers::Common
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
- designated_point = AIXM.designated_point(
14
- type: :icao,
15
- id: tds[0].text.strip,
16
- xy: xy_from(tds[1].text)
17
- )
18
- designated_point.source = source(position: tr.line)
19
- add designated_point
20
- rescue => error
21
- warn("error parsing designated point at ##{index}: #{error.message}", pry: error)
22
- end
23
- end
24
- end
25
-
26
- end
27
- end
28
- end
@@ -1,75 +0,0 @@
1
- module AIPP
2
- module LF
3
-
4
- # D/P/R Zones
5
- class ENR51 < AIP
6
-
7
- include AIPP::LF::Helpers::Common
8
-
9
- # Map source types to type and optional local type
10
- SOURCE_TYPES = {
11
- 'D' => { type: 'D' },
12
- 'P' => { type: 'P' },
13
- 'R' => { type: 'R' },
14
- 'ZIT' => { type: 'P', local_type: 'ZIT' }
15
- }.freeze
16
-
17
- def parse
18
- prepare(html: read).css('tbody:has(tr[id^=mid])').each do |tbody|
19
- airspace = nil
20
- tbody.css('tr').to_enum.with_index(1).each do |tr, index|
21
- tds = tr.css('td')
22
- case
23
- when tr.attr(:id).match?(/TXT_NAME/) # airspace
24
- airspace = airspace_from tr
25
- when tds.count == 1 # big comment on separate row
26
- airspace.layers.first.remarks.
27
- concat("\n", tds.text.cleanup).
28
- remove!(/\((\d)\)\s*\(\1\)\W*/)
29
- else # layer
30
- begin
31
- tds = tr.css('td')
32
- airspace.geometry = geometry_from tds[0].text
33
- fail("geometry is not closed") unless airspace.geometry.closed?
34
- airspace.layers << layer_from(tds[1].text)
35
- airspace.layers.first.timetable = timetable_from! tds[2].text
36
- airspace.layers.first.remarks = remarks_from(tds[2], tds[3], tds[4])
37
- add airspace
38
- rescue => error
39
- warn("error parsing airspace `#{airspace.name}' at ##{index}: #{error.message}", pry: error)
40
- end
41
- end
42
- end
43
- end
44
- end
45
-
46
- private
47
-
48
- def airspace_from(tr)
49
- spans = tr.css('span:not([class*=strong])')
50
- source_type = spans[1].text.blank_to_nil
51
- fail "unknown type `#{source_type}'" unless SOURCE_TYPES.has_key? source_type
52
- AIXM.airspace(
53
- name: spans.map { |s| s.text.strip.blank_to_nil }.compact.join(' '),
54
- type: SOURCE_TYPES.dig(source_type, :type),
55
- local_type: SOURCE_TYPES.dig(source_type, :local_type)
56
- ).tap do |airspace|
57
- airspace.source = source(position: tr.line)
58
- end
59
- end
60
-
61
- def remarks_from(*parts)
62
- part_titles = ['TIMETABLE', 'RESTRICTION', 'AUTHORITY/CONDITIONS']
63
- [].tap do |remarks|
64
- parts.each.with_index do |part, index|
65
- if part = part.text.gsub(/ +/, ' ').gsub(/(\n ?)+/, "\n").strip.blank_to_nil
66
- unless index.zero? && part == 'H24'
67
- remarks << "**#{part_titles[index]}**\n#{part}"
68
- end
69
- end
70
- end
71
- end.join("\n\n").blank_to_nil
72
- end
73
- end
74
- end
75
- end
@@ -1,53 +0,0 @@
1
- module AIPP
2
- module LF
3
-
4
- # Sporting and Recreational Activities
5
- class ENR55 < AIP
6
-
7
- include AIPP::LF::Helpers::Common
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_limits, timetable = trs.first.css('td')
23
- activity, name = activity_and_name.css('span')
24
- lateral_limits, lower_limits, remarks = trs.last.css('td')
25
- lateral_limits.search('br').each { |br| br.replace("|||") }
26
- geometry, lateral_limits = lateral_limits.text.split('|||', 2)
27
- lateral_limits&.gsub!('|||', "\n")
28
- remarks = [remarks&.text&.cleanup&.blank_to_nil]
29
- s = timetable&.text&.cleanup and remarks.prepend('**SCHEDULE**', s, '')
30
- s = lateral_limits&.cleanup and remarks.prepend('**LATERAL LIMITS**', 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.layers << layer_from([upper_limits.text, lower_limits.text].join('---').cleanup).tap do |layer|
39
- layer.activity = ACTIVITIES.fetch(activity.text.downcase).fetch(:activity)
40
- layer.remarks = remarks.compact.join("\n")
41
- end
42
- end
43
- rescue => error
44
- warn("error parsing #{airspace.type} `#{airspace.name}' at ##{index}: #{error.message}", pry: error)
45
- end
46
- add airspace
47
- end
48
- end
49
- end
50
-
51
- end
52
- end
53
- end