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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +2 -2
- data/CHANGELOG.md +17 -1
- data/README.md +269 -150
- data/exe/aip2aixm +2 -8
- data/exe/aip2ofmx +2 -8
- data/exe/notam2aixm +5 -0
- data/exe/notam2ofmx +5 -0
- data/lib/aipp/aip/README.md +10 -0
- data/lib/aipp/aip/executable.rb +40 -0
- data/lib/aipp/aip/parser.rb +9 -0
- data/lib/aipp/aip/runner.rb +85 -0
- data/lib/aipp/border.rb +2 -2
- data/lib/aipp/debugger.rb +14 -19
- data/lib/aipp/downloader/file.rb +57 -0
- data/lib/aipp/downloader/graphql.rb +29 -0
- data/lib/aipp/downloader/http.rb +48 -0
- data/lib/aipp/downloader.rb +78 -29
- data/lib/aipp/environment.rb +88 -0
- data/lib/aipp/executable.rb +36 -53
- data/lib/aipp/notam/README.md +25 -0
- data/lib/aipp/notam/executable.rb +27 -0
- data/lib/aipp/notam/parser.rb +9 -0
- data/lib/aipp/notam/runner.rb +28 -0
- data/lib/aipp/parser.rb +133 -160
- data/lib/aipp/patcher.rb +4 -5
- data/lib/aipp/regions/LF/README.md +6 -2
- data/lib/aipp/regions/LF/aip/aerodromes.rb +220 -0
- data/lib/aipp/regions/LF/aip/d_p_r_airspaces.rb +53 -0
- data/lib/aipp/regions/LF/aip/dangerous_activities.rb +48 -0
- data/lib/aipp/regions/LF/aip/designated_points.rb +44 -0
- data/lib/aipp/regions/LF/aip/helipads.rb +119 -0
- data/lib/aipp/regions/LF/aip/navigational_aids.rb +82 -0
- data/lib/aipp/regions/LF/aip/obstacles.rb +150 -0
- data/lib/aipp/regions/LF/aip/serviced_airspaces.rb +67 -0
- data/lib/aipp/regions/LF/aip/services.rb +169 -0
- data/lib/aipp/regions/LF/fixtures/aerodromes.yml +2 -2
- data/lib/aipp/regions/LF/helpers/base.rb +32 -32
- data/lib/aipp/regions/LS/README.md +59 -0
- data/lib/aipp/regions/LS/helpers/base.rb +111 -0
- data/lib/aipp/regions/LS/notam/ENR.rb +173 -0
- data/lib/aipp/runner.rb +152 -0
- data/lib/aipp/version.rb +1 -1
- data/lib/aipp.rb +30 -11
- data/lib/core_ext/array.rb +13 -0
- data/lib/core_ext/nokogiri.rb +56 -8
- data/lib/core_ext/string.rb +63 -1
- data.tar.gz.sig +0 -0
- metadata +115 -64
- metadata.gz.sig +0 -0
- data/lib/aipp/aip.rb +0 -166
- data/lib/aipp/regions/LF/aerodromes.rb +0 -223
- data/lib/aipp/regions/LF/d_p_r_airspaces.rb +0 -56
- data/lib/aipp/regions/LF/dangerous_activities.rb +0 -49
- data/lib/aipp/regions/LF/designated_points.rb +0 -47
- data/lib/aipp/regions/LF/helipads.rb +0 -122
- data/lib/aipp/regions/LF/navigational_aids.rb +0 -85
- data/lib/aipp/regions/LF/obstacles.rb +0 -153
- data/lib/aipp/regions/LF/serviced_airspaces.rb +0 -70
- 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
|