aixm 0.3.4 → 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cbcadda83460463ad9ab68569e04738a5811b10e3534f3f91fde0ed1f4104874
4
- data.tar.gz: aa14c72ecf04553fde4243ff74b68e5cfe2b1d3f19efbb4ef1f473a6dce79a38
3
+ metadata.gz: efe8ab0dda077e481484589c0e2943b6417cefb5386e16d872692161b46add3f
4
+ data.tar.gz: f7fb02c428760481b9159d81c93baec8e4e2d1ef1267e1884f02422d44af7b9b
5
5
  SHA512:
6
- metadata.gz: 18f3c67e93a4d6527d8240f819c942d82e81cef33332902749ebcde5e62c3aa4aaf2e12ea2ddce4b642b4ba63b2913e557207161dd4400b01685ec673c436885
7
- data.tar.gz: f35b079b357d0b00e5c7bc11a71f0e610cc787863eb678f49aa6697ab11eccc32804c33e73e9f2d4851209d9604b16a271c9f85759b2c41b7e8094a928ff5034
6
+ metadata.gz: 1dcee1f3e19c58ada30b66bfa48201bae02a382bb6a8746b6f144de6c3280ef58ce2e6b6dc25ba5343ff9cfc9589e81e2217573bdfce642623f73530bf626e56
7
+ data.tar.gz: 1cf3f783adfa14e2af626d97810c8bfbd3e776718a207e70343ed1bf1f2f5ccbaa52142aa5ab32727af99a8c07733d056817536f3157b171878c0d03b9363208
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  language: ruby
3
3
  rvm:
4
- - 2.6.0
4
+ - 2.6.3
@@ -1,3 +1,14 @@
1
+ ## 0.3.5
2
+
3
+ #### Additions
4
+ * Refinement `Object#then_if`
5
+ * Airspace activity types `:aeromodelling` and `:glider_winch`
6
+ * `AIXM::XY#to_point` convenience method
7
+
8
+ #### Breaking Changes
9
+ * Renamed airspace activity type "TOWING" from `:winch_activity` to `:towing_traffic`
10
+ * Updated obstacles and obstacle groups to reflect recent changes in OFMX
11
+
1
12
  ## 0.3.4
2
13
 
3
14
  #### Additions
@@ -26,7 +26,7 @@ module AIXM
26
26
  # @example Built by adding elements
27
27
  # geometry = AIXM.geometry
28
28
  # geometry << AIXM.point(...)
29
- # geometry << AIXM.point(...)
29
+ # geometry.concat [AIXM.point(...), AIXM.point(...), ...]
30
30
  #
31
31
  # @see https://github.com/openflightmaps/ofmx/wiki/Airspace#avx-border-vertex
32
32
  class Geometry
@@ -37,7 +37,9 @@ module AIXM
37
37
  # @return [Enumerator] see Array#each
38
38
  # @!method <<
39
39
  # @return [Array] see Array#<<
40
- def_delegators :@result_array, :each, :<<
40
+ # @!method concat
41
+ # @return [Array] see Array#concat
42
+ def_delegators :@result_array, :each, :<<, :concat
41
43
 
42
44
  def initialize(*segments)
43
45
  @result_array = segments
@@ -26,7 +26,7 @@ module AIXM
26
26
 
27
27
  # @return [String]
28
28
  def inspect
29
- %Q(#<#{self.class} xy="#{xy.to_s}">)
29
+ %Q(#<#{self.class} xy="#{xy}" center_xy="#{center_xy}" clockwise=#{clockwise}>)
30
30
  end
31
31
 
32
32
  def center_xy=(value)
@@ -25,7 +25,7 @@ module AIXM
25
25
 
26
26
  # @return [String]
27
27
  def inspect
28
- %Q(#<#{self.class} xy="#{xy.to_s}">)
28
+ %Q(#<#{self.class} xy="#{xy}" name=#{name.inspect}>)
29
29
  end
30
30
 
31
31
  def name=(value)
@@ -26,7 +26,7 @@ module AIXM
26
26
 
27
27
  # @return [String]
28
28
  def inspect
29
- %Q(#<#{self.class} xy="#{xy.to_s}">)
29
+ %Q(#<#{self.class} center_xy="#{center_xy}" radius="#{radius.to_s}">)
30
30
  end
31
31
 
32
32
  def center_xy=(value)
@@ -22,7 +22,7 @@ module AIXM
22
22
 
23
23
  # @return [String]
24
24
  def inspect
25
- %Q(#<#{self.class} xy="#{xy.to_s}">)
25
+ %Q(#<#{self.class} xy="#{xy}">)
26
26
  end
27
27
 
28
28
  def xy=(value)
@@ -25,6 +25,7 @@ module AIXM
25
25
  ACCIDENT: :accident_investigation,
26
26
  ACROBAT: :acrobatics,
27
27
  AIRGUN: :aerial_gunnery,
28
+ AIRMODEL: :aeromodelling,
28
29
  AIRSHOW: :air_show,
29
30
  ANTIHAIL: :anti_hail_rocket,
30
31
  ARTILERY: :artillary_firing,
@@ -71,7 +72,7 @@ module AIXM
71
72
  TECHNICAL: :technical_activity,
72
73
  'TFC-AD': :aerodrome_traffic,
73
74
  'TFC-HELI': :helicopter_traffic,
74
- TOWING: :winch_activity,
75
+ TOWING: :towing_traffic,
75
76
  TRG: :training,
76
77
  UAV: :drone,
77
78
  ULM: :ultra_light_flight,
@@ -79,6 +80,7 @@ module AIXM
79
80
  'VIP-PRES': :president,
80
81
  'VIP-VICE': :vice_president,
81
82
  WATERBLAST: :underwater_explosion,
83
+ WINCH: :glider_winch,
82
84
  WORK: :aerial_work,
83
85
  OTHER: :other
84
86
  }.freeze
@@ -161,7 +163,9 @@ module AIXM
161
163
  builder = Builder::XmlMarkup.new(indent: 2)
162
164
  builder.codeClass(self.class.to_s) if self.class
163
165
  builder.codeLocInd(location_indicator) if location_indicator
164
- builder.codeActivity(ACTIVITIES.key(activity).to_s) if activity
166
+ if activity
167
+ builder.codeActivity(ACTIVITIES.key(activity).to_s.then_if(AIXM.aixm?) { |a| { 'AIRMODEL' => 'UAV', 'WINCH' => 'GLIDER' }[a] || a })
168
+ end
165
169
  builder << vertical_limits.to_xml
166
170
  builder << timetable.to_xml(as: :Att) if timetable
167
171
  builder.codeSelAvbl(selective? ? 'Y' : 'N') if AIXM.ofmx?
@@ -6,15 +6,17 @@ module AIXM
6
6
  # Vertical limits define a 3D airspace vertically. They are often noted in
7
7
  # AIP as follows:
8
8
  #
9
- # upper_z (or max_z whichever is higher)
9
+ # upper_z
10
+ # (max_z) whichever is higher
10
11
  # -------
11
- # lower_z (or min_z whichever is lower)
12
+ # lower_z
13
+ # (min_z) whichever is lower
12
14
  #
13
15
  # ===Cheat Sheet in Pseudo Code:
14
16
  # vertical_limits = AIXM.vertical_limits(
15
17
  # upper_z: AIXM.z
16
- # lower_z: AIXM.z
17
18
  # max_z: AIXM.z or nil
19
+ # lower_z: AIXM.z
18
20
  # min_z: AIXM.z or nil
19
21
  # )
20
22
  #
@@ -25,7 +27,7 @@ module AIXM
25
27
  # @see https://github.com/openflightmaps/ofmx/wiki/Airspace#ase-airspace
26
28
  class VerticalLimits
27
29
  # @api private
28
- TAGS = { upper: :Upper, lower: :Lower, max: :Max, min: :Mnm }.freeze
30
+ TAGS = { upper_z: :Upper, lower_z: :Lower, max_z: :Max, min_z: :Mnm }.freeze
29
31
 
30
32
  # @api private
31
33
  CODES = { qfe: :HEI, qnh: :ALT, qne: :STD }.freeze
@@ -42,13 +44,14 @@ module AIXM
42
44
  # @return [AIXM::Z] alternative lower limit ("whichever is lower")
43
45
  attr_reader :min_z
44
46
 
45
- def initialize(upper_z:, lower_z:, max_z: nil, min_z: nil)
46
- self.upper_z, self.lower_z, self.max_z, self.min_z = upper_z, lower_z, max_z, min_z
47
+ def initialize(upper_z:, max_z: nil, lower_z:, min_z: nil)
48
+ self.upper_z, self.max_z, self.lower_z, self.min_z = upper_z, max_z, lower_z, min_z
47
49
  end
48
50
 
49
51
  # @return [String]
50
52
  def inspect
51
- %Q(#<#{self.class} upper_z="#{upper_z.to_s}" lower_z="#{lower_z.to_s}">)
53
+ payload = %i(upper_z max_z lower_z min_z).map { |l| %Q(#{l}="#{send(l)}") if send(l) }.compact
54
+ %Q(#<#{self.class} #{payload.join(' ')}>)
52
55
  end
53
56
 
54
57
  def upper_z=(value)
@@ -56,16 +59,16 @@ module AIXM
56
59
  @upper_z = value
57
60
  end
58
61
 
59
- def lower_z=(value)
60
- fail(ArgumentError, "invalid lower_z") unless value.is_a? AIXM::Z
61
- @lower_z = value
62
- end
63
-
64
62
  def max_z=(value)
65
63
  fail(ArgumentError, "invalid max_z") unless value.nil? || value.is_a?(AIXM::Z)
66
64
  @max_z = value
67
65
  end
68
66
 
67
+ def lower_z=(value)
68
+ fail(ArgumentError, "invalid lower_z") unless value.is_a? AIXM::Z
69
+ @lower_z = value
70
+ end
71
+
69
72
  def min_z=(value)
70
73
  fail(ArgumentError, "invalid min_z") unless value.nil? || value.is_a?(AIXM::Z)
71
74
  @min_z = value
@@ -73,8 +76,8 @@ module AIXM
73
76
 
74
77
  # @return [String] AIXM or OFMX markup
75
78
  def to_xml
76
- %i(upper lower max min).each_with_object(Builder::XmlMarkup.new(indent: 2)) do |limit, builder|
77
- if z = send(:"#{limit}_z")
79
+ TAGS.keys.each_with_object(Builder::XmlMarkup.new(indent: 2)) do |limit, builder|
80
+ if z = send(limit)
78
81
  builder.tag!(:"codeDistVer#{TAGS[limit]}", CODES[z.code].to_s)
79
82
  builder.tag!(:"valDistVer#{TAGS[limit]}", z.alt.to_s)
80
83
  builder.tag!(:"uomDistVer#{TAGS[limit]}", z.unit.upcase.to_s)
@@ -35,6 +35,6 @@ module AIXM
35
35
  )x.freeze
36
36
 
37
37
  # Pattern matching timetable working hour codes
38
- H_RE = /H(?:24|J|N|X|O)/.freeze
38
+ H_RE = /(?<code>H24|HJ|HN|HX|HO)/.freeze
39
39
 
40
40
  end
@@ -79,7 +79,7 @@ module AIXM
79
79
  builder = Builder::XmlMarkup.new(indent: 2)
80
80
  builder.tag!(as) do |tag|
81
81
  tag << addressable.to_uid.indent(2) if addressable
82
- tag.codeType(TYPES.key(type).to_s.then { |t| AIXM.aixm? ? t.sub(/-\w+$/, '') : t })
82
+ tag.codeType(TYPES.key(type).to_s.then_if(AIXM.aixm?) { |t| t.sub(/-\w+$/, '') })
83
83
  tag.noSeq(sequence)
84
84
  end
85
85
  end
@@ -140,9 +140,7 @@ module AIXM
140
140
  fail(LayerError.new("no layers defined", self)) unless layers.any?
141
141
  builder = Builder::XmlMarkup.new(indent: 2)
142
142
  builder.comment! "Airspace: [#{TYPES.key(type)}] #{name || :UNNAMED}"
143
- builder.Ase({
144
- source: (source if AIXM.ofmx?)
145
- }.compact) do |ase|
143
+ builder.Ase({ source: (source if AIXM.ofmx?) }.compact) do |ase|
146
144
  ase << to_uid.indent(2)
147
145
  ase.txtLocalType(local_type) if local_type && local_type != name
148
146
  ase.txtName(name) if name
@@ -12,20 +12,29 @@ module AIXM
12
12
  # name: String or nil
13
13
  # type: TYPES
14
14
  # xy: AIXM.xy
15
- # radius: AIXM.d
16
15
  # z: AIXM.z
16
+ # radius: AIXM.d
17
17
  # )
18
18
  # obstacle.lighting = true or false (default for AIXM) or nil (means: unknown, default for OFMX)
19
19
  # obstacle.lighting_remarks = String or nil
20
20
  # obstacle.marking = true or false or nil (means: unknown, default)
21
21
  # obstacle.marking_remarks = String or nil
22
22
  # obstacle.height = AIXM.d or nil
23
+ # obstacle.height_accurate = true or false or nil (means: unknown, default)
23
24
  # obstacle.xy_accuracy = AIXM.d or nil
24
25
  # obstacle.z_accuracy = AIXM.d or nil
25
- # obstacle.height_accurate = true or false or nil (means: unknown, default)
26
26
  # obstacle.valid_from = Time or Date or String or nil
27
27
  # obstacle.valid_until = Time or Date or String or nil
28
28
  # obstacle.remarks = String or nil
29
+ # obstacle.link_to # => AIXM.obstacle or nil
30
+ # obstacle.link_type # => LINK_TYPE or nil
31
+ #
32
+ # See {AIXM::Feature::ObstacleGroup} for how to define physical links
33
+ # between two obstacles (e.g. cables between powerline towers).
34
+ #
35
+ # Please note: As soon as an obstacle is added to an obstacle group, the
36
+ # +xy_accuracy+ and +z_accuracy+ of the obstacle group overwrite whatever
37
+ # is set on the individual obstacles!
29
38
  #
30
39
  # @see https://github.com/openflightmaps/ofmx/wiki/Obstacle
31
40
  class Obstacle < Feature
@@ -42,6 +51,12 @@ module AIXM
42
51
  OTHER: :other # specify in remarks
43
52
  }.freeze
44
53
 
54
+ LINK_TYPES = {
55
+ CABLE: :cable,
56
+ SOLID: :solid,
57
+ OTHER: :other
58
+ }.freeze
59
+
45
60
  # @return [String] full name
46
61
  attr_reader :name
47
62
 
@@ -74,16 +89,16 @@ module AIXM
74
89
  # @return [AIXM::D, nil] height from ground to top point
75
90
  attr_reader :height
76
91
 
92
+ # @return [Boolean, nil] height accuracy
93
+ # true => height measured, false => height estimated, nil => unknown
94
+ attr_reader :height_accurate
95
+
77
96
  # @return [AIXM::D, nil] margin of error for circular base center point
78
97
  attr_reader :xy_accuracy
79
98
 
80
99
  # @return [AIXM::D, nil] margin of error for top point
81
100
  attr_reader :z_accuracy
82
101
 
83
- # @return [Boolean, nil] height accuracy
84
- # true => height measured, false => height estimated, nil => unknown
85
- attr_reader :height_accurate
86
-
87
102
  # @return [Time, Date, String, nil] effective after this point in time
88
103
  attr_reader :valid_from
89
104
 
@@ -93,9 +108,18 @@ module AIXM
93
108
  # @return [String, nil] free text remarks
94
109
  attr_reader :remarks
95
110
 
96
- def initialize(source: nil, name: nil, type:, xy:, radius:, z:)
111
+ # @return [AIXM::Feature::ObstacleGroup] group this obstacle belongs to
112
+ attr_reader :obstacle_group
113
+
114
+ # @return [Symbol, nil] another obstacle to which a physical link exists
115
+ attr_reader :linked_to
116
+
117
+ # @return [Symbol, nil] type of physical link between this and another obstacle
118
+ attr_reader :link_type
119
+
120
+ def initialize(source: nil, name: nil, type:, xy:, z:, radius:)
97
121
  super(source: source)
98
- self.name, self.type, self.xy, self.radius, self.z = name, type, xy, radius, z
122
+ self.name, self.type, self.xy, self.z, self.radius = name, type, xy, z, radius
99
123
  @lighting = @marking = @height_accurate = false
100
124
  end
101
125
 
@@ -118,16 +142,16 @@ module AIXM
118
142
  @xy = value
119
143
  end
120
144
 
121
- def radius=(value)
122
- fail(ArgumentError, "invalid radius") unless value.is_a?(AIXM::D) && value.dist > 0
123
- @radius = value
124
- end
125
-
126
145
  def z=(value)
127
146
  fail(ArgumentError, "invalid z") unless value.is_a?(AIXM::Z) && value.qnh?
128
147
  @z = value
129
148
  end
130
149
 
150
+ def radius=(value)
151
+ fail(ArgumentError, "invalid radius") unless value.nil? || (value.is_a?(AIXM::D) && value.dist > 0)
152
+ @radius = value
153
+ end
154
+
131
155
  def lighting=(value)
132
156
  fail(ArgumentError, "invalid lighting") unless [true, false, nil].include? value
133
157
  @lighting = value
@@ -151,6 +175,11 @@ module AIXM
151
175
  @height = value
152
176
  end
153
177
 
178
+ def height_accurate=(value)
179
+ fail(ArgumentError, "invalid height accurate") unless [true, false, nil].include? value
180
+ @height_accurate = value
181
+ end
182
+
154
183
  def xy_accuracy=(value)
155
184
  fail(ArgumentError, "invalid xy accuracy") unless value.nil? || value.is_a?(AIXM::D)
156
185
  @xy_accuracy = value
@@ -161,11 +190,6 @@ module AIXM
161
190
  @z_accuracy = value
162
191
  end
163
192
 
164
- def height_accurate=(value)
165
- fail(ArgumentError, "invalid height accurate") unless [true, false, nil].include? value
166
- @height_accurate = value
167
- end
168
-
169
193
  def valid_from=(value)
170
194
  @valid_from = value&.to_time
171
195
  end
@@ -178,39 +202,59 @@ module AIXM
178
202
  @remarks = value&.to_s
179
203
  end
180
204
 
181
- # @return [Boolean] estimation whether one obstacle (e.g. single tree) or
182
- # a cluster of obstacles (e.g. a few trees)
183
- def clustered?
184
- height && radius > height
205
+ def obstacle_group=(value)
206
+ fail(ArgumentError, "invalid obstacle group") unless value.is_a?(AIXM::Feature::ObstacleGroup)
207
+ @obstacle_group = value
208
+ end
209
+ private :obstacle_group=
210
+
211
+ def linked_to=(value)
212
+ fail(ArgumentError, "invalid linked to") unless value.is_a?(AIXM::Feature::Obstacle)
213
+ @linked_to = value
185
214
  end
215
+ private :linked_to=
216
+
217
+ def link_type=(value)
218
+ @link_type = LINK_TYPES.lookup(value&.to_s&.to_sym, nil) || fail(ArgumentError, "invalid link type")
219
+ end
220
+ private :link_type=
186
221
 
187
222
  # @return [Boolean] whether part of an obstacle group
188
223
  def grouped?
189
- respond_to? :group
224
+ obstacle_group && obstacle_group.obstacles.count > 1
225
+ end
226
+
227
+ # @return [Boolean] whether obstacle is linked to another one
228
+ def linked?
229
+ !!linked_to
190
230
  end
191
231
 
192
232
  # @return [String] UID markup
193
233
  def to_uid(as: :ObsUid)
234
+ self.obstacle_group ||= singleton_obstacle_group
194
235
  builder = Builder::XmlMarkup.new(indent: 2)
195
236
  builder.tag!(as) do |tag|
237
+ tag << obstacle_group.to_uid.indent(2) if AIXM.ofmx?
196
238
  tag.geoLat((xy.lat(AIXM.schema)))
197
239
  tag.geoLong((xy.long(AIXM.schema)))
198
240
  end
199
241
  end
200
242
 
201
243
  # @return [String] AIXM or OFMX markup
202
- def to_xml
244
+ def to_xml(delegate: true)
245
+ self.obstacle_group ||= singleton_obstacle_group
246
+ return obstacle_group.to_xml if delegate && AIXM.ofmx?
203
247
  builder = Builder::XmlMarkup.new(indent: 2)
204
248
  builder.comment! "Obstacle: [#{type}] #{xy.to_s} #{name}".strip
205
- builder.Obs({ source: ((source || (group.source if grouped?)) if AIXM.ofmx?) }.compact) do |obs|
249
+ builder.Obs do |obs|
206
250
  obs << to_uid.indent(2)
207
251
  obs.txtName(name) if name
208
252
  if AIXM.ofmx?
209
253
  obs.codeType(TYPES.key(type).to_s)
210
254
  else
211
255
  obs.txtDescrType(TYPES.key(type).to_s)
212
- obs.codeGroup(grouped? || clustered? ? 'Y' : 'N')
213
256
  end
257
+ obs.codeGroup(grouped? ? 'Y' : 'N')
214
258
  if AIXM.ofmx?
215
259
  obs.codeLgt(lighting ? 'Y' : 'N') unless lighting.nil?
216
260
  obs.codeMarking(marking ? 'Y' : 'N') unless marking.nil?
@@ -220,27 +264,27 @@ module AIXM
220
264
  obs.txtDescrLgt(lighting_remarks) if lighting_remarks
221
265
  obs.txtDescrMarking(marking_remarks) if marking_remarks
222
266
  obs.codeDatum('WGE')
223
- if xy_accuracy
224
- obs.valGeoAccuracy(xy_accuracy.dist.trim)
225
- obs.uomGeoAccuracy(xy_accuracy.unit.upcase.to_s)
267
+ if AIXM.aixm? && obstacle_group.xy_accuracy
268
+ obs.valGeoAccuracy(obstacle_group.xy_accuracy.dist.trim)
269
+ obs.uomGeoAccuracy(obstacle_group.xy_accuracy.unit.upcase.to_s)
226
270
  end
227
271
  obs.valElev(z.alt)
228
- obs.valElevAccuracy(z_accuracy.to_ft.dist.round) if z_accuracy
272
+ if AIXM.aixm? && obstacle_group.z_accuracy
273
+ obs.valElevAccuracy(obstacle_group.z_accuracy.to_ft.dist.round)
274
+ end
229
275
  obs.valHgt(height.to_ft.dist.round) if height
276
+ obs.uomDistVer('FT')
230
277
  if AIXM.ofmx? && !height_accurate.nil?
231
278
  obs.codeHgtAccuracy(height_accurate ? 'Y' : 'N')
232
279
  end
233
- obs.uomDistVer('FT')
234
280
  if AIXM.ofmx?
235
- obs.valRadius(radius.dist.trim)
236
- obs.uomRadius(radius.unit.upcase.to_s)
237
- if grouped?
238
- obs.codeGroupId(group.id)
239
- obs.txtGroupName(group.name) if group.name
240
- if linked?
241
- obs << linked_to.to_uid(as: :ObsUidLink).indent(2)
242
- obs.codeLinkType(AIXM::Feature::ObstacleGroup::LINK_TYPES.key(link_type).to_s)
243
- end
281
+ if radius
282
+ obs.valRadius(radius.dist.trim)
283
+ obs.uomRadius(radius.unit.upcase.to_s)
284
+ end
285
+ if grouped? && linked?
286
+ obs << linked_to.to_uid(as: :ObsUidLink).indent(2)
287
+ obs.codeLinkType(LINK_TYPES.key(link_type).to_s)
244
288
  end
245
289
  obs.datetimeValidWef(valid_from.xmlschema) if valid_from
246
290
  obs.datetimeValidTil(valid_until.xmlschema) if valid_until
@@ -249,38 +293,12 @@ module AIXM
249
293
  end
250
294
  end
251
295
 
252
- # Extensions used by ObstacleGroup feature
253
- module Grouped
254
- # @return [AIXM::Feature::ObstacleGroup] group this obstacle belongs to
255
- attr_reader :group
256
-
257
- # @return [AIXM::Feature::Obstacle] another obstacle this one is linked to
258
- attr_reader :linked_to
259
-
260
- # @return [Symbol] type of link between two obstacles
261
- attr_reader :link_type
296
+ private
262
297
 
263
- def group=(value)
264
- fail(ArgumentError, "invalid group") unless value.is_a?(AIXM::Feature::ObstacleGroup)
265
- @group = value
266
- end
267
- private :group=
268
-
269
- # @return [Boolean] whether obstacle is linked to another one
270
- def linked?
271
- !!linked_to
272
- end
273
-
274
- def linked_to=(value)
275
- fail(ArgumentError, "invalid linked to") unless value.is_a?(AIXM::Feature::Obstacle)
276
- @linked_to = value
277
- end
278
- private :linked_to=
279
-
280
- def link_type=(value)
281
- @link_type = AIXM::Feature::ObstacleGroup::LINK_TYPES.lookup(value&.to_s&.to_sym, nil) || fail(ArgumentError, "invalid link type")
282
- end
283
- private :link_type=
298
+ # OFMX requires single, ungrouped obstacles to be the only member of a
299
+ # singleton obstacle group.
300
+ def singleton_obstacle_group
301
+ AIXM.obstacle_group.add_obstacle self
284
302
  end
285
303
 
286
304
  end