aixm 0.3.4 → 0.3.5

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