aixm 0.1.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 (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +5 -0
  5. data/CHANGELOG.md +14 -0
  6. data/Gemfile +3 -0
  7. data/Guardfile +7 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +100 -0
  10. data/Rakefile +12 -0
  11. data/aixm.gemspec +32 -0
  12. data/lib/aixm.rb +25 -0
  13. data/lib/aixm/constants.rb +6 -0
  14. data/lib/aixm/document.rb +65 -0
  15. data/lib/aixm/feature/airspace.rb +74 -0
  16. data/lib/aixm/geometry.rb +71 -0
  17. data/lib/aixm/horizontal/arc.rb +50 -0
  18. data/lib/aixm/horizontal/border.rb +45 -0
  19. data/lib/aixm/horizontal/circle.rb +53 -0
  20. data/lib/aixm/horizontal/point.rb +39 -0
  21. data/lib/aixm/refinements.rb +61 -0
  22. data/lib/aixm/schemas/4.5/AIXM-DataTypes.xsd +5231 -0
  23. data/lib/aixm/schemas/4.5/AIXM-Features.xsd +10066 -0
  24. data/lib/aixm/schemas/4.5/AIXM-Snapshot.xsd +352 -0
  25. data/lib/aixm/version.rb +3 -0
  26. data/lib/aixm/vertical/limits.rb +59 -0
  27. data/lib/aixm/xy.rb +52 -0
  28. data/lib/aixm/z.rb +39 -0
  29. data/spec/factory.rb +66 -0
  30. data/spec/lib/aixm/document_spec.rb +211 -0
  31. data/spec/lib/aixm/feature/airspace_spec.rb +96 -0
  32. data/spec/lib/aixm/geometry_spec.rb +218 -0
  33. data/spec/lib/aixm/horizontal/arc_spec.rb +69 -0
  34. data/spec/lib/aixm/horizontal/border_spec.rb +47 -0
  35. data/spec/lib/aixm/horizontal/circle_spec.rb +65 -0
  36. data/spec/lib/aixm/horizontal/point_spec.rb +42 -0
  37. data/spec/lib/aixm/refinements_spec.rb +180 -0
  38. data/spec/lib/aixm/version_spec.rb +7 -0
  39. data/spec/lib/aixm/vertical/limits_spec.rb +78 -0
  40. data/spec/lib/aixm/xy_spec.rb +131 -0
  41. data/spec/lib/aixm/z_spec.rb +59 -0
  42. data/spec/sounds/failure.mp3 +0 -0
  43. data/spec/sounds/success.mp3 +0 -0
  44. data/spec/spec_helper.rb +28 -0
  45. metadata +243 -0
@@ -0,0 +1,69 @@
1
+ require_relative '../../../spec_helper'
2
+
3
+ describe AIXM::Horizontal::Arc do
4
+ describe :initialize do
5
+ it "won't accept invalid arguments" do
6
+ xy = AIXM::XY.new(lat: 11.1, long: 22.2)
7
+ -> { AIXM::Horizontal::Arc.new(xy: 0, center_xy: xy, clockwise: true) }.must_raise ArgumentError
8
+ -> { AIXM::Horizontal::Arc.new(xy: xy, center_xy: 0, clockwise: true) }.must_raise ArgumentError
9
+ -> { AIXM::Horizontal::Arc.new(xy: xy, center_xy: xy, clockwise: 0) }.must_raise ArgumentError
10
+ end
11
+ end
12
+
13
+ describe :clockwise? do
14
+ it "must return true or false" do
15
+ xy = AIXM::XY.new(lat: 11.1, long: 22.2)
16
+ AIXM::Horizontal::Arc.new(xy: xy, center_xy: xy, clockwise: true).must_be :clockwise?
17
+ AIXM::Horizontal::Arc.new(xy: xy, center_xy: xy, clockwise: false).wont_be :clockwise?
18
+ end
19
+ end
20
+
21
+ describe :to_digest do
22
+ it "must return digest of payload" do
23
+ subject = AIXM::Horizontal::Arc.new(
24
+ xy: AIXM::XY.new(lat: 11.1, long: 33.3),
25
+ center_xy: AIXM::XY.new(lat: 22.2, long: 33.3),
26
+ clockwise: true
27
+ )
28
+ subject.to_digest.must_equal '35B2E1AF'
29
+ end
30
+ end
31
+
32
+ describe :to_xml do
33
+ it "must build correct XML for clockwise arcs" do
34
+ subject = AIXM::Horizontal::Arc.new(
35
+ xy: AIXM::XY.new(lat: 11.1, long: 33.3),
36
+ center_xy: AIXM::XY.new(lat: 22.2, long: 33.3),
37
+ clockwise: true
38
+ )
39
+ subject.to_xml.must_equal <<~END
40
+ <Avx>
41
+ <codeType>CWA</codeType>
42
+ <geoLat>110600.00N</geoLat>
43
+ <geoLong>0331800.00E</geoLong>
44
+ <codeDatum>WGE</codeDatum>
45
+ <geoLatArc>221200.00N</geoLatArc>
46
+ <geoLongArc>0331800.00E</geoLongArc>
47
+ </Avx>
48
+ END
49
+ end
50
+
51
+ it "must build correct XML for counter-clockwise arcs" do
52
+ subject = AIXM::Horizontal::Arc.new(
53
+ xy: AIXM::XY.new(lat: 11.1, long: 33.3),
54
+ center_xy: AIXM::XY.new(lat: 22.2, long: 33.3),
55
+ clockwise: false
56
+ )
57
+ subject.to_xml.must_equal <<~END
58
+ <Avx>
59
+ <codeType>CCA</codeType>
60
+ <geoLat>110600.00N</geoLat>
61
+ <geoLong>0331800.00E</geoLong>
62
+ <codeDatum>WGE</codeDatum>
63
+ <geoLatArc>221200.00N</geoLatArc>
64
+ <geoLongArc>0331800.00E</geoLongArc>
65
+ </Avx>
66
+ END
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,47 @@
1
+ require_relative '../../../spec_helper'
2
+
3
+ describe AIXM::Horizontal::Border do
4
+ describe :to_digest do
5
+ it "must return digest of payload" do
6
+ subject = AIXM::Horizontal::Border.new(
7
+ xy: AIXM::XY.new(lat: 11.1, long: 22.2),
8
+ name: 'foobar',
9
+ name_mid: 123
10
+ )
11
+ subject.to_digest.must_equal '8955450F'
12
+ end
13
+ end
14
+
15
+ describe :to_xml do
16
+ it "must build correct XML with name_mid" do
17
+ subject = AIXM::Horizontal::Border.new(
18
+ xy: AIXM::XY.new(lat: 11.1, long: 22.2),
19
+ name: 'foobar',
20
+ name_mid: 123
21
+ )
22
+ subject.to_xml.must_equal <<~END
23
+ <Avx>
24
+ <codeType>FNT</codeType>
25
+ <geoLat>110600.00N</geoLat>
26
+ <geoLong>0221200.00E</geoLong>
27
+ <codeDatum>WGE</codeDatum>
28
+ </Avx>
29
+ END
30
+ end
31
+
32
+ it "must build correct XML without name_mid" do
33
+ subject = AIXM::Horizontal::Border.new(
34
+ xy: AIXM::XY.new(lat: 11.1, long: 22.2),
35
+ name: 'foobar'
36
+ )
37
+ subject.to_xml.must_equal <<~END
38
+ <Avx>
39
+ <codeType>FNT</codeType>
40
+ <geoLat>110600.00N</geoLat>
41
+ <geoLong>0221200.00E</geoLong>
42
+ <codeDatum>WGE</codeDatum>
43
+ </Avx>
44
+ END
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,65 @@
1
+ require_relative '../../../spec_helper'
2
+
3
+ describe AIXM::Horizontal::Circle do
4
+ describe :initialize do
5
+ it "won't accept invalid arguments" do
6
+ -> { AIXM::Horizontal::Circle.new(center_xy: 0, radius: 0) }.must_raise ArgumentError
7
+ end
8
+ end
9
+
10
+ describe :north_xy do
11
+ it "must calculate approximation of northmost point on the circumference" do
12
+ subject = AIXM::Horizontal::Circle.new(
13
+ center_xy: AIXM::XY.new(lat: 12.12345678, long: -23.12345678),
14
+ radius: 15
15
+ )
16
+ subject.send(:north_xy).must_equal AIXM::XY.new(lat: 12.25835502, long: -23.12345678)
17
+ end
18
+ end
19
+
20
+ describe :to_digest do
21
+ it "must return digest of payload" do
22
+ subject = AIXM::Horizontal::Circle.new(
23
+ center_xy: AIXM::XY.new(lat: 12.12345678, long: -23.12345678),
24
+ radius: 15
25
+ )
26
+ subject.to_digest.must_equal '914C5F08'
27
+ end
28
+ end
29
+
30
+ describe :to_xml do
31
+ it "must build correct XML for circles not near the equator" do
32
+ subject = AIXM::Horizontal::Circle.new(
33
+ center_xy: AIXM::XY.new(lat: 11.1, long: 22.2),
34
+ radius: 25
35
+ )
36
+ subject.to_xml.must_equal <<~END
37
+ <Avx>
38
+ <codeType>CWA</codeType>
39
+ <geoLat>111929.39N</geoLat>
40
+ <geoLong>0221200.00E</geoLong>
41
+ <codeDatum>WGE</codeDatum>
42
+ <geoLatArc>110600.00N</geoLatArc>
43
+ <geoLongArc>0221200.00E</geoLongArc>
44
+ </Avx>
45
+ END
46
+ end
47
+
48
+ it "must build correct XML for circles near the equator" do
49
+ subject = AIXM::Horizontal::Circle.new(
50
+ center_xy: AIXM::XY.new(lat: -0.0005, long: -22.2),
51
+ radius: 50
52
+ )
53
+ subject.to_xml.must_equal <<~END
54
+ <Avx>
55
+ <codeType>CWA</codeType>
56
+ <geoLat>002656.98N</geoLat>
57
+ <geoLong>0221200.00W</geoLong>
58
+ <codeDatum>WGE</codeDatum>
59
+ <geoLatArc>000001.80S</geoLatArc>
60
+ <geoLongArc>0221200.00W</geoLongArc>
61
+ </Avx>
62
+ END
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,42 @@
1
+ require_relative '../../../spec_helper'
2
+
3
+ describe AIXM::Horizontal::Point do
4
+ describe :initialize do
5
+ it "won't accept invalid arguments" do
6
+ -> { AIXM::Horizontal::Point.new(xy: 0) }.must_raise ArgumentError
7
+ end
8
+ end
9
+
10
+ describe :to_digest do
11
+ it "must return digest of payload" do
12
+ subject = AIXM::Horizontal::Point.new(xy: AIXM::XY.new(lat: 11.1, long: 22.2))
13
+ subject.to_digest.must_equal '215D7CBA'
14
+ end
15
+ end
16
+
17
+ describe :to_xml do
18
+ it "must build correct XML for N/E points" do
19
+ subject = AIXM::Horizontal::Point.new(xy: AIXM::XY.new(lat: 11.1, long: 22.2))
20
+ subject.to_xml.must_equal <<~END
21
+ <Avx>
22
+ <codeType>GRC</codeType>
23
+ <geoLat>110600.00N</geoLat>
24
+ <geoLong>0221200.00E</geoLong>
25
+ <codeDatum>WGE</codeDatum>
26
+ </Avx>
27
+ END
28
+ end
29
+
30
+ it "must build correct XML for S/W points" do
31
+ subject = AIXM::Horizontal::Point.new(xy: AIXM::XY.new(lat: -11.1, long: -22.2))
32
+ subject.to_xml.must_equal <<~END
33
+ <Avx>
34
+ <codeType>GRC</codeType>
35
+ <geoLat>110600.00S</geoLat>
36
+ <geoLong>0221200.00W</geoLong>
37
+ <codeDatum>WGE</codeDatum>
38
+ </Avx>
39
+ END
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,180 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ using AIXM::Refinements
4
+
5
+ describe AIXM::Refinements do
6
+
7
+ describe 'Array#to_digest' do
8
+ it "must digest single string" do
9
+ %w(a).to_digest.must_equal Digest::MD5.hexdigest('a')[0, 8].upcase
10
+ end
11
+
12
+ it "must digest double string" do
13
+ %w(a b).to_digest.must_equal Digest::MD5.hexdigest('a|b')[0, 8].upcase
14
+ end
15
+
16
+ it "must digest integer" do
17
+ [5].to_digest.must_equal Digest::MD5.hexdigest('5')[0, 8].upcase
18
+ end
19
+
20
+ it "must digest float" do
21
+ [5.0].to_digest.must_equal Digest::MD5.hexdigest('5.0')[0, 8].upcase
22
+ end
23
+
24
+ it "must digest boolean" do
25
+ [true, false].to_digest.must_equal Digest::MD5.hexdigest('true|false')[0, 8].upcase
26
+ end
27
+
28
+ it "must digest nil" do
29
+ [nil].to_digest.must_equal Digest::MD5.hexdigest('')[0, 8].upcase
30
+ end
31
+ end
32
+
33
+ describe 'String#indent' do
34
+ it "must indent single line string" do
35
+ 'foobar'.indent(2).must_equal ' foobar'
36
+ end
37
+
38
+ it "must indent multi line string" do
39
+ "foo\nbar".indent(2).must_equal " foo\n bar"
40
+ "foo\nbar\n".indent(2).must_equal " foo\n bar\n"
41
+ end
42
+ end
43
+
44
+ describe 'String#to_dd' do
45
+ it "must convert +6.2 DMS to DD" do
46
+ %q(12°34'56.78").to_dd.must_equal 12.58243888888889
47
+ %q(12 34 56.78).to_dd.must_equal 12.58243888888889
48
+ %q(123456.78).to_dd.must_equal 12.58243888888889
49
+ end
50
+
51
+ it "must convert -6.2 DMS to DD" do
52
+ %q(-12°34'56.78").to_dd.must_equal(-12.58243888888889)
53
+ %q(-12 34 56.78).to_dd.must_equal(-12.58243888888889)
54
+ %q(-123456.78).to_dd.must_equal(-12.58243888888889)
55
+ end
56
+
57
+ it "must convert +7.2 DMS to DD" do
58
+ %q(111°22'33.44").to_dd.must_equal 111.37595555555555
59
+ %q(111 22 33.44).to_dd.must_equal 111.37595555555555
60
+ %q(1112233.44).to_dd.must_equal 111.37595555555555
61
+ end
62
+
63
+ it "must convert -7.2 DMS to DD" do
64
+ %q(-111°22'33.44").to_dd.must_equal(-111.37595555555555)
65
+ %q(-111 22 33.44).to_dd.must_equal(-111.37595555555555)
66
+ %q(-1112233.44).to_dd.must_equal(-111.37595555555555)
67
+ end
68
+
69
+ it "must convert +6.1 DMS to DD" do
70
+ %q(12°34'56.7").to_dd.must_equal 12.582416666666667
71
+ %q(12 34 56.7).to_dd.must_equal 12.582416666666667
72
+ %q(123456.7).to_dd.must_equal 12.582416666666667
73
+ end
74
+
75
+ it "must convert -6.1 DMS to DD" do
76
+ %q(-12°34'56.7").to_dd.must_equal(-12.582416666666667)
77
+ %q(-12 34 56.7).to_dd.must_equal(-12.582416666666667)
78
+ %q(-123456.7).to_dd.must_equal(-12.582416666666667)
79
+ end
80
+
81
+ it "must convert +7.1 DMS to DD" do
82
+ %q(111°22'33.4").to_dd.must_equal 111.37594444444444
83
+ %q(111 22 33.4).to_dd.must_equal 111.37594444444444
84
+ %q(1112233.4).to_dd.must_equal 111.37594444444444
85
+ end
86
+
87
+ it "must convert +7.1 DMS to DD" do
88
+ %q(-111°22'33.4").to_dd.must_equal(-111.37594444444444)
89
+ %q(-111 22 33.4).to_dd.must_equal(-111.37594444444444)
90
+ %q(-1112233.4).to_dd.must_equal(-111.37594444444444)
91
+ end
92
+
93
+ it "must convert +6.0 DMS to DD" do
94
+ %q(12°34'56").to_dd.must_equal 12.582222222222223
95
+ %q(12 34 56).to_dd.must_equal 12.582222222222223
96
+ %q(123456).to_dd.must_equal 12.582222222222223
97
+ end
98
+
99
+ it "must convert -6.0 DMS to DD" do
100
+ %q(-12°34'56").to_dd.must_equal(-12.582222222222223)
101
+ %q(-12 34 56).to_dd.must_equal(-12.582222222222223)
102
+ %q(-123456).to_dd.must_equal(-12.582222222222223)
103
+ end
104
+
105
+ it "must convert +7.0 DMS to DD" do
106
+ %q(111°22'33").to_dd.must_equal 111.37583333333333
107
+ %q(111 22 33).to_dd.must_equal 111.37583333333333
108
+ %q(1112233).to_dd.must_equal 111.37583333333333
109
+ end
110
+
111
+ it "must convert +7.0 DMS to DD" do
112
+ %q(-111°22'33").to_dd.must_equal(-111.37583333333333)
113
+ %q(-111 22 33).to_dd.must_equal(-111.37583333333333)
114
+ %q(-1112233).to_dd.must_equal(-111.37583333333333)
115
+ end
116
+
117
+ it "must do all possible roundtrip conversions" do
118
+ 2.times.with_index do |degrees|
119
+ 60.times.with_index do |minutes|
120
+ 60.times.with_index do |seconds|
121
+ 100.times.with_index do |fractions|
122
+ subject = %q(%03d°%02d'%02d.%02d") % [degrees, minutes, seconds, fractions]
123
+ subject.to_dd.to_dms.must_equal subject
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+ describe 'Float#to_dms' do
132
+ it "must convert +1. DD to DMS" do
133
+ 1.37595556.to_dms.must_equal %q(001°22'33.44")
134
+ end
135
+
136
+ it "must convert -1. DD to DMS" do
137
+ -1.37595556.to_dms.must_equal %q(-001°22'33.44")
138
+ end
139
+
140
+ it "must convert +2. DD to DMS" do
141
+ 11.37595556.to_dms.must_equal %q(011°22'33.44")
142
+ end
143
+
144
+ it "must convert -2. DD to DMS" do
145
+ -11.37595556.to_dms.must_equal %q(-011°22'33.44")
146
+ end
147
+
148
+ it "must convert +3. DD to DMS" do
149
+ 111.37595556.to_dms.must_equal %q(111°22'33.44")
150
+ end
151
+
152
+ it "must convert -3. DD to DMS" do
153
+ -111.37595556.to_dms.must_equal %q(-111°22'33.44")
154
+ end
155
+
156
+ it "must convert DD to DMS with degrees only" do
157
+ 11.0.to_dms.must_equal %q(011°00'00.00")
158
+ end
159
+
160
+ it "must convert DD to DMS with degrees and minutes only" do
161
+ 11.36666667.to_dms.must_equal %q(011°22'00.00")
162
+ end
163
+
164
+ it "must convert DD to DMS with tenth of seconds only" do
165
+ 1.37594444.to_dms.must_equal %q(001°22'33.40")
166
+ end
167
+
168
+ it "must convert DD to DMS with whole seconds only" do
169
+ 1.37583333.to_dms.must_equal %q(001°22'33.00")
170
+ end
171
+
172
+ it "must convert DD to two zero padded DMS" do
173
+ 1.37595556.to_dms(2).must_equal %q(01°22'33.44")
174
+ end
175
+
176
+ it "must convert DD to no zero padded DMS" do
177
+ 1.37595556.to_dms(0).must_equal %q(1°22'33.44")
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,7 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ describe AIXM do
4
+ it "must be defined" do
5
+ AIXM::VERSION.wont_be_nil
6
+ end
7
+ end
@@ -0,0 +1,78 @@
1
+ require_relative '../../../spec_helper'
2
+
3
+ describe AIXM::Vertical::Limits do
4
+ describe :initialize do
5
+ it "won't accept invalid arguments" do
6
+ z = AIXM::Z.new(alt: 1000, code: :QNH)
7
+ -> { AIXM::Vertical::Limits.new(upper_z: 0, lower_z: z, max_z: z, min_z: z) }.must_raise ArgumentError
8
+ -> { AIXM::Vertical::Limits.new(upper_z: z, lower_z: 0, max_z: z, min_z: z) }.must_raise ArgumentError
9
+ -> { AIXM::Vertical::Limits.new(upper_z: z, lower_z: z, max_z: 0, min_z: z) }.must_raise ArgumentError
10
+ -> { AIXM::Vertical::Limits.new(upper_z: z, lower_z: z, max_z: z, min_z: 0) }.must_raise ArgumentError
11
+ end
12
+ end
13
+
14
+ describe :to_digest do
15
+ it "must return digest of payload" do
16
+ subject = AIXM::Vertical::Limits.new(
17
+ upper_z: AIXM::Z.new(alt: 2000, code: :QNH),
18
+ lower_z: AIXM::GROUND
19
+ )
20
+ subject.to_digest.must_equal 'ABF2A04F'
21
+ end
22
+ end
23
+
24
+ describe :to_xml do
25
+ it "must build correct XML with only upper_z and lower_z" do
26
+ subject = AIXM::Vertical::Limits.new(
27
+ upper_z: AIXM::Z.new(alt: 2000, code: :QNH),
28
+ lower_z: AIXM::GROUND
29
+ )
30
+ subject.to_xml.must_equal <<~END
31
+ <codeDistVerUpper>ALT</codeDistVerUpper>
32
+ <valDistVerUpper>2000</valDistVerUpper>
33
+ <uomDistVerUpper>FT</uomDistVerUpper>
34
+ <codeDistVerLower>HEI</codeDistVerLower>
35
+ <valDistVerLower>0</valDistVerLower>
36
+ <uomDistVerLower>FT</uomDistVerLower>
37
+ END
38
+ end
39
+
40
+ it "must build correct XML with additional max_z" do
41
+ subject = AIXM::Vertical::Limits.new(
42
+ upper_z: AIXM::Z.new(alt: 65, code: :QNE),
43
+ lower_z: AIXM::Z.new(alt: 1000, code: :QFE),
44
+ max_z: AIXM::Z.new(alt: 6000, code: :QNH)
45
+ )
46
+ subject.to_xml.must_equal <<~END
47
+ <codeDistVerUpper>STD</codeDistVerUpper>
48
+ <valDistVerUpper>65</valDistVerUpper>
49
+ <uomDistVerUpper>FL</uomDistVerUpper>
50
+ <codeDistVerLower>HEI</codeDistVerLower>
51
+ <valDistVerLower>1000</valDistVerLower>
52
+ <uomDistVerLower>FT</uomDistVerLower>
53
+ <codeDistVerMax>ALT</codeDistVerMax>
54
+ <valDistVerMax>6000</valDistVerMax>
55
+ <uomDistVerMax>FT</uomDistVerMax>
56
+ END
57
+ end
58
+
59
+ it "must build correct XML with additional min_z" do
60
+ subject = AIXM::Vertical::Limits.new(
61
+ upper_z: AIXM::Z.new(alt: 65, code: :QNE),
62
+ lower_z: AIXM::Z.new(alt: 45, code: :QNE),
63
+ min_z: AIXM::Z.new(alt: 3000, code: :QNH)
64
+ )
65
+ subject.to_xml.must_equal <<~END
66
+ <codeDistVerUpper>STD</codeDistVerUpper>
67
+ <valDistVerUpper>65</valDistVerUpper>
68
+ <uomDistVerUpper>FL</uomDistVerUpper>
69
+ <codeDistVerLower>STD</codeDistVerLower>
70
+ <valDistVerLower>45</valDistVerLower>
71
+ <uomDistVerLower>FL</uomDistVerLower>
72
+ <codeDistVerMnm>ALT</codeDistVerMnm>
73
+ <valDistVerMnm>3000</valDistVerMnm>
74
+ <uomDistVerMnm>FT</uomDistVerMnm>
75
+ END
76
+ end
77
+ end
78
+ end