aipp 0.2.4 → 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 (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