aipp 2.1.1 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5a3f910ee20e31b9a2d0ee0644714c43898917cd9fda36bb5f721c17fcb758be
4
- data.tar.gz: a996dd39757199cdadd5ceab2c7014b871785c857dc2054c4d9e5c264b16b5a9
3
+ metadata.gz: cb4a7ae75e5b180c62d60b63a28ef1e28de3d23e0fe008defc4cc7bc0cf61f14
4
+ data.tar.gz: 6705dbfab25adfb9c71b1781c8ded9106ab1adc6906ec67f3c1330beb73de3e7
5
5
  SHA512:
6
- metadata.gz: d8cea4595823cf29656e25e252e205007de55a74240c1bca96f47ccd4e69194a112cb4665da0c784c24289f2d117d9b04e1a43625f86820763553a04bde03ab0
7
- data.tar.gz: 7b3ae6c428f88231456a94a882c51ec8c5436bd2ff1398e61a0465b48939ccc3a443ff20ab98039a9ee9fc1ac2a5b70cbbc54ebdf29f9712a84a35460250b312
6
+ metadata.gz: f394783a21b6aaac134155d06c1e3f5c082dcfdd835d0cb3abf708bc1a417fcc2a4bebf382fa1cc70b82c8fe8fa440cacb6a2f73eb73a1efbd0d3b72c437d5e4
7
+ data.tar.gz: 6c9d845076a9c2f894c2d4e586f66a32616d1a1903beb54423844b26d12e930bf85089b72494abe51a47c2701450044ef35508b0221c994d9ed512e3a821b6be
checksums.yaml.gz.sig CHANGED
@@ -1,3 +1,2 @@
1
- (��F���S�> *���
2
- ͻ��\��� k��1�<��>%���#cu�Q�L��Y+<��`%e��#�{k 2�'� ��M�����&a����*�ɝ7W��T��L��fX�O ki<i�����{�� i�+�ޔ
3
- r�}��Q==J����ÑeF��0*L2��!���?���r�L�������
1
+ M{��T��8(��3;p =[PR��oS�WoE���ʦ�q�3���U~���
2
+ \�ܚ)��o�>�g���
data/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  Nothing so far
4
4
 
5
+ ## 2.1.2
6
+
7
+ #### Changes
8
+ * Improve timesheet calculation for shooting grounds in LS
9
+ * Improve safety margins for shooting grounds in LS
10
+ * Switch to unzipped OFMX for now due to issues upstream
11
+
5
12
  ## 2.1.1
6
13
 
7
14
  #### Additions
@@ -92,6 +92,14 @@ TOT | 0 | Row | text(3) | yes | "TOT" (aka: Total)
92
92
  TOT | 1 | Created at | datetime(yyyymmddhhmmss) | yes | Date and time of CSV creation
93
93
  TOT | 2 | SPL count | number | yes | Number of SPL records
94
94
 
95
+ ### Safety Margins
96
+
97
+ The max height (BSZ col 15) has to be treated as an advisory value, not a guarantee:
98
+
99
+ If no value is given, the ammunition *should* not exceed 250m above ground. However, regulations for certain ammunition types allow for higher peaks. As of March 2023, 6cm mortars *may* reach up to 500m and future weapon systems *may* even go beyond that.
100
+
101
+ To account for this, the two constants `DEFAULT_Z` and `SAFETY` should be revisited from time to time.
102
+
95
103
  ## Asynchronous Use
96
104
 
97
105
  ### Command Line Arguments
@@ -48,9 +48,12 @@ module AIPP
48
48
  fail "not yet implemented"
49
49
  when 'AIP'
50
50
  AIPP::Downloader::HTTP.new(
51
- archive: "https://snapshots.openflightmaps.org/live/#{AIRAC::Cycle.new.id}/ofmx/lsas/latest/ofmx_ls.zip",
52
- file: "ofmx_ls/isolated/ofmx_ls.ofmx"
51
+ file: "https://storage.googleapis.com/snapshots.openflightmaps.org/live/#{AIRAC::Cycle.new.id}/ofmx/lsas/latest/isolated/ofmx_ls.xml"
53
52
  )
53
+ # AIPP::Downloader::HTTP.new(
54
+ # archive: "https://snapshots.openflightmaps.org/live/#{AIRAC::Cycle.new.id}/ofmx/lsas/latest/ofmx_ls.zip",
55
+ # file: "ofmx_ls/isolated/ofmx_ls.ofmx"
56
+ # )
54
57
  when 'DABS'
55
58
  if aixm.effective_at.to_date == Date.today # DABS cross check works reliably for today only
56
59
  AIPP::Downloader::HTTP.new(
@@ -5,33 +5,40 @@ module AIPP::LS::SHOOT
5
5
 
6
6
  include AIPP::LS::Helpers::Base
7
7
 
8
- DEFAULT_Z = AIXM.z(1000, :qfe) # height 300m unless present in publication
8
+ DEFAULT_Z = AIXM.z(2000, :qfe) # fallback if no max height is defined
9
+ SAFETY = 100 # safety margin in meters added to max height
9
10
 
10
11
  def parse
11
- effective_date = aixm.effective_at.strftime('%Y%m%d')
12
+ effective_date = AIPP.options.local_effective_at.strftime('%Y%m%d')
12
13
  airac_date = AIRAC::Cycle.new(aixm.effective_at).to_s('%Y-%m-%d')
13
14
  shooting_grounds = {}
14
15
  read.each_with_index do |row, line|
15
16
  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)
17
+ next unless type == 'BSZ'
18
+ next if no_shooting || date != effective_date
19
+ next if AIPP.options.id && AIPP.options.id != id
20
+ # TODO: Several BSZ lines may exist for the same shooting area with
21
+ # different location codes (aka: partial activations). The geometries
22
+ # of those location codes are not currently available, we therefore
23
+ # have to merge the data into one record. The geometries should become
24
+ # available by the end of 2023 which will make it possible to map
25
+ # each line to one geometry and remove the merging logic.
26
+ upper_z = row[15] ? AIXM.z(AIXM.d(row[15].to_i + SAFETY, :m).to_ft.dim.round, :qfe) : DEFAULT_Z
27
+ (shooting_grounds[id] ||= { schedules: [], upper_z: AIXM::GROUND }).then do |s|
28
+ s[:feature] ||= read("shooting_grounds-#{id}").fetch(:feature)
29
+ s[:csv_line] ||= line
30
+ s[:url] ||= row[10].blank_to_nil
31
+ s[:details] = [s[:details], row[6].blank_to_nil].compact.join("\n")
32
+ s[:dabs] ||= (row[16] == '1')
33
+ s[:upper_z] = upper_z if upper_z.alt > s[:upper_z].alt
34
+ s[:schedules] += schedules_for(row)
29
35
  end
30
36
  end
31
37
  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]
38
+ data in csv_line:, details:, url:, upper_z:, schedules:, dabs:, feature: { geometry: polygons, properties: { bezeichnung: name, infotelefonnr: phone, infoemail: email } }
39
+ schedules = consolidate(schedules)
40
+ if schedules.any?
41
+ geometries = geometries_for polygons
35
42
  indexed = geometries.count > 1
36
43
  geometries.each_with_index do |geometry, index|
37
44
  remarks = {
@@ -49,7 +56,7 @@ module AIPP::LS::SHOOT
49
56
  ).tap do |airspace|
50
57
  airspace.add_layer layer_for(upper_z, schedules, remarks)
51
58
  airspace.geometry = geometry
52
- airspace.comment = "DABS: marked for publication"
59
+ airspace.comment = "DABS: marked for publication" if dabs
53
60
  end
54
61
  )
55
62
  end
@@ -59,18 +66,34 @@ module AIPP::LS::SHOOT
59
66
 
60
67
  private
61
68
 
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)
69
+ def schedules_for(row)
70
+ from, to = time_for(row[3]), time_for(row[4], ending: true)
71
+ if from.to_date == to.to_date
72
+ [
73
+ [AIXM.date(from), (AIXM.time(from)..AIXM.time(to))]
74
+ ]
75
+ else
76
+ [
77
+ [AIXM.date(from), (AIXM.time(from)..AIXM::END_OF_DAY)],
78
+ [AIXM.date(to), (AIXM::BEGINNING_OF_DAY..AIXM.time(to))]
79
+ ]
71
80
  end
72
81
  end
73
82
 
83
+ def time_for(string, ending: false)
84
+ hour, min = case string.strip
85
+ when /(\d{2})(\d{2})/
86
+ [$1.to_i, $2.to_i]
87
+ when '', '0'
88
+ [0, 0]
89
+ else
90
+ warn("ignoring malformed time `#{string}'")
91
+ [0, 0]
92
+ end
93
+ hour = 24 if hour.zero? && min.zero? && ending
94
+ AIPP.options.local_effective_at.change(hour: hour, min: min).utc
95
+ end
96
+
74
97
  def geometries_for(polygons)
75
98
  fail "only type MultiPolygon supported" unless polygons[:type] == 'MultiPolygon'
76
99
  fail "polygon coordinates missing" unless polygons[:coordinates]
@@ -97,20 +120,30 @@ module AIPP::LS::SHOOT
97
120
 
98
121
  def timetable_for(schedules)
99
122
  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
- )
123
+ schedules.each do |date, times_array|
124
+ times_array.each do |times|
125
+ timetable.add_timesheet(
126
+ AIXM.timesheet(
127
+ adjust_to_dst: true,
128
+ dates: (date..date)
129
+ # TODO: transform to...
130
+ # dates: AIXM.date(date)
131
+ ).tap do |timesheet|
132
+ timesheet.times = times
133
+ end
134
+ )
135
+ end
111
136
  end
112
137
  end
113
138
  end
114
139
 
140
+ # TODO: Consolidate will become obsolete once location code geometries
141
+ # are available.
142
+ def consolidate(schedules)
143
+ schedules
144
+ .group_by(&:first)
145
+ .transform_values { _1.map(&:last).consolidate_ranges(:time) }
146
+ end
147
+
115
148
  end
116
149
  end
@@ -6,7 +6,8 @@ module AIPP
6
6
  def options
7
7
  AIPP.options.merge(
8
8
  module: 'Shoot',
9
- effective_at: Time.now.at_midnight
9
+ local_effective_at: Time.now.at_midnight,
10
+ id: nil
10
11
  )
11
12
  end
12
13
 
@@ -15,12 +16,13 @@ module AIPP
15
16
  Download online shooting activities and convert them to #{AIPP.options.schema.upcase}.
16
17
  Usage: #{File.basename($0)} shoot [options]
17
18
  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
+ o.on('-t', '--effective (DATE)', String, %Q[effective on this date (default: "#{AIPP.options.local_effective_at.to_date}")]) { AIPP.options.local_effective_at = Time.parse("#{_1} CET") }
20
+ o.on('-i', '--id ID', String, %Q[process shooting ground with this ID only]) { AIPP.options.id = _1 }
19
21
  end
20
22
 
21
23
  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
+ AIPP.options.time_zone = AIPP.options.local_effective_at.at_noon.strftime('%z')
25
+ AIPP.options.effective_at = AIPP.options.local_effective_at.at_midnight.utc
24
26
  end
25
27
 
26
28
  end
@@ -8,7 +8,7 @@ module AIPP
8
8
  end
9
9
 
10
10
  def expiration_at
11
- effective_at.end_of_day.round - 1
11
+ effective_at + 86399
12
12
  end
13
13
 
14
14
  def run
data/lib/aipp/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module AIPP
2
- VERSION = "2.1.1".freeze
2
+ VERSION = "2.1.2".freeze
3
3
  end
@@ -10,4 +10,27 @@ class Array
10
10
  map(&:to_s).join('::').constantize
11
11
  end
12
12
 
13
+ # Consolidate array of possibly overlapping ranges.
14
+ #
15
+ # @example
16
+ # [15..17, 7..11, 12..13, 8..12, 12..13].consolidate_ranges
17
+ # # => [7..13, 15..17]
18
+ #
19
+ # @param [Symbol, nil] method to call on range members for comparison
20
+ # @return [Array] consolidated array
21
+ def consolidate_ranges(method=:itself)
22
+ uniq.sort_by { [_1.begin, _1.end] }.then do |ranges|
23
+ consolidated, a = [], ranges.first
24
+ Array(ranges[1..]).each do |b|
25
+ if a.end.send(method) >= b.begin.send(method) # overlapping
26
+ a = (a.begin..(a.end.send(method) > b.end.send(method) ? a.end : b.end))
27
+ else # not overlapping
28
+ consolidated << a
29
+ a = b
30
+ end
31
+ end
32
+ consolidated << a
33
+ end.compact
34
+ end
35
+
13
36
  end
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.1.1
4
+ version: 2.1.2
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-02-27 00:00:00.000000000 Z
32
+ date: 2023-03-23 00:00:00.000000000 Z
33
33
  dependencies:
34
34
  - !ruby/object:Gem::Dependency
35
35
  name: airac
@@ -60,7 +60,7 @@ dependencies:
60
60
  version: '1'
61
61
  - - ">="
62
62
  - !ruby/object:Gem::Version
63
- version: 1.4.1
63
+ version: 1.4.2
64
64
  type: :runtime
65
65
  prerelease: false
66
66
  version_requirements: !ruby/object:Gem::Requirement
@@ -70,7 +70,7 @@ dependencies:
70
70
  version: '1'
71
71
  - - ">="
72
72
  - !ruby/object:Gem::Version
73
- version: 1.4.1
73
+ version: 1.4.2
74
74
  - !ruby/object:Gem::Dependency
75
75
  name: notam
76
76
  requirement: !ruby/object:Gem::Requirement
@@ -442,7 +442,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
442
442
  - !ruby/object:Gem::Version
443
443
  version: '0'
444
444
  requirements: []
445
- rubygems_version: 3.4.7
445
+ rubygems_version: 3.4.9
446
446
  signing_key:
447
447
  specification_version: 4
448
448
  summary: Parser for aeronautical information publications
metadata.gz.sig CHANGED
Binary file