aipp 0.2.6 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,223 @@
1
+ module AIPP
2
+ module LF
3
+
4
+ class Aerodromes < AIP
5
+
6
+ include AIPP::LF::Helpers::Base
7
+ include AIPP::LF::Helpers::UsageLimitation
8
+ include AIPP::LF::Helpers::Surface
9
+
10
+ APPROACH_LIGHTING_TYPES = {
11
+ 'CAT I' => :cat_1,
12
+ 'CAT II' => :cat_2,
13
+ 'CAT III' => :cat_3,
14
+ 'CAT II-III' => :cat_2_and_3
15
+ }.freeze
16
+
17
+ LIGHTING_POSITIONS = {
18
+ threshold: 'Thr',
19
+ touch_down_zone: 'Tdz',
20
+ center_line: 'Axe',
21
+ edge: 'Bord',
22
+ runway_end: 'Fin',
23
+ stopway_center_line: 'Swy'
24
+ }.freeze
25
+
26
+ LIGHTING_COLORS = {
27
+ 'W' => :white,
28
+ 'R' => :red,
29
+ 'G' => :green,
30
+ 'B' => :blue,
31
+ 'Y' => :yellow
32
+ }.freeze
33
+
34
+ ICAO_LIGHTING_COLORS = {
35
+ center_line: :white,
36
+ edge: :white
37
+ }.freeze
38
+
39
+ def parse
40
+ cache.ad.css(%Q(Ad[lk^="[LF]"])).each do |ad_node|
41
+ # Build airport
42
+ next unless limitation_type = LIMITATION_TYPES.fetch(ad_node.(:AdStatut))
43
+ airport = AIXM.airport(
44
+ source: source(section: 'AD', position: ad_node.line),
45
+ organisation: organisation_lf,
46
+ id: id_from(ad_node.(:AdCode)),
47
+ name: ad_node.(:AdNomComplet),
48
+ xy: xy_from(ad_node.(:Geometrie))
49
+ ).tap do |airport|
50
+ airport.meta = ad_node.attr('pk')
51
+ airport.z = given(ad_node.(:AdRefAltFt)) { AIXM.z(_1.to_i, :qnh) }
52
+ airport.declination = ad_node.(:AdMagVar)&.to_f
53
+ airport.add_usage_limitation(type: limitation_type.fetch(:limitation)) do |limitation|
54
+ limitation.remarks = limitation_type[:remarks]
55
+ [
56
+ (:scheduled if ad_node.(:TfcRegulier?)),
57
+ (:not_scheduled if ad_node.(:TfcNonRegulier?)),
58
+ (:private if ad_node.(:TfcPrive?)),
59
+ (:other unless ad_node.(:TfcRegulier?) || ad_node.(:TfcNonRegulier?) || ad_node.(:TfcPrive?))
60
+ ].compact.each do |purpose|
61
+ limitation.add_condition do |condition|
62
+ condition.realm = limitation_type.fetch(:realm)
63
+ condition.origin = case
64
+ when ad_node.(:TfcIntl?) && ad_node.(:TfcNtl?) then :any
65
+ when ad_node.(:TfcIntl?) then :international
66
+ when ad_node.(:TfcNtl?) then :national
67
+ else :other
68
+ end
69
+ condition.rule = case
70
+ when ad_node.(:TfcIfr?) && ad_node.(:TfcVfr?) then :ifr_and_vfr
71
+ when ad_node.(:TfcIfr?) then :ifr
72
+ when ad_node.(:TfcVfr?) then :vfr
73
+ else
74
+ warn("falling back to VFR rule for `#{airport.id}'", severe: false)
75
+ :vfr
76
+ end
77
+ condition.purpose = purpose
78
+ end
79
+ end
80
+ end
81
+ # TODO: link to VAC once supported downstream
82
+ # # Link to VAC
83
+ # airport.remarks = [
84
+ # airport.remarks.to_s,
85
+ # link_to('VAC-AD', url_for("VAC-#{airport.id}"))
86
+ # ].join("\n")
87
+ cache.rwy.css(%Q(Rwy:has(Ad[pk="#{ad_node.attr(:pk)}"]))).each do |rwy_node|
88
+ add_runway_to(airport, rwy_node)
89
+ end
90
+ end
91
+ add airport
92
+ end
93
+ end
94
+
95
+ private
96
+
97
+ def id_from(content)
98
+ case content
99
+ when /^\d{2}$/ then 'LF00' + content # private aerodromes without official ID
100
+ else 'LF' + content
101
+ end
102
+ end
103
+
104
+ def add_runway_to(airport, rwy_node)
105
+ AIXM.runway(
106
+ name: rwy_node.(:Rwy)
107
+ ).tap do |runway|
108
+ rwylgt_nodes = cache.rwylgt.css(%Q(RwyLgt:has(Rwy[pk="#{rwy_node.attr(:pk)}"])))
109
+ airport.add_runway(runway)
110
+ runway.dimensions = AIXM.r(AIXM.d(rwy_node.(:Longueur)&.to_i, :m), AIXM.d(rwy_node.(:Largeur)&.to_i, :m))
111
+ runway.surface = surface_from(rwy_node)
112
+ runway.forth.geographic_bearing = given(rwy_node.(:OrientationGeo)) { AIXM.a(_1.to_f) }
113
+ runway.forth.xy = given(rwy_node.(:LatThr1), rwy_node.(:LongThr1)) { AIXM.xy(lat: _1.to_f, long: _2.to_f) }
114
+ runway.forth.displaced_threshold = given(rwy_node.(:LatDThr1), rwy_node.(:LongDThr1)) { AIXM.xy(lat: _1.to_f, long: _2.to_f) }
115
+ runway.forth.z = given(rwy_node.(:AltFtDThr1)) { AIXM.z(_1.to_i, :qnh) }
116
+ runway.forth.z ||= given(rwy_node.(:AltFtThr1)) { AIXM.z(_1.to_i, :qnh) }
117
+ if rwylgt_node = rwylgt_nodes[0]
118
+ runway.forth.vasis = vasis_from(rwylgt_node)
119
+ given(approach_lighting_from(rwylgt_node)) { runway.forth.add_approach_lighting(_1) }
120
+ LIGHTING_POSITIONS.each_key do |position|
121
+ given(lighting_from(rwylgt_node, position)) { runway.forth.add_lighting(_1) }
122
+ end
123
+ end
124
+ if rwy_node.(:Rwy).match? '/'
125
+ runway.back.xy = given(rwy_node.(:LatThr2), rwy_node.(:LongThr2)) { AIXM.xy(lat: _1.to_f, long: _2.to_f) }
126
+ runway.back.displaced_threshold = given(rwy_node.(:LatDThr2), rwy_node.(:LongDThr2)) { AIXM.xy(lat: _1.to_f, long: _2.to_f) }
127
+ runway.back.z = given(rwy_node.(:AltFtDThr2)) { AIXM.z(_1.to_i, :qnh) }
128
+ runway.back.z ||= given(rwy_node.(:AltFtThr2)) { AIXM.z(_1.to_i, :qnh) }
129
+ if rwylgt_node = rwylgt_nodes[1]
130
+ runway.back.vasis = vasis_from(rwylgt_node)
131
+ given(approach_lighting_from(rwylgt_node)) { runway.back.add_approach_lighting(_1) }
132
+ LIGHTING_POSITIONS.each_key do |position|
133
+ given(lighting_from(rwylgt_node, position)) { runway.back.add_lighting(_1) }
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ def vasis_from(rwylgt_node)
141
+ if rwylgt_node.(:PapiVasis)
142
+ AIXM.vasis.tap do |vasis|
143
+ vasis.type = rwylgt_node.(:PapiVasis)
144
+ vasis.slope_angle = AIXM.a(rwylgt_node.(:PapiVasisPente).to_f)
145
+ vasis.meht = AIXM.z(rwylgt_node.(:MehtFt).to_i, :qfe)
146
+ end
147
+ end
148
+ end
149
+
150
+ def approach_lighting_from(rwylgt_node)
151
+ if rwylgt_node.(:LgtApchCat)
152
+ AIXM.approach_lighting(
153
+ type: APPROACH_LIGHTING_TYPES.fetch(rwylgt_node.(:LgtApchCat) , :other)
154
+ ).tap do |approach_lighting|
155
+ approach_lighting.length = AIXM.d(rwylgt_node.(:LgtApchLongueur).to_i, :m) if rwylgt_node.(:LgtApchLongueur)
156
+ approach_lighting.intensity = rwylgt_node.(:LgtApchIntensite)&.first_match(/LIH/, /LIM/, /LIL/, default: :other)
157
+ approach_lighting.remarks = {
158
+ 'type' => (rwylgt_node.(:LgtApchCat) if approach_lighting.type == :other),
159
+ 'intensité/intensity' => (rwylgt_node.(:LgtApchIntensite) if approach_lighting.intensity == :other)
160
+ }.to_remarks
161
+ end
162
+ end
163
+ end
164
+
165
+ def lighting_from(rwylgt_node, position)
166
+ prefix = "Lgt" + LIGHTING_POSITIONS.fetch(position)
167
+ if rwylgt_node.(:"#{prefix}Couleur") || rwylgt_node.(:"#{prefix}Longueur")
168
+ AIXM.lighting(position: position).tap do |lighting|
169
+ couleur, intensite = rwylgt_node.(:"#{prefix}Couleur"), rwylgt_node.(:"#{prefix}Intensite")
170
+ lighting.intensity = if intensite
171
+ intensite.first_match(/LIH/, /LIM/, /LIL/, default: :other)
172
+ elsif couleur
173
+ couleur.first_match(/LIH/, /LIM/, /LIL/).tap { couleur.remove!(/LIH|LIM|LIL/) }
174
+ end
175
+ lighting.color = if couleur
176
+ if couleur.match? /ICAO|EASA|OACI|AESA/
177
+ ICAO_LIGHTING_COLORS[position]
178
+ else
179
+ couleur.remove(/[^#{LIGHTING_COLORS.keys.join}]/).compact
180
+ LIGHTING_COLORS.fetch(couleur, :other)
181
+ end
182
+ end
183
+ lighting.description = {
184
+ 'couleur/color' => (rwylgt_node.(:"#{prefix}Couleur") if [nil, :other].include?(lighting.color)),
185
+ 'longueur/length' => rwylgt_node.(:"#{prefix}Longueur"),
186
+ 'espace/spacing' => rwylgt_node.(:"#{prefix}Espace")
187
+ }.to_remarks
188
+ lighting.remarks = rwylgt_node.(:LgtRem)
189
+ end
190
+ end
191
+ end
192
+
193
+ patch AIXM::Feature::Airport, :xy do |parser, object, value|
194
+ throw(:abort) unless coordinate = parser.fixture.dig(object.id, 'xy')
195
+ lat, long = coordinate.split(/\s+/)
196
+ AIXM.xy(lat: lat, long: long)
197
+ end
198
+
199
+ patch AIXM::Feature::Airport, :z do |parser, object, value|
200
+ throw(:abort) unless value.nil?
201
+ throw(:abort, 'fixture missing') unless elevation = parser.fixture.dig(object.id, 'z')
202
+ AIXM.z(elevation, :qnh)
203
+ end
204
+
205
+ patch AIXM::Component::Runway, :dimensions do |parser, object, value|
206
+ throw(:abort) unless value.surface.zero?
207
+ throw(:abort, 'fixture missing') unless dimensions = parser.fixture.dig(object.airport.id, object.name, 'dimensions')
208
+ length, width = dimensions.split(/\D+/)
209
+ length = length&.match?(/^\d+$/) ? AIXM.d(length.to_i, :m) : value.length
210
+ width = width&.match?(/^\d+$/) ? AIXM.d(width.to_i, :m) : value.width
211
+ AIXM.r(length, width).tap { |r| throw(:abort, 'fixture incomplete') if r.surface.zero? }
212
+ end
213
+
214
+ patch AIXM::Component::Runway::Direction, :xy do |parser, object, value|
215
+ throw(:abort) unless value.nil?
216
+ throw(:abort, 'fixture missing') unless coordinate = parser.fixture.dig(object.runway.airport.id, object.name.to_s(:runway), 'xy')
217
+ lat, long = coordinate.split(/\s+/)
218
+ AIXM.xy(lat: lat, long: long)
219
+ end
220
+
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,56 @@
1
+ module AIPP
2
+ module LF
3
+
4
+ class DPRAirspaces < AIP
5
+
6
+ include AIPP::LF::Helpers::Base
7
+
8
+ # Map source types to type and optional local type
9
+ SOURCE_TYPES = {
10
+ 'D' => { type: 'D' },
11
+ 'P' => { type: 'P' },
12
+ 'R' => { type: 'R' },
13
+ 'ZIT' => { type: 'P', local_type: 'ZIT' }
14
+ }.freeze
15
+
16
+ # Radius to use for zones consisting of one point only
17
+ POINT_RADIUS = AIXM.d(1, :km).freeze
18
+
19
+ def parse
20
+ SOURCE_TYPES.each do |source_type, target|
21
+ verbose_info("processing #{source_type}")
22
+ cache.espace.css(%Q(Espace[lk^="[LF][#{source_type} "])).each do |espace_node|
23
+ # UPSTREAM: Espace[pk=300343] has no Partie/Volume (reported)
24
+ next if espace_node['pk'] == '300343'
25
+ partie_node = cache.partie.at_css(%Q(Partie:has(Espace[pk="#{espace_node['pk']}"])))
26
+ volume_node = cache.volume.at_css(%Q(Volume:has(Partie[pk="#{partie_node['pk']}"])))
27
+ name = "#{options[:region]}-#{source_type}#{espace_node.(:Nom)}".remove(/\s/)
28
+ add(
29
+ AIXM.airspace(
30
+ source: source(section: 'ENR', position: espace_node.line),
31
+ name: "#{name} #{partie_node.(:NomUsuel)}".strip,
32
+ type: target[:type],
33
+ local_type: target[:local_type]
34
+ ).tap do |airspace|
35
+ airspace.geometry = geometry_from(partie_node.(:Contour))
36
+ if airspace.geometry.point? # convert point to circle
37
+ airspace.geometry = AIXM.geometry(
38
+ AIXM.circle(
39
+ center_xy: airspace.geometry.segments.first.xy,
40
+ radius: POINT_RADIUS
41
+ )
42
+ )
43
+ end
44
+ fail("geometry is not closed") unless airspace.geometry.closed?
45
+ airspace.add_layer layer_from(volume_node)
46
+ airspace.layers.first.timetable = timetable_from(volume_node.(:HorCode))
47
+ airspace.layers.first.remarks = volume_node.(:Activite)
48
+ end
49
+ )
50
+ end
51
+ end
52
+ end
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,49 @@
1
+ module AIPP
2
+ module LF
3
+
4
+ class DangerousActivities < AIP
5
+
6
+ include AIPP::LF::Helpers::Base
7
+
8
+ # Map raw activities to type of activity airspace
9
+ ACTIVITIES = {
10
+ 'AP' => { activity: :other, airspace: :dangerous_activities_area },
11
+ 'Aer' => { activity: :aeromodelling, airspace: :dangerous_activities_area },
12
+ 'Bal' => { activity: :balloon, airspace: :dangerous_activities_area },
13
+ 'Pje' => { activity: :parachuting, airspace: :dangerous_activities_area },
14
+ 'TrPVL' => { activity: :glider_winch, airspace: :dangerous_activities_area },
15
+ 'TrPla' => { activity: :glider_winch, airspace: :dangerous_activities_area },
16
+ 'TrVL' => { activity: :glider_winch, airspace: :dangerous_activities_area },
17
+ 'Vol' => { activity: :acrobatics, airspace: :dangerous_activities_area }
18
+ }.freeze
19
+
20
+ def parse
21
+ ACTIVITIES.each do |code, type|
22
+ verbose_info("processing #{code}")
23
+ cache.espace.css(%Q(Espace[lk^="[LF][#{code} "])).each do |espace_node|
24
+ partie_node = cache.partie.at_css(%Q(Partie:has(Espace[pk="#{espace_node['pk']}"])))
25
+ volume_node = cache.volume.at_css(%Q(Volume:has(Partie[pk="#{partie_node['pk']}"])))
26
+ add(
27
+ AIXM.airspace(
28
+ source: source(section: 'ENR', position: espace_node.line),
29
+ id: espace_node.(:Nom),
30
+ type: type[:airspace],
31
+ local_type: code.upcase,
32
+ name: [espace_node.(:Nom), partie_node.(:NomUsuel)].join(' ')
33
+ ).tap do |airspace|
34
+ airspace.geometry = geometry_from partie_node.(:Contour)
35
+ layer_from(volume_node).then do |layer|
36
+ layer.activity = type[:activity]
37
+ airspace.add_layer layer
38
+ end
39
+ airspace.layers.first.timetable = timetable_from(volume_node.(:HorCode))
40
+ airspace.layers.first.remarks = volume_node.(:Remarque)
41
+ end
42
+ )
43
+ end
44
+ end
45
+ end
46
+
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,47 @@
1
+ module AIPP
2
+ module LF
3
+
4
+ class DesignatedPoints < AIP
5
+
6
+ include AIPP::LF::Helpers::Base
7
+
8
+ DEPENDS = %w(aerodromes)
9
+
10
+ SOURCE_TYPES = {
11
+ 'VFR' => :vfr_reporting_point,
12
+ 'WPT' => :icao
13
+ }.freeze
14
+
15
+ def parse
16
+ SOURCE_TYPES.each do |source_type, type|
17
+ verbose_info("processing #{source_type}")
18
+ cache.navfix.css(%Q(NavFix[lk^="[LF][#{source_type} "])).each do |navfix_node|
19
+ ident = navfix_node.(:Ident)
20
+ add(
21
+ AIXM.designated_point(
22
+ source: source(section: 'ENR', position: navfix_node.line),
23
+ type: type,
24
+ id: ident.split('-').last.remove(/[^a-z\d]/i), # only use last segment of ID
25
+ name: ident,
26
+ xy: xy_from(navfix_node.(:Geometrie))
27
+ ).tap do |designated_point|
28
+ designated_point.remarks = navfix_node.(:Description)
29
+ if ident.match? /-/
30
+ airport = find_by(:airport, id: "LF#{ident.split('-').first}").first
31
+ designated_point.airport = airport
32
+ end
33
+ end
34
+ )
35
+ end
36
+ end
37
+ AIXM::Memoize.method :to_uid do
38
+ aixm.features.find_by(:designated_point).duplicates.each do |duplicates|
39
+ duplicates.first.name += '/' + duplicates[1..].map(&:name).join('/')
40
+ aixm.remove_features(duplicates[1..])
41
+ end
42
+ end
43
+ end
44
+
45
+ end
46
+ end
47
+ end