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.
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