aipp 0.2.1 → 0.2.2
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
- 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
|