aipp 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +1 -2
  4. data/CHANGELOG.md +15 -0
  5. data/README.md +122 -37
  6. data/TODO.md +4 -0
  7. data/aipp.gemspec +8 -3
  8. data/lib/aipp.rb +14 -2
  9. data/lib/aipp/aip.rb +44 -29
  10. data/lib/aipp/downloader.rb +115 -0
  11. data/lib/aipp/executable.rb +6 -6
  12. data/lib/aipp/parser.rb +23 -23
  13. data/lib/aipp/patcher.rb +47 -0
  14. data/lib/aipp/pdf.rb +123 -0
  15. data/lib/aipp/regions/LF/AD-1.3.rb +162 -0
  16. data/lib/aipp/regions/LF/AD-1.3.yml +511 -0
  17. data/lib/aipp/regions/LF/AD-1.6.rb +31 -0
  18. data/lib/aipp/regions/LF/AD-2.rb +316 -0
  19. data/lib/aipp/regions/LF/AD-2.yml +185 -0
  20. data/lib/aipp/regions/LF/AD-3.1.rb-NEW +11 -0
  21. data/lib/aipp/regions/LF/ENR-2.1.rb +25 -24
  22. data/lib/aipp/regions/LF/ENR-4.1.rb +24 -23
  23. data/lib/aipp/regions/LF/ENR-4.3.rb +8 -6
  24. data/lib/aipp/regions/LF/ENR-5.1.rb +32 -22
  25. data/lib/aipp/regions/LF/ENR-5.5.rb-NEW +11 -0
  26. data/lib/aipp/regions/LF/helpers/AD_radio.rb +90 -0
  27. data/lib/aipp/regions/LF/helpers/URL.rb +26 -0
  28. data/lib/aipp/regions/LF/helpers/common.rb +186 -0
  29. data/lib/aipp/version.rb +1 -1
  30. data/lib/core_ext/enumerable.rb +52 -0
  31. data/lib/core_ext/nil_class.rb +10 -0
  32. data/lib/core_ext/object.rb +42 -0
  33. data/lib/core_ext/string.rb +105 -0
  34. data/spec/fixtures/archive.zip +0 -0
  35. data/spec/fixtures/document.pdf +0 -0
  36. data/spec/fixtures/document.pdf.json +1 -0
  37. data/spec/fixtures/new.html +6 -0
  38. data/spec/fixtures/new.pdf +0 -0
  39. data/spec/fixtures/new.txt +1 -0
  40. data/spec/lib/aipp/downloader_spec.rb +81 -0
  41. data/spec/lib/aipp/patcher_spec.rb +46 -0
  42. data/spec/lib/aipp/pdf_spec.rb +124 -0
  43. data/spec/lib/core_ext/enumberable_spec.rb +76 -0
  44. data/spec/lib/core_ext/nil_class_spec.rb +11 -0
  45. data/spec/lib/core_ext/string_spec.rb +88 -0
  46. data/spec/spec_helper.rb +1 -0
  47. metadata +123 -23
  48. data/lib/aipp/progress.rb +0 -40
  49. data/lib/aipp/refinements.rb +0 -114
  50. data/lib/aipp/regions/LF/helper.rb +0 -177
  51. data/spec/lib/aipp/refinements_spec.rb +0 -123
@@ -0,0 +1,11 @@
1
+ module AIPP
2
+ module LF
3
+
4
+ # Helipads
5
+ class AD31 < AIP
6
+
7
+ include AIPP::LF::Helpers::Common
8
+
9
+ end
10
+ end
11
+ end
@@ -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
- # Map source types to type and local type
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', local_type: nil },
11
- 'UIR' => { type: 'UIR', local_type: nil },
12
- 'UTA' => { type: 'UTA', local_type: nil },
13
- 'CTA' => { type: 'CTA', local_type: nil },
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', local_type: nil },
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
- load_html.css('tbody').each do |tbody|
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 cleanup(node: tr).css(:td).first
35
- info "Parsing #{airspace.type} #{airspace.name}" unless airspace.type == :terminal_control_area
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 = cleanup(node: tr).css('td')
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
- info "Parsing #{airspace.type} #{airspace.name}"
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}", context: error)
59
+ warn("error parsing #{airspace.type} `#{airspace.name}' at ##{index}: #{error.message}", pry: error)
59
60
  end
60
61
  end
61
- aixm.features << airspace if airspace
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 = source_for(td)
77
+ airspace.source = source(position: td.line)
77
78
  end
78
79
  end
79
80
 
80
- def class_from(td)
81
- td.text.strip
81
+ def class_from(text)
82
+ text.strip
82
83
  end
83
84
 
84
- def remarks_from(td)
85
- td.text.strip.gsub(/(\s)\s+/, '\1').blank_to_nil
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
- using AIPP::Refinements
6
+
7
+ include AIPP::LF::Helpers::Common
7
8
 
8
9
  def parse
9
- load_html.css('tbody').each do |tbody|
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 = cleanup(node: tr).css('td')
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 = source_for(tr)
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
- aixm.features << navaid
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}", context: error)
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: frequency_from(tds[3]),
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: frequency_from(tds[3])
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(td)
65
- parts = td.text.strip.split(/\s+/)
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 frequency_from(td)
70
- parts = td.text.strip.split(/\s+/)
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(td)
75
- parts = td.text.strip.split(/\s+/)
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(td)
80
- code = td.text.strip
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 << "#{part_titles[index]}:\n#{text}" if text
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
- load_html.css('tbody').each do |tbody|
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 = cleanup(node: tr).css('td')
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 = source_for(tr)
17
- aixm.features << designated_point
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}", context: error)
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
- TYPES = {
9
- 'D' => 'D',
10
- 'P' => 'P',
11
- 'R' => 'R',
12
- 'ZIT' => 'P'
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
- load_html.css('tbody:has(tr[id^=mid])').each do |tbody|
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
- if tr.attr(:class) =~ /keep-with-next-row/
20
- airspace = airspace_from cleanup(node: tr)
21
- else
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 = cleanup(node: tr).css('td')
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
- aixm.features << airspace
37
+ write airspace
30
38
  rescue => error
31
- warn("error parsing airspace `#{airspace.name}' at ##{index}: #{error.message}", context: error)
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(:span)
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: [spans[1], spans[2], spans[3], spans[5].text.blank_to_nil].compact.join(' '),
44
- local_type: [spans[1], spans[2], spans[3]].compact.join(' '),
45
- type: TYPES.fetch(spans[2].text)
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 = source_for(tr)
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 << "#{part_titles[index]}:\n#{part}"
67
+ remarks << "**#{part_titles[index]}**\n#{part}"
58
68
  end
59
69
  end
60
70
  end
@@ -0,0 +1,11 @@
1
+ module AIPP
2
+ module LF
3
+
4
+ # Sporting and Recreational Activities
5
+ class ENR55 < AIP
6
+
7
+ include AIPP::LF::Helpers::Common
8
+
9
+ end
10
+ end
11
+ 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