aipp 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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