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
@@ -1,30 +1,46 @@
1
+ using AIXM::Refinements
2
+
1
3
  module AIXM
2
- module Component
4
+ class Component
3
5
  class Geometry
4
6
 
5
- ##
6
- # Points are defined by +xy+ coordinates.
7
- class Point < Base
7
+ # Points are defined by {#xy} coordinates.
8
+ #
9
+ # ===Cheat Sheet in Pseudo Code:
10
+ # point = AIXM.point(
11
+ # xy: AIXM.xy
12
+ # )
13
+ #
14
+ # @see https://github.com/openflightmaps/ofmx/wiki/Airspace#point
15
+ class Point
8
16
  extend Forwardable
9
- using AIXM::Refinements
10
17
 
11
- def_delegators :xy, :to_digest
18
+ def_delegators :xy
12
19
 
20
+ # @return [AIXM::XY] (starting) point
13
21
  attr_reader :xy
14
22
 
15
23
  def initialize(xy:)
16
- fail(ArgumentError, "invalid xy") unless xy.is_a? AIXM::XY
17
- @xy = xy
24
+ self.xy = xy
25
+ end
26
+
27
+ # @return [String]
28
+ def inspect
29
+ %Q(#<#{self.class} xy="#{xy.to_s}">)
30
+ end
31
+
32
+ def xy=(value)
33
+ fail(ArgumentError, "invalid xy") unless value.is_a? AIXM::XY
34
+ @xy = value
18
35
  end
19
36
 
20
- ##
21
- # Render AIXM markup
22
- def to_aixm(*extensions)
37
+ # @return [String] AIXM or OFMX markup
38
+ def to_xml
23
39
  builder = Builder::XmlMarkup.new(indent: 2)
24
40
  builder.Avx do |avx|
25
41
  avx.codeType('GRC')
26
- avx.geoLat(xy.lat(format_for(*extensions)))
27
- avx.geoLong(xy.long(format_for(*extensions)))
42
+ avx.geoLat(xy.lat(AIXM.schema))
43
+ avx.geoLong(xy.long(AIXM.schema))
28
44
  avx.codeDatum('WGE')
29
45
  end
30
46
  end
@@ -0,0 +1,154 @@
1
+ using AIXM::Refinements
2
+
3
+ module AIXM
4
+ class Component
5
+
6
+ # Helipads are TLOF (touch-down and lift-off areas) e.g. for helicopters.
7
+ #
8
+ # ===Cheat Sheet in Pseudo Code:
9
+ # helipad = AIXM.helipad(
10
+ # name: String
11
+ # )
12
+ # helipad.xy = AIXM.xy
13
+ # helipad.z = AIXM.z or nil
14
+ # helipad.length = Integer or nil # meters
15
+ # helipad.width = Integer or nil # meters
16
+ # helipad.composition = COMPOSITIONS or nil
17
+ # helipad.status = STATUSES or nil
18
+ # helipad.remarks = String or nil
19
+ #
20
+ # @see https://github.com/openflightmaps/ofmx/wiki/Airport#tla-helipad-tlof
21
+ class Helipad
22
+ COMPOSITIONS = {
23
+ ASPH: :asphalt,
24
+ BITUM: :bitumen, # dug up, bound and rolled ground
25
+ CONC: :concrete,
26
+ GRAVE: :gravel, # small and midsize rounded stones
27
+ MACADAM: :macadam, # small rounded stones
28
+ SAND: :sand,
29
+ GRADE: :graded_earth, # graded or rolled earth possibly with some grass
30
+ GRASS: :grass, # lawn
31
+ WATER: :water,
32
+ OTHER: :other # specify in remarks
33
+ }.freeze
34
+
35
+ STATUSES = {
36
+ CLSD: :closed,
37
+ WIP: :work_in_progress, # e.g. construction work
38
+ PARKED: :parked_aircraft, # parked or disabled aircraft on helipad
39
+ FAILAID: :visual_aids_failure, # failure or irregular operation of visual aids
40
+ SPOWER: :secondary_power, # secondary power supply in operation
41
+ OTHER: :other # specify in remarks
42
+ }.freeze
43
+
44
+ # @return [AIXM::Feature::Airport] airport this helipad belongs to
45
+ attr_reader :airport
46
+
47
+ # @return [String] full name (e.g. "H1")
48
+ attr_reader :name
49
+
50
+ # @return [AIXM::XY] center point
51
+ attr_reader :xy
52
+
53
+ # @return [AIXM:Z, nil] elevation in +:qnh+
54
+ attr_reader :z
55
+
56
+ # @return [Integer, nil] length in meters
57
+ attr_reader :length
58
+
59
+ # @return [Integer, nil] width in meters
60
+ attr_reader :width
61
+
62
+ # @return [Symbol, nil] composition of the surface (see {COMPOSITIONS})
63
+ attr_reader :composition
64
+
65
+ # @return [Symbol, nil] status of the helipad (see {STATUSES}) or +nil+ for normal operation
66
+ attr_reader :status
67
+
68
+ # @return [String, nil] free text remarks
69
+ attr_reader :remarks
70
+
71
+ def initialize(name:)
72
+ self.name = name
73
+ end
74
+
75
+ # @return [String]
76
+ def inspect
77
+ %Q(#<#{self.class} name=#{name.inspect}>)
78
+ end
79
+
80
+ def airport=(value)
81
+ fail(ArgumentError, "invalid airport") unless value.is_a? AIXM::Feature::Airport
82
+ @airport = value
83
+ end
84
+ private :airport=
85
+
86
+ def name=(value)
87
+ fail(ArgumentError, "invalid name") unless value.is_a? String
88
+ @name = value.uptrans
89
+ end
90
+
91
+ def xy=(value)
92
+ fail(ArgumentError, "invalid xy") unless value.is_a? AIXM::XY
93
+ @xy = value
94
+ end
95
+
96
+ def z=(value)
97
+ fail(ArgumentError, "invalid z") unless value.nil? || (value.is_a?(AIXM::Z) && value.qnh?)
98
+ @z = value
99
+ end
100
+
101
+ def length=(value)
102
+ fail(ArgumentError, "invalid length") unless value.nil? || (value.is_a?(Numeric) && value > 0)
103
+ @length = value.nil? ? nil : value.to_i
104
+ end
105
+
106
+ def width=(value)
107
+ fail(ArgumentError, "invalid width") unless value.nil? || (value.is_a?(Numeric) && value > 0)
108
+ @width = value.nil? ? nil : value.to_i
109
+ end
110
+
111
+ def composition=(value)
112
+ @composition = value.nil? ? nil : COMPOSITIONS.lookup(value.to_s.to_sym, nil) || fail(ArgumentError, "invalid composition")
113
+ end
114
+
115
+ def status=(value)
116
+ @status = value.nil? ? nil : (STATUSES.lookup(value.to_s.to_sym, nil) || fail(ArgumentError, "invalid status"))
117
+ end
118
+
119
+ def remarks=(value)
120
+ @remarks = value&.to_s
121
+ end
122
+
123
+ # @return [String] UID markup
124
+ def to_uid
125
+ builder = Builder::XmlMarkup.new(indent: 2)
126
+ builder.TlaUid do |tla_uid|
127
+ tla_uid << airport.to_uid.indent(2)
128
+ tla_uid.txtDesig(name)
129
+ end
130
+ end
131
+
132
+ # @return [String] AIXM or OFMX markup
133
+ def to_xml
134
+ builder = Builder::XmlMarkup.new(indent: 2)
135
+ builder.Tla do |tla|
136
+ tla << to_uid.indent(2)
137
+ tla.geoLat(xy.lat(AIXM.schema))
138
+ tla.geoLong(xy.long(AIXM.schema))
139
+ tla.codeDatum('WGE')
140
+ if z
141
+ tla.valElev(z.alt)
142
+ tla.uomDistVer(z.unit.upcase.to_s)
143
+ end
144
+ tla.valLen(length) if length
145
+ tla.valWid(width) if width
146
+ tla.uomDim('M') if length || width
147
+ tla.codeComposition(COMPOSITIONS.key(composition).to_s) if composition
148
+ tla.codeSts(STATUSES.key(status).to_s) if status
149
+ tla.txtRmk(remarks) if remarks
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,91 @@
1
+ using AIXM::Refinements
2
+
3
+ module AIXM
4
+ class Component
5
+
6
+ # Each airspace has one or more layers with optional airspace class and
7
+ # mandatory vertical limits.
8
+ #
9
+ # ===Cheat Sheet in Pseudo Code:
10
+ # layer = AIXM.layer(
11
+ # class: String or nil
12
+ # vertical_limits: AIXM.vertical_limits
13
+ # )
14
+ # layer.timetable = AIXM.timetable or nil
15
+ # layer.selective = true or false (default)
16
+ # layer.remarks = String or nil
17
+ #
18
+ # @see https://github.com/openflightmaps/ofmx/wiki/Airspace
19
+ class Layer
20
+ CLASSES = (:A..:G).freeze
21
+
22
+ # @return [AIXM::Component::VerticalLimits] vertical limits of this layer
23
+ attr_reader :vertical_limits
24
+
25
+ # @return [AIXM::Component::Timetable, nil] activation hours
26
+ attr_reader :timetable
27
+
28
+ # @return [String, nil] free text remarks
29
+ attr_reader :remarks
30
+
31
+ def initialize(class: nil, vertical_limits:)
32
+ self.class = binding.local_variable_get(:class)
33
+ self.vertical_limits = vertical_limits
34
+ self.selective = false
35
+ end
36
+
37
+ # @return [String]
38
+ def inspect
39
+ %Q(#<#{self.class} class=#{@klass.inspect}>)
40
+ end
41
+
42
+ # @!attribute class
43
+ # @return [Symbol] class of layer (see {CLASSES})
44
+ def class
45
+ @klass
46
+ end
47
+
48
+ def class=(value)
49
+ @klass = value&.to_sym&.upcase
50
+ fail(ArgumentError, "invalid class") unless @klass.nil? || CLASSES.include?(@klass)
51
+ end
52
+
53
+ def vertical_limits=(value)
54
+ fail(ArgumentError, "invalid vertical limits") unless value.is_a? AIXM::Component::VerticalLimits
55
+ @vertical_limits = value
56
+ end
57
+
58
+ def timetable=(value)
59
+ fail(ArgumentError, "invalid timetable") unless value.nil? || value.is_a?(AIXM::Component::Timetable)
60
+ @timetable = value
61
+ end
62
+
63
+ # @!attribute [w] selective
64
+ # @return [Boolean] whether the layer may be activated selectively
65
+ def selective?
66
+ @selective
67
+ end
68
+
69
+ def selective=(value)
70
+ fail(ArgumentError, "invalid selective") unless [true, false].include? value
71
+ @selective = value
72
+ end
73
+
74
+ def remarks=(value)
75
+ @remarks = value&.to_s
76
+ end
77
+
78
+ # @return [String] AIXM or OFMX markup
79
+ def to_xml
80
+ builder = Builder::XmlMarkup.new(indent: 2)
81
+ builder.codeClass(self.class.to_s) if self.class
82
+ builder << vertical_limits.to_xml
83
+ builder << timetable.to_xml(as: :Att) if timetable
84
+ builder.codeSelAvbl(selective? ? 'Y' : 'N') if AIXM.ofmx?
85
+ builder.txtRmk(remarks) if remarks
86
+ builder.target!
87
+ end
88
+ end
89
+
90
+ end
91
+ end
@@ -0,0 +1,294 @@
1
+ using AIXM::Refinements
2
+
3
+ module AIXM
4
+ class Component
5
+
6
+ # Runways are landing and takeoff strips for forward propelled aircraft.
7
+ #
8
+ # By convention, the runway name is usually the composition of the runway
9
+ # forth name (smaller number) and the runway back name (bigger number)
10
+ # joined with a forward slash e.g. "12/30" or "16R/34L".
11
+ #
12
+ # A runway has one or to directions accessible as +runway.forth+ (mandatory)
13
+ # and +runway.back+ (optional). Both have identical properties.
14
+ #
15
+ # ===Cheat Sheet in Pseudo Code:
16
+ # runway = AIXM.runway(
17
+ # name: String
18
+ # )
19
+ # runway.length = Integer or nil # meters
20
+ # runway.width = Integer or nil # meters
21
+ # runway.composition = COMPOSITIONS or nil
22
+ # runway.status = STATUSES or nil
23
+ # runway.remarks = String or nil
24
+ # runway.forth.name = String # preset based on the runway name
25
+ # runway.forth.geographic_orientation = Integer or nil # degrees
26
+ # runway.forth.xy = AIXM.xy
27
+ # runway.forth.z = AIXM.z or nil
28
+ # runway.forth.displaced_threshold = Integer or nil # meters
29
+ # runway.forth.remarks = String or nil
30
+ #
31
+ # @example Bidirectional runway
32
+ # runway = AIXM.runway(name: '16L/34R')
33
+ # runway.name # => '16L/34R'
34
+ # runway.forth.name = '16L'
35
+ # runway.forth.geographic_orientation = 165
36
+ # runway.back.name = '34R'
37
+ # runway.back.geographic_orientation = 345
38
+ #
39
+ # @example Unidirectional runway:
40
+ # runway = AIXM.runway(name: '16L')
41
+ # runway.name # => '16L'
42
+ # runway.forth.name = '16L'
43
+ # runway.forth.geographic_orientation = 165
44
+ # runway.back = nil
45
+ #
46
+ # @see https://github.com/openflightmaps/ofmx/wiki/Airport#rwy-runway
47
+ class Runway
48
+ COMPOSITIONS = {
49
+ ASPH: :asphalt,
50
+ BITUM: :bitumen, # dug up, bound and rolled ground
51
+ CONC: :concrete,
52
+ GRAVE: :gravel, # small and midsize rounded stones
53
+ MACADAM: :macadam, # small rounded stones
54
+ SAND: :sand,
55
+ GRADE: :graded_earth, # graded or rolled earth possibly with some grass
56
+ GRASS: :grass, # lawn
57
+ WATER: :water,
58
+ OTHER: :other # specify in remarks
59
+ }
60
+
61
+ STATUSES = {
62
+ CLSD: :closed,
63
+ WIP: :work_in_progress, # e.g. construction work
64
+ PARKED: :parked_aircraft, # parked or disabled aircraft on helipad
65
+ FAILAID: :visual_aids_failure, # failure or irregular operation of visual aids
66
+ SPOWER: :secondary_power, # secondary power supply in operation
67
+ OTHER: :other # specify in remarks
68
+ }
69
+
70
+ # @return [AIXM::Feature::Airport] airport the runway belongs to
71
+ attr_reader :airport
72
+
73
+ # @return [String] full name of runway (e.g. "12/30" or "16L/34R")
74
+ attr_reader :name
75
+
76
+ # @return [Integer, nil] length in meters
77
+ attr_reader :length
78
+
79
+ # @return [Integer, nil] width in meters
80
+ attr_reader :width
81
+
82
+ # @return [Symbol, nil] composition of the surface (see {COMPOSITIONS})
83
+ attr_reader :composition
84
+
85
+ # @return [Symbol, nil] status of the runway (see {STATUSES}) or +nil+ for normal operation
86
+ attr_reader :status
87
+
88
+ # @return [String, nil] free text remarks
89
+ attr_reader :remarks
90
+
91
+ # @return [AIXM::Component::Runway::Direction] main direction
92
+ attr_accessor :forth
93
+
94
+ # @return [AIXM::Component::Runway::Direction] reverse direction
95
+ attr_accessor :back
96
+
97
+ def initialize(name:)
98
+ self.name = name
99
+ @name.split('/').tap do |forth, back|
100
+ @forth = Direction.new(runway: self, name: forth)
101
+ @back = Direction.new(runway: self, name: back) if back
102
+ end
103
+ end
104
+
105
+ # @return [String]
106
+ def inspect
107
+ %Q(#<#{self.class} name=#{name.inspect}>)
108
+ end
109
+
110
+ def airport=(value)
111
+ fail(ArgumentError, "invalid airport") unless value.is_a? AIXM::Feature::Airport
112
+ @airport = value
113
+ end
114
+ private :airport=
115
+
116
+ def name=(value)
117
+ fail(ArgumentError, "invalid name") unless value.is_a? String
118
+ @name = value.uptrans
119
+ end
120
+
121
+ def length=(value)
122
+ fail(ArgumentError, "invalid length") unless value.nil? || (value.is_a?(Numeric) && value > 0)
123
+ @length = value.nil? ? nil : value.to_i
124
+ end
125
+
126
+ def width=(value)
127
+ fail(ArgumentError, "invalid width") unless value.nil? || (value.is_a?(Numeric) && value > 0)
128
+ @width = value.nil? ? nil : value.to_i
129
+ end
130
+
131
+ def composition=(value)
132
+ @composition = value.nil? ? nil : COMPOSITIONS.lookup(value.to_s.to_sym, nil) || fail(ArgumentError, "invalid composition")
133
+ end
134
+
135
+ def status=(value)
136
+ @status = value.nil? ? nil : (STATUSES.lookup(value.to_s.to_sym, nil) || fail(ArgumentError, "invalid status"))
137
+ end
138
+
139
+ def remarks=(value)
140
+ @remarks = value&.to_s
141
+ end
142
+
143
+ # @return [String] UID markup
144
+ def to_uid
145
+ builder = Builder::XmlMarkup.new(indent: 2)
146
+ builder.RwyUid do |rwy_uid|
147
+ rwy_uid << airport.to_uid.indent(2)
148
+ rwy_uid.txtDesig(name)
149
+ end
150
+ end
151
+
152
+ # @return [String] AIXM or OFMX markup
153
+ def to_xml
154
+ builder = Builder::XmlMarkup.new(indent: 2)
155
+ builder.Rwy do |rwy|
156
+ rwy << to_uid.indent(2)
157
+ rwy.valLen(length) if length
158
+ rwy.valWid(width) if width
159
+ rwy.uomDimRwy('M') if length || width
160
+ rwy.codeComposition(COMPOSITIONS.key(composition).to_s) if composition
161
+ rwy.codeSts(STATUSES.key(status).to_s) if status
162
+ rwy.txtRmk(remarks) if remarks
163
+ end
164
+ %i(@forth @back).each do |direction|
165
+ direction = instance_variable_get(direction)
166
+ builder << direction.to_xml if direction
167
+ end
168
+ builder.target!
169
+ end
170
+
171
+ # Runway directions further describe each direction {#forth} and {#back}
172
+ # of a runway.
173
+ #
174
+ # @see https://github.com/openflightmaps/ofmx/wiki/Airport#rdn-runway-direction
175
+ class Direction
176
+ # @return [AIXM::Component::Runway] runway the runway direction is further describing
177
+ attr_reader :runway
178
+
179
+ # @return [String] partial name of runway (e.g. "12" or "16L")
180
+ attr_reader :name
181
+
182
+ # @return [Integer, nil] geographic orientation (true bearing) in degrees
183
+ attr_reader :geographic_orientation
184
+
185
+ # @return [AIXM::XY] beginning point (middle of the runway width)
186
+ attr_reader :xy
187
+
188
+ # @return [AIXM::Z, nil] elevation of the touch down zone in +qnh+
189
+ attr_reader :z
190
+
191
+ # @return [AIXM::XY, Integer, nil] displaced threshold point either as
192
+ # coordinates (AIXM::XY) or distance (Integer) in meters from the
193
+ # beginning point
194
+ attr_reader :displaced_threshold
195
+
196
+ # @return [String, nil] free text remarks
197
+ attr_reader :remarks
198
+
199
+ def initialize(runway:, name:)
200
+ self.runway, self.name = runway, name
201
+ end
202
+
203
+ # @return [String]
204
+ def inspect
205
+ %Q(#<#{self.class} name=#{name.inspect}>)
206
+ end
207
+
208
+ def runway=(value)
209
+ fail(ArgumentError, "invalid runway") unless value.is_a? AIXM::Component::Runway
210
+ @runway = value
211
+ end
212
+ private :runway
213
+
214
+ def name=(value)
215
+ fail(ArgumentError, "invalid name") unless value.is_a? String
216
+ @name = value.uptrans
217
+ end
218
+
219
+ def geographic_orientation=(value)
220
+ return @geographic_orientation = nil if value.nil?
221
+ fail(ArgumentError, "invalid geographic orientation") unless value.is_a? Numeric
222
+ @geographic_orientation = value.to_i
223
+ fail(ArgumentError, "invalid geographic orientation") unless (0..359).include? @geographic_orientation
224
+ end
225
+
226
+ def xy=(value)
227
+ fail(ArgumentError, "invalid xy") unless value.is_a? AIXM::XY
228
+ @xy = value
229
+ end
230
+
231
+ def z=(value)
232
+ fail(ArgumentError, "invalid z") unless value.nil? || (value.is_a?(AIXM::Z) && value.qnh?)
233
+ @z = value
234
+ end
235
+
236
+ def displaced_threshold=(value)
237
+ @displaced_threshold = case value
238
+ when AIXM::XY then @xy.distance(value).to_i
239
+ when Numeric then value.to_i
240
+ when NilClass then nil
241
+ else fail(ArgumentError, "invalid displaced threshold")
242
+ end
243
+ end
244
+
245
+ def remarks=(value)
246
+ @remarks = value&.to_s
247
+ end
248
+
249
+ # @return [Integer] magnetic orientation (magnetic bearing) in degrees
250
+ def magnetic_orientation
251
+ if geographic_orientation && runway.airport.declination
252
+ (geographic_orientation + runway.airport.declination).round
253
+ end
254
+ end
255
+
256
+ # @return [String] AIXM or OFMX markup
257
+ def to_xml
258
+ builder = Builder::XmlMarkup.new(indent: 2)
259
+ builder.Rdn do |rdn|
260
+ rdn.RdnUid do |rdn_uid|
261
+ rdn_uid << runway.to_uid.indent(4)
262
+ rdn_uid.txtDesig(name)
263
+ end
264
+ rdn.geoLat(xy.lat(AIXM.schema))
265
+ rdn.geoLong(xy.long(AIXM.schema))
266
+ rdn.valTrueBrg(geographic_orientation) if geographic_orientation
267
+ rdn.valMagBrg(magnetic_orientation) if magnetic_orientation
268
+ if z
269
+ rdn.valElevTdz(z.alt)
270
+ rdn.uomElevTdz(z.unit.upcase.to_s)
271
+ end
272
+ rdn.txtRmk(remarks) if remarks
273
+ end
274
+ if displaced_threshold
275
+ builder.Rdd do |rdd|
276
+ rdd.RddUid do |rdd_uid|
277
+ rdd_uid.RdnUid do |rdn_uid|
278
+ rdn_uid << runway.to_uid.indent(6)
279
+ rdn_uid.txtDesig(name)
280
+ end
281
+ rdd_uid.codeType('DPLM')
282
+ rdd_uid.codeDayPeriod('A')
283
+ end
284
+ rdd.valDist(displaced_threshold)
285
+ rdd.uomDist('M')
286
+ rdd.txtRmk(remarks) if remarks
287
+ end
288
+ end
289
+ builder.target!
290
+ end
291
+ end
292
+ end
293
+ end
294
+ end