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