aipp 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/.travis.yml +1 -2
- data/CHANGELOG.md +15 -0
- data/README.md +122 -37
- data/TODO.md +4 -0
- data/aipp.gemspec +8 -3
- data/lib/aipp.rb +14 -2
- data/lib/aipp/aip.rb +44 -29
- data/lib/aipp/downloader.rb +115 -0
- data/lib/aipp/executable.rb +6 -6
- data/lib/aipp/parser.rb +23 -23
- data/lib/aipp/patcher.rb +47 -0
- data/lib/aipp/pdf.rb +123 -0
- data/lib/aipp/regions/LF/AD-1.3.rb +162 -0
- data/lib/aipp/regions/LF/AD-1.3.yml +511 -0
- data/lib/aipp/regions/LF/AD-1.6.rb +31 -0
- data/lib/aipp/regions/LF/AD-2.rb +316 -0
- data/lib/aipp/regions/LF/AD-2.yml +185 -0
- data/lib/aipp/regions/LF/AD-3.1.rb-NEW +11 -0
- data/lib/aipp/regions/LF/ENR-2.1.rb +25 -24
- data/lib/aipp/regions/LF/ENR-4.1.rb +24 -23
- data/lib/aipp/regions/LF/ENR-4.3.rb +8 -6
- data/lib/aipp/regions/LF/ENR-5.1.rb +32 -22
- data/lib/aipp/regions/LF/ENR-5.5.rb-NEW +11 -0
- data/lib/aipp/regions/LF/helpers/AD_radio.rb +90 -0
- data/lib/aipp/regions/LF/helpers/URL.rb +26 -0
- data/lib/aipp/regions/LF/helpers/common.rb +186 -0
- data/lib/aipp/version.rb +1 -1
- data/lib/core_ext/enumerable.rb +52 -0
- data/lib/core_ext/nil_class.rb +10 -0
- data/lib/core_ext/object.rb +42 -0
- data/lib/core_ext/string.rb +105 -0
- data/spec/fixtures/archive.zip +0 -0
- data/spec/fixtures/document.pdf +0 -0
- data/spec/fixtures/document.pdf.json +1 -0
- data/spec/fixtures/new.html +6 -0
- data/spec/fixtures/new.pdf +0 -0
- data/spec/fixtures/new.txt +1 -0
- data/spec/lib/aipp/downloader_spec.rb +81 -0
- data/spec/lib/aipp/patcher_spec.rb +46 -0
- data/spec/lib/aipp/pdf_spec.rb +124 -0
- data/spec/lib/core_ext/enumberable_spec.rb +76 -0
- data/spec/lib/core_ext/nil_class_spec.rb +11 -0
- data/spec/lib/core_ext/string_spec.rb +88 -0
- data/spec/spec_helper.rb +1 -0
- metadata +123 -23
- data/lib/aipp/progress.rb +0 -40
- data/lib/aipp/refinements.rb +0 -114
- data/lib/aipp/regions/LF/helper.rb +0 -177
- data/spec/lib/aipp/refinements_spec.rb +0 -123
@@ -3,16 +3,17 @@ module AIPP
|
|
3
3
|
|
4
4
|
# FIR, TMA etc
|
5
5
|
class ENR21 < AIP
|
6
|
-
using AIPP::Refinements
|
7
6
|
|
8
|
-
|
7
|
+
include AIPP::LF::Helpers::Common
|
8
|
+
|
9
|
+
# Map source types to type and optional local type
|
9
10
|
SOURCE_TYPES = {
|
10
|
-
'FIR' => { type: 'FIR'
|
11
|
-
'UIR' => { type: 'UIR'
|
12
|
-
'UTA' => { type: 'UTA'
|
13
|
-
'CTA' => { type: 'CTA'
|
11
|
+
'FIR' => { type: 'FIR' },
|
12
|
+
'UIR' => { type: 'UIR' },
|
13
|
+
'UTA' => { type: 'UTA' },
|
14
|
+
'CTA' => { type: 'CTA' },
|
14
15
|
'LTA' => { type: 'CTA', local_type: 'LTA' },
|
15
|
-
'TMA' => { type: 'TMA'
|
16
|
+
'TMA' => { type: 'TMA' },
|
16
17
|
'SIV' => { type: 'SECTOR', local_type: 'SIV' } # providing FIS
|
17
18
|
}.freeze
|
18
19
|
|
@@ -26,39 +27,39 @@ module AIPP
|
|
26
27
|
}.freeze
|
27
28
|
|
28
29
|
def parse
|
29
|
-
|
30
|
+
prepare(html: read).css('tbody').each do |tbody|
|
30
31
|
airspace = nil
|
31
32
|
tbody.css('tr').to_enum.with_index(1).each do |tr, index|
|
32
33
|
if tr.attr(:id).match?(/--TXT_NAME/)
|
33
34
|
aixm.features << airspace if airspace
|
34
|
-
airspace = airspace_from
|
35
|
-
|
35
|
+
airspace = airspace_from tr.css(:td).first
|
36
|
+
debug "Parsing #{airspace.type} #{airspace.name}" unless airspace.type == :terminal_control_area
|
36
37
|
next
|
37
38
|
end
|
38
39
|
begin
|
39
|
-
tds =
|
40
|
+
tds = tr.css('td')
|
40
41
|
if airspace.type == :terminal_control_area && tds[0].text.blank_to_nil
|
41
42
|
airspace = airspace_from tds[0]
|
42
|
-
|
43
|
+
debug "Parsing #{airspace.type} #{airspace.name}"
|
43
44
|
end
|
44
45
|
if airspace
|
45
46
|
if tds[0].text.blank_to_nil
|
46
|
-
airspace.geometry = geometry_from tds[0]
|
47
|
+
airspace.geometry = geometry_from tds[0].text
|
47
48
|
fail("geometry is not closed") unless airspace.geometry.closed?
|
48
49
|
end
|
49
|
-
layer = layer_from(tds[-3])
|
50
|
-
layer.class = class_from(tds[1]) if tds.count == 5
|
50
|
+
layer = layer_from(tds[-3].text)
|
51
|
+
layer.class = class_from(tds[1].text) if tds.count == 5
|
51
52
|
layer.location_indicator = LOCATION_INDICATORS.fetch("#{airspace.type} #{airspace.name}", nil)
|
52
53
|
# TODO: unit, call sign and frequency from tds[-2]
|
53
|
-
layer.timetable = timetable_from(tds[-1])
|
54
|
-
layer.remarks = remarks_from(tds[-1])
|
54
|
+
layer.timetable = timetable_from(tds[-1].text)
|
55
|
+
layer.remarks = remarks_from(tds[-1].text)
|
55
56
|
airspace.layers << layer
|
56
57
|
end
|
57
58
|
rescue => error
|
58
|
-
warn("error parsing #{airspace.type} `#{airspace.name}' at ##{index}: #{error.message}",
|
59
|
+
warn("error parsing #{airspace.type} `#{airspace.name}' at ##{index}: #{error.message}", pry: error)
|
59
60
|
end
|
60
61
|
end
|
61
|
-
|
62
|
+
write airspace if airspace
|
62
63
|
end
|
63
64
|
end
|
64
65
|
|
@@ -73,16 +74,16 @@ module AIPP
|
|
73
74
|
type: SOURCE_TYPES.dig(source_type, :type),
|
74
75
|
local_type: SOURCE_TYPES.dig(source_type, :local_type)
|
75
76
|
).tap do |airspace|
|
76
|
-
airspace.source =
|
77
|
+
airspace.source = source(position: td.line)
|
77
78
|
end
|
78
79
|
end
|
79
80
|
|
80
|
-
def class_from(
|
81
|
-
|
81
|
+
def class_from(text)
|
82
|
+
text.strip
|
82
83
|
end
|
83
84
|
|
84
|
-
def remarks_from(
|
85
|
-
|
85
|
+
def remarks_from(text)
|
86
|
+
text.strip.gsub(/(\s)\s+/, '\1').blank_to_nil
|
86
87
|
end
|
87
88
|
end
|
88
89
|
end
|
@@ -3,21 +3,22 @@ module AIPP
|
|
3
3
|
|
4
4
|
# ENR Navaids
|
5
5
|
class ENR41 < AIP
|
6
|
-
|
6
|
+
|
7
|
+
include AIPP::LF::Helpers::Common
|
7
8
|
|
8
9
|
def parse
|
9
|
-
|
10
|
+
prepare(html: read).css('tbody').each do |tbody|
|
10
11
|
tbody.css('tr').to_enum.with_index(1).each do |tr, index|
|
11
|
-
tds =
|
12
|
+
tds = tr.css('td')
|
12
13
|
master, slave = tds[1].text.strip.gsub(/[^\w-]/, '').downcase.split('-')
|
13
14
|
navaid = AIXM.send(master, base_from(tds).merge(send("#{master}_from", tds)))
|
14
|
-
navaid.source =
|
15
|
-
navaid.timetable = timetable_from(tds[4])
|
15
|
+
navaid.source = source(position: tr.line)
|
16
|
+
navaid.timetable = timetable_from(tds[4].text)
|
16
17
|
navaid.remarks = remarks_from(tds[5], tds[7], tds[9])
|
17
|
-
navaid.send("associate_#{slave}", channel: channel_from(tds[3])) if slave
|
18
|
-
|
18
|
+
navaid.send("associate_#{slave}", channel: channel_from(tds[3].text)) if slave
|
19
|
+
write navaid
|
19
20
|
rescue => error
|
20
|
-
warn("error parsing navigational aid at ##{index}: #{error.message}",
|
21
|
+
warn("error parsing navigational aid at ##{index}: #{error.message}", pry: error)
|
21
22
|
end
|
22
23
|
end
|
23
24
|
end
|
@@ -29,55 +30,55 @@ module AIPP
|
|
29
30
|
organisation: organisation_lf,
|
30
31
|
id: tds[2].text.strip,
|
31
32
|
name: tds[0].text.strip,
|
32
|
-
xy: xy_from(tds[5]),
|
33
|
-
z: z_from(tds[6])
|
33
|
+
xy: xy_from(tds[5].text),
|
34
|
+
z: z_from(tds[6].text)
|
34
35
|
}
|
35
36
|
end
|
36
37
|
|
37
38
|
def vor_from(tds)
|
38
39
|
{
|
39
40
|
type: :conventional,
|
40
|
-
f:
|
41
|
+
f: f_from(tds[3].text),
|
41
42
|
north: :magnetic,
|
42
43
|
}
|
43
44
|
end
|
44
45
|
|
45
46
|
def dme_from(tds)
|
46
47
|
{
|
47
|
-
channel: channel_from(tds[3])
|
48
|
+
channel: channel_from(tds[3].text)
|
48
49
|
}
|
49
50
|
end
|
50
51
|
|
51
52
|
def ndb_from(tds)
|
52
53
|
{
|
53
54
|
type: :en_route,
|
54
|
-
f:
|
55
|
+
f: f_from(tds[3].text)
|
55
56
|
}
|
56
57
|
end
|
57
58
|
|
58
59
|
def tacan_from(tds)
|
59
60
|
{
|
60
|
-
channel: channel_from(tds[3])
|
61
|
+
channel: channel_from(tds[3].text)
|
61
62
|
}
|
62
63
|
end
|
63
64
|
|
64
|
-
def z_from(
|
65
|
-
parts =
|
65
|
+
def z_from(text)
|
66
|
+
parts = text.strip.split(/\s+/)
|
66
67
|
AIXM.z(parts[0].to_i, :qnh) if parts[1] == 'ft'
|
67
68
|
end
|
68
69
|
|
69
|
-
def
|
70
|
-
parts =
|
70
|
+
def f_from(text)
|
71
|
+
parts = text.strip.split(/\s+/)
|
71
72
|
AIXM.f(parts[0].to_f, parts[1]) if parts[1] =~ /hz$/i
|
72
73
|
end
|
73
74
|
|
74
|
-
def channel_from(
|
75
|
-
parts =
|
75
|
+
def channel_from(text)
|
76
|
+
parts = text.strip.split(/\s+/)
|
76
77
|
parts.last if parts[-2].downcase == 'ch'
|
77
78
|
end
|
78
79
|
|
79
|
-
def timetable_from(
|
80
|
-
code =
|
80
|
+
def timetable_from(text)
|
81
|
+
code = text.strip
|
81
82
|
AIXM.timetable(code: code) unless code.empty?
|
82
83
|
end
|
83
84
|
|
@@ -92,7 +93,7 @@ module AIPP
|
|
92
93
|
else
|
93
94
|
part.text.strip.blank_to_nil
|
94
95
|
end
|
95
|
-
remarks << "
|
96
|
+
remarks << "**#{part_titles[index]}**\n#{text}" if text
|
96
97
|
end
|
97
98
|
end.join("\n\n").blank_to_nil
|
98
99
|
end
|
@@ -4,19 +4,21 @@ module AIPP
|
|
4
4
|
# Designated Points
|
5
5
|
class ENR43 < AIP
|
6
6
|
|
7
|
+
include AIPP::LF::Helpers::Common
|
8
|
+
|
7
9
|
def parse
|
8
|
-
|
10
|
+
prepare(html: read).css('tbody').each do |tbody|
|
9
11
|
tbody.css('tr').to_enum.with_index(1).each do |tr, index|
|
10
|
-
tds =
|
12
|
+
tds = tr.css('td')
|
11
13
|
designated_point = AIXM.designated_point(
|
12
14
|
type: :icao,
|
13
15
|
id: tds[0].text.strip,
|
14
|
-
xy: xy_from(tds[1])
|
16
|
+
xy: xy_from(tds[1].text)
|
15
17
|
)
|
16
|
-
designated_point.source =
|
17
|
-
|
18
|
+
designated_point.source = source(position: tr.line)
|
19
|
+
write designated_point
|
18
20
|
rescue => error
|
19
|
-
warn("error parsing designated point at ##{index}: #{error.message}",
|
21
|
+
warn("error parsing designated point at ##{index}: #{error.message}", pry: error)
|
20
22
|
end
|
21
23
|
end
|
22
24
|
end
|
@@ -3,32 +3,40 @@ module AIPP
|
|
3
3
|
|
4
4
|
# D/P/R Zones
|
5
5
|
class ENR51 < AIP
|
6
|
-
using AIPP::Refinements
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
'
|
7
|
+
include AIPP::LF::Helpers::Common
|
8
|
+
|
9
|
+
# Map source types to type and optional local type
|
10
|
+
SOURCE_TYPES = {
|
11
|
+
'D' => { type: 'D' },
|
12
|
+
'P' => { type: 'P' },
|
13
|
+
'R' => { type: 'R' },
|
14
|
+
'ZIT' => { type: 'P', local_type: 'ZIT' }
|
13
15
|
}.freeze
|
14
16
|
|
15
17
|
def parse
|
16
|
-
|
18
|
+
prepare(html: read).css('tbody:has(tr[id^=mid])').each do |tbody|
|
17
19
|
airspace = nil
|
18
20
|
tbody.css('tr').to_enum.with_index(1).each do |tr, index|
|
19
|
-
|
20
|
-
|
21
|
-
|
21
|
+
tds = tr.css('td')
|
22
|
+
case
|
23
|
+
when tr.attr(:id).match?(/TXT_NAME/) # airspace
|
24
|
+
airspace = airspace_from tr
|
25
|
+
when tds.count == 1 # big comment on separate row
|
26
|
+
airspace.layers.first.remarks.
|
27
|
+
concat("\n", tds.text.cleanup).
|
28
|
+
remove!(/\((\d)\)\s*\(\1\)\W*/)
|
29
|
+
else # layer
|
22
30
|
begin
|
23
|
-
tds =
|
24
|
-
airspace.geometry = geometry_from tds[0]
|
31
|
+
tds = tr.css('td')
|
32
|
+
airspace.geometry = geometry_from tds[0].text
|
25
33
|
fail("geometry is not closed") unless airspace.geometry.closed?
|
26
|
-
airspace.layers << layer_from(tds[1])
|
27
|
-
airspace.layers.first.timetable = timetable_from tds[2]
|
34
|
+
airspace.layers << layer_from(tds[1].text)
|
35
|
+
airspace.layers.first.timetable = timetable_from tds[2].text
|
28
36
|
airspace.layers.first.remarks = remarks_from(tds[2], tds[3], tds[4])
|
29
|
-
|
37
|
+
write airspace
|
30
38
|
rescue => error
|
31
|
-
warn("error parsing airspace `#{airspace.name}' at ##{index}: #{error.message}",
|
39
|
+
warn("error parsing airspace `#{airspace.name}' at ##{index}: #{error.message}", pry: error)
|
32
40
|
end
|
33
41
|
end
|
34
42
|
end
|
@@ -38,13 +46,15 @@ module AIPP
|
|
38
46
|
private
|
39
47
|
|
40
48
|
def airspace_from(tr)
|
41
|
-
spans = tr.css(:
|
49
|
+
spans = tr.css('span:not([class*=strong])')
|
50
|
+
source_type = spans[1].text.blank_to_nil
|
51
|
+
fail "unknown type `#{source_type}'" unless SOURCE_TYPES.has_key? source_type
|
42
52
|
AIXM.airspace(
|
43
|
-
name:
|
44
|
-
|
45
|
-
|
53
|
+
name: spans.map { |s| s.text.strip.blank_to_nil }.compact.join(' '),
|
54
|
+
type: SOURCE_TYPES.dig(source_type, :type),
|
55
|
+
local_type: SOURCE_TYPES.dig(source_type, :local_type)
|
46
56
|
).tap do |airspace|
|
47
|
-
airspace.source =
|
57
|
+
airspace.source = source(position: tr.line)
|
48
58
|
end
|
49
59
|
end
|
50
60
|
|
@@ -54,7 +64,7 @@ module AIPP
|
|
54
64
|
parts.each.with_index do |part, index|
|
55
65
|
if part = part.text.gsub(/ +/, ' ').gsub(/(\n ?)+/, "\n").strip.blank_to_nil
|
56
66
|
unless index.zero? && part == 'H24'
|
57
|
-
remarks << "
|
67
|
+
remarks << "**#{part_titles[index]}**\n#{part}"
|
58
68
|
end
|
59
69
|
end
|
60
70
|
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module AIPP
|
2
|
+
module LF
|
3
|
+
module Helpers
|
4
|
+
module ADRadio
|
5
|
+
|
6
|
+
# Service types to be ignored
|
7
|
+
IGNORED_TYPES = %w(D-ATIS).freeze
|
8
|
+
|
9
|
+
# Service types to be encoded as addresses
|
10
|
+
ADDRESS_TYPES = %w(A/A A/G).freeze
|
11
|
+
|
12
|
+
# Unknown service types to be encoded as units
|
13
|
+
SERVICE_TYPES = {
|
14
|
+
'CEV' => { type: :other, remarks: "CEV (centre d'essais en vol / flight test center)" },
|
15
|
+
'SRE' => { type: :other, remarks: "SRE (elément radar de surveillance du PAR / surveillance radar element of PAR)" }
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
def parts_from(tds)
|
19
|
+
{
|
20
|
+
f: AIXM.f(tds[2].css('span').first.text.to_f, tds[2].css('span').last.text),
|
21
|
+
callsign: tds[1].text.strip,
|
22
|
+
timetable: tds[3].text.strip,
|
23
|
+
remarks: tds[4].text.strip.sub(/Canal (8.33|25)/i, '') # TEMP: ignore canal spacing warnings
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def addresses_from(trs)
|
28
|
+
trs.map do |tr|
|
29
|
+
tds = tr.css('td')
|
30
|
+
type = tds[0].text.strip
|
31
|
+
next if IGNORED_TYPES.include? type
|
32
|
+
f, callsign, _, remarks = parts_from(tds).values
|
33
|
+
if ADDRESS_TYPES.include?(type)
|
34
|
+
AIXM.address(
|
35
|
+
source: source(position: tr.line),
|
36
|
+
type: :radio_frequency,
|
37
|
+
address: f.to_s
|
38
|
+
).tap do |address|
|
39
|
+
address.remarks = ["#{type} - indicatif/callsign #{callsign}", remarks.blank_to_nil].compact.join("\n")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end.compact
|
43
|
+
end
|
44
|
+
|
45
|
+
def units_from(trs)
|
46
|
+
trs.each_with_object({}) do |tr, services|
|
47
|
+
tds = tr.css('td')
|
48
|
+
type = tds[0].text.strip
|
49
|
+
next if IGNORED_TYPES.include?(type) || ADDRESS_TYPES.include?(type)
|
50
|
+
f, callsign, timetable, remarks = parts_from(tds).values
|
51
|
+
if SERVICE_TYPES.include? type
|
52
|
+
type = SERVICE_TYPES.dig(type, :type)
|
53
|
+
remarks = [SERVICE_TYPES.dig(type, :remarks), remarks.blank_to_nil].compact.join("\n")
|
54
|
+
end
|
55
|
+
unless services.include? type
|
56
|
+
services[type] = AIXM.service(
|
57
|
+
source: source(position: tr.line),
|
58
|
+
type: type
|
59
|
+
)
|
60
|
+
end
|
61
|
+
code = $1 if timetable.sub!(/(#{AIXM::H_RE})\b/, '')
|
62
|
+
services[type].add_frequency(
|
63
|
+
AIXM.frequency(
|
64
|
+
transmission_f: f,
|
65
|
+
callsigns: { fr: callsign }
|
66
|
+
).tap do |frequency|
|
67
|
+
frequency.type = :standard
|
68
|
+
frequency.type = :alternative if remarks.sub!(%r{fréquence supplétive/auxiliary frequency\S*}i, '')
|
69
|
+
frequency.timetable = AIXM.timetable(code: code) if code
|
70
|
+
frequency.remarks = [remarks, timetable.blank_to_nil].compact.join("\n").cleanup.blank_to_nil
|
71
|
+
end
|
72
|
+
)
|
73
|
+
end.values.map do |service|
|
74
|
+
AIXM.unit(
|
75
|
+
source: service.source,
|
76
|
+
organisation: organisation_lf, # TODO: not yet implemented
|
77
|
+
type: (type = service.guessed_unit_type),
|
78
|
+
name: "#{@id} #{AIXM::Feature::Unit::TYPES.key(type)}",
|
79
|
+
class: :icao # TODO: verify whether all units are ICAO
|
80
|
+
).tap do |unit|
|
81
|
+
unit.airport = @airport
|
82
|
+
unit.add_service(service)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module AIPP
|
2
|
+
module LF
|
3
|
+
module Helpers
|
4
|
+
module URL
|
5
|
+
|
6
|
+
# @param aip_file [String] e.g. ENR-5.1, AD-2.LFMV or VAC-LFMV
|
7
|
+
def url_for(aip_file)
|
8
|
+
case aip_file
|
9
|
+
when /^VAC\-(\w+)/
|
10
|
+
"https://www.sia.aviation-civile.gouv.fr/dvd/eAIP_%s/Atlas-VAC/PDF_AIPparSSection/VAC/AD/AD-2.%s.pdf" % [
|
11
|
+
options[:airac].date.strftime('%d_%^b_%Y'), # 04_JAN_2018
|
12
|
+
$1
|
13
|
+
]
|
14
|
+
else
|
15
|
+
"https://www.sia.aviation-civile.gouv.fr/dvd/eAIP_%s/FRANCE/AIRAC-%s/html/eAIP/FR-%s-fr-FR.html" % [
|
16
|
+
options[:airac].date.strftime('%d_%^b_%Y'), # 04_JAN_2018
|
17
|
+
options[:airac].date.xmlschema, # 2018-01-04
|
18
|
+
aip_file
|
19
|
+
]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|