aipp 2.0.3 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +2 -2
  3. data/CHANGELOG.md +8 -0
  4. data/README.md +21 -14
  5. data/exe/aip2aixm +1 -1
  6. data/exe/aip2ofmx +1 -1
  7. data/lib/aipp/debugger.rb +5 -1
  8. data/lib/aipp/downloader/file.rb +7 -5
  9. data/lib/aipp/downloader/http.rb +4 -4
  10. data/lib/aipp/downloader.rb +9 -2
  11. data/lib/aipp/executable.rb +52 -31
  12. data/lib/aipp/regions/LF/aip/services.rb +24 -4
  13. data/lib/aipp/regions/LF/helpers/base.rb +4 -6
  14. data/lib/aipp/regions/LS/README.md +58 -2
  15. data/lib/aipp/regions/LS/helpers/base.rb +10 -5
  16. data/lib/aipp/regions/LS/notam/ENR.rb +12 -10
  17. data/lib/aipp/regions/LS/shoot/shooting_grounds.rb +116 -0
  18. data/lib/aipp/runner.rb +6 -6
  19. data/lib/aipp/scopes/aip/executable.rb +38 -0
  20. data/lib/aipp/{aip → scopes/aip}/runner.rb +7 -3
  21. data/lib/aipp/scopes/notam/executable.rb +28 -0
  22. data/lib/aipp/{notam → scopes/notam}/runner.rb +6 -2
  23. data/lib/aipp/scopes/shoot/README.md +10 -0
  24. data/lib/aipp/scopes/shoot/executable.rb +28 -0
  25. data/lib/aipp/scopes/shoot/parser.rb +9 -0
  26. data/lib/aipp/scopes/shoot/runner.rb +32 -0
  27. data/lib/aipp/version.rb +1 -1
  28. data/lib/aipp.rb +10 -8
  29. data.tar.gz.sig +0 -0
  30. metadata +16 -15
  31. metadata.gz.sig +0 -0
  32. data/exe/notam2aixm +0 -5
  33. data/exe/notam2ofmx +0 -5
  34. data/lib/aipp/aip/executable.rb +0 -40
  35. data/lib/aipp/notam/executable.rb +0 -27
  36. /data/lib/aipp/{aip → scopes/aip}/README.md +0 -0
  37. /data/lib/aipp/{aip → scopes/aip}/parser.rb +0 -0
  38. /data/lib/aipp/{notam → scopes/notam}/README.md +0 -0
  39. /data/lib/aipp/{notam → scopes/notam}/parser.rb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4d5b1987ad76642974e3525037cd251b9d191e6ddf3bb355452c06fc31c47734
4
- data.tar.gz: b282045f0717e48248e8c876bc0b117fee79c394f751bf3c20cf4c1e96597a3a
3
+ metadata.gz: d141c73e057eda545578568c2817d648c998e56154f1d68ecd40c59589a04738
4
+ data.tar.gz: 70986dde2f1a7120f49061a65ee1b07c980469ff5f9ea6da4527f0160f613ed8
5
5
  SHA512:
6
- metadata.gz: 80481622154b8a77f614758d41bddc1190d38bed8530f55dc08a6736e9852dac5cbf0bd6d767025dfdf1d73b8e727bb567c193a390546b4f8080d5686ce5163a
7
- data.tar.gz: dc7991927db97747f7e643ec7484420c741e8d64e33e57187e8c63a361ab45b0ea21666b7ff6e72c464283eee6670bea7f49e9e9ccbe1aeece75aedfdcad4396
6
+ metadata.gz: 1e97fc5731a4356836ce60703cddd076d33d99d94cfaa667a8f1cf203f28003710d3c27263efd8e638e902f6ee9a076c46fb36881575425bf994202842b2a3d3
7
+ data.tar.gz: '08648422f96c2dd925873ec4153b3420754323704df5ec037042bed1f5a4611929d01ae2ee6ab02b718e6e28f9ea57ef76481eaf7ab4abe0a6dd0d89a547fa90'
checksums.yaml.gz.sig CHANGED
@@ -1,2 +1,2 @@
1
- "N�F��������"LT����6��bЪ_��9�d�����8q����.�@�K���w���4Q�Y���x� ���-je$O}r��h_՞ˁ� �Mֈ�pU����.
2
- b/!�H�CBJ�Y�Ȩ�~�Lt�*� �Lx��qA�V��t�
1
+ 1�ǵm��Gy
2
+ Z��ѳ�y@�'i��:��x��� 琇�� �����%�\
data/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  Nothing so far
4
4
 
5
+ ## 2.1.0
6
+
7
+ #### Breaking Changes
8
+ * Unify all executables into `aip2aixm` and `aip2aixm` respectively
9
+
10
+ #### Additions
11
+ * Support for shooting grounds in LS
12
+
5
13
  ## 2.0.3
6
14
 
7
15
  #### Breaking Changes
data/README.md CHANGED
@@ -55,22 +55,29 @@ bundle install --trust-policy MediumSecurity
55
55
 
56
56
  ## Usage
57
57
 
58
- AIPP parses different kind of information sources. The parsers are organized in three levels:
58
+ AIPP parses different kind of information sources and converts them to different output formats depending on which executable you use:
59
+
60
+ Executable | Output Format
61
+ -----------|--------------
62
+ `aip2aixm` | AIXM
63
+ `aip2ofmx` | OFMX
64
+
65
+ The parsers are organized in three levels:
59
66
 
60
67
  ```
61
68
  region ⬅︎ aeronautical region such as "LF" (France)
62
- └── module ⬅︎ subject area such as "AIP" or "NOTAM"
63
- └── section ⬅︎ part of the subject area such as "ENR-2.1" or "aerodromes"
69
+ └── scope ⬅︎ scope such as "AIP" or "NOTAM"
70
+ └── section ⬅︎ section of the scope such as "ENR-2.1" or "aerodromes"
64
71
  ```
65
72
 
66
- The following modules are currently available:
73
+ The following scopes are currently available:
67
74
 
68
- Module | Content | Executables | Cache
69
- -------|---------|-------------|------
70
- [AIP](lib/aipp/aip/README.md) | aeronautical information publication | `aip2aixm` and `aip2ofmx` | by AIRAC cycle
71
- [NOTAM](lib/aipp/notam/README.md) | notice to airmen | `notam2aixm` and `notam2ofmx` | by effective date and hour
75
+ Scope | Content | Cache
76
+ ------|---------|------
77
+ [AIP](lib/aipp/aip/README.md) (default) | aeronautical information publication | by AIRAC cycle
78
+ [NOTAM](lib/aipp/notam/README.md) | notice to airmen | by effective date and hour
72
79
 
73
- To list all available regions and sections for a given module:
80
+ To list all available scopes, regions and sections:
74
81
 
75
82
  ```
76
83
  aip2aixm --list
@@ -92,15 +99,15 @@ You'll find the OFMX file in the current directory if the binary exits successfu
92
99
 
93
100
  ## Regions
94
101
 
95
- To implement a region, you have to create a directory <samp>lib/aipp/regions/{REGION}/</samp> off the gem root and then subdirectories for each module as well as for support files. Here's a simplified overview for the region "LF" (France):
102
+ To implement a region, you have to create a directory <samp>lib/aipp/regions/{REGION}/</samp> off the gem root and then subdirectories for each scope as well as for support files. Here's a simplified overview for the region "LF" (France):
96
103
 
97
104
  ```
98
105
  LF/ ⬅︎ region "LF"
99
106
  ├── README.md
100
- ├── aip ⬅︎ module "AIP"
107
+ ├── aip ⬅︎ scope "AIP"
101
108
  │   ├── AD-2.rb ⬅︎ section "AD-2"
102
109
  │   └── ENR-4.3.rb ⬅︎ section "ENR-4.3"
103
- ├── notam ⬅︎ module "NOTAM"
110
+ ├── notam ⬅︎ scope "NOTAM"
104
111
  │   ├── AD.rb ⬅︎ section "AD"
105
112
  │   └── ENR.rb ⬅︎ section "ENR"
106
113
  ├── borders
@@ -315,7 +322,7 @@ module MyAPI
315
322
  end
316
323
  ```
317
324
 
318
- For performance, all downloads are cached and subsequent runs will use the cached data rather than fetching the sources anew. Each module defines a cache time window, see the [table of modules above](#label-Usage). You can discard existing and rebuild caches by use of the `--clean` command line argument.
325
+ For performance, all downloads are cached and subsequent runs will use the cached data rather than fetching the sources anew. Each scope defines a cache time window, see the [table of scopes above](#label-Usage). You can discard existing and rebuild caches by use of the `--clean` command line argument.
319
326
 
320
327
  #### Optional `setup` Method
321
328
 
@@ -499,7 +506,7 @@ debugger
499
506
 
500
507
  AIPP uses a storage directory for configuration, caching and in order to keep the results of previous runs. The default location is `~/.aipp`, however, you can pass a different directory with the `--storage` argument.
501
508
 
502
- You'll find a directory for each region and module which contains the following items:
509
+ You'll find a directory for each region and scope which contains the following items:
503
510
 
504
511
  * `sources/`<br>ZIP archives which cache all source files used to build.
505
512
  * `builds/`<br>ZIP archives of successful builds containing:
data/exe/aip2aixm CHANGED
@@ -2,4 +2,4 @@
2
2
 
3
3
  require 'aipp'
4
4
 
5
- AIPP::AIP::Executable.new(File.basename($0)).run
5
+ AIPP::Executable.new(File.basename($0)).run
data/exe/aip2ofmx CHANGED
@@ -2,4 +2,4 @@
2
2
 
3
3
  require 'aipp'
4
4
 
5
- AIPP::AIP::Executable.new(File.basename($0)).run
5
+ AIPP::Executable.new(File.basename($0)).run
data/lib/aipp/debugger.rb CHANGED
@@ -78,7 +78,11 @@ module AIPP
78
78
  rescue => error
79
79
  message = error.respond_to?(:original_message) ? error.original_message : error.message
80
80
  puts "ERROR: #{message}".magenta
81
- raise if AIPP.options.verbose
81
+ if AIPP.options.verbose
82
+ raise
83
+ else
84
+ exit 1
85
+ end
82
86
  end
83
87
 
84
88
  def call_without_rescue(&block)
@@ -3,6 +3,8 @@ module AIPP
3
3
 
4
4
  # Local file
5
5
  class File
6
+ attr_reader :file
7
+
6
8
  def initialize(archive: nil, file:, type: nil)
7
9
  @archive = Pathname(archive) if archive
8
10
  @file, @type = Pathname(file), type&.to_s
@@ -12,10 +14,10 @@ module AIPP
12
14
  path.join(fetched_file).tap do |target|
13
15
  if @archive
14
16
  fail NotFoundError unless @archive.exist?
15
- extract(@file, from: @archive, as: target)
17
+ extract(file, from: @archive, as: target)
16
18
  else
17
- fail NotFoundError unless @file.exist?
18
- FileUtils.cp(@file, target)
19
+ fail NotFoundError unless file.exist?
20
+ FileUtils.cp(file, target)
19
21
  end
20
22
  end
21
23
  self
@@ -28,11 +30,11 @@ module AIPP
28
30
  private
29
31
 
30
32
  def name
31
- @file.basename(@file.extname).to_s
33
+ file.basename(file.extname).to_s
32
34
  end
33
35
 
34
36
  def type
35
- @type || @file.extname[1..] || fail("type must be declared")
37
+ @type || file.extname[1..] || fail("type must be declared")
36
38
  end
37
39
 
38
40
  def extract(file, from:, as:)
@@ -16,14 +16,14 @@ module AIPP
16
16
  # @param path [Pathname] directory where to write the fetched file
17
17
  # @return [File] fetched file
18
18
  def fetch_to(path)
19
- response = Excon.get((@archive || @file).to_s, headers: @headers)
19
+ response = Excon.get((@archive || file).to_s, headers: @headers)
20
20
  fail NotFoundError if response.status == 404
21
21
  mime_type = ARCHIVE_MIME_TYPES.fetch(response.headers['Content-Type'], :dat)
22
22
  downloaded_file = path.join([@digest, mime_type].join('.'))
23
23
  ::File.write(downloaded_file, response.body)
24
24
  path.join(fetched_file).tap do |target|
25
25
  if @archive
26
- extract(@file, from: downloaded_file, as: target)
26
+ extract(file, from: downloaded_file, as: target)
27
27
  ::File.delete(downloaded_file)
28
28
  else
29
29
  ::File.rename(downloaded_file, target)
@@ -35,12 +35,12 @@ module AIPP
35
35
  private
36
36
 
37
37
  def name
38
- path = Pathname(@file.path)
38
+ path = Pathname(file.path)
39
39
  path.basename(path.extname).to_s.blank_to_nil || @digest
40
40
  end
41
41
 
42
42
  def type
43
- @type || Pathname(@file.path).extname[1..].blank_to_nil || fail("type must be declared")
43
+ @type || Pathname(file.path).extname[1..].blank_to_nil || fail("type must be declared")
44
44
  end
45
45
  end
46
46
 
@@ -165,13 +165,20 @@ module AIPP
165
165
  case file.extname
166
166
  when '.xml', '.ofmx' then Nokogiri.XML(::File.open(file), &:noblanks)
167
167
  when '.html' then Nokogiri.HTML5(::File.open(file))
168
- when '.json' then JSON.load_file(file)
168
+ when '.json' then JSON.load_file(file, symbolize_names: true)
169
169
  when '.pdf' then AIPP::PDF.new(file)
170
- when '.xlsx', '.ods', '.csv' then Roo::Spreadsheet.open(file.to_s)
170
+ when '.xlsx', '.ods' then Roo::Spreadsheet.open(file.to_s)
171
+ when '.csv' then Roo::Spreadsheet.open(file.to_s, csv_options: { col_sep: separator(file) })
171
172
  when '.txt' then ::File.read(file)
172
173
  else fail(ArgumentError, "unrecognized file type")
173
174
  end
174
175
  end
175
176
 
177
+ # @return [String] most likely separator character of CSV and similar files
178
+ def separator(file)
179
+ content = file.read
180
+ %W(, ; \t).map { [content.scan(_1).count, _1] }.sort.last.last
181
+ end
182
+
176
183
  end
177
184
  end
@@ -5,7 +5,9 @@ module AIPP
5
5
  include AIPP::Debugger
6
6
 
7
7
  def initialize(exe_file)
8
+ require_scope
8
9
  AIPP.options.replace(
10
+ scope: scope,
9
11
  schema: exe_file.split('2').last.to_sym,
10
12
  storage: Pathname(Dir.home).join('.aipp'),
11
13
  clean: false,
@@ -16,13 +18,36 @@ module AIPP
16
18
  debug_on_warning: false,
17
19
  debug_on_error: false
18
20
  )
21
+ options
22
+ OptionParser.new do |o|
23
+ o.on('-r', '--region STRING', String, 'region (e.g. "LF")') { AIPP.options.region = _1.upcase }
24
+ o.on('-s', '--section STRING', String, 'process this section only') { AIPP.options.section = _1.classify }
25
+ o.on('-d', '--storage DIR', String, 'storage directory (default: "~/.aipp")') { AIPP.options.storage = Pathname(_1) }
26
+ o.on('-o', '--output FILE', String, 'output file') { AIPP.options.output_file = _1 }
27
+ option_parser(o)
28
+ if AIPP.options.schema == :ofmx
29
+ o.on('-m', '--[no-]mid', 'insert mid attributes into all Uid elements (default: false)') { AIPP.options.mid = _1 }
30
+ end
31
+ o.on('-h', '--[no-]check-links', 'check all links with HEAD requests') { AIPP.options.check_links = _1 }
32
+ o.on('-c', '--[no-]clean', 'clean cache and download from sources anew (default: false)') { AIPP.options.clean = _1 }
33
+ o.on('-f', '--[no-]force', 'continue on non-fatal errors (default: false)') { AIPP.options.force = _1 }
34
+ o.on('-q', '--[no-]quiet', 'suppress all informational output (default: false)') { AIPP.options.quiet = _1 }
35
+ o.on('-v', '--[no-]verbose', 'verbose output including unsevere warnings (default: false)') { AIPP.options.verbose = _1 }
36
+ o.on('-w', '--debug-on-warning [ID]', Integer, 'open debug session on warning with ID (default: false)') { AIPP.options.debug_on_warning = _1 || true }
37
+ o.on('-e', '--[no-]debug-on-error', 'open debug session on error (default: false)') { AIPP.options.debug_on_error = _1 }
38
+ o.on('-A', '--about', 'show author/license information and exit') { about }
39
+ o.on('-R', '--readme', 'show README and exit') { readme }
40
+ o.on('-L', '--list', 'list implemented regions') { list }
41
+ o.on('-V', '--version', 'show version and exit') { version }
42
+ end.parse!
43
+ guard if respond_to? :guard
19
44
  end
20
45
 
21
46
  def run
22
47
  with_debugger do
23
48
  String.disable_colorization = !STDOUT.tty?
24
49
  starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
25
- [:AIPP, AIPP.options.module, :Runner].constantize.new.run
50
+ [:AIPP, AIPP.options.scope, :Runner].constantize.new.run
26
51
  ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
27
52
  info("finished after %s" % Time.at(ending - starting).utc.strftime("%H:%M:%S"))
28
53
  end
@@ -30,30 +55,6 @@ module AIPP
30
55
 
31
56
  private
32
57
 
33
- def common_options(o)
34
- o.on('-r', '--region STRING', String, 'region (e.g. "LF")') { AIPP.options.region = _1.upcase }
35
- o.on('-s', '--section STRING', String, 'process this section only') { AIPP.options.section = _1.classify }
36
- o.on('-d', '--storage DIR', String, 'storage directory (default: "~/.aipp")') { AIPP.options.storage = Pathname(_1) }
37
- o.on('-o', '--output FILE', String, 'output file') { AIPP.options.output_file = _1 }
38
- end
39
-
40
- def developer_options(o)
41
- if AIPP.options.schema == :ofmx
42
- o.on('-m', '--[no-]mid', 'insert mid attributes into all Uid elements (default: false)') { AIPP.options.mid = _1 }
43
- end
44
- o.on('-h', '--[no-]check-links', 'check all links with HEAD requests') { AIPP.options.check_links = _1 }
45
- o.on('-c', '--[no-]clean', 'clean cache and download from sources anew (default: false)') { AIPP.options.clean = _1 }
46
- o.on('-f', '--[no-]force', 'continue on non-fatal errors (default: false)') { AIPP.options.force = _1 }
47
- o.on('-q', '--[no-]quiet', 'suppress all informational output (default: false)') { AIPP.options.quiet = _1 }
48
- o.on('-v', '--[no-]verbose', 'verbose output including unsevere warnings (default: false)') { AIPP.options.verbose = _1 }
49
- o.on('-w', '--debug-on-warning [ID]', Integer, 'open debug session on warning with ID (default: false)') { AIPP.options.debug_on_warning = _1 || true }
50
- o.on('-e', '--[no-]debug-on-error', 'open debug session on error (default: false)') { AIPP.options.debug_on_error = _1 }
51
- o.on('-A', '--about', 'show author/license information and exit') { about }
52
- o.on('-R', '--readme', 'show README and exit') { readme }
53
- o.on('-L', '--list', 'list implemented regions') { list }
54
- o.on('-V', '--version', 'show version and exit') { version }
55
- end
56
-
57
58
  def about
58
59
  puts 'Written by Sven Schwyn (bitcetera.com) and distributed under MIT license.'
59
60
  exit
@@ -65,13 +66,18 @@ module AIPP
65
66
  end
66
67
 
67
68
  def list
68
- hash = Pathname(__dir__).join('regions').glob('*').each.with_object({}) do |dir, hash|
69
- region = "Sections for region #{dir.basename}"
70
- hash[region] = dir.join(AIPP.options.module.downcase).glob('*.rb').map do |file|
71
- File.basename(file, '.rb')
72
- end.compact
69
+ puts "Available scopes -> regions -> sections:"
70
+ lib_dir.join('scopes').each_child do |scope_dir|
71
+ next unless scope_dir.directory?
72
+ puts "\n#{scope_dir.basename} ->".upcase
73
+ lib_dir.join('regions').each_child do |region_dir|
74
+ next unless region_dir.directory? && region_dir.join(scope_dir.basename).exist?
75
+ puts " #{region_dir.basename} ->"
76
+ region_dir.join(scope_dir.basename).glob('*.rb') do |section_file|
77
+ puts " #{section_file.basename('.rb')}"
78
+ end
79
+ end
73
80
  end
74
- puts hash.to_yaml.lines[1..]
75
81
  exit
76
82
  end
77
83
 
@@ -80,5 +86,20 @@ module AIPP
80
86
  exit
81
87
  end
82
88
 
89
+ def lib_dir
90
+ Pathname(__FILE__).dirname
91
+ end
92
+
93
+ def scope
94
+ @scope ||= ARGV.first.match?(/^-/) ? 'AIP' : ARGV.shift.upcase
95
+ end
96
+
97
+ def require_scope
98
+ lib_dir.join('scopes', scope.downcase).glob('*.rb').each { require _1 }
99
+ extend [:AIPP, scope, :Executable].constantize
100
+ rescue NameError
101
+ puts "ERROR: unknown scope `#{scope}'".magenta
102
+ exit 1
103
+ end
83
104
  end
84
105
  end
@@ -68,10 +68,12 @@ module AIPP::LF::AIP
68
68
  end
69
69
  end
70
70
  end
71
- # Assign fallback address (default A/A frequency) to all yet radioless airports
71
+ # Assign A/A address to all yet radioless airports
72
72
  find_by(:airport).each do |airport|
73
73
  unless airport.services.find_by(:service, type: :aerodrome_control_tower_service).any? || airport.addresses.any?
74
- airport.add_address(fallback_address_for(airport.name))
74
+ airport.add_address(
75
+ address_from_vac_for(airport) || fallback_address_for(airport)
76
+ )
75
77
  end
76
78
  end
77
79
  end
@@ -98,14 +100,32 @@ module AIPP::LF::AIP
98
100
  end.compact
99
101
  end
100
102
 
101
- def fallback_address_for(callsign)
103
+ # TODO: A/A adresses are read unreliably from VAC due to data inconsistencies
104
+ # in XML. Once fixed, integrate this into `addresses_from` as per:
105
+ # https://gitlab.com/openflightmaps/region-issues/-/issues/68
106
+ def address_from_vac_for(airport)
107
+ if aa = read("VAC-#{airport.id}").text.first_match(%r(A/A\s+\(?(\d{3}\.\d{1,3})))
108
+ AIXM.address(
109
+ type: :radio_frequency,
110
+ address: AIXM.f(aa.to_f, :mhz)
111
+ ).tap do |address|
112
+ address.remarks = {
113
+ 'type' => 'A/A',
114
+ 'indicatif/callsign' => airport.name
115
+ }.to_remarks
116
+ end
117
+ end
118
+ rescue AIPP::Downloader::NotFoundError
119
+ end
120
+
121
+ def fallback_address_for(airport)
102
122
  AIXM.address(
103
123
  type: :radio_frequency,
104
124
  address: AIXM.f(123.5, :mhz)
105
125
  ).tap do |address|
106
126
  address.remarks = {
107
127
  'type' => 'A/A',
108
- 'indicatif/callsign' => callsign
128
+ 'indicatif/callsign' => airport.name
109
129
  }.to_remarks
110
130
  end
111
131
  end
@@ -28,12 +28,10 @@ module AIPP
28
28
  case document
29
29
  when /^Obstacles$/ # obstacles spreadsheet
30
30
  AIPP::Downloader::HTTP.new(file: "#{sia_url}/FRANCE/ObstaclesDataZone1MFRANCE_#{xml_date.remove('-')}.xlsx")
31
- # when /^VAC\-(\w+)/ # aerodrome VAC PDF
32
- # AIPP::Downloader::HTTP.new(file: "#{sia_url}/Atlas-VAC/PDF_AIPparSSection/VAC/AD/AD-2.#{$1}.pdf")
33
- # when /^VACH\-(\w+)/ # helipad VAC PDF
34
- # AIPP::Downloader::HTTP.new(file: "#{sia_url}/Atlas-VAC/PDF_AIPparSSection/VACH/AD/AD-3.#{$1}.pdf")
35
- # when /^[A-Z]+-/ # eAIP HTML page (e.g. ENR-5.5)
36
- # AIPP::Downloader::HTTP.new(file: "#{sia_url}/FRANCE/AIRAC-#{xml_date}/html/eAIP/FR-#{document}-fr-FR.html")
31
+ when /^VAC\-(\w+)/ # aerodrome VAC PDF
32
+ AIPP::Downloader::HTTP.new(file: "#{sia_url}/Atlas-VAC/PDF_AIPparSSection/VAC/AD/AD-2.#{$1}.pdf")
33
+ when /^VACH\-(\w+)/ # helipad VAC PDF
34
+ AIPP::Downloader::HTTP.new(file: "#{sia_url}/Atlas-VAC/PDF_AIPparSSection/VACH/AD/AD-3.#{$1}.pdf")
37
35
  else # SIA XML database dump
38
36
  AIPP::Downloader::File.new(file: "XML_SIA_#{xml_date}.xml")
39
37
  end
@@ -1,8 +1,8 @@
1
1
  # LS – Switzerland
2
2
 
3
- ## Neway API
3
+ ## NOTAM API
4
4
 
5
- The NOTAM messages are fetched from the Neway API which is a non-public
5
+ The NOTAM messages are fetched from the Neway API which is a **non-public**
6
6
  GraphQL API.
7
7
 
8
8
  You have to set the following environment variables:
@@ -36,6 +36,62 @@ The following query object shows all parameters and columns:
36
36
  }
37
37
  ```
38
38
 
39
+ ## SHOOT API
40
+
41
+ Geometries and most shooting details are available on the [geoinformation portal](https://geo.admin.ch) managed by GKG/swisstopo. However, all details as well as a list of active shooting ranges is only available in the original `schiessanzeigen.csv` file compiled by the Swiss army and distributed on the portal as well:
42
+
43
+ * [geo.admin.ch JSON API](https://api.geo.admin.ch/services/sdiservices.html) [(example)](https://api3.geo.admin.ch/rest/services/api/MapServer/ch.vbs.schiessanzeigen/1201.050?sr=4326&geometryFormat=geojson)
44
+ * [schiessanzeigen.csv](https://data.geo.admin.ch/ch.vbs.schiessanzeigen/schiessanzeigen/schiessanzeigen.csv)
45
+
46
+ As of Feburary 2023, the structure of the CSV is as follows:
47
+
48
+ ### Shooting Ground Description Records
49
+
50
+ Row | Col | Attribute | Content | Mand | Remarks
51
+ ----|-----|------------|-----------|------|--------
52
+ SPL | 0 | Row | text(3) | yes | **"SPL" (aka: Schiessplatz Stammdaten)**
53
+ SPL | 1 | Belplan ID | text(8) | yes | **ID of the shooting ground from Belplan as "nnnn.nnn" (equal to module# and object#) e.g. "3104.010"**
54
+ SPL | 2 | arimmo ID | text(10) | yes | ID of the shooting ground from arimmo e.g. "04.203"
55
+ SPL | 3 | Name | text(50) | yes | **Name of the shooting ground, e.g. "DAMMASTOCK / SUSTENHORN"**
56
+ SPL | 4 | URL DE | text(100) | yes | Info URL in DE from CMS-VBS
57
+ SPL | 5 | URL FR | text(100) | yes | Info URL in FR from CMS-VBS
58
+ SPL | 6 | URL IT | text(100) | yes | Info URL in IT from CMS-VBS
59
+ SPL | 7 | URL EN | text(100) | yes | **Info URL in EN from CMS-VBS**
60
+ SPL | 8 | Info name | text(100) | yes | Name of the info point e.g. "Koordinationsstelle Terreg 3, Altdorf"
61
+ SPL | 9 | Info phone | text(20) | yes | **Phone of the info point**
62
+ SPL | 10 | Info email | text(100) | no   | **Email of the info point**
63
+
64
+ ### Shooting Ground Activity Records
65
+
66
+ Row | Col | Attribute | Content | Mand | Remarks
67
+ ----|-----|----------------|----------------|------|--------
68
+ BSZ | 0 | Row | text(3) | yes | **"BSZ" (aka: Belegungszeiten eines SPL)**
69
+ BSZ | 1 | Belplan ID | text(8) | yes | **ID of the shooting ground from Belplan as "nnnn.nnn" (equal to module# and object#) e.g. "3104.010"**
70
+ BSZ | 2 | Act date | date(yyyymmdd) | yes | **Datum of activity**
71
+ BSZ | 3 | Act time from | time(hhmm) | no   | **Time when activity begins**
72
+ BSZ | 4 | Act time until | time(hhmm) | no   | **Time when activity ends**
73
+ BSZ | 5 | Locations | text(50) | no   | Locations of shooting activity [R2, (max 50 char)]
74
+ BSZ | 6 | Remarks | text(100) | no   | Remarks [R2]
75
+ BSZ | 7 | URL DE | text(200) | no   | Announcement URL DE [R2]
76
+ BSZ | 8 | URL FR | text(200) | no   | Announcement URL FR [R2]
77
+ BSZ | 9 | URL IT | text(200) | no   | Announcement URL IT [R2]
78
+ BSZ | 10 | URL EN | text(200) | no   | **Announcement URL EN [R2]**
79
+ BSZ | 11 | Unit | text(120) | no   | Military unit involved [R2]
80
+ BSZ | 12 | Weapons | text(50) | no   | Weapons and ammunition involved [R2]
81
+ BSZ | 13 | Positions | text(50) | no   | Shooting positions [R2]
82
+ BSZ | 14 | Coordinates | text(25) | no   | Shooting coordinates [R2]
83
+ BSZ | 15 | Vertex height | number | no   | **Max height of activity [R2]**
84
+ BSZ | 16 | DABS | boolean | no   | **Relevant for DABS (formerly KOSIF): 0=no, 1=yes [R2]**
85
+ BSZ | 17 | No shooting | boolean | no   | **0=no, 1=yes [R2]**
86
+
87
+ ### Total Records
88
+
89
+ Row | Col | Attribute | Content | Mand | Remarks
90
+ ----|-----|------------|---------------------------|------|--------
91
+ TOT | 0 | Row | text(3) | yes | "TOT" (aka: Total)
92
+ TOT | 1 | Created at | datetime(yyyymmddhhmmss) | yes | Date and time of CSV creation
93
+ TOT | 2 | SPL count | number | yes | Number of SPL records
94
+
39
95
  ## Asynchronous Use
40
96
 
41
97
  ### Command Line Arguments
@@ -29,11 +29,6 @@ module AIPP
29
29
 
30
30
  # Mandatory Interface
31
31
 
32
- def setup
33
- AIPP.cache.aip = read('AIP').css('Ase')
34
- AIPP.cache.dabs = read('DABS')
35
- end
36
-
37
32
  def origin_for(document)
38
33
  case document
39
34
  when 'ENR'
@@ -63,6 +58,16 @@ module AIPP
63
58
  type: :pdf
64
59
  )
65
60
  end
61
+ when 'shooting_grounds'
62
+ AIPP::Downloader::HTTP.new(
63
+ file: "https://data.geo.admin.ch/ch.vbs.schiessanzeigen/schiessanzeigen/schiessanzeigen.csv",
64
+ type: :csv
65
+ )
66
+ when /^shooting_grounds-(\d+\.\d+)/
67
+ AIPP::Downloader::HTTP.new(
68
+ file: "https://api3.geo.admin.ch/rest/services/api/MapServer/ch.vbs.schiessanzeigen/#{$1}?sr=4326&geometryFormat=geojson",
69
+ type: :json
70
+ )
66
71
  else
67
72
  fail "document not recognized"
68
73
  end
@@ -6,30 +6,32 @@ module AIPP::LS::NOTAM
6
6
  include AIPP::LS::Helpers::Base
7
7
 
8
8
  def parse
9
+ AIPP.cache.aip ||= read('AIP').css('Ase')
10
+ AIPP.cache.dabs ||= read('DABS')
9
11
  json = read
10
- fail "malformed JSON received from API" unless json.has_key?('queryNOTAMs')
12
+ fail "malformed JSON received from API" unless json.has_key?(:queryNOTAMs)
11
13
  added_notam_ids = []
12
- json['queryNOTAMs'].each do |row|
13
- next unless row['notamRaw'].match? /^Q\) LS/ # only parse national NOTAM
14
+ json[:queryNOTAMs].each do |row|
15
+ next unless row[:notamRaw].match? /^Q\) LS/ # only parse national NOTAM
14
16
 
15
17
  # HACK: try to add missing commas to D-item of A- and B-series NOTAM
16
- if row['notamRaw'].match? /\A[AB]/
17
- if row['notamRaw'].gsub!(/(#{NOTAM::Schedule::HOUR_RE.decapture}-#{NOTAM::Schedule::HOUR_RE.decapture})/, '\1,')
18
- row['notamRaw'].gsub!(/,+/, ',')
19
- row['notamRaw'].sub!(/,\n/, "\n")
18
+ if row[:notamRaw].match? /\A[AB]/
19
+ if row[:notamRaw].gsub!(/(#{NOTAM::Schedule::HOUR_RE.decapture}-#{NOTAM::Schedule::HOUR_RE.decapture})/, '\1,')
20
+ row[:notamRaw].gsub!(/,+/, ',')
21
+ row[:notamRaw].sub!(/,\n/, "\n")
20
22
  warn("HACK: added missing commas to D item")
21
23
  end
22
24
  end
23
25
 
24
26
  # HACK: remove braindead years from D-item of W-series NOTAM
25
- if row['notamRaw'].match? /\AW/
27
+ if row[:notamRaw].match? /\AW/
26
28
  year = Time.now.year
27
- if row['notamRaw'].gsub!(/\s*(?:#{year}|#{year+1})\s*(#{NOTAM::Schedule::MONTH_RE})/, ' \1')
29
+ if row[:notamRaw].gsub!(/\s*(?:#{year}|#{year+1})\s*(#{NOTAM::Schedule::MONTH_RE})/, ' \1')
28
30
  warn("HACK: removed braindead years from D item")
29
31
  end
30
32
  end
31
33
 
32
- (notam = notam_for(row['notamRaw'])) or next
34
+ (notam = notam_for(row[:notamRaw])) or next
33
35
  if respect? notam
34
36
  next if notam.data[:five_day_schedules] == []
35
37
  added_notam_ids << notam.data[:id]
@@ -0,0 +1,116 @@
1
+ using AIXM::Refinements
2
+
3
+ module AIPP::LS::SHOOT
4
+ class ShootingGrounds < AIPP::SHOOT::Parser
5
+
6
+ include AIPP::LS::Helpers::Base
7
+
8
+ DEFAULT_Z = AIXM.z(1000, :qfe) # height 300m unless present in publication
9
+
10
+ def parse
11
+ effective_date = aixm.effective_at.strftime('%Y%m%d')
12
+ airac_date = AIRAC::Cycle.new(aixm.effective_at).to_s('%Y-%m-%d')
13
+ shooting_grounds = {}
14
+ read.each_with_index do |row, line|
15
+ type, id, date, no_shooting = row[0], row[1], row[2], (row[17] == "1")
16
+ if type == 'BSZ' && !no_shooting && date == effective_date
17
+ shooting_grounds[id] ||= read("shooting_grounds-#{id}")
18
+ .fetch(:feature)
19
+ .merge(
20
+ csv_line: line,
21
+ location_codes: row[5].split(/ *, */), # TODO: currently ignored - not available as separate geometries
22
+ details: row[6].blank_to_nil,
23
+ url: row[10].blank_to_nil,
24
+ upper_z: (AIXM.z(AIXM.d(row[15].to_i, :m).to_ft.dim.round, :qfe) if row[15]),
25
+ dabs: (row[16] == '1'),
26
+ schedules: []
27
+ )
28
+ shooting_grounds[id][:schedules] << schedule_for(row)
29
+ end
30
+ end
31
+ shooting_grounds.each do |id, data|
32
+ data in csv_line:, location_codes:, details:, url:, upper_z:, schedules:, properties: { bezeichnung: name, infotelefonnr: phone, infoemail: email }
33
+ if schedules.compact.any?
34
+ geometries = geometries_for data[:geometry]
35
+ indexed = geometries.count > 1
36
+ geometries.each_with_index do |geometry, index|
37
+ remarks = {
38
+ details: details,
39
+ phone: phone,
40
+ email: email,
41
+ bulletin: url
42
+ }.to_remarks
43
+ add(
44
+ AIXM.airspace(
45
+ source: "LS|OTHER|schiessgebiete.csv|#{airac_date}|#{csv_line}",
46
+ region: 'LS',
47
+ type: :dangerous_activities_area,
48
+ name: "LS-S#{id} #{name} #{index if indexed}".strip
49
+ ).tap do |airspace|
50
+ airspace.add_layer layer_for(upper_z, schedules, remarks)
51
+ airspace.geometry = geometry
52
+ airspace.comment = "DABS: marked for publication"
53
+ end
54
+ )
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ # Returns +nil+ if neither beginning nor ending time is declared which
63
+ # has to be treated as "no shooting".
64
+ def schedule_for(row)
65
+ from = AIXM.time("#{row[3]} #{AIPP.options.time_zone}") if row[3]
66
+ to = AIXM.time("#{row[4]} #{AIPP.options.time_zone}") if row[4]
67
+ case
68
+ when from && to then (from..to)
69
+ when from then (from..AIXM::END_OF_DAY)
70
+ when to then (AIXM::BEGINNING_OF_DAY..to)
71
+ end
72
+ end
73
+
74
+ def geometries_for(polygons)
75
+ fail "only type MultiPolygon supported" unless polygons[:type] == 'MultiPolygon'
76
+ fail "polygon coordinates missing" unless polygons[:coordinates]
77
+ polygons[:coordinates].map do |(outer_polygon, inner_polygon)|
78
+ warn "hole in polygon is ignored" if inner_polygon
79
+ AIXM.geometry(
80
+ *outer_polygon.map { AIXM.point(xy: AIXM.xy(long: _1.first , lat: _1.last)) }
81
+ )
82
+ end
83
+ end
84
+
85
+ def layer_for(upper_z, schedules, remarks)
86
+ AIXM.layer(
87
+ vertical_limit: AIXM.vertical_limit(
88
+ upper_z: (upper_z || DEFAULT_Z),
89
+ lower_z: AIXM::GROUND
90
+ )
91
+ ).tap do |layer|
92
+ layer.activity = :shooting_from_ground
93
+ layer.timetable = timetable_for(schedules)
94
+ layer.remarks = remarks
95
+ end
96
+ end
97
+
98
+ def timetable_for(schedules)
99
+ AIXM.timetable.tap do |timetable|
100
+ schedules.each do |schedule|
101
+ timetable.add_timesheet(
102
+ AIXM.timesheet(
103
+ adjust_to_dst: true,
104
+ dates: (AIXM.date(aixm.effective_at)..AIXM.date(aixm.effective_at))
105
+ # TODO: transform to...
106
+ # dates: AIXM.date(aixm.effective_at)
107
+ ).tap do |timesheet|
108
+ timesheet.times = schedule
109
+ end
110
+ )
111
+ end
112
+ end
113
+ end
114
+
115
+ end
116
+ end
data/lib/aipp/runner.rb CHANGED
@@ -9,7 +9,7 @@ module AIPP
9
9
  attr_reader :aixm
10
10
 
11
11
  def initialize
12
- AIPP.options.storage = AIPP.options.storage.join(AIPP.options.region, AIPP.options.module.downcase)
12
+ AIPP.options.storage = AIPP.options.storage.join(AIPP.options.region, AIPP.options.scope.downcase)
13
13
  AIPP.options.storage.mkpath
14
14
  @dependencies = THash.new
15
15
  @aixm = AIXM.document(effective_at: effective_at, expiration_at: expiration_at)
@@ -49,7 +49,7 @@ module AIPP
49
49
  end
50
50
 
51
51
  def output_file
52
- "#{AIPP.options.region}_#{AIPP.options.module}_#{effective_at.strftime('%F_%HZ')}.#{AIPP.options.schema}"
52
+ "#{AIPP.options.region}_#{AIPP.options.scope}_#{effective_at.strftime('%F_%HZ')}.#{AIPP.options.schema}"
53
53
  end
54
54
 
55
55
  # @return [Pathname] directory containing the builds
@@ -86,7 +86,7 @@ module AIPP
86
86
  # Read parser files.
87
87
  def read_parsers
88
88
  verbose_info("reading parsers")
89
- region_dir.join(AIPP.options.module.downcase).glob('*.rb').each do |file|
89
+ region_dir.join(AIPP.options.scope.downcase).glob('*.rb').each do |file|
90
90
  verbose_info "requiring #{file.basename}"
91
91
  require file
92
92
  section = file.basename('.*').to_s.classify
@@ -123,7 +123,7 @@ module AIPP
123
123
  details = aixm.errors.map(&:message).join("\n")
124
124
  AIPP.options.force ? warn(message) : fail([message, details].join(":\n"))
125
125
  end
126
- info("counting #{aixm.features.count} features")
126
+ info("counting #{aixm.features.count} feature(s)")
127
127
  end
128
128
 
129
129
  # Write the AIXM document.
@@ -135,7 +135,7 @@ module AIPP
135
135
 
136
136
  # Write build information.
137
137
  def write_build
138
- info ("skipping build")
138
+ info("skipping build")
139
139
  end
140
140
 
141
141
  # Write the configuration to config.yml.
@@ -145,7 +145,7 @@ module AIPP
145
145
  end
146
146
 
147
147
  def class_for(section)
148
- [:AIPP, AIPP.options.region, AIPP.options.module.upcase, section.classify].constantize
148
+ [:AIPP, AIPP.options.region, AIPP.options.scope, section.classify].constantize
149
149
  end
150
150
  end
151
151
 
@@ -0,0 +1,38 @@
1
+ module AIPP
2
+ module AIP
3
+
4
+ module Executable
5
+
6
+ def options
7
+ AIPP.options.merge(
8
+ scope: 'AIP',
9
+ airac: AIRAC::Cycle.new,
10
+ region_options: []
11
+ )
12
+ end
13
+
14
+ def option_parser(o)
15
+ o.banner = <<~END
16
+ Download online AIP and convert it to #{AIPP.options.schema.upcase}.
17
+ Usage: #{File.basename($0)} [options]
18
+ END
19
+ o.on('-a', '--airac (DATE|INTEGER)', String, %Q[AIRAC date or delta e.g. "+1" (default: "#{AIPP.options.airac.date.xmlschema}")]) { AIPP.options.airac = airac_for(_1) }
20
+ if AIPP.options.schema == :ofmx
21
+ o.on('-g', '--[no-]grouped-obstacles', 'group obstacles (default: false)') { AIPP.options.grouped_obstacles = _1 }
22
+ end
23
+ o.on('-O', '--region-options STRING', String, %Q[comma separated region specific options]) { AIPP.options.region_options = _1.split(',') }
24
+ end
25
+
26
+ private
27
+
28
+ def airac_for(argument)
29
+ if argument.match?(/^[+-]\d+$/) # delta
30
+ AIRAC::Cycle.new + argument.to_i
31
+ else # date
32
+ AIRAC::Cycle.new(argument)
33
+ end
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -17,9 +17,13 @@ module AIPP
17
17
  read_region
18
18
  read_parsers
19
19
  parse_sections
20
- validate_aixm
21
- write_build
22
- write_aixm(AIPP.options.output_file || output_file)
20
+ if aixm.features.any?
21
+ validate_aixm
22
+ write_build
23
+ write_aixm(AIPP.options.output_file || output_file)
24
+ else
25
+ warn("no features to write")
26
+ end
23
27
  write_config
24
28
  end
25
29
 
@@ -0,0 +1,28 @@
1
+ module AIPP
2
+ module NOTAM
3
+
4
+ module Executable
5
+
6
+ def options
7
+ AIPP.options.merge(
8
+ module: 'NOTAM',
9
+ effective_at: Time.now.change(min: 0, sec: 0)
10
+ )
11
+ end
12
+
13
+ def option_parser(o)
14
+ o.banner = <<~END
15
+ Download online NOTAM and convert it to #{AIPP.options.schema.upcase}.
16
+ Usage: #{File.basename($0)} notam [options]
17
+ END
18
+ o.on('-t', '--effective (TIME)', String, %Q[effective at this time (default: "#{AIPP.options.effective_at}")]) { AIPP.options.effective_at = Time.parse(_1) }
19
+ o.on('-x', '--crossload DIR', String, 'crossload directory') { AIPP.options.crossload = Pathname(_1) }
20
+ end
21
+
22
+ def guard
23
+ AIPP.options.effective_at = AIPP.options.effective_at.change(min: 0, sec: 0).utc
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -17,8 +17,12 @@ module AIPP
17
17
  read_region
18
18
  read_parsers
19
19
  parse_sections
20
- validate_aixm
21
- write_aixm(AIPP.options.output_file || output_file)
20
+ if aixm.features.any?
21
+ validate_aixm
22
+ write_aixm(AIPP.options.output_file || output_file)
23
+ else
24
+ warn("no features to write")
25
+ end
22
26
  write_config
23
27
  end
24
28
 
@@ -0,0 +1,10 @@
1
+ # AIPP Shoot Module
2
+
3
+ ## Cache Time Window
4
+
5
+ The default time window for SHOOT is the day. This means:
6
+
7
+ * Source data is downloaded and cached based on the day.
8
+ * The effective date and time is rounded down to the previous midnight.
9
+
10
+ To force a rebuild within this time window, you have to clean the cache using the `-c` command line argument.
@@ -0,0 +1,28 @@
1
+ module AIPP
2
+ module SHOOT
3
+
4
+ module Executable
5
+
6
+ def options
7
+ AIPP.options.merge(
8
+ module: 'Shoot',
9
+ effective_at: Time.now.at_midnight
10
+ )
11
+ end
12
+
13
+ def option_parser(o)
14
+ o.banner = <<~END
15
+ Download online shooting activities and convert them to #{AIPP.options.schema.upcase}.
16
+ Usage: #{File.basename($0)} shoot [options]
17
+ END
18
+ o.on('-t', '--effective (DATE)', String, %Q[effective on this date (default: "#{AIPP.options.effective_at.to_date}")]) { AIPP.options.effective_at = Time.parse("#{_1} CET") }
19
+ end
20
+
21
+ def guard
22
+ AIPP.options.time_zone = AIPP.options.effective_at.at_noon.strftime('%z')
23
+ AIPP.options.effective_at = AIPP.options.effective_at.at_midnight.utc
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,9 @@
1
+ module AIPP
2
+ module SHOOT
3
+
4
+ # @abstract
5
+ class Parser < AIPP::Parser
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,32 @@
1
+ module AIPP
2
+ module SHOOT
3
+
4
+ class Runner < AIPP::Runner
5
+
6
+ def effective_at
7
+ AIPP.options.effective_at
8
+ end
9
+
10
+ def expiration_at
11
+ effective_at.end_of_day.round - 1
12
+ end
13
+
14
+ def run
15
+ info("SHOOT effective #{effective_at}", color: :green)
16
+ read_config
17
+ read_region
18
+ read_parsers
19
+ parse_sections
20
+ if aixm.features.any?
21
+ validate_aixm
22
+ write_aixm(AIPP.options.output_file || output_file)
23
+ else
24
+ warn("no features to write")
25
+ end
26
+ write_config
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+ end
data/lib/aipp/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module AIPP
2
- VERSION = "2.0.3".freeze
2
+ VERSION = "2.1.0".freeze
3
3
  end
data/lib/aipp.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  require 'debug/session'
2
2
  require 'singleton'
3
- require 'colorize'
4
3
  require 'optparse'
5
4
  require 'yaml'
5
+ require 'json'
6
6
  require 'pathname'
7
7
  require 'fileutils'
8
8
  require 'tmpdir'
@@ -10,6 +10,8 @@ require 'securerandom'
10
10
  require 'tsort'
11
11
  require 'ostruct'
12
12
  require 'date'
13
+
14
+ require 'colorize'
13
15
  require 'excon'
14
16
  require 'graphql/client'
15
17
  require 'graphql/client/http'
@@ -17,7 +19,6 @@ require 'nokogiri'
17
19
  require 'csv'
18
20
  require 'roo'
19
21
  require 'pdf-reader'
20
- require 'json'
21
22
  require 'zip'
22
23
  require 'airac'
23
24
  require 'aixm'
@@ -53,10 +54,11 @@ require_relative 'aipp/t_hash'
53
54
  require_relative 'aipp/executable'
54
55
  require_relative 'aipp/runner'
55
56
 
56
- require_relative 'aipp/aip/executable'
57
- require_relative 'aipp/aip/runner'
58
- require_relative 'aipp/aip/parser'
57
+ require_relative 'aipp/scopes/aip/executable'
58
+ require_relative 'aipp/scopes/aip/runner'
59
+ require_relative 'aipp/scopes/aip/parser'
60
+
61
+ require_relative 'aipp/scopes/notam/executable'
62
+ require_relative 'aipp/scopes/notam/runner'
63
+ require_relative 'aipp/scopes/notam/parser'
59
64
 
60
- require_relative 'aipp/notam/executable'
61
- require_relative 'aipp/notam/runner'
62
- require_relative 'aipp/notam/parser'
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aipp
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.3
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sven Schwyn
@@ -29,7 +29,7 @@ cert_chain:
29
29
  kAyiRqgxF4dJviwtqI7mZIomWL63+kXLgjOjMe1SHxfIPo/0ji6+r1p4KYa7o41v
30
30
  fwIwU1MKlFBdsjkd
31
31
  -----END CERTIFICATE-----
32
- date: 2023-01-22 00:00:00.000000000 Z
32
+ date: 2023-02-27 00:00:00.000000000 Z
33
33
  dependencies:
34
34
  - !ruby/object:Gem::Dependency
35
35
  name: airac
@@ -344,8 +344,6 @@ email:
344
344
  executables:
345
345
  - aip2aixm
346
346
  - aip2ofmx
347
- - notam2aixm
348
- - notam2ofmx
349
347
  extensions: []
350
348
  extra_rdoc_files:
351
349
  - README.md
@@ -357,13 +355,7 @@ files:
357
355
  - README.md
358
356
  - exe/aip2aixm
359
357
  - exe/aip2ofmx
360
- - exe/notam2aixm
361
- - exe/notam2ofmx
362
358
  - lib/aipp.rb
363
- - lib/aipp/aip/README.md
364
- - lib/aipp/aip/executable.rb
365
- - lib/aipp/aip/parser.rb
366
- - lib/aipp/aip/runner.rb
367
359
  - lib/aipp/border.rb
368
360
  - lib/aipp/debugger.rb
369
361
  - lib/aipp/downloader.rb
@@ -372,10 +364,6 @@ files:
372
364
  - lib/aipp/downloader/http.rb
373
365
  - lib/aipp/environment.rb
374
366
  - lib/aipp/executable.rb
375
- - lib/aipp/notam/README.md
376
- - lib/aipp/notam/executable.rb
377
- - lib/aipp/notam/parser.rb
378
- - lib/aipp/notam/runner.rb
379
367
  - lib/aipp/parser.rb
380
368
  - lib/aipp/patcher.rb
381
369
  - lib/aipp/pdf.rb
@@ -400,7 +388,20 @@ files:
400
388
  - lib/aipp/regions/LS/README.md
401
389
  - lib/aipp/regions/LS/helpers/base.rb
402
390
  - lib/aipp/regions/LS/notam/ENR.rb
391
+ - lib/aipp/regions/LS/shoot/shooting_grounds.rb
403
392
  - lib/aipp/runner.rb
393
+ - lib/aipp/scopes/aip/README.md
394
+ - lib/aipp/scopes/aip/executable.rb
395
+ - lib/aipp/scopes/aip/parser.rb
396
+ - lib/aipp/scopes/aip/runner.rb
397
+ - lib/aipp/scopes/notam/README.md
398
+ - lib/aipp/scopes/notam/executable.rb
399
+ - lib/aipp/scopes/notam/parser.rb
400
+ - lib/aipp/scopes/notam/runner.rb
401
+ - lib/aipp/scopes/shoot/README.md
402
+ - lib/aipp/scopes/shoot/executable.rb
403
+ - lib/aipp/scopes/shoot/parser.rb
404
+ - lib/aipp/scopes/shoot/runner.rb
404
405
  - lib/aipp/t_hash.rb
405
406
  - lib/aipp/version.rb
406
407
  - lib/core_ext/array.rb
@@ -441,7 +442,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
441
442
  - !ruby/object:Gem::Version
442
443
  version: '0'
443
444
  requirements: []
444
- rubygems_version: 3.4.5
445
+ rubygems_version: 3.4.7
445
446
  signing_key:
446
447
  specification_version: 4
447
448
  summary: Parser for aeronautical information publications
metadata.gz.sig CHANGED
Binary file
data/exe/notam2aixm DELETED
@@ -1,5 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'aipp'
4
-
5
- AIPP::NOTAM::Executable.new(File.basename($0)).run
data/exe/notam2ofmx DELETED
@@ -1,5 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'aipp'
4
-
5
- AIPP::NOTAM::Executable.new(File.basename($0)).run
@@ -1,40 +0,0 @@
1
- module AIPP
2
- module AIP
3
-
4
- class Executable < AIPP::Executable
5
-
6
- def initialize(exe_file)
7
- super
8
- AIPP.options.merge(
9
- module: 'AIP',
10
- airac: AIRAC::Cycle.new,
11
- region_options: []
12
- )
13
- OptionParser.new do |o|
14
- o.banner = <<~END
15
- Download online AIP and convert it to #{AIPP.options.schema.upcase}.
16
- Usage: #{File.basename($0)} [options]
17
- END
18
- common_options(o)
19
- o.on('-a', '--airac (DATE|INTEGER)', String, %Q[AIRAC date or delta e.g. "+1" (default: "#{AIPP.options.airac.date.xmlschema}")]) { AIPP.options.airac = airac_for(_1) }
20
- if AIPP.options.schema == :ofmx
21
- o.on('-g', '--[no-]grouped-obstacles', 'group obstacles (default: false)') { AIPP.options.grouped_obstacles = _1 }
22
- end
23
- o.on('-O', '--region-options STRING', String, %Q[comma separated region specific options]) { AIPP.options.region_options = _1.split(',') }
24
- developer_options(o)
25
- end.parse!
26
- end
27
-
28
- private
29
-
30
- def airac_for(argument)
31
- if argument.match?(/^[+-]\d+$/) # delta
32
- AIRAC::Cycle.new + argument.to_i
33
- else # date
34
- AIRAC::Cycle.new(argument)
35
- end
36
- end
37
-
38
- end
39
- end
40
- end
@@ -1,27 +0,0 @@
1
- module AIPP
2
- module NOTAM
3
-
4
- class Executable < AIPP::Executable
5
-
6
- def initialize(exe_file)
7
- super
8
- now = Time.now.utc.round
9
- AIPP.options.merge(
10
- module: 'NOTAM',
11
- effective_at: now - now.sec - (now.min * 60) # previous full hour
12
- )
13
- OptionParser.new do |o|
14
- o.banner = <<~END
15
- Download online NOTAM and convert it to #{AIPP.options.schema.upcase}.
16
- Usage: #{File.basename($0)} [options]
17
- END
18
- common_options(o)
19
- o.on('-t', '--effective (TIME)', String, %Q[effective after this point in time (default: #{AIPP.options.effective_at})]) { AIPP.options.effective_at = Time.parse(_1) }
20
- o.on('-x', '--crossload DIR', String, 'crossload directory') { AIPP.options.crossload = Pathname(_1) }
21
- developer_options(o)
22
- end.parse!
23
- end
24
-
25
- end
26
- end
27
- end
File without changes
File without changes
File without changes
File without changes