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,52 +1,70 @@
1
+ using AIXM::Refinements
2
+
1
3
  module AIXM
2
- module Feature
3
- module NavigationalAid
4
+ class Feature
5
+ class NavigationalAid
4
6
 
5
- ##
6
- # VOR (VHF omnidirectional range) operate in the frequency band between
7
- # 108.00 Mhz to 117.95 MHz. Two type of VORs exist:
8
- #
9
- # Types:
10
- # * +:vor+ (+:VOR+) - conventional VOR (also known as CVOR)
11
- # * +:doppler_vor+ (+:DVOR+) - Doppler VOR
7
+ # VHF omni directional radio range (VOR) is a type of radio navigation for
8
+ # aircraft to determine their position and course. They operate in the
9
+ # frequency band between 108.00 Mhz to 117.95 MHz.
12
10
  #
13
- # North types:
14
- # * +:geographic+ (+:TRUE+) - VOR aligned towards geographic north
15
- # * +:grid+ (+:GRID+) - VOR aligned along north-south lines of the
16
- # universal transverse mercator grid imposed on
17
- # topographic maps by the USA and NATO
18
- # * +:magnetic+ (+:MAG+) - VOR aligned towards magnetic north
11
+ # ===Cheat Sheet in Pseudo Code:
12
+ # vor = AIXM.vor(
13
+ # source: String or nil
14
+ # region: String or nil (falls back to AIXM.config.region)
15
+ # organisation: AIXM.organisation
16
+ # id: String
17
+ # name: String
18
+ # xy: AIXM.xy
19
+ # z: AIXM.z or nil
20
+ # type: TYPES
21
+ # f: AIXM.f
22
+ # north: NORTHS
23
+ # )
24
+ # vor.timetable = AIXM.timetable or nil
25
+ # vor.remarks = String or nil
26
+ # vor.associate_dme(channel: String) # turns the VOR into a VOR/DME
27
+ # vor.associate_tacan(channel: String) # turns the VOR into a VORTAC
19
28
  #
20
- # https://en.wikipedia.org/wiki/VHF_omnidirectional_range
21
- class VOR < Base
22
- using AIXM::Refinements
29
+ # @see https://github.com/openflightmaps/ofmx/wiki/Navigational-aid#vor-vor
30
+ class VOR < NavigationalAid
31
+ public_class_method :new
23
32
 
24
33
  TYPES = {
25
34
  VOR: :conventional,
26
- DVOR: :doppler
35
+ DVOR: :doppler,
36
+ OTHER: :other # specify in remarks
27
37
  }.freeze
28
38
 
29
39
  NORTHS = {
30
40
  TRUE: :geographic,
31
- GRID: :grid,
32
- MAG: :magnetic
41
+ GRID: :grid, # parallel to the north-south lines of the UTM grid
42
+ MAG: :magnetic,
43
+ OTHER: :other # specify in remarks
33
44
  }.freeze
34
45
 
35
- attr_reader :type, :f, :north, :dme, :tacan
46
+ # @return [Symbol] type of VOR (see {TYPES})
47
+ attr_reader :type
36
48
 
37
- public_class_method :new
49
+ # @return [AIXM::F] radio requency
50
+ attr_reader :f
51
+
52
+ # @return [Symbol] north indication (see {NORTHS})
53
+ attr_reader :north
54
+
55
+ # @return [AIXM::Feature::NavigationalAid::DME, nil] associated DME
56
+ attr_reader :dme
57
+
58
+ # @return [AIXM::Feature::NavigationalAid::TACAN, nil] associated TACAN
59
+ attr_reader :tacan
38
60
 
39
- def initialize(id:, name:, xy:, z: nil, type:, f:, north:)
40
- super(id: id, name: name, xy: xy, z: z)
61
+ def initialize(type:, f:, north:, **arguments)
62
+ super(**arguments)
41
63
  self.type, self.f, self.north = type, f, north
42
64
  end
43
65
 
44
66
  def type=(value)
45
- @type = TYPES.lookup(value&.to_sym, nil) || fail(ArgumentError, "invalid type")
46
- end
47
-
48
- def type_key
49
- TYPES.key(type)
67
+ @type = TYPES.lookup(value&.to_s&.to_sym, nil) || fail(ArgumentError, "invalid type")
50
68
  end
51
69
 
52
70
  def f=(value)
@@ -55,55 +73,39 @@ module AIXM
55
73
  end
56
74
 
57
75
  def north=(value)
58
- @north = NORTHS.lookup(value&.to_sym, nil) || fail(ArgumentError, "invalid north")
59
- end
60
-
61
- def north_key
62
- NORTHS.key(north)
76
+ @north = NORTHS.lookup(value&.to_s&.to_sym, nil) || fail(ArgumentError, "invalid north")
63
77
  end
64
78
 
65
- ##
66
- # Associate a DME (also known as VOR/DME)
79
+ # Associate a DME which turns the VOR into a VOR/DME
67
80
  def associate_dme(channel:)
68
- @dme = AIXM.dme(id: id, name: name, xy: xy, z: z, channel: channel)
69
- @dme.schedule = schedule
70
- @dme.remarks = remarks
71
- @dme.vor = self
81
+ @dme = AIXM.dme(organisation: organisation, id: id, name: name, xy: xy, z: z, channel: channel)
82
+ @dme.region, @dme.timetable, @dme.remarks = region, timetable, remarks
83
+ @dme.send(:vor=, self)
72
84
  end
73
85
 
74
- ##
75
- # Associate a TACAN (also known as VORTAC)
86
+ # Associate a TACAN which turns the VOR into a VORTAC
76
87
  def associate_tacan(channel:)
77
- @tacan = AIXM.tacan(id: id, name: name, xy: xy, z: z, channel: channel)
78
- @tacan.schedule = schedule
79
- @tacan.remarks = remarks
80
- @tacan.vor = self
81
- end
82
-
83
- ##
84
- # Digest to identify the payload
85
- def to_digest
86
- [super, type, f.to_digest, north].to_digest
88
+ @tacan = AIXM.tacan(organisation: organisation, id: id, name: name, xy: xy, z: z, channel: channel)
89
+ @tacan.region, @tacan.timetable, @tacan.remarks = region, timetable, remarks
90
+ @tacan.send(:vor=, self)
87
91
  end
88
92
 
89
- ##
90
- # Render UID markup
91
- def to_uid(*extensions)
93
+ # @return [String] UID markup
94
+ def to_uid
92
95
  builder = Builder::XmlMarkup.new(indent: 2)
93
- builder.VorUid({ mid: to_digest, newEntity: (true if extensions >> :ofm) }.compact) do |voruid|
94
- voruid.codeId(id)
95
- voruid.geoLat(xy.lat(format_for(*extensions)))
96
- voruid.geoLong(xy.long(format_for(*extensions)))
96
+ builder.VorUid({ region: (region if AIXM.ofmx?) }.compact) do |vor_uid|
97
+ vor_uid.codeId(id)
98
+ vor_uid.geoLat(xy.lat(AIXM.schema))
99
+ vor_uid.geoLong(xy.long(AIXM.schema))
97
100
  end
98
101
  end
99
102
 
100
- ##
101
- # Render AIXM markup
102
- def to_aixm(*extensions)
103
- builder = to_builder(*extensions)
104
- builder.Vor do |vor|
105
- vor << to_uid(*extensions).indent(2)
106
- vor.OrgUid
103
+ # @return [String] AIXM or OFMX markup
104
+ def to_xml
105
+ builder = to_builder
106
+ builder.Vor({ source: (source if AIXM.ofmx?) }.compact) do |vor|
107
+ vor << to_uid.indent(2)
108
+ vor << organisation.to_uid.indent(2)
107
109
  vor.txtName(name) if name
108
110
  vor.codeType(type_key.to_s)
109
111
  vor.valFreq(f.freq.trim)
@@ -112,18 +114,24 @@ module AIXM
112
114
  vor.codeDatum('WGE')
113
115
  if z
114
116
  vor.valElev(z.alt)
115
- vor.uomDistVer(z.unit.to_s)
116
- end
117
- if schedule
118
- vor.Vtt do |vtt|
119
- vtt << schedule.to_aixm(*extensions).indent(4)
120
- end
117
+ vor.uomDistVer(z.unit.upcase.to_s)
121
118
  end
119
+ vor << timetable.to_xml(as: :Vtt).indent(2) if timetable
122
120
  vor.txtRmk(remarks) if remarks
123
121
  end
124
- builder << @dme.to_aixm(*extensions) if @dme
125
- builder << @tacan.to_aixm(*extensions) if @tacan
126
- builder.target! # see https://github.com/jimweirich/builder/issues/42
122
+ builder << @dme.to_xml if @dme
123
+ builder << @tacan.to_xml if @tacan
124
+ builder.target!
125
+ end
126
+
127
+ # @api private
128
+ def type_key
129
+ TYPES.key(type)
130
+ end
131
+
132
+ # @api private
133
+ def north_key
134
+ NORTHS.key(north)
127
135
  end
128
136
  end
129
137
 
@@ -0,0 +1,97 @@
1
+ using AIXM::Refinements
2
+
3
+ module AIXM
4
+ class Feature
5
+
6
+ # Organisations and authorities such as ATS organisations, aircraft
7
+ # operating agencies, states and so forth.
8
+ #
9
+ # ===Cheat Sheet in Pseudo Code:
10
+ # organisation = AIXM.organisation(
11
+ # source: String or nil
12
+ # region: String or nil (falls back to +AIXM.config.region+)
13
+ # name: String
14
+ # type: TYPES
15
+ # )
16
+ # organisation.id = String or nil
17
+ # organisation.remarks = String or nil
18
+ #
19
+ # @see https://github.com/openflightmaps/ofmx/wiki/Organisation#org-organisation
20
+ class Organisation < Feature
21
+ public_class_method :new
22
+
23
+ TYPES = {
24
+ S: :state,
25
+ GS: :group_of_states,
26
+ O: :national_organisation,
27
+ IO: :international_organisation,
28
+ AOA: :aircraft_operating_agency,
29
+ ATS: :air_traffic_services_provider,
30
+ HA: :handling_authority,
31
+ A: :national_authority,
32
+ OTHER: :other # specify in remarks
33
+ }.freeze
34
+
35
+ # @return [String] name of organisation (e.g. "FRANCE")
36
+ attr_reader :name
37
+
38
+ # @return [Symbol] type of organisation (see {TYPES})
39
+ attr_reader :type
40
+
41
+ # @return [String, nil] code of the organisation (e.g. "LF")
42
+ attr_reader :id
43
+
44
+ # @return [String, nil] free text remarks
45
+ attr_reader :remarks
46
+
47
+ def initialize(source: nil, region: nil, name:, type:)
48
+ super(source: source, region: region)
49
+ self.name, self.type = name, type
50
+ end
51
+
52
+ # @return [String]
53
+ def inspect
54
+ %Q(#<#{self.class} name=#{name.inspect} type=#{type.inspect}>)
55
+ end
56
+
57
+ def name=(value)
58
+ fail(ArgumentError, "invalid name") unless value.is_a? String
59
+ @name = value.uptrans
60
+ end
61
+
62
+ def type=(value)
63
+ @type = TYPES.lookup(value&.to_s&.to_sym, nil) || fail(ArgumentError, "invalid type")
64
+ end
65
+
66
+ def id=(value)
67
+ fail(ArgumentError, "invalid id") unless value.nil? || value.is_a?(String)
68
+ @id = value&.upcase
69
+ end
70
+
71
+ def remarks=(value)
72
+ @remarks = value&.to_s
73
+ end
74
+
75
+ # @return [String] UID markup
76
+ def to_uid
77
+ builder = Builder::XmlMarkup.new(indent: 2)
78
+ builder.OrgUid({ region: (region if AIXM.ofmx?) }.compact) do |org_uid|
79
+ org_uid.txtName(name)
80
+ end
81
+ end
82
+
83
+ # @return [String] AIXM or OFMX markup
84
+ def to_xml
85
+ builder = Builder::XmlMarkup.new(indent: 2)
86
+ builder.comment! "Organisation: #{name}"
87
+ builder.Org({ source: (source if AIXM.ofmx?) }.compact) do |org|
88
+ org << to_uid.indent(2)
89
+ org.codeId(id) if id
90
+ org.codeType(TYPES.key(type).to_s)
91
+ org.txtRmk(remarks) if remarks
92
+ end
93
+ end
94
+ end
95
+
96
+ end
97
+ end
@@ -0,0 +1,152 @@
1
+ using AIXM::Refinements
2
+
3
+ module AIXM
4
+ class Feature
5
+
6
+ # Units providing all kind of services such as air traffic management,
7
+ # search and rescue, meteorological services and so forth.
8
+ #
9
+ # ===Cheat Sheet in Pseudo Code:
10
+ # unit = AIXM.unit(
11
+ # source: String or nil
12
+ # region: String or nil (falls back to +AIXM.config.region+)
13
+ # organisation: AIXM.organisation
14
+ # name: String
15
+ # type: TYPES
16
+ # class: :icao or :other
17
+ # )
18
+ # unit.airport = AIXM.airport or nil
19
+ # unit.remarks = String or nil
20
+ # unit.add_service(AIXM.service)
21
+ #
22
+ # @see https://github.com/openflightmaps/ofmx/wiki/Organisation#uni-unit
23
+ class Unit < Feature
24
+ public_class_method :new
25
+
26
+ TYPES = {
27
+ ACC: :area_control_centre,
28
+ APP: :approach_control_office,
29
+ ARO: :ats_reporting_office,
30
+ ATSU: :air_traffic_services_unit,
31
+ COM: :communications_office,
32
+ FIC: :flight_information_centre,
33
+ FSS: :flight_service_station,
34
+ MET: :meteorological_office,
35
+ NOF: :international_notam_office,
36
+ RAD: :radar_office,
37
+ TWR: :aerodrome_control_tower,
38
+ OTHER: :other # specify in remarks
39
+ }.freeze
40
+
41
+ CLASSES = {
42
+ ICAO: :icao,
43
+ OTHER: :other # specify in remarks
44
+ }.freeze
45
+
46
+ # @return [AIXM::Feature::Organisation] superior organisation
47
+ attr_reader :organisation
48
+
49
+ # @return [String] name of unit (e.g. "MARSEILLE ACS")
50
+ attr_reader :name
51
+
52
+ # @return [Symbol] type of unit (see {TYPES})
53
+ attr_reader :type
54
+
55
+ # @return [AIXM::Feature::Airport, nil] airport
56
+ attr_reader :airport
57
+
58
+ # @return [String, nil] free text remarks
59
+ attr_reader :remarks
60
+
61
+ def initialize(source: nil, region: nil, organisation:, name:, type:, class:)
62
+ super(source: source, region: region)
63
+ self.organisation, self.name, self.type = organisation, name, type
64
+ self.class = binding.local_variable_get(:class)
65
+ @services = []
66
+ end
67
+
68
+ # @return [String]
69
+ def inspect
70
+ %Q(#<#{self.class} name=#{name.inspect} type=#{type.inspect}>)
71
+ end
72
+
73
+ def organisation=(value)
74
+ fail(ArgumentError, "invalid organisation") unless value.is_a? AIXM::Feature::Organisation
75
+ @organisation = value
76
+ end
77
+
78
+ def name=(value)
79
+ fail(ArgumentError, "invalid name") unless value.is_a? String
80
+ @name = value.uptrans
81
+ end
82
+
83
+ def type=(value)
84
+ @type = TYPES.lookup(value&.to_s&.to_sym, nil) || fail(ArgumentError, "invalid type")
85
+ end
86
+
87
+ # @!attribute class
88
+ # @return [Symbol] class of unit (see {CLASSES})
89
+ def class
90
+ @klass
91
+ end
92
+
93
+ def class=(value)
94
+ @klass = CLASSES.lookup(value&.to_s&.to_sym, nil) || fail(ArgumentError, "invalid class")
95
+ end
96
+
97
+ def airport=(value)
98
+ fail(ArgumentError, "invalid airport") unless value.nil? || value.is_a?(AIXM::Feature::Airport)
99
+ @airport = value
100
+ end
101
+
102
+ def remarks=(value)
103
+ @remarks = value&.to_s
104
+ end
105
+
106
+ # Add a service provided by this unit.
107
+ #
108
+ # @param service [AIXM::Component::Service] service instance
109
+ # @return [self]
110
+ def add_service(service)
111
+ fail(ArgumentError, "invalid service") unless service.is_a? AIXM::Component::Service
112
+ service.send(:unit=, self)
113
+ @services << service
114
+ self
115
+ end
116
+
117
+ # @!attribute [r] services
118
+ # @return [Array<AIXM::Component::Service>] services provided by this unit
119
+ def services
120
+ @services.sort { |a, b| [a.type, a.name] <=> [b.type, b.name] }
121
+ end
122
+
123
+ # @return [String] UID markup
124
+ def to_uid
125
+ builder = Builder::XmlMarkup.new(indent: 2)
126
+ builder.UniUid({ region: (region if AIXM.ofmx?) }.compact) do |uni_uid|
127
+ uni_uid.txtName(name)
128
+ end
129
+ end
130
+
131
+ # @return [String] AIXM or OFMX markup
132
+ def to_xml
133
+ builder = Builder::XmlMarkup.new(indent: 2)
134
+ builder.comment! "Unit: #{name}"
135
+ builder.Uni({ source: (source if AIXM.ofmx?) }.compact) do |uni|
136
+ uni << to_uid.indent(2)
137
+ uni << organisation.to_uid.indent(2)
138
+ uni << airport.to_uid.indent(2) if airport
139
+ uni.codeType(TYPES.key(type).to_s)
140
+ uni.codeClass(CLASSES.key(self.class).to_s)
141
+ uni.txtRmk(remarks) if remarks
142
+ end
143
+ services.each.with_object({}) do |service, sequences|
144
+ sequences[service.type] = (sequences[service.type] || 0) + 1
145
+ builder << service.to_xml(sequences[service.type])
146
+ end
147
+ builder.target!
148
+ end
149
+ end
150
+
151
+ end
152
+ end