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
@@ -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