aipp 1.0.0 → 2.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 (60) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +2 -2
  3. data/CHANGELOG.md +17 -1
  4. data/README.md +269 -150
  5. data/exe/aip2aixm +2 -8
  6. data/exe/aip2ofmx +2 -8
  7. data/exe/notam2aixm +5 -0
  8. data/exe/notam2ofmx +5 -0
  9. data/lib/aipp/aip/README.md +10 -0
  10. data/lib/aipp/aip/executable.rb +40 -0
  11. data/lib/aipp/aip/parser.rb +9 -0
  12. data/lib/aipp/aip/runner.rb +85 -0
  13. data/lib/aipp/border.rb +2 -2
  14. data/lib/aipp/debugger.rb +14 -19
  15. data/lib/aipp/downloader/file.rb +57 -0
  16. data/lib/aipp/downloader/graphql.rb +29 -0
  17. data/lib/aipp/downloader/http.rb +48 -0
  18. data/lib/aipp/downloader.rb +78 -29
  19. data/lib/aipp/environment.rb +88 -0
  20. data/lib/aipp/executable.rb +36 -53
  21. data/lib/aipp/notam/README.md +25 -0
  22. data/lib/aipp/notam/executable.rb +27 -0
  23. data/lib/aipp/notam/parser.rb +9 -0
  24. data/lib/aipp/notam/runner.rb +28 -0
  25. data/lib/aipp/parser.rb +133 -160
  26. data/lib/aipp/patcher.rb +4 -5
  27. data/lib/aipp/regions/LF/README.md +6 -2
  28. data/lib/aipp/regions/LF/aip/aerodromes.rb +220 -0
  29. data/lib/aipp/regions/LF/aip/d_p_r_airspaces.rb +53 -0
  30. data/lib/aipp/regions/LF/aip/dangerous_activities.rb +48 -0
  31. data/lib/aipp/regions/LF/aip/designated_points.rb +44 -0
  32. data/lib/aipp/regions/LF/aip/helipads.rb +119 -0
  33. data/lib/aipp/regions/LF/aip/navigational_aids.rb +82 -0
  34. data/lib/aipp/regions/LF/aip/obstacles.rb +150 -0
  35. data/lib/aipp/regions/LF/aip/serviced_airspaces.rb +67 -0
  36. data/lib/aipp/regions/LF/aip/services.rb +169 -0
  37. data/lib/aipp/regions/LF/fixtures/aerodromes.yml +2 -2
  38. data/lib/aipp/regions/LF/helpers/base.rb +32 -32
  39. data/lib/aipp/regions/LS/README.md +59 -0
  40. data/lib/aipp/regions/LS/helpers/base.rb +111 -0
  41. data/lib/aipp/regions/LS/notam/ENR.rb +173 -0
  42. data/lib/aipp/runner.rb +152 -0
  43. data/lib/aipp/version.rb +1 -1
  44. data/lib/aipp.rb +30 -11
  45. data/lib/core_ext/array.rb +13 -0
  46. data/lib/core_ext/nokogiri.rb +56 -8
  47. data/lib/core_ext/string.rb +63 -1
  48. data.tar.gz.sig +0 -0
  49. metadata +115 -64
  50. metadata.gz.sig +0 -0
  51. data/lib/aipp/aip.rb +0 -166
  52. data/lib/aipp/regions/LF/aerodromes.rb +0 -223
  53. data/lib/aipp/regions/LF/d_p_r_airspaces.rb +0 -56
  54. data/lib/aipp/regions/LF/dangerous_activities.rb +0 -49
  55. data/lib/aipp/regions/LF/designated_points.rb +0 -47
  56. data/lib/aipp/regions/LF/helipads.rb +0 -122
  57. data/lib/aipp/regions/LF/navigational_aids.rb +0 -85
  58. data/lib/aipp/regions/LF/obstacles.rb +0 -153
  59. data/lib/aipp/regions/LF/serviced_airspaces.rb +0 -70
  60. data/lib/aipp/regions/LF/services.rb +0 -172
@@ -1,56 +0,0 @@
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
@@ -1,49 +0,0 @@
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
@@ -1,47 +0,0 @@
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
@@ -1,122 +0,0 @@
1
- module AIPP
2
- module LF
3
-
4
- class Helipads < AIP
5
-
6
- include AIPP::LF::Helpers::Base
7
- include AIPP::LF::Helpers::UsageLimitation
8
- include AIPP::LF::Helpers::Surface
9
-
10
- DEPENDS = %w(aerodromes)
11
-
12
- HOSTILITIES = {
13
- 'hostile habitée' => 'Zone hostile habitée / hostile populated area',
14
- 'hostile non habitée' => 'Zone hostile non habitée / hostile unpopulated area',
15
- 'non hostile' => 'Zone non hostile / non-hostile area'
16
- }.freeze
17
-
18
- ELEVATED = {
19
- true => 'En terrasse / on deck',
20
- false => 'En surface / on ground'
21
- }.freeze
22
-
23
- def parse
24
- cache.helistation.css(%Q(Helistation[lk^="[LF]"])).each do |helistation_node|
25
- # Build airport if necessary
26
- next unless limitation_type = LIMITATION_TYPES.fetch(helistation_node.(:Statut))
27
- name = helistation_node.(:Nom)
28
- airport = find_by(:airport, name: name).first || add(
29
- AIXM.airport(
30
- source: source(section: 'AD', position: helistation_node.line),
31
- organisation: organisation_lf,
32
- id: options[:region],
33
- name: name,
34
- xy: xy_from(helistation_node.(:Geometrie))
35
- ).tap do |airport|
36
- airport.z = AIXM.z(helistation_node.(:AltitudeFt).to_i, :qnh)
37
- airport.add_usage_limitation(type: limitation_type.fetch(:limitation)) do |limitation|
38
- limitation.remarks = limitation_type[:remarks]
39
- [:private].each do |purpose| # TODO: check and simplify
40
- limitation.add_condition do |condition|
41
- condition.realm = limitation_type.fetch(:realm)
42
- condition.origin = :any
43
- condition.rule = case
44
- when helistation_node.(:Ifr?) then :ifr_and_vfr
45
- else :vfr
46
- end
47
- condition.purpose = purpose
48
- end
49
- end
50
- end
51
- end
52
- )
53
- # TODO: link to VAC once supported downstream
54
- # # Link to VAC
55
- # if helistation_node.(:Atlas?)
56
- # vac = "VAC-#{airport.id}" if airport.id.match?(/^LF[A-Z]{2}$/)
57
- # vac ||= "VACH-H#{airport.name[0, 3].upcase}"
58
- # airport.remarks = [
59
- # airport.remarks.to_s,
60
- # link_to('VAC-HP', url_for(vac))
61
- # ].join("\n")
62
- # end
63
- # Add helipad and FATO
64
- airport.add_helipad(
65
- AIXM.helipad(
66
- name: 'TLOF',
67
- xy: xy_from(helistation_node.(:Geometrie))
68
- ).tap do |helipad|
69
- helipad.z = AIXM.z(helistation_node.(:AltitudeFt).to_i, :qnh)
70
- helipad.dimensions = dimensions_from(helistation_node.(:DimTlof))
71
- end.tap do |helipad|
72
- airport.add_helipad(helipad)
73
- helipad.performance_class = performance_class_from(helistation_node.(:ClassePerf))
74
- helipad.surface = surface_from(helistation_node)
75
- helipad.marking = helistation_node.(:Balisage) unless helistation_node.(:Balisage)&.match?(/^nil$/i)
76
- helipad.add_lighting(AIXM.lighting(position: :other)) if helistation_node.(:Nuit?) || helistation_node.(:Balisage)&.match?(/feu/i)
77
- helipad.remarks = {
78
- 'position/positioning' => [
79
- (HOSTILITIES.fetch(helistation_node.(:ZoneHabitee)) if helistation_node.(:ZoneHabitee)),
80
- (ELEVATED.fetch(helistation_node.(:EnTerrasse?)) if helistation_node.(:EnTerrasse)),
81
- ].compact.join("\n"),
82
- 'hauteur/height' => given(helistation_node.(:HauteurFt)) { "#{_1} ft" },
83
- 'exploitant/operator' => helistation_node.(:Exploitant)
84
- }.to_remarks
85
- if fato_dimensions = dimensions_from(helistation_node.(:DimFato))
86
- AIXM.fato(name: 'FATO').tap do |fato|
87
- fato.dimensions = fato_dimensions
88
- airport.add_fato(fato)
89
- helipad.fato = fato
90
- end
91
- end
92
- end
93
- )
94
- end
95
- end
96
-
97
- private
98
-
99
- def dimensions_from(content)
100
- if content
101
- dims = content.remove(/[^x\d.,]/i).split(/x/i).map { _1.to_ff.floor }
102
- case dims.size
103
- when 1
104
- AIXM.r(AIXM.d(dims[0], :m))
105
- when 2
106
- AIXM.r(AIXM.d(dims[0], :m), AIXM.d(dims[1], :m))
107
- when 4
108
- AIXM.r(AIXM.d(dims.min, :m))
109
- else
110
- warn("ignoring dimensions `#{content}'", severe: false)
111
- nil
112
- end
113
- end
114
- end
115
-
116
- def performance_class_from(content)
117
- content.remove(/\d{2,}/).scan(/\d/).map(&:to_i).min&.to_s if content
118
- end
119
-
120
- end
121
- end
122
- end
@@ -1,85 +0,0 @@
1
- module AIPP
2
- module LF
3
-
4
- class NavigationalAids < AIP
5
-
6
- include AIPP::LF::Helpers::Base
7
-
8
- SOURCE_TYPES = {
9
- 'DME-ATT' => [:dme],
10
- 'TACAN' => [:tacan],
11
- 'VOR' => [:vor],
12
- 'VOR-DME' => [:vor, :dme],
13
- 'VORTAC' => [:vor, :tacan],
14
- 'NDB' => [:ndb]
15
- }.freeze
16
-
17
- def parse
18
- SOURCE_TYPES.each do |source_type, (primary_type, secondary_type)|
19
- verbose_info("processing #{source_type}")
20
- cache.navfix.css(%Q(NavFix[lk^="[LF][#{source_type} "])).each do |navfix_node|
21
- attributes = {
22
- source: source(section: 'ENR', position: navfix_node.line),
23
- organisation: organisation_lf,
24
- id: navfix_node.(:Ident),
25
- xy: xy_from(navfix_node.(:Geometrie))
26
- }
27
- if radionav_node = cache.radionav.at_css(%Q(RadioNav:has(NavFix[pk="#{navfix_node.attr(:pk)}"])))
28
- attributes.merge! send(primary_type, radionav_node)
29
- add(
30
- AIXM.send(primary_type, **attributes).tap do |navigational_aid|
31
- navigational_aid.name = radionav_node.(:NomPhraseo) || radionav_node.(:Station)
32
- navigational_aid.timetable = timetable_from(radionav_node.(:HorCode))
33
- navigational_aid.remarks = {
34
- "location/situation" => radionav_node.(:Situation),
35
- "range/portée" => range_from(radionav_node)
36
- }.to_remarks
37
- navigational_aid.send("associate_#{secondary_type}") if secondary_type
38
- end
39
- )
40
- else
41
- verbose_info("skipping incomplete #{source_type} #{attributes[:id]}")
42
- end
43
- end
44
- end
45
- end
46
-
47
- private
48
-
49
- def dme(radionav_node)
50
- {
51
- ghost_f: AIXM.f(radionav_node.(:Frequence).to_f, :mhz),
52
- z: AIXM.z(radionav_node.(:AltitudeFt).to_i, :qnh)
53
- }
54
- end
55
- alias_method :tacan, :dme
56
-
57
- def vor(radionav_node)
58
- {
59
- type: :conventional,
60
- north: :magnetic,
61
- name: radionav_node.(:Station),
62
- f: AIXM.f(radionav_node.(:Frequence).to_f, :mhz),
63
- z: AIXM.z(radionav_node.(:AltitudeFt).to_i, :qnh),
64
- }
65
- end
66
-
67
- def ndb(radionav_node)
68
- {
69
- type: :en_route,
70
- f: AIXM.f(radionav_node.(:Frequence).to_f, :khz),
71
- z: AIXM.z(radionav_node.(:AltitudeFt).to_i, :qnh)
72
- }
73
- end
74
-
75
- def range_from(radionav_node)
76
- [
77
- radionav_node.(:Portee).blank_to_nil&.concat('NM'),
78
- radionav_node.(:FlPorteeVert).blank_to_nil&.prepend('FL'),
79
- radionav_node.(:Couverture).blank_to_nil
80
- ].compact.join(' / ')
81
- end
82
-
83
- end
84
- end
85
- end
@@ -1,153 +0,0 @@
1
- module AIPP
2
- module LF
3
-
4
- class Obstacles < AIP
5
-
6
- include AIPP::LF::Helpers::Base
7
-
8
- # Map type descriptions to AIXM types and remarks
9
- TYPES = {
10
- 'Antenne' => [:antenna],
11
- 'Autre' => [:other],
12
- 'Bâtiment' => [:building],
13
- 'Câble' => [:other, 'Cable / Câble'],
14
- 'Centrale thermique' => [:building, 'Thermal power plant / Centrale thermique'],
15
- "Château d'eau" => [:tower, "Water tower / Château d'eau"],
16
- 'Cheminée' => [:chimney],
17
- 'Derrick' => [:tower, 'Derrick'],
18
- 'Eglise' => [:tower, 'Church / Eglise'],
19
- 'Eolienne' => [:wind_turbine],
20
- 'Eolienne(s)' => [:wind_turbine],
21
- 'Grue' => [:tower, 'Crane / Grue'],
22
- 'Mât' => [:mast],
23
- 'Phare marin' => [:tower, 'Lighthouse / Phare marin'],
24
- 'Pile de pont' => [:other, 'Bridge piers / Pile de pont'],
25
- 'Portique' => [:building, 'Arch / Portique'],
26
- 'Pylône' => [:mast, 'Pylon / Pylône'],
27
- 'Silo' => [:tower, 'Silo'],
28
- 'Terril' => [:other, 'Spoil heap / Teril'],
29
- 'Torchère' => [:chimney, 'Flare / Torchère'],
30
- 'Tour' => [:tower],
31
- 'Treillis métallique' => [:other, 'Metallic grid / Treillis métallique']
32
- }.freeze
33
-
34
- def parse
35
- if options[:region_options].include? 'lf_obstacles_xlsx'
36
- info("reading obstacles from XLSX")
37
- @xlsx = read('Obstacles')
38
- parse_from_xlsx
39
- else
40
- parse_from_xml
41
- end
42
- end
43
-
44
- private
45
-
46
- def parse_from_xlsx
47
- # Build obstacles
48
- @xlsx.sheet(@xlsx.sheets.find(/^data/i).first).each(
49
- name: 'IDENTIFICATEUR',
50
- type: 'TYPE',
51
- count: 'NOMBRE',
52
- longitude: 'LONGITUDE DECIMALE',
53
- latitude: 'LATITUDE DECIMALE',
54
- elevation: 'ALTITUDE AU SOMMET',
55
- height: 'HAUTEUR HORS SOL',
56
- height_unit: 'UNITE',
57
- horizontal_accuracy: 'PRECISION HORIZONTALE',
58
- vertical_accuracy: 'PRECISION VERTICALE',
59
- visibility: 'BALISAGE',
60
- remarks: 'REMARK',
61
- effective_on: 'DATE DE MISE EN VIGUEUR'
62
- ).with_index(0) do |row, index|
63
- next unless row[:effective_on].to_s.match? /\d{8}/
64
- type, type_remarks = TYPES.fetch(row[:type])
65
- count = row[:count].to_i
66
- obstacle = AIXM.obstacle(
67
- source: source(section: 'ENR', position: index),
68
- name: row[:name],
69
- type: type,
70
- xy: AIXM.xy(lat: row[:latitude].to_f, long: row[:longitude].to_f),
71
- z: AIXM.z(row[:elevation].to_i, :qnh)
72
- ).tap do |obstacle|
73
- obstacle.height = AIXM.d(row[:height].to_i, row[:height_unit])
74
- if row[:horizontal_accuracy]
75
- accuracy = row[:horizontal_accuracy].split
76
- obstacle.xy_accuracy = AIXM.d(accuracy.first.to_i, accuracy.last)
77
- end
78
- if row[:vertical_accuracy]
79
- accuracy = row[:horizontal_accuracy].split
80
- obstacle.z_accuracy = AIXM.d(accuracy.first.to_i, accuracy.last)
81
- end
82
- obstacle.marking = row[:visibility].match?(/jour/i)
83
- obstacle.lighting = row[:visibility].match?(/nuit/i)
84
- obstacle.remarks = {
85
- 'type' => type_remarks,
86
- 'number/nombre' => (count if count > 1),
87
- 'details' => row[:remarks],
88
- 'effective/mise en vigueur' => (row[:effective_on].to_s.unpack("a4a2a2").join("-") if row[:updated_on])
89
- }.to_remarks
90
- # Group obstacles
91
- if aixm.features.find_by(:obstacle, xy: obstacle.xy).any?
92
- warn("duplicate obstacle #{obstacle.name}", severe: false)
93
- else
94
- if count > 1
95
- obstacle_group = AIXM.obstacle_group(
96
- source: obstacle.source,
97
- name: obstacle.name
98
- ).tap do |obstacle_group|
99
- obstacle_group.remarks = "#{count} obstacles"
100
- end
101
- obstacle_group.add_obstacle obstacle
102
- add obstacle_group
103
- else
104
- add obstacle
105
- end
106
- end
107
- end
108
- end
109
- end
110
-
111
- def parse_from_xml
112
- cache.obstacle.css(%Q(Obstacle[lk^="[LF]"])).each do |node|
113
- # Build obstacles
114
- type, type_remarks = TYPES.fetch(node.(:TypeObst))
115
- count = node.(:Combien).to_i
116
- obstacle = AIXM.obstacle(
117
- source: source(section: 'ENR', position: node.line),
118
- name: node.(:NumeroNom),
119
- type: type,
120
- xy: xy_from(node.(:Geometrie)),
121
- z: AIXM.z(node.(:AmslFt).to_i, :qnh)
122
- ).tap do |obstacle|
123
- obstacle.height = AIXM.d(node.(:AglFt).to_i, :ft)
124
- obstacle.marking = node.(:Balisage).match?(/jour/i)
125
- obstacle.lighting = node.(:Balisage).match?(/nuit/i)
126
- obstacle.remarks = {
127
- 'type' => type_remarks,
128
- 'number/nombre' => (count if count > 1)
129
- }.to_remarks
130
- end
131
- # Group obstacles
132
- if aixm.features.find_by(:obstacle, xy: obstacle.xy).any?
133
- warn("duplicate obstacle #{obstacle.name}", severe: false)
134
- else
135
- if count > 1
136
- obstacle_group = AIXM.obstacle_group(
137
- source: obstacle.source,
138
- name: obstacle.name
139
- ).tap do |obstacle_group|
140
- obstacle_group.remarks = "#{count} obstacles"
141
- end
142
- obstacle_group.add_obstacle obstacle
143
- add obstacle_group
144
- else
145
- add obstacle
146
- end
147
- end
148
- end
149
- end
150
-
151
- end
152
- end
153
- end
@@ -1,70 +0,0 @@
1
- module AIPP
2
- module LF
3
-
4
- class ServicedAirspaces < AIP
5
-
6
- include AIPP::LF::Helpers::Base
7
-
8
- # Map source types to type and optional local type and skip regexp
9
- SOURCE_TYPES = {
10
- 'FIR' => { type: 'FIR' },
11
- 'UIR' => { type: 'UIR' },
12
- 'UTA' => { type: 'UTA' },
13
- 'CTA' => { type: 'CTA' },
14
- 'LTA' => { type: 'CTA', local_type: 'LTA' },
15
- 'TMA' => { type: 'TMA', skip: /geneve/i }, # Geneva listed FYI only
16
- 'SIV' => { type: 'SECTOR', local_type: 'FIZ/SIV' }, # providing FIS
17
- 'CTR' => { type: 'CTR' },
18
- 'RMZ' => { type: 'RAS', local_type: 'RMZ' },
19
- 'TMZ' => { type: 'RAS', local_type: 'TMZ' },
20
- 'RMZ-TMZ' => { type: 'RAS', local_type: 'RMZ-TMZ' }
21
- }.freeze
22
-
23
- # Map airspace "<type> <name>" to location indicator
24
- FIR_LOCATION_INDICATORS = {
25
- 'BORDEAUX' => 'LFBB',
26
- 'BREST' => 'LFRR',
27
- 'MARSEILLE' => 'LFMM',
28
- 'PARIS' => 'LFFF',
29
- 'REIMS' => 'LFRR'
30
- }.freeze
31
-
32
- def parse
33
- SOURCE_TYPES.each do |source_type, target|
34
- verbose_info("processing #{source_type}")
35
- cache.espace.css(%Q(Espace[lk^="[LF][#{source_type} "])).each do |espace_node|
36
- # Skip all delegated airspaces
37
- next if espace_node.(:Nom).match? /deleg/i
38
- next if (re = target[:skip]) && espace_node.(:Nom).match?(re)
39
- # Build airspaces and layers
40
- cache.partie.css(%Q(Partie:has(Espace[pk="#{espace_node['pk']}"]))).each do |partie_node|
41
- add(
42
- AIXM.airspace(
43
- source: source(section: 'ENR', position: espace_node.line),
44
- name: [
45
- espace_node.(:Nom),
46
- partie_node.(:NomPartie).remove(/^\.$/).blank_to_nil
47
- ].compact.join(' '),
48
- type: target[:type],
49
- local_type: target[:local_type]
50
- ).tap do |airspace|
51
- airspace.meta = espace_node.attr('pk')
52
- airspace.geometry = geometry_from(partie_node.(:Contour))
53
- fail("geometry is not closed") unless airspace.geometry.closed?
54
- cache.volume.css(%Q(Volume:has(Partie[pk="#{partie_node['pk']}"]))).each do |volume_node|
55
- airspace.add_layer(
56
- layer_from(volume_node).tap do |layer|
57
- layer.location_indicator = FIR_LOCATION_INDICATORS.fetch(airspace.name) if airspace.type == :flight_information_region
58
- end
59
- )
60
- end
61
- end
62
- )
63
- end
64
- end
65
- end
66
- end
67
- end
68
-
69
- end
70
- end