aixm 0.3.3 → 0.3.4

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +1 -2
  4. data/CHANGELOG.md +20 -0
  5. data/README.md +7 -2
  6. data/aixm.gemspec +2 -3
  7. data/lib/aixm.rb +6 -2
  8. data/lib/aixm/a.rb +151 -0
  9. data/lib/aixm/component/frequency.rb +2 -2
  10. data/lib/aixm/component/geometry.rb +4 -0
  11. data/lib/aixm/component/geometry/point.rb +0 -4
  12. data/lib/aixm/component/helipad.rb +8 -22
  13. data/lib/aixm/component/runway.rb +36 -36
  14. data/lib/aixm/component/surface.rb +121 -0
  15. data/lib/aixm/component/timetable.rb +1 -0
  16. data/lib/aixm/constants.rb +40 -0
  17. data/lib/aixm/d.rb +5 -0
  18. data/lib/aixm/document.rb +2 -2
  19. data/lib/aixm/f.rb +6 -0
  20. data/lib/aixm/feature/address.rb +100 -0
  21. data/lib/aixm/feature/airport.rb +26 -7
  22. data/lib/aixm/feature/airspace.rb +10 -1
  23. data/lib/aixm/feature/navigational_aid.rb +1 -1
  24. data/lib/aixm/feature/navigational_aid/designated_point.rb +20 -5
  25. data/lib/aixm/feature/navigational_aid/dme.rb +2 -2
  26. data/lib/aixm/{component → feature}/service.rb +67 -16
  27. data/lib/aixm/feature/unit.rb +40 -6
  28. data/lib/aixm/refinements.rb +63 -6
  29. data/lib/aixm/shortcuts.rb +12 -4
  30. data/lib/aixm/version.rb +1 -1
  31. data/lib/aixm/xy.rb +6 -1
  32. data/lib/aixm/z.rb +6 -0
  33. data/schemas/ofmx/0/OFMX-DataTypes.xsd +5 -2
  34. data/schemas/ofmx/0/OFMX-Features.xsd +2 -0
  35. data/spec/factory.rb +32 -10
  36. data/spec/lib/aixm/a_spec.rb +203 -0
  37. data/spec/lib/aixm/component/helipad_spec.rb +11 -17
  38. data/spec/lib/aixm/component/runway_spec.rb +46 -32
  39. data/spec/lib/aixm/component/surface_spec.rb +88 -0
  40. data/spec/lib/aixm/d_spec.rb +10 -0
  41. data/spec/lib/aixm/document_spec.rb +104 -32
  42. data/spec/lib/aixm/f_spec.rb +10 -0
  43. data/spec/lib/aixm/feature/address_spec.rb +55 -0
  44. data/spec/lib/aixm/feature/airport_spec.rb +73 -3
  45. data/spec/lib/aixm/feature/navigational_aid/designated_point_spec.rb +43 -6
  46. data/spec/lib/aixm/feature/navigational_aid/dme_spec.rb +2 -2
  47. data/spec/lib/aixm/feature/navigational_aid/marker_spec.rb +2 -2
  48. data/spec/lib/aixm/feature/navigational_aid/ndb_spec.rb +2 -2
  49. data/spec/lib/aixm/feature/navigational_aid/tacan_spec.rb +2 -2
  50. data/spec/lib/aixm/feature/navigational_aid/vor_spec.rb +6 -6
  51. data/spec/lib/aixm/{component → feature}/service_spec.rb +12 -14
  52. data/spec/lib/aixm/feature/unit_spec.rb +7 -4
  53. data/spec/lib/aixm/refinements_spec.rb +100 -15
  54. data/spec/lib/aixm/z_spec.rb +10 -0
  55. metadata +17 -25
  56. data/lib/aixm/h.rb +0 -87
  57. data/spec/lib/aixm/h_spec.rb +0 -113
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a4e0b32c04cba8526128ce56c60abf4343b7ac8f2e8a3e8252e1046ce3ccbfe3
4
- data.tar.gz: c88875a5a1921f90a1d4055302469c1608ae610bce5b513c9a3e2d50ed32f842
3
+ metadata.gz: cbcadda83460463ad9ab68569e04738a5811b10e3534f3f91fde0ed1f4104874
4
+ data.tar.gz: aa14c72ecf04553fde4243ff74b68e5cfe2b1d3f19efbb4ef1f473a6dce79a38
5
5
  SHA512:
6
- metadata.gz: e0f11994c12259bee9a013b92162ed14f763206855adfb5f0d25fc45807a885da5b330010c1c7768590cd3cca19c260976c68674b0354091b2ef64288a70914e
7
- data.tar.gz: 800ef3ea1d7e298281454957f3471e06452d9a23a4333c0f978c24590cd9fc140b21948d7588a07a896995731897810bffc317ae610e39822b6e27e51554cb10
6
+ metadata.gz: 18f3c67e93a4d6527d8240f819c942d82e81cef33332902749ebcde5e62c3aa4aaf2e12ea2ddce4b642b4ba63b2913e557207161dd4400b01685ec673c436885
7
+ data.tar.gz: f35b079b357d0b00e5c7bc11a71f0e610cc787863eb678f49aa6697ab11eccc32804c33e73e9f2d4851209d9604b16a271c9f85759b2c41b7e8094a928ff5034
@@ -1 +1 @@
1
- ruby-2.5.3
1
+ ruby-2.6.3
@@ -1,5 +1,4 @@
1
1
  ---
2
2
  language: ruby
3
3
  rvm:
4
- - 2.5.3
5
- before_install: gem install bundler -v 1.17.1
4
+ - 2.6.0
@@ -1,3 +1,22 @@
1
+ ## 0.3.4
2
+
3
+ #### Additions
4
+ * Address feature
5
+ * `Runway#preparation`, `Runway#condition` and `Runway#vfr_pattern`
6
+ * `Service#guessed_unit_type`
7
+ * Surface for `Runway|Helipad#surface`
8
+ * Extracted `AIXM::MIN`, `AIXM::SEC` and `AIXM::DMS_RE` to scan for coordinates in texts
9
+ * Refinement `String#payload_hash`
10
+
11
+ #### Breaking Changes
12
+ * Require Ruby 2.6
13
+ * Renamed `AIXM::H` to `AIXM::A` (angle) and add simple arithmetics to make it more versatile
14
+ * `Runway|Helipad#composition` moved to `Runway|Helipad#surface`
15
+ * DMS notation `{-}{DD}DMMSS{.SS}[NESW]` now requires compulsory cardinal direction (N, E, S or W) at the end
16
+
17
+ #### Changes
18
+ * Service is a feature now
19
+
1
20
  ## 0.3.3
2
21
 
3
22
  #### Additions
@@ -132,6 +151,7 @@
132
151
  ## 0.1.0
133
152
 
134
153
  #### Initial Implementation
154
+ * Require Ruby 2.5
135
155
  * `AIXM::XY` (coordinates)
136
156
  * `AIXM::Z` (altitude or elevation)
137
157
  * AIXM-Snapshot 4.5 Document
data/README.md CHANGED
@@ -37,13 +37,15 @@ document.aixm! # not really necessary since AIXM is the default schema
37
37
  document.to_xml
38
38
  ```
39
39
 
40
- You can initialize all elements either traditionally or by use of shorter AIXM class methods. The following two statements are identical:
40
+ You can initialize all elements either traditionally or by use of the corresponding shorthand AIXM class method. The following two statements are identical:
41
41
 
42
42
  ```ruby
43
43
  AIXM::Feature::NavigationalAid::DesignatedPoint.new(...)
44
44
  AIXM.designated_point(...)
45
45
  ```
46
46
 
47
+ See `AIXM::CLASSES` for the complete list of shorthand names.
48
+
47
49
  ## Configuration
48
50
 
49
51
  The following configuration options are available for setting and getting:
@@ -76,10 +78,13 @@ AIXM.schema(:version) # => 0
76
78
  * [Z (height, elevation or altitude)](http://www.rubydoc.info/gems/aixm/AIXM/Z.html)
77
79
  * [D (distance or length)](http://www.rubydoc.info/gems/aixm/AIXM/D.html)
78
80
  * [F (frequency)](http://www.rubydoc.info/gems/aixm/AIXM/F.html)
81
+ * [A (angle)](http://www.rubydoc.info/gems/aixm/AIXM/A.html)
79
82
 
80
83
  ### Features
84
+ * [Address](http://www.rubydoc.info/gems/aixm/AIXM/Feature/Address.html)
81
85
  * [Organisation](http://www.rubydoc.info/gems/aixm/AIXM/Feature/Organisation.html)
82
86
  * [Unit](http://www.rubydoc.info/gems/aixm/AIXM/Feature/Unit.html)
87
+ * [Service](http://www.rubydoc.info/gems/aixm/AIXM/Component/Service.html)
83
88
  * [Airport](http://www.rubydoc.info/gems/aixm/AIXM/Feature/Airport.html)
84
89
  * [Airspace](http://www.rubydoc.info/gems/aixm/AIXM/Feature/Airspace.html)
85
90
  * [Navigational aid](http://www.rubydoc.info/gems/aixm/AIXM/NavigationalAid.html)
@@ -92,7 +97,6 @@ AIXM.schema(:version) # => 0
92
97
  * [Obstacle and obstacle group](http://www.rubydoc.info/gems/aixm/AIXM/Feature/Obstacle.html)
93
98
 
94
99
  ### Components
95
- * [Service](http://www.rubydoc.info/gems/aixm/AIXM/Component/Service.html)
96
100
  * [Frequency](http://www.rubydoc.info/gems/aixm/AIXM/Component/Frequency.html)
97
101
  * [Geometry](http://www.rubydoc.info/gems/aixm/AIXM/Component/Geometry.html)
98
102
  * [Point](http://www.rubydoc.info/gems/aixm/AIXM/Component/Point.html)
@@ -101,6 +105,7 @@ AIXM.schema(:version) # => 0
101
105
  * [Circle](http://www.rubydoc.info/gems/aixm/AIXM/Component/Circle.html)
102
106
  * [Runway](http://www.rubydoc.info/gems/aixm/AIXM/Component/Runway.html)
103
107
  * [Helipad](http://www.rubydoc.info/gems/aixm/AIXM/Component/Helipad.html)
108
+ * [Surface](http://www.rubydoc.info/gems/aixm/AIXM/Component/Surface.html)
104
109
  * [Layer](http://www.rubydoc.info/gems/aixm/AIXM/Component/Layer.html)
105
110
  * [Vertical limits](http://www.rubydoc.info/gems/aixm/AIXM/Component/VerticalLimits.html)
106
111
  * [Timetable](http://www.rubydoc.info/gems/aixm/AIXM/Component/Timetable.html)
@@ -18,9 +18,8 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ['lib']
20
20
 
21
- spec.required_ruby_version = '>= 2.5'
22
-
23
- spec.add_development_dependency 'bundler'
21
+ spec.required_ruby_version = '>= 2.6'
22
+
24
23
  spec.add_development_dependency 'rake'
25
24
  spec.add_development_dependency 'minitest'
26
25
  spec.add_development_dependency 'minitest-reporters'
@@ -5,8 +5,10 @@ require 'nokogiri'
5
5
  require 'forwardable'
6
6
  require 'digest'
7
7
  require 'time'
8
+ require 'pathname'
8
9
 
9
10
  require_relative 'aixm/version'
11
+ require_relative 'aixm/constants'
10
12
  require_relative 'aixm/refinements'
11
13
  require_relative 'aixm/config'
12
14
  require_relative 'aixm/errors'
@@ -16,10 +18,9 @@ require_relative 'aixm/xy'
16
18
  require_relative 'aixm/z'
17
19
  require_relative 'aixm/d'
18
20
  require_relative 'aixm/f'
19
- require_relative 'aixm/h'
21
+ require_relative 'aixm/a'
20
22
 
21
23
  require_relative 'aixm/component'
22
- require_relative 'aixm/component/service'
23
24
  require_relative 'aixm/component/frequency'
24
25
  require_relative 'aixm/component/geometry'
25
26
  require_relative 'aixm/component/geometry/point'
@@ -31,10 +32,13 @@ require_relative 'aixm/component/vertical_limits'
31
32
  require_relative 'aixm/component/timetable'
32
33
  require_relative 'aixm/component/runway'
33
34
  require_relative 'aixm/component/helipad'
35
+ require_relative 'aixm/component/surface'
34
36
 
35
37
  require_relative 'aixm/feature'
38
+ require_relative 'aixm/feature/address'
36
39
  require_relative 'aixm/feature/organisation'
37
40
  require_relative 'aixm/feature/unit'
41
+ require_relative 'aixm/feature/service'
38
42
  require_relative 'aixm/feature/airspace'
39
43
  require_relative 'aixm/feature/airport'
40
44
  require_relative 'aixm/feature/navigational_aid'
@@ -0,0 +1,151 @@
1
+ using AIXM::Refinements
2
+
3
+ module AIXM
4
+
5
+ # Angle from 0 to 359 degrees with an optional suffix used for azimuths,
6
+ # bearings, headings, courses etc.
7
+ #
8
+ # @example Initialized with Numeric
9
+ # a = AIXM.a(12) # 12 degrees, 1 degree precision, no suffix
10
+ # a.precision # => 3 (three digits = steps of 1 degree)
11
+ # a.to_s # => "012"
12
+ # a.suffix # => nil
13
+ # a.deg # => 12
14
+ # a.deg += 7 # => 19
15
+ # a.deg += 341 # => 0 - deg is always within (0..359)
16
+ # a.to_s # => "000" - to_s is always within ("000".."359")
17
+ #
18
+ # @example Initialized with String
19
+ # a = AIXM.a('06L') # 60 degrees, 10 degree precision, suffix :L
20
+ # a.precision # => 2 (two digits = steps of 10 degrees)
21
+ # a.to_s # => "06L"
22
+ # a.suffix # => :L
23
+ # a.deg # => 60
24
+ # a.deg += 7 # => 70
25
+ # a.deg += 190 # => 0 - deg is always within (0..359)
26
+ # a.to_s # => "36L" - to_s converts to ("01".."36")
27
+ class A
28
+ SUFFIX_INVERSIONS = {
29
+ R: :L,
30
+ L: :R
31
+ }.freeze
32
+
33
+ # @return [Integer] angle
34
+ attr_reader :deg
35
+
36
+ # @return [Integer] precision: +2+ (10 degree steps) or +3+ (1 degree steps)
37
+ attr_reader :precision
38
+
39
+ # @return [Symbol, nil] suffix
40
+ attr_reader :suffix
41
+
42
+ def initialize(deg_and_suffix)
43
+ case deg_and_suffix
44
+ when Numeric
45
+ self.deg, @precision = deg_and_suffix, 3
46
+ when String
47
+ fail(ArgumentError, "invalid angle") unless deg_and_suffix.to_s =~ /\A(\d+)([A-Z]+)?\z/
48
+ self.deg, @precision, self.suffix = $1.to_i * 10, 2, $2
49
+ when Symbol # used only by private build method
50
+ fail(ArgumentError, "invalid precision") unless %i(2 3).include? deg_and_suffix
51
+ @deg, @precision = 0, deg_and_suffix.to_s.to_i
52
+ else
53
+ fail(ArgumentError, "invalid angle")
54
+ end
55
+ end
56
+
57
+ # @return [String]
58
+ def inspect
59
+ %Q(#<#{self.class}[precision=#{precision}] #{to_s}>)
60
+ end
61
+
62
+ # @return [String] human readable representation according to precision
63
+ def to_s
64
+ if precision == 2
65
+ [('%02d' % ((deg / 10 + 35) % 36 + 1)), suffix].map(&:to_s).join
66
+ else
67
+ ('%03d' % deg)
68
+ end
69
+ end
70
+
71
+ def deg=(value)
72
+ fail(ArgumentError, "invalid deg `#{value}'") unless value.is_a?(Numeric) && value.round.between?(0, 360)
73
+ @deg = (precision == 2 ? (value.to_f / 10).round * 10 : value.round) % 360
74
+ end
75
+
76
+ def suffix=(value)
77
+ fail(RuntimeError, "suffix only allowed when precision is 2") unless value.nil? || precision == 2
78
+ fail(ArgumentError, "invalid suffix") unless value.nil? || value.to_s =~ /\A[A-Z]+\z/
79
+ @suffix = value&.to_s&.to_sym
80
+ end
81
+
82
+ # Invert an angle by 180 degrees
83
+ #
84
+ # @example
85
+ # AIXM.a(120).invert # => AIXM.a(300)
86
+ # AIXM.a("34L").invert # => AIXM.a("16R")
87
+ # AIXM.a("33X").invert # => AIXM.a("33X")
88
+ #
89
+ # @return [AIXM::A] inverted angle
90
+ def invert
91
+ build(precision: precision, deg: (deg + 180) % 360, suffix: SUFFIX_INVERSIONS.fetch(suffix, suffix))
92
+ end
93
+
94
+ # Check whether +other+ angle is the inverse
95
+ #
96
+ # @example
97
+ # AIXM.a(120).inverse_of? AIXM.a(300) # => true
98
+ # AIXM.a("34L").inverse_of? AIXM.a("16R") # => true
99
+ # AIXM.a("33X").inverse_of? AIXM.a("33X") # => true
100
+ # AIXM.a("16R").inverse_of? AIXM.a("16L") # => false
101
+ #
102
+ # @return [Boolean] whether the inverted angle or not
103
+ def inverse_of?(other)
104
+ invert == other
105
+ end
106
+
107
+ # Add degrees
108
+ #
109
+ # @return [AIXM::A]
110
+ def +(numeric_or_angle)
111
+ fail ArgumentError unless numeric_or_angle.respond_to? :round
112
+ build(precision: precision, deg: (deg + numeric_or_angle.round) % 360, suffix: suffix)
113
+ end
114
+
115
+ # Subtract degrees
116
+ #
117
+ # @return [AIXM::A]
118
+ def -(numeric_or_angle)
119
+ fail ArgumentError unless numeric_or_angle.respond_to? :round
120
+ build(precision: precision, deg: (deg - numeric_or_angle.round + 360) % 360, suffix: suffix)
121
+ end
122
+
123
+ # @private
124
+ def round
125
+ deg
126
+ end
127
+
128
+ # @see Object#==
129
+ # @return [Boolean]
130
+ def ==(other)
131
+ self.class === other && deg == other.deg && precision == other.precision && suffix == other.suffix
132
+ end
133
+ alias_method :eql?, :==
134
+
135
+ # @see Object#hash
136
+ # @return [Integer]
137
+ def hash
138
+ to_s.hash
139
+ end
140
+
141
+ private
142
+
143
+ def build(precision:, deg:, suffix: nil)
144
+ self.class.new(precision.to_s.to_sym).tap do |a|
145
+ a.deg = deg
146
+ a.suffix = suffix
147
+ end
148
+ end
149
+ end
150
+
151
+ end
@@ -32,7 +32,7 @@ module AIXM
32
32
  OTHER: :other # specify in remarks
33
33
  }.freeze
34
34
 
35
- # @return [AIXM::Component::Service] service the frequency belongs to
35
+ # @return [AIXM::Feature::Service] service the frequency belongs to
36
36
  attr_reader :service
37
37
 
38
38
  # @return [AIXM::F] frequency for transmission (outgoing)
@@ -69,7 +69,7 @@ module AIXM
69
69
  end
70
70
 
71
71
  def service=(value)
72
- fail(ArgumentError, "invalid service") unless value.is_a? AIXM::Component::Service
72
+ fail(ArgumentError, "invalid service") unless value.is_a? AIXM::Feature::Service
73
73
  @service = value
74
74
  end
75
75
  private :service=
@@ -33,6 +33,10 @@ module AIXM
33
33
  include Enumerable
34
34
  extend Forwardable
35
35
 
36
+ # @!method each
37
+ # @return [Enumerator] see Array#each
38
+ # @!method <<
39
+ # @return [Array] see Array#<<
36
40
  def_delegators :@result_array, :each, :<<
37
41
 
38
42
  def initialize(*segments)
@@ -13,10 +13,6 @@ module AIXM
13
13
  #
14
14
  # @see https://github.com/openflightmaps/ofmx/wiki/Airspace#point
15
15
  class Point
16
- extend Forwardable
17
-
18
- def_delegators :xy
19
-
20
16
  # @return [AIXM::XY] (starting) point
21
17
  attr_reader :xy
22
18
 
@@ -13,25 +13,12 @@ module AIXM
13
13
  # helipad.z = AIXM.z or nil
14
14
  # helipad.length = AIXM.d or nil # must use same unit as width
15
15
  # helipad.width = AIXM.d or nil # must use same unit as length
16
- # helipad.composition = COMPOSITIONS or nil
16
+ # helipad.surface = AIXM.surface
17
17
  # helipad.status = STATUSES or nil
18
18
  # helipad.remarks = String or nil
19
19
  #
20
20
  # @see https://github.com/openflightmaps/ofmx/wiki/Airport#tla-helipad-tlof
21
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
22
  STATUSES = {
36
23
  CLSD: :closed,
37
24
  WIP: :work_in_progress, # e.g. construction work
@@ -59,8 +46,8 @@ module AIXM
59
46
  # @return [AIXM::D, nil] width
60
47
  attr_reader :width
61
48
 
62
- # @return [Symbol, nil] composition of the surface (see {COMPOSITIONS})
63
- attr_reader :composition
49
+ # @return [AIXM::Component::Surface] surface of the helipad
50
+ attr_reader :surface
64
51
 
65
52
  # @return [Symbol, nil] status of the helipad (see {STATUSES}) or +nil+ for normal operation
66
53
  attr_reader :status
@@ -70,11 +57,12 @@ module AIXM
70
57
 
71
58
  def initialize(name:)
72
59
  self.name = name
60
+ @surface = AIXM.surface
73
61
  end
74
62
 
75
63
  # @return [String]
76
64
  def inspect
77
- %Q(#<#{self.class} name=#{name.inspect}>)
65
+ %Q(#<#{self.class} airport=#{airport&.id.inspect} name=#{name.inspect}>)
78
66
  end
79
67
 
80
68
  def airport=(value)
@@ -114,10 +102,6 @@ module AIXM
114
102
  end
115
103
  end
116
104
 
117
- def composition=(value)
118
- @composition = value.nil? ? nil : COMPOSITIONS.lookup(value.to_s.to_sym, nil) || fail(ArgumentError, "invalid composition")
119
- end
120
-
121
105
  def status=(value)
122
106
  @status = value.nil? ? nil : (STATUSES.lookup(value.to_s.to_sym, nil) || fail(ArgumentError, "invalid status"))
123
107
  end
@@ -151,7 +135,9 @@ module AIXM
151
135
  tla.valWid(width.dist.trim) if width
152
136
  tla.uomDim(length.unit.to_s.upcase) if length
153
137
  tla.uomDim(width.unit.to_s.upcase) if width && !length
154
- tla.codeComposition(COMPOSITIONS.key(composition).to_s) if composition
138
+ unless (xml = surface.to_xml).empty?
139
+ tla << xml.indent(2)
140
+ end
155
141
  tla.codeSts(STATUSES.key(status).to_s) if status
156
142
  tla.txtRmk(remarks) if remarks
157
143
  end
@@ -18,14 +18,15 @@ module AIXM
18
18
  # )
19
19
  # runway.length = AIXM.d or nil # must use same unit as width
20
20
  # runway.width = AIXM.d or nil # must use same unit as length
21
- # runway.composition = COMPOSITIONS or nil
21
+ # runway.surface = AIXM.surface
22
22
  # runway.status = STATUSES or nil
23
23
  # runway.remarks = String or nil
24
- # runway.forth.name = AIXM.h # preset based on the runway name
25
- # runway.forth.geographic_orientation = Integer or nil # degrees
24
+ # runway.forth.name = AIXM.a[precision=2] # preset based on the runway name
25
+ # runway.forth.geographic_orientation = AIXM.a[precision=3] or nil
26
26
  # runway.forth.xy = AIXM.xy
27
27
  # runway.forth.z = AIXM.z or nil
28
28
  # runway.forth.displaced_threshold = AIXM.xy or AIXM.d or nil
29
+ # runway.forth.vfr_pattern = VFR_PATTERNS or nil
29
30
  # runway.forth.remarks = String or nil
30
31
  #
31
32
  # @example Bidirectional runway
@@ -45,19 +46,6 @@ module AIXM
45
46
  #
46
47
  # @see https://github.com/openflightmaps/ofmx/wiki/Airport#rwy-runway
47
48
  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
49
  STATUSES = {
62
50
  CLSD: :closed,
63
51
  WIP: :work_in_progress, # e.g. construction work
@@ -79,8 +67,8 @@ module AIXM
79
67
  # @return [AIXM::D, nil] width
80
68
  attr_reader :width
81
69
 
82
- # @return [Symbol, nil] composition of the surface (see {COMPOSITIONS})
83
- attr_reader :composition
70
+ # @return [AIXM::Component::Surface] surface of the runway
71
+ attr_reader :surface
84
72
 
85
73
  # @return [Symbol, nil] status of the runway (see {STATUSES}) or +nil+ for normal operation
86
74
  attr_reader :status
@@ -96,16 +84,17 @@ module AIXM
96
84
 
97
85
  def initialize(name:)
98
86
  self.name = name
99
- @name.split('/').tap do |forth, back|
100
- @forth = Direction.new(runway: self, name: AIXM.h(forth))
101
- @back = Direction.new(runway: self, name: AIXM.h(back)) if back
87
+ @name.split("/").tap do |forth, back|
88
+ @forth = Direction.new(runway: self, name: AIXM.a(forth))
89
+ @back = Direction.new(runway: self, name: AIXM.a(back)) if back
102
90
  fail(ArgumentError, "invalid name") unless !@back || @back.name.inverse_of?(@forth.name)
103
91
  end
92
+ @surface = AIXM.surface
104
93
  end
105
94
 
106
95
  # @return [String]
107
96
  def inspect
108
- %Q(#<#{self.class} name=#{name.inspect}>)
97
+ %Q(#<#{self.class} airport=#{airport&.id.inspect} name=#{name.inspect}>)
109
98
  end
110
99
 
111
100
  def airport=(value)
@@ -135,10 +124,6 @@ module AIXM
135
124
  end
136
125
  end
137
126
 
138
- def composition=(value)
139
- @composition = value.nil? ? nil : COMPOSITIONS.lookup(value.to_s.to_sym, nil) || fail(ArgumentError, "invalid composition")
140
- end
141
-
142
127
  def status=(value)
143
128
  @status = value.nil? ? nil : (STATUSES.lookup(value.to_s.to_sym, nil) || fail(ArgumentError, "invalid status"))
144
129
  end
@@ -165,7 +150,9 @@ module AIXM
165
150
  rwy.valWid(width.dist.trim) if width
166
151
  rwy.uomDimRwy(length.unit.to_s.upcase) if length
167
152
  rwy.uomDimRwy(width.unit.to_s.upcase) if width && !length
168
- rwy.codeComposition(COMPOSITIONS.key(composition).to_s) if composition
153
+ unless (xml = surface.to_xml).empty?
154
+ rwy << xml.indent(2)
155
+ end
169
156
  rwy.codeSts(STATUSES.key(status).to_s) if status
170
157
  rwy.txtRmk(remarks) if remarks
171
158
  end
@@ -181,13 +168,19 @@ module AIXM
181
168
  #
182
169
  # @see https://github.com/openflightmaps/ofmx/wiki/Airport#rdn-runway-direction
183
170
  class Direction
171
+ VFR_PATTERNS = {
172
+ L: :left,
173
+ R: :right,
174
+ E: :left_or_right
175
+ }
176
+
184
177
  # @return [AIXM::Component::Runway] runway the runway direction is further describing
185
178
  attr_reader :runway
186
179
 
187
- # @return [AIXM::H] partial name of runway (e.g. "12" or "16L")
180
+ # @return [AIXM::A] partial name of runway (e.g. "12" or "16L")
188
181
  attr_reader :name
189
182
 
190
- # @return [Integer, nil] geographic orientation (true bearing) in degrees
183
+ # @return [AIXM::A, nil] geographic orientation (true bearing) in degrees
191
184
  attr_reader :geographic_orientation
192
185
 
193
186
  # @return [AIXM::XY] beginning point (middle of the runway width)
@@ -201,6 +194,9 @@ module AIXM
201
194
  # point
202
195
  attr_reader :displaced_threshold
203
196
 
197
+ # @return [Symbol, nil] direction of the VFR flight pattern (see {VFR_PATTERNS})
198
+ attr_reader :vfr_pattern
199
+
204
200
  # @return [String, nil] free text remarks
205
201
  attr_reader :remarks
206
202
 
@@ -210,7 +206,7 @@ module AIXM
210
206
 
211
207
  # @return [String]
212
208
  def inspect
213
- %Q(#<#{self.class} name=#{name.inspect}>)
209
+ %Q(#<#{self.class} airport=#{runway&.airport&.id.inspect} name=#{name.inspect}>)
214
210
  end
215
211
 
216
212
  def runway=(value)
@@ -220,15 +216,14 @@ module AIXM
220
216
  private :runway
221
217
 
222
218
  def name=(value)
223
- fail(ArgumentError, "invalid name") unless value.is_a? AIXM::H
219
+ fail(ArgumentError, "invalid name") unless value.is_a? AIXM::A
224
220
  @name = value
225
221
  end
226
222
 
227
223
  def geographic_orientation=(value)
228
224
  return @geographic_orientation = nil if value.nil?
229
- fail(ArgumentError, "invalid geographic orientation") unless value.is_a? Numeric
230
- @geographic_orientation = value.to_i
231
- fail(ArgumentError, "invalid geographic orientation") unless (1..360).include? @geographic_orientation
225
+ fail(ArgumentError, "invalid geographic orientation") unless value.is_a? AIXM::A
226
+ @geographic_orientation = value
232
227
  end
233
228
 
234
229
  def xy=(value)
@@ -255,14 +250,18 @@ module AIXM
255
250
  end
256
251
  end
257
252
 
253
+ def vfr_pattern=(value)
254
+ @vfr_pattern = value.nil? ? nil : (VFR_PATTERNS.lookup(value.to_s.to_sym, nil) || fail(ArgumentError, "invalid VFR pattern"))
255
+ end
256
+
258
257
  def remarks=(value)
259
258
  @remarks = value&.to_s
260
259
  end
261
260
 
262
- # @return [Integer] magnetic orientation (magnetic bearing) in degrees
261
+ # @return [AIXM::A] magnetic orientation (magnetic bearing) in degrees
263
262
  def magnetic_orientation
264
263
  if geographic_orientation && runway.airport.declination
265
- (geographic_orientation + runway.airport.declination).round
264
+ geographic_orientation + runway.airport.declination
266
265
  end
267
266
  end
268
267
 
@@ -282,6 +281,7 @@ module AIXM
282
281
  rdn.valElevTdz(z.alt)
283
282
  rdn.uomElevTdz(z.unit.upcase.to_s)
284
283
  end
284
+ rdn.codeVfrPattern(VFR_PATTERNS.key(vfr_pattern).to_s) if vfr_pattern
285
285
  rdn.txtRmk(remarks) if remarks
286
286
  end
287
287
  if displaced_threshold