aixm 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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