aixm 0.2.3 → 0.3.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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.ruby-version +1 -1
  4. data/.yardopts +3 -0
  5. data/CHANGELOG.md +34 -14
  6. data/Guardfile +1 -0
  7. data/README.md +64 -257
  8. data/lib/aixm.rb +16 -7
  9. data/lib/aixm/component.rb +6 -0
  10. data/lib/aixm/component/frequency.rb +135 -0
  11. data/lib/aixm/component/geometry.rb +34 -23
  12. data/lib/aixm/component/geometry/arc.rb +37 -22
  13. data/lib/aixm/component/geometry/border.rb +29 -20
  14. data/lib/aixm/component/geometry/circle.rb +39 -22
  15. data/lib/aixm/component/geometry/point.rb +29 -13
  16. data/lib/aixm/component/helipad.rb +154 -0
  17. data/lib/aixm/component/layer.rb +91 -0
  18. data/lib/aixm/component/runway.rb +294 -0
  19. data/lib/aixm/component/service.rb +170 -0
  20. data/lib/aixm/component/timetable.rb +65 -0
  21. data/lib/aixm/component/vertical_limits.rb +65 -29
  22. data/lib/aixm/config.rb +87 -0
  23. data/lib/aixm/document.rb +66 -42
  24. data/lib/aixm/errors.rb +11 -0
  25. data/lib/aixm/f.rb +34 -20
  26. data/lib/aixm/feature.rb +38 -0
  27. data/lib/aixm/feature/airport.rb +473 -0
  28. data/lib/aixm/feature/airspace.rb +145 -92
  29. data/lib/aixm/feature/navigational_aid.rb +94 -0
  30. data/lib/aixm/feature/navigational_aid/designated_point.rb +50 -54
  31. data/lib/aixm/feature/navigational_aid/dme.rb +48 -40
  32. data/lib/aixm/feature/navigational_aid/marker.rb +55 -45
  33. data/lib/aixm/feature/navigational_aid/ndb.rb +54 -50
  34. data/lib/aixm/feature/navigational_aid/tacan.rb +38 -31
  35. data/lib/aixm/feature/navigational_aid/vor.rb +84 -76
  36. data/lib/aixm/feature/organisation.rb +97 -0
  37. data/lib/aixm/feature/unit.rb +152 -0
  38. data/lib/aixm/refinements.rb +132 -47
  39. data/lib/aixm/shortcuts.rb +11 -6
  40. data/lib/aixm/version.rb +1 -1
  41. data/lib/aixm/xy.rb +64 -20
  42. data/lib/aixm/z.rb +51 -22
  43. data/{lib/aixm/schemas → schemas/aixm}/4.5/AIXM-DataTypes.xsd +0 -0
  44. data/{lib/aixm/schemas → schemas/aixm}/4.5/AIXM-Features.xsd +0 -0
  45. data/{lib/aixm/schemas → schemas/aixm}/4.5/AIXM-Snapshot.xsd +0 -0
  46. data/schemas/ofmx/0/OFMX-DataTypes.xsd +5077 -0
  47. data/schemas/ofmx/0/OFMX-Features.xsd +9955 -0
  48. data/schemas/ofmx/0/OFMX-Snapshot.xsd +217 -0
  49. data/spec/factory.rb +209 -33
  50. data/spec/lib/aixm/component/frequency_spec.rb +75 -0
  51. data/spec/lib/aixm/component/geometry/arc_spec.rb +28 -22
  52. data/spec/lib/aixm/component/geometry/border_spec.rb +23 -20
  53. data/spec/lib/aixm/component/geometry/circle_spec.rb +31 -22
  54. data/spec/lib/aixm/component/geometry/point_spec.rb +11 -14
  55. data/spec/lib/aixm/component/geometry_spec.rb +150 -69
  56. data/spec/lib/aixm/component/helipad_spec.rb +136 -0
  57. data/spec/lib/aixm/component/layer_spec.rb +110 -0
  58. data/spec/lib/aixm/component/runway_spec.rb +402 -0
  59. data/spec/lib/aixm/component/service_spec.rb +61 -0
  60. data/spec/lib/aixm/component/timetable_spec.rb +49 -0
  61. data/spec/lib/aixm/component/vertical_limits_spec.rb +39 -20
  62. data/spec/lib/aixm/config_spec.rb +41 -0
  63. data/spec/lib/aixm/document_spec.rb +637 -147
  64. data/spec/lib/aixm/errors_spec.rb +14 -0
  65. data/spec/lib/aixm/f_spec.rb +17 -10
  66. data/spec/lib/aixm/feature/airport_spec.rb +546 -0
  67. data/spec/lib/aixm/feature/airspace_spec.rb +349 -226
  68. data/spec/lib/aixm/feature/navigational_aid/designated_point_spec.rb +47 -36
  69. data/spec/lib/aixm/feature/navigational_aid/dme_spec.rb +61 -36
  70. data/spec/lib/aixm/feature/navigational_aid/marker_spec.rb +61 -113
  71. data/spec/lib/aixm/feature/navigational_aid/ndb_spec.rb +65 -79
  72. data/spec/lib/aixm/feature/navigational_aid/tacan_spec.rb +57 -36
  73. data/spec/lib/aixm/feature/navigational_aid/vor_spec.rb +86 -112
  74. data/spec/lib/aixm/feature/navigational_aid_spec.rb +52 -0
  75. data/spec/lib/aixm/feature/organisation_spec.rb +77 -0
  76. data/spec/lib/aixm/feature/unit_spec.rb +227 -0
  77. data/spec/lib/aixm/feature_spec.rb +58 -0
  78. data/spec/lib/aixm/refinements_spec.rb +187 -178
  79. data/spec/lib/aixm/xy_spec.rb +45 -34
  80. data/spec/lib/aixm/z_spec.rb +19 -21
  81. data/spec/macros/organisation.rb +11 -0
  82. data/spec/macros/remarks.rb +12 -0
  83. data/spec/macros/timetable.rb +11 -0
  84. data/spec/macros/xy.rb +11 -0
  85. data/spec/macros/z_qnh.rb +11 -0
  86. data/spec/spec_helper.rb +26 -0
  87. metadata +60 -19
  88. data/lib/aixm/base.rb +0 -10
  89. data/lib/aixm/component/base.rb +0 -6
  90. data/lib/aixm/component/class_layer.rb +0 -46
  91. data/lib/aixm/component/geometry/base.rb +0 -8
  92. data/lib/aixm/component/schedule.rb +0 -43
  93. data/lib/aixm/feature/base.rb +0 -6
  94. data/lib/aixm/feature/navigational_aid/base.rb +0 -79
  95. data/spec/lib/aixm/component/class_layer_spec.rb +0 -74
  96. data/spec/lib/aixm/component/schedule_spec.rb +0 -33
  97. data/spec/lib/aixm/feature/navigational_aid/base_spec.rb +0 -41
@@ -0,0 +1,11 @@
1
+ module AIXM
2
+
3
+ # @see AIXM::Component::Geometry
4
+ # @see AIXM::Feature::Airspace#to_xml
5
+ class GeometryError < StandardError; end
6
+
7
+ # @see AIXM::Component::Layer
8
+ # @see AIXM::Feature::Airspace#to_xml
9
+ class LayerError < StandardError; end
10
+
11
+ end
@@ -1,37 +1,51 @@
1
+ using AIXM::Refinements
2
+
1
3
  module AIXM
2
4
 
3
- ##
4
- # Frequency
5
+ # Radio frequency for communication, navigation and so forth.
5
6
  #
6
- # The following units are recognized:
7
- # * +:mhz+ - megahertz
8
- # * +:khz+ - kilohertz
9
- class F < Base
10
- using AIXM::Refinements
7
+ # @example
8
+ # AIXM.f(123.35, :mhz)
9
+ class F
10
+ UNITS = %i(ghz mhz khz).freeze
11
11
 
12
- UNITS = %i(mhz khz).freeze
12
+ # @return [Float] frequency
13
+ attr_reader :freq
13
14
 
14
- attr_reader :freq, :unit
15
+ # @return [Symbol] unit (see {UNITS})
16
+ attr_reader :unit
15
17
 
16
18
  def initialize(freq, unit)
17
- @freq, @unit = freq.to_f, unit&.to_sym&.downcase
18
- fail(ArgumentError, "unrecognized unit `#{@unit}'") unless UNITS.include? @unit
19
+ self.freq, self.unit = freq, unit
20
+ end
21
+
22
+ # @return [String]
23
+ def inspect
24
+ %Q(#<#{self.class} #{to_s}>)
25
+ end
26
+
27
+ # @return [String] human readable representation (e.g. "123.35 mhz")
28
+ def to_s
29
+ [freq, unit].join(' ')
30
+ end
31
+
32
+ def freq=(value)
33
+ fail(ArgumentError, "invalid freq") unless value.is_a? Numeric
34
+ @freq = value.to_f
19
35
  end
20
36
 
21
- ##
22
- # Digest to identify the payload
23
- def to_digest
24
- [freq, unit].to_digest
37
+ def unit=(value)
38
+ fail(ArgumentError, "invalid unit") unless value.respond_to? :to_sym
39
+ @unit = value.to_sym.downcase
40
+ fail(ArgumentError, "invalid unit") unless UNITS.include? @unit
25
41
  end
26
42
 
27
- ##
28
- # Check whether two frequencies are identical
43
+ # @return [Boolean]
29
44
  def ==(other)
30
- other.is_a?(F) && freq == other.freq && unit == other.unit
45
+ other.is_a?(self.class) && freq == other.freq && unit == other.unit
31
46
  end
32
47
 
33
- ##
34
- # Check whether this frequency is part of a frequency band
48
+ # @return [Boolean] whether this frequency is part of a frequency band
35
49
  def between?(lower_freq, upper_freq, unit)
36
50
  freq.between?(lower_freq, upper_freq) && self.unit == unit
37
51
  end
@@ -0,0 +1,38 @@
1
+ module AIXM
2
+
3
+ # @abstract
4
+ class Feature
5
+ private_class_method :new
6
+
7
+ # @return [String] reference to source of the feature data
8
+ attr_reader :source
9
+
10
+ def initialize(source: nil, region: nil)
11
+ self.source, self.region = source, region
12
+ end
13
+
14
+ # @return [String] reference to source of the feature data
15
+ def source=(value)
16
+ fail(ArgumentError, "invalid source") unless value.nil? || value.is_a?(String)
17
+ @source = value
18
+ end
19
+
20
+ # @!attribute region
21
+ # @note When assigning +nil+, the global default +AIXM.config.region+ is written instead.
22
+ # @return [String] region the feature belongs to
23
+ def region
24
+ @region || AIXM.config.region&.upcase
25
+ end
26
+
27
+ def region=(value)
28
+ fail(ArgumentError, "invalid region") unless value.nil? || value.is_a?(String)
29
+ @region = value&.upcase
30
+ end
31
+
32
+ # @return [Boolean]
33
+ def ==(other)
34
+ other.is_a?(self.class) && self.to_uid == other.to_uid
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,473 @@
1
+ using AIXM::Refinements
2
+
3
+ module AIXM
4
+ class Feature
5
+
6
+ # Defined area on land or water to be used for the arrival, departure and
7
+ # surface movement of aircraft.
8
+ #
9
+ # ===Cheat Sheet in Pseudo Code:
10
+ # airport = AIXM.airport(
11
+ # source: String or nil
12
+ # region: String or nil (falls back to AIXM.config.region)
13
+ # organisation: AIXM.organisation
14
+ # code: String
15
+ # name: String
16
+ # xy: AIXM.xy
17
+ # )
18
+ # airport.gps = String or nil
19
+ # airport.type = TYPES
20
+ # airport.z = AIXM.z or nil
21
+ # airport.declination = Float or nil
22
+ # airport.transition_z = AIXM.z or nil
23
+ # airport.timetable = AIXM.timetable or nil
24
+ # airport.remarks = String or nil
25
+ # airport.add_runway(AIXM.runway)
26
+ # airport.add_helipad(AIXM.helipad)
27
+ # airport.add_usage_limitation(UsageLimitation::TYPES)
28
+ #
29
+ # @see https://github.com/openflightmaps/ofmx/wiki/Airport#ahp-airport
30
+ class Airport < Feature
31
+ public_class_method :new
32
+
33
+ CODE_PATTERN = /^[A-Z]{2}([A-Z]{1,2}|\d{4})$/.freeze
34
+
35
+ TYPES = {
36
+ AD: :aerodrome,
37
+ HP: :heliport,
38
+ AH: :aerodrome_and_heliport,
39
+ LS: :landing_site
40
+ }.freeze
41
+
42
+ # @return [AIXM::Feature::Organisation] superior organisation
43
+ attr_reader :organisation
44
+
45
+ # ICAO indicator, IATA indicator or ICAO serial number
46
+ #
47
+ # * four letter ICAO indicator (e.g. "LFMV")
48
+ # * three letter IATA indicator (e.g. "AVN")
49
+ # * two letter ICAO country code + four digit number (e.g. "LF1234")
50
+ #
51
+ # @return [String] airport indicator code
52
+ attr_reader :code
53
+
54
+ # @return [String] full name
55
+ attr_reader :name
56
+
57
+ # @return [AIXM::XY] reference point
58
+ attr_reader :xy
59
+
60
+ # @return [String, nil] GPS code
61
+ attr_reader :gps
62
+
63
+ # @return [AIXM::Z, nil] elevation in +:qnh+
64
+ attr_reader :z
65
+
66
+ # When looking towards the geographic (aka: true) north, a positive
67
+ # declination represents the magnetic north is to the right (aka: east)
68
+ # by this angle.
69
+ #
70
+ # @see https://en.wikipedia.org/wiki/Magnetic_declination
71
+ # @return [Float, nil] magnetic declination in degrees
72
+ attr_reader :declination
73
+
74
+ # @return [AIXM::Z, nil] transition altitude in +:qnh+
75
+ attr_reader :transition_z
76
+
77
+ # @return [AIXM::Component::Timetable, nil] operating hours
78
+ attr_reader :timetable
79
+
80
+ # @return [String, nil] free text remarks
81
+ attr_reader :remarks
82
+
83
+ # @return [Array<AIXM::Component::Runway>] runways present at this airport
84
+ attr_reader :runways
85
+
86
+ # @return [Array<AIXM::Component::Helipad>] helipads present at this airport
87
+ attr_reader :helipads
88
+
89
+ # @return [Array<AIXM::Feature::Airport::UsageLimitation>] usage limitations
90
+ attr_accessor :usage_limitations
91
+
92
+ def initialize(source: nil, region: nil, organisation:, code:, name:, xy:)
93
+ super(source: source, region: region)
94
+ self.organisation, self.code, self.name, self.xy = organisation, code, name, xy
95
+ @runways, @helipads, @usage_limitations = [], [], []
96
+ end
97
+
98
+ # @return [String]
99
+ def inspect
100
+ %Q(#<#{self.class} code=#{code.inspect}>)
101
+ end
102
+
103
+ def organisation=(value)
104
+ fail(ArgumentError, "invalid organisation") unless value.is_a? AIXM::Feature::Organisation
105
+ @organisation = value
106
+ end
107
+
108
+ def code=(value)
109
+ fail(ArgumentError, "invalid code `#{code}'") unless value&.upcase&.match? CODE_PATTERN
110
+ @code = value.upcase
111
+ end
112
+
113
+ def name=(value)
114
+ fail(ArgumentError, "invalid name") unless value.is_a? String
115
+ @name = value.uptrans
116
+ end
117
+
118
+ def gps=(value)
119
+ fail(ArgumentError, "invalid gps") unless value.nil? || value.is_a?(String)
120
+ @gps = value&.upcase
121
+ end
122
+
123
+ # The type is usually derived from the presence of runways and helipads,
124
+ # however, this may be overridden by setting an alternative value, most
125
+ # notably +:landing_site+.
126
+ #
127
+ # @return [Symbol] type of airport (see {TYPES})
128
+ def type
129
+ @type = case
130
+ when @type then @type
131
+ when runways.any? && helipads.any? then :aerodrome_and_heliport
132
+ when runways.any? then :aerodrome
133
+ when helipads.any? then :heliport
134
+ end
135
+ end
136
+
137
+ def type=(value)
138
+ resolved_value = TYPES.lookup(value&.to_s&.to_sym, nil)
139
+ fail(ArgumentError, "invalid type") unless resolved_value == :landing_site
140
+ @type = resolved_value
141
+ end
142
+
143
+ def xy=(value)
144
+ fail(ArgumentError, "invalid xy") unless value.is_a? AIXM::XY
145
+ @xy = value
146
+ end
147
+
148
+ def z=(value)
149
+ fail(ArgumentError, "invalid z") unless value.nil? || (value.is_a?(AIXM::Z) && value.qnh?)
150
+ @z = value
151
+ end
152
+
153
+ def declination=(value)
154
+ return @declination = value if value.nil?
155
+ fail(ArgumentError, "invalid declination") unless value.is_a?(Numeric) && (-180..180).include?(value)
156
+ @declination = value.to_f
157
+ end
158
+
159
+ def transition_z=(value)
160
+ fail(ArgumentError, "invalid transition_z") unless value.nil? || (value.is_a?(AIXM::Z) && value.qnh?)
161
+ @transition_z = value
162
+ end
163
+
164
+ def timetable=(value)
165
+ fail(ArgumentError, "invalid timetable") unless value.nil? || value.is_a?(AIXM::Component::Timetable)
166
+ @timetable = value
167
+ end
168
+
169
+ def remarks=(value)
170
+ @remarks = value&.to_s
171
+ end
172
+
173
+ # Add a runway to the airport.
174
+ #
175
+ # @param runway [AIXM::Component::Runway] runway instance
176
+ # @return [self]
177
+ def add_runway(runway)
178
+ fail(ArgumentError, "invalid runway") unless runway.is_a? AIXM::Component::Runway
179
+ runway.send(:airport=, self)
180
+ @runways << runway
181
+ self
182
+ end
183
+
184
+ # Add a helipad to the airport.
185
+ #
186
+ # @param helipad [AIXM::Component::Helipad] helipad instance
187
+ # @return [self]
188
+ def add_helipad(helipad)
189
+ fail(ArgumentError, "invalid helipad") unless helipad.is_a? AIXM::Component::Helipad
190
+ helipad.send(:airport=, self)
191
+ @helipads << helipad
192
+ self
193
+ end
194
+
195
+ # Add an airport usage limitation.
196
+ #
197
+ # See {AIXM::Feature::Airport::UsageLimitation::TYPES UsageLimitation::TYPES}
198
+ # for recognized limitations and {AIXM::Feature::Airport::UsageLimitation#add_condition UsageLimitation#add_condition}
199
+ # for recognized conditions.
200
+ #
201
+ # Multiple conditions are joined with an implicit *or* whereas the
202
+ # specifics of a condition (aircraft, rule etc) are joined with an
203
+ # implicit *and*.
204
+ #
205
+ # @example Limitation applying to any traffic
206
+ # airport.add_usage_limitation(:permitted)
207
+ #
208
+ # @example Limitation applying to specific traffic
209
+ # airport.add_usage_limitation(:reservation_required) do |reservation_required|
210
+ # reservation_required.add_condition do |condition|
211
+ # condition.aircraft = :glider
212
+ # end
213
+ # reservation_required.add_condition do |condition|
214
+ # condition.rule = :ifr
215
+ # condition.origin = :international
216
+ # end
217
+ # reservation_required.timetable = AIXM::H24
218
+ # reservation_required.remarks = "Reservation 24 HRS prior to arrival"
219
+ # end
220
+ #
221
+ # @yieldparam usage_limitation [AIXM::Feature::Airport::UsageLimitation]
222
+ # @return [self]
223
+ def add_usage_limitation(type)
224
+ usage_limitation = UsageLimitation.new(type: type)
225
+ yield(usage_limitation) if block_given?
226
+ @usage_limitations << usage_limitation
227
+ self
228
+ end
229
+
230
+ # @return [String] UID markup
231
+ def to_uid
232
+ builder = Builder::XmlMarkup.new(indent: 2)
233
+ builder.AhpUid({ region: (region if AIXM.ofmx?) }.compact) do |ahp_uid|
234
+ ahp_uid.codeId(code)
235
+ end
236
+ end
237
+
238
+ # @return [String] AIXM or OFMX markup
239
+ def to_xml
240
+ builder = Builder::XmlMarkup.new(indent: 2)
241
+ builder.comment! "Airport: #{code} #{name}"
242
+ builder.Ahp({ source: (source if AIXM.ofmx?) }.compact) do |ahp|
243
+ ahp << to_uid.indent(2)
244
+ ahp << organisation.to_uid.indent(2)
245
+ ahp.txtName(name)
246
+ ahp.codeIcao(code) if code.length == 4
247
+ ahp.codeIata(code) if code.length == 3
248
+ ahp.codeGps(gps) if AIXM.ofmx? && gps
249
+ ahp.codeType(TYPES.key(type).to_s) if type
250
+ ahp.geoLat(xy.lat(AIXM.schema))
251
+ ahp.geoLong(xy.long(AIXM.schema))
252
+ ahp.codeDatum('WGE')
253
+ if z
254
+ ahp.valElev(z.alt)
255
+ ahp.uomDistVer(z.unit.upcase.to_s)
256
+ end
257
+ ahp.valMagVar(declination) if declination
258
+ if transition_z
259
+ ahp.valTransitionAlt(transition_z.alt)
260
+ ahp.uomTransitionAlt(transition_z.unit.upcase.to_s)
261
+ end
262
+ ahp << timetable.to_xml(as: :Aht).indent(2) if timetable
263
+ ahp.txtRmk(remarks) if remarks
264
+ end
265
+ runways.each do |runway|
266
+ builder << runway.to_xml
267
+ end
268
+ helipads.each do |helipad|
269
+ builder << helipad.to_xml
270
+ end
271
+ if usage_limitations.any?
272
+ builder.Ahu do |ahu|
273
+ ahu.AhuUid do |ahu_uid|
274
+ ahu_uid << to_uid.indent(4)
275
+ end
276
+ usage_limitations.each do |usage_limitation|
277
+ ahu << usage_limitation.to_xml.indent(2)
278
+ end
279
+ end
280
+ end
281
+ builder.target!
282
+ end
283
+
284
+ # Limitations concerning the availability of an airport for certain flight
285
+ # types, aircraft types etc during specific hours.
286
+ #
287
+ # @see AIXM::Feature::Airport#add_usage_limitation
288
+ # @see https://github.com/openflightmaps/ofmx/wiki/Airport#ahu-airport-usage
289
+ class UsageLimitation
290
+ TYPES = {
291
+ PERMIT: :permitted,
292
+ FORBID: :forbidden,
293
+ RESERV: :reservation_required,
294
+ OTHER: :other # specify in remarks
295
+ }.freeze
296
+
297
+ # @return [AIXM::Feature::Airport] airport this usage limitation is assigned to
298
+ attr_reader :airport
299
+
300
+ # @return [Symbol] type of limitation
301
+ attr_reader :type
302
+
303
+ # @return [Array<AIXM::Feature::Airport::UsageLimitation::Condition>] conditions for this limitation to apply
304
+ attr_reader :conditions
305
+
306
+ # @return [AIXM::Component::Timetable, nil] limitation application hours
307
+ attr_reader :timetable
308
+
309
+ # @return [String, nil] free text remarks
310
+ attr_reader :remarks
311
+
312
+ def initialize(type:)
313
+ self.type = type
314
+ @conditions = []
315
+ end
316
+
317
+ # @return [String]
318
+ def inspect
319
+ %Q(#<#{self.class} type=#{type.inspect}>)
320
+ end
321
+
322
+ def type=(value)
323
+ @type = TYPES.lookup(value&.to_s&.to_sym, nil) || fail(ArgumentError, "invalid type")
324
+ end
325
+
326
+ # Add a condition to the usage limitation.
327
+ #
328
+ # @yieldparam condition [AIXM::Feature::Airport::UsageLimitation::Condition]
329
+ # @return [self]
330
+ def add_condition
331
+ condition = Condition.new
332
+ yield(condition)
333
+ @conditions << condition
334
+ self
335
+ end
336
+
337
+ def timetable=(value)
338
+ fail(ArgumentError, "invalid timetable") unless value.nil? || value.is_a?(AIXM::Component::Timetable)
339
+ @timetable = value
340
+ end
341
+
342
+ def remarks=(value)
343
+ @remarks = value&.to_s
344
+ end
345
+
346
+ # @return [String] AIXM or OFMX markup
347
+ def to_xml
348
+ builder = Builder::XmlMarkup.new(indent: 2)
349
+ builder.UsageLimitation do |usage_limitation|
350
+ usage_limitation.codeUsageLimitation(TYPES.key(type).to_s)
351
+ conditions.each do |condition|
352
+ usage_limitation << condition.to_xml.indent(2)
353
+ end
354
+ usage_limitation << timetable.to_xml(as: :Timetable).indent(2) if timetable
355
+ usage_limitation.txtRmk(remarks) if remarks
356
+ end
357
+ end
358
+
359
+ # Flight and/or aircraft characteristics used to target a usage
360
+ # limitation.
361
+ #
362
+ # @see AIXM::Feature::Airport#add_usage_limitation
363
+ # @see https://github.com/openflightmaps/ofmx/wiki/Airport#ahu-airport-usage
364
+ class Condition
365
+ AIRCRAFT = {
366
+ L: :landplane,
367
+ S: :seaplane,
368
+ A: :amphibian,
369
+ H: :helicopter,
370
+ G: :gyrocopter,
371
+ T: :tilt_wing,
372
+ R: :short_takeoff_and_landing,
373
+ E: :glider,
374
+ N: :hangglider,
375
+ P: :paraglider,
376
+ U: :ultra_light,
377
+ B: :balloon,
378
+ D: :unmanned_drone,
379
+ OTHER: :other # specify in remarks
380
+ }.freeze
381
+
382
+ RULES = {
383
+ I: :ifr,
384
+ V: :vfr,
385
+ IV: :ifr_and_vfr
386
+ }.freeze
387
+
388
+ REALMS = {
389
+ CIVIL: :civilian,
390
+ MIL: :military,
391
+ OTHER: :other # specify in remarks
392
+ }.freeze
393
+
394
+ ORIGINS = {
395
+ NTL: :national,
396
+ INTL: :international,
397
+ ANY: :any,
398
+ OTHER: :other # specify in remarks
399
+ }.freeze
400
+
401
+ PURPOSES = {
402
+ S: :scheduled,
403
+ NS: :not_scheduled,
404
+ P: :private,
405
+ TRG: :school_or_training,
406
+ WORK: :aerial_work,
407
+ OTHER: :other # specify in remarks
408
+ }.freeze
409
+
410
+ # @return [Symbol, nil] kind of aircraft (see {AIRCRAFT})
411
+ attr_reader :aircraft
412
+
413
+ # @return [String, nil] flight rule (see {RULES})
414
+ attr_reader :rule
415
+
416
+ # @return [String, nil] whether military or civil (see {REALMS})
417
+ attr_reader :realm
418
+
419
+ # @return [String, nil] geographic origin of the flight (see {ORIGINS})
420
+ attr_reader :origin
421
+
422
+ # @return [String, nil] purpose of the flight (see {PURPOSES})
423
+ attr_reader :purpose
424
+
425
+ # @return [String]
426
+ def inspect
427
+ %Q(#<#{self.class} aircraft=#{aircraft.inspect} rule=#{rule.inspect} realm=#{realm.inspect} origin=#{origin.inspect} purpose=#{purpose.inspect}>)
428
+ end
429
+
430
+ def aircraft=(value)
431
+ @aircraft = value.nil? ? nil : AIRCRAFT.lookup(value.to_s.to_sym, nil) || fail(ArgumentError, "invalid aircraft")
432
+ end
433
+
434
+ def rule=(value)
435
+ @rule = value.nil? ? nil : RULES.lookup(value.to_s.to_sym, nil) || fail(ArgumentError, "invalid rule")
436
+ end
437
+
438
+ def realm=(value)
439
+ @realm = value.nil? ? nil : REALMS.lookup(value.to_s.to_sym, nil) || fail(ArgumentError, "invalid realm")
440
+ end
441
+
442
+ def origin=(value)
443
+ @origin = value.nil? ? nil : ORIGINS.lookup(value.to_s.to_sym, nil) || fail(ArgumentError, "invalid origin")
444
+ end
445
+
446
+ def purpose=(value)
447
+ @purpose = value.nil? ? nil : PURPOSES.lookup(value.to_s.to_sym, nil) || fail(ArgumentError, "invalid purpose")
448
+ end
449
+
450
+ # @return [String] AIXM or OFMX markup
451
+ def to_xml
452
+ builder = Builder::XmlMarkup.new(indent: 2)
453
+ builder.UsageCondition do |usage_condition|
454
+ if aircraft
455
+ usage_condition.AircraftClass do |aircraft_class|
456
+ aircraft_class.codeType(AIRCRAFT.key(aircraft).to_s)
457
+ end
458
+ end
459
+ if rule || realm || origin || purpose
460
+ usage_condition.FlightClass do |flight_class|
461
+ flight_class.codeRule(RULES.key(rule).to_s) if rule
462
+ flight_class.codeMil(REALMS.key(realm).to_s) if realm
463
+ flight_class.codeOrigin(ORIGINS.key(origin).to_s) if origin
464
+ flight_class.codePurpose(PURPOSES.key(purpose).to_s) if purpose
465
+ end
466
+ end
467
+ end
468
+ end
469
+ end
470
+ end
471
+ end
472
+ end
473
+ end