aipp 2.0.3 → 2.1.0

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