aixm 0.1.0 → 0.1.3

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -1
  3. data/Guardfile +1 -1
  4. data/README.md +146 -17
  5. data/aixm.gemspec +3 -1
  6. data/lib/aixm.rb +12 -10
  7. data/lib/aixm/component/base.rb +6 -0
  8. data/lib/aixm/component/class_layer.rb +49 -0
  9. data/lib/aixm/component/geometry.rb +73 -0
  10. data/lib/aixm/component/geometry/arc.rb +53 -0
  11. data/lib/aixm/component/geometry/border.rb +49 -0
  12. data/lib/aixm/component/geometry/circle.rb +56 -0
  13. data/lib/aixm/component/geometry/point.rb +42 -0
  14. data/lib/aixm/component/schedule.rb +45 -0
  15. data/lib/aixm/{vertical/limits.rb → component/vertical_limits.rb} +9 -14
  16. data/lib/aixm/document.rb +30 -19
  17. data/lib/aixm/feature/airspace.rb +60 -29
  18. data/lib/aixm/refinements.rb +49 -2
  19. data/lib/aixm/shortcuts.rb +30 -0
  20. data/lib/aixm/version.rb +1 -1
  21. data/spec/factory.rb +42 -25
  22. data/spec/lib/aixm/component/class_layer_spec.rb +74 -0
  23. data/spec/lib/aixm/{horizontal → component/geometry}/arc_spec.rb +11 -11
  24. data/spec/lib/aixm/component/geometry/border_spec.rb +30 -0
  25. data/spec/lib/aixm/{horizontal → component/geometry}/circle_spec.rb +8 -8
  26. data/spec/lib/aixm/{horizontal → component/geometry}/point_spec.rb +7 -7
  27. data/spec/lib/aixm/{geometry_spec.rb → component/geometry_spec.rb} +39 -40
  28. data/spec/lib/aixm/component/schedule_spec.rb +33 -0
  29. data/spec/lib/aixm/{vertical/limits_spec.rb → component/vertical_limits_spec.rb} +10 -10
  30. data/spec/lib/aixm/document_spec.rb +97 -36
  31. data/spec/lib/aixm/feature/airspace_spec.rb +230 -71
  32. data/spec/lib/aixm/refinements_spec.rb +52 -12
  33. metadata +30 -23
  34. data/lib/aixm/constants.rb +0 -6
  35. data/lib/aixm/geometry.rb +0 -71
  36. data/lib/aixm/horizontal/arc.rb +0 -50
  37. data/lib/aixm/horizontal/border.rb +0 -45
  38. data/lib/aixm/horizontal/circle.rb +0 -53
  39. data/lib/aixm/horizontal/point.rb +0 -39
  40. data/spec/lib/aixm/horizontal/border_spec.rb +0 -47
@@ -1,11 +1,38 @@
1
1
  module AIXM
2
2
  module Refinements
3
3
 
4
+ UPTRANS_FILTER = %r{[^A-Z0-9, !"&#$%'\(\)\*\+\-\./:;<=>\?@\[\\\]\^_\|\{\}]}.freeze
5
+
6
+ UPTRANS_MAP = {
7
+ 'Ä' => 'AE',
8
+ 'Ö' => 'OE',
9
+ 'Ü' => 'UE',
10
+ 'Æ' => 'AE',
11
+ 'Œ' => 'OE',
12
+ "Å" => "Aa",
13
+ "Ø" => "Oe"
14
+ }.freeze
15
+
16
+ KM_FACTORS = {
17
+ km: 1,
18
+ m: 0.001,
19
+ nm: 1.852,
20
+ ft: 0.0003048
21
+ }.freeze
22
+
4
23
  refine Array do
5
24
  ##
6
- # Build 8 character upcase hex digest from payload (one or more strings)
25
+ # Shortcut for +include?+
26
+ #
27
+ # Example:
28
+ # extensions.include?(:OFM) # => true
29
+ # extensions >> :OFM # => true
30
+ alias_method :>>, :include?
31
+
32
+ ##
33
+ # Build a 1 to 9 digit integer digest (which fits in signed 32bit) from payload
7
34
  def to_digest
8
- ::Digest::MD5.hexdigest(join('|'))[0, 8].upcase
35
+ ::Digest::SHA512.hexdigest(flatten.join('|')).gsub(/\D/, '')[0, 9].to_i
9
36
  end
10
37
  end
11
38
 
@@ -17,6 +44,18 @@ module AIXM
17
44
  gsub(/^/, whitespace)
18
45
  end
19
46
 
47
+ ##
48
+ # Upcase and transliterate to match the reduced character set for
49
+ # AIXM names and titles
50
+ def uptrans
51
+ self.dup.tap do |string|
52
+ string.upcase!
53
+ string.gsub!(/(#{UPTRANS_MAP.keys.join('|')})/, UPTRANS_MAP)
54
+ string.unicode_normalize!(:nfd)
55
+ string.gsub!(UPTRANS_FILTER, '')
56
+ end
57
+ end
58
+
20
59
  ##
21
60
  # Convert DMS angle to DD or +nil+ if the format is not recognized
22
61
  #
@@ -56,6 +95,14 @@ module AIXM
56
95
  seconds.abs
57
96
  ]
58
97
  end
98
+
99
+ ##
100
+ # Convert a distance +from+ unit (+:km+, +:m+, +:nm+ or +:ft+) to kilometers
101
+ def to_km(from:)
102
+ self * KM_FACTORS.fetch(from.downcase.to_sym)
103
+ rescue KeyError
104
+ raise(ArgumentError, "unit `#{from}' not supported")
105
+ end
59
106
  end
60
107
  end
61
108
  end
@@ -0,0 +1,30 @@
1
+ module AIXM
2
+
3
+ SCHEMA = Pathname(__dir__).join('schemas', '4.5', 'AIXM-Snapshot.xsd').freeze
4
+
5
+ GROUND = Z.new(alt: 0, code: :QFE).freeze
6
+ UNLIMITED = Z.new(alt: 999, code: :QNE).freeze
7
+ H24 = Component::Schedule.new(code: :H24).freeze
8
+
9
+ ELEMENTS = {
10
+ document: Document,
11
+ xy: XY,
12
+ z: Z,
13
+ airspace: Feature::Airspace,
14
+ class_layer: Component::ClassLayer,
15
+ geometry: Component::Geometry,
16
+ schedule: Component::Schedule,
17
+ vertical_limits: Component::VerticalLimits,
18
+ arc: Component::Geometry::Arc,
19
+ border: Component::Geometry::Border,
20
+ circle: Component::Geometry::Circle,
21
+ point: Component::Geometry::Point
22
+ }.freeze
23
+
24
+ ELEMENTS.each do |element, klass|
25
+ define_singleton_method(element) do |*arguments|
26
+ klass.new(*arguments)
27
+ end
28
+ end
29
+
30
+ end
@@ -1,3 +1,3 @@
1
1
  module AIXM
2
- VERSION = "0.1.0".freeze
2
+ VERSION = "0.1.3".freeze
3
3
  end
@@ -3,51 +3,68 @@ module AIXM
3
3
  class << self
4
4
 
5
5
  def vertical_limits
6
- AIXM::Vertical::Limits.new(
7
- upper_z: AIXM::Z.new(alt: 65, code: :QNE),
8
- lower_z: AIXM::Z.new(alt: 45, code: :QNE),
9
- max_z: AIXM::Z.new(alt: 6000, code: :QNH),
10
- min_z: AIXM::Z.new(alt: 3000, code: :QFE)
6
+ AIXM.vertical_limits(
7
+ upper_z: AIXM.z(alt: 65, code: :QNE),
8
+ lower_z: AIXM.z(alt: 45, code: :QNE),
9
+ max_z: AIXM.z(alt: 6000, code: :QNH),
10
+ min_z: AIXM.z(alt: 3000, code: :QFE)
11
+ )
12
+ end
13
+
14
+ def class_layer
15
+ AIXM.class_layer(
16
+ class: :C,
17
+ vertical_limits: vertical_limits
11
18
  )
12
19
  end
13
20
 
14
21
  def polygon_geometry
15
- AIXM::Geometry.new.tap do |geometry|
16
- geometry << AIXM::Horizontal::Arc.new(
17
- xy: AIXM::XY.new(lat: %q(47°51'33"N), long: %q(007°33'36"E)),
18
- center_xy: AIXM::XY.new(lat: %q(47°54'15"N), long: %q(007°33'48"E)),
22
+ AIXM.geometry.tap do |geometry|
23
+ geometry << AIXM.arc(
24
+ xy: AIXM.xy(lat: %q(47°51'33"N), long: %q(007°33'36"E)),
25
+ center_xy: AIXM.xy(lat: %q(47°54'15"N), long: %q(007°33'48"E)),
19
26
  clockwise: true
20
27
  )
21
- geometry << AIXM::Horizontal::Border.new(
22
- xy: AIXM::XY.new(lat: %q(47°56'37"N), long: %q(007°35'45"E)),
28
+ geometry << AIXM.border(
29
+ xy: AIXM.xy(lat: %q(47°56'37"N), long: %q(007°35'45"E)),
23
30
  name: 'FRANCE_GERMANY'
24
31
  )
25
- geometry << AIXM::Horizontal::Point.new(
26
- xy: AIXM::XY.new(lat: %q(47°51'33"N), long: %q(007°33'36"E))
32
+ geometry << AIXM.point(
33
+ xy: AIXM.xy(lat: %q(47°51'33"N), long: %q(007°33'36"E))
27
34
  )
28
35
  end
29
36
  end
30
37
 
31
38
  def circle_geometry
32
- AIXM::Geometry.new.tap do |geometry|
33
- geometry << AIXM::Horizontal::Circle.new(
34
- center_xy: AIXM::XY.new(lat: %q(47°35'00"N), long: %q(004°53'00"E)),
39
+ AIXM.geometry.tap do |geometry|
40
+ geometry << AIXM.circle(
41
+ center_xy: AIXM.xy(lat: %q(47°35'00"N), long: %q(004°53'00"E)),
35
42
  radius: 10
36
43
  )
37
44
  end
38
45
  end
39
46
 
40
- def polygon_airspace
41
- AIXM::Feature::Airspace.new(name: 'POLYGON AIRSPACE', type: 'D').tap do |airspace|
42
- airspace.vertical_limits = vertical_limits
47
+ def polygon_airspace(short_name: 'POLYGON', schedule: :H24)
48
+ AIXM.airspace(
49
+ name: 'POLYGON AIRSPACE',
50
+ short_name: short_name,
51
+ type: 'D'
52
+ ).tap do |airspace|
53
+ airspace.schedule = AIXM.schedule(code: schedule) if schedule
54
+ airspace.class_layers << class_layer
43
55
  airspace.geometry = polygon_geometry
44
56
  airspace.remarks = 'polygon airspace'
45
57
  end
46
58
  end
47
59
 
48
- def circle_airspace
49
- AIXM::Feature::Airspace.new(name: 'CIRCLE AIRSPACE', type: 'D').tap do |airspace|
50
- airspace.vertical_limits = vertical_limits
60
+ def circle_airspace(short_name: 'CIRCLE', schedule: :H24)
61
+ AIXM.airspace(
62
+ name: 'CIRCLE AIRSPACE',
63
+ short_name: short_name,
64
+ type: 'D'
65
+ ).tap do |airspace|
66
+ airspace.schedule = AIXM.schedule(code: schedule) if schedule
67
+ airspace.class_layers << class_layer
51
68
  airspace.geometry = circle_geometry
52
69
  airspace.remarks = 'circle airspace'
53
70
  end
@@ -55,9 +72,9 @@ module AIXM
55
72
 
56
73
  def document
57
74
  time = Time.parse('2018-01-18 12:00:00 +0100')
58
- AIXM::Document.new(created_at: time, effective_at: time).tap do |document|
59
- document << AIXM::Factory.polygon_airspace
60
- document << AIXM::Factory.circle_airspace
75
+ AIXM.document(created_at: time, effective_at: time).tap do |document|
76
+ document.features << AIXM::Factory.polygon_airspace
77
+ document.features << AIXM::Factory.circle_airspace
61
78
  end
62
79
  end
63
80
 
@@ -0,0 +1,74 @@
1
+ require_relative '../../../spec_helper'
2
+
3
+ describe AIXM::Component::ClassLayer do
4
+ describe :initialize do
5
+ it "won't accept invalid arguments" do
6
+ -> { AIXM::Component::ClassLayer.new(class: 'X', vertical_limits: AIXM::Factory.vertical_limits ) }.must_raise ArgumentError
7
+ -> { AIXM::Component::ClassLayer.new(class: 'A', vertical_limits: 'foobar') }.must_raise ArgumentError
8
+ end
9
+ end
10
+
11
+ context "with class" do
12
+ subject do
13
+ AIXM::Component::ClassLayer.new(class: :C, vertical_limits: AIXM::Factory.vertical_limits)
14
+ end
15
+
16
+ describe :to_digest do
17
+ it "must return digest of payload" do
18
+ subject.to_digest.must_equal 612555203
19
+ end
20
+ end
21
+
22
+ describe :to_xml do
23
+ it "must build correct XML" do
24
+ subject.to_xml.must_equal <<~END
25
+ <codeClass>C</codeClass>
26
+ <codeDistVerUpper>STD</codeDistVerUpper>
27
+ <valDistVerUpper>65</valDistVerUpper>
28
+ <uomDistVerUpper>FL</uomDistVerUpper>
29
+ <codeDistVerLower>STD</codeDistVerLower>
30
+ <valDistVerLower>45</valDistVerLower>
31
+ <uomDistVerLower>FL</uomDistVerLower>
32
+ <codeDistVerMax>ALT</codeDistVerMax>
33
+ <valDistVerMax>6000</valDistVerMax>
34
+ <uomDistVerMax>FT</uomDistVerMax>
35
+ <codeDistVerMnm>HEI</codeDistVerMnm>
36
+ <valDistVerMnm>3000</valDistVerMnm>
37
+ <uomDistVerMnm>FT</uomDistVerMnm>
38
+ END
39
+ end
40
+ end
41
+ end
42
+
43
+ context "without class" do
44
+ subject do
45
+ AIXM::Component::ClassLayer.new(vertical_limits: AIXM::Factory.vertical_limits)
46
+ end
47
+
48
+ describe :to_digest do
49
+ it "must return digest of payload" do
50
+ subject.to_digest.must_equal 486148039
51
+ end
52
+ end
53
+
54
+ describe :to_xml do
55
+ it "must build correct XML" do
56
+ subject.to_xml.must_equal <<~END
57
+ <codeDistVerUpper>STD</codeDistVerUpper>
58
+ <valDistVerUpper>65</valDistVerUpper>
59
+ <uomDistVerUpper>FL</uomDistVerUpper>
60
+ <codeDistVerLower>STD</codeDistVerLower>
61
+ <valDistVerLower>45</valDistVerLower>
62
+ <uomDistVerLower>FL</uomDistVerLower>
63
+ <codeDistVerMax>ALT</codeDistVerMax>
64
+ <valDistVerMax>6000</valDistVerMax>
65
+ <uomDistVerMax>FT</uomDistVerMax>
66
+ <codeDistVerMnm>HEI</codeDistVerMnm>
67
+ <valDistVerMnm>3000</valDistVerMnm>
68
+ <uomDistVerMnm>FT</uomDistVerMnm>
69
+ END
70
+ end
71
+ end
72
+ end
73
+
74
+ end
@@ -1,37 +1,37 @@
1
- require_relative '../../../spec_helper'
1
+ require_relative '../../../../spec_helper'
2
2
 
3
- describe AIXM::Horizontal::Arc do
3
+ describe AIXM::Component::Geometry::Arc do
4
4
  describe :initialize do
5
5
  it "won't accept invalid arguments" do
6
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
7
+ -> { AIXM::Component::Geometry::Arc.new(xy: 0, center_xy: xy, clockwise: true) }.must_raise ArgumentError
8
+ -> { AIXM::Component::Geometry::Arc.new(xy: xy, center_xy: 0, clockwise: true) }.must_raise ArgumentError
9
+ -> { AIXM::Component::Geometry::Arc.new(xy: xy, center_xy: xy, clockwise: 0) }.must_raise ArgumentError
10
10
  end
11
11
  end
12
12
 
13
13
  describe :clockwise? do
14
14
  it "must return true or false" do
15
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?
16
+ AIXM::Component::Geometry::Arc.new(xy: xy, center_xy: xy, clockwise: true).must_be :clockwise?
17
+ AIXM::Component::Geometry::Arc.new(xy: xy, center_xy: xy, clockwise: false).wont_be :clockwise?
18
18
  end
19
19
  end
20
20
 
21
21
  describe :to_digest do
22
22
  it "must return digest of payload" do
23
- subject = AIXM::Horizontal::Arc.new(
23
+ subject = AIXM::Component::Geometry::Arc.new(
24
24
  xy: AIXM::XY.new(lat: 11.1, long: 33.3),
25
25
  center_xy: AIXM::XY.new(lat: 22.2, long: 33.3),
26
26
  clockwise: true
27
27
  )
28
- subject.to_digest.must_equal '35B2E1AF'
28
+ subject.to_digest.must_equal 712900173
29
29
  end
30
30
  end
31
31
 
32
32
  describe :to_xml do
33
33
  it "must build correct XML for clockwise arcs" do
34
- subject = AIXM::Horizontal::Arc.new(
34
+ subject = AIXM::Component::Geometry::Arc.new(
35
35
  xy: AIXM::XY.new(lat: 11.1, long: 33.3),
36
36
  center_xy: AIXM::XY.new(lat: 22.2, long: 33.3),
37
37
  clockwise: true
@@ -49,7 +49,7 @@ describe AIXM::Horizontal::Arc do
49
49
  end
50
50
 
51
51
  it "must build correct XML for counter-clockwise arcs" do
52
- subject = AIXM::Horizontal::Arc.new(
52
+ subject = AIXM::Component::Geometry::Arc.new(
53
53
  xy: AIXM::XY.new(lat: 11.1, long: 33.3),
54
54
  center_xy: AIXM::XY.new(lat: 22.2, long: 33.3),
55
55
  clockwise: false
@@ -0,0 +1,30 @@
1
+ require_relative '../../../../spec_helper'
2
+
3
+ describe AIXM::Component::Geometry::Border do
4
+ describe :to_digest do
5
+ it "must return digest of payload" do
6
+ subject = AIXM::Component::Geometry::Border.new(
7
+ xy: AIXM::XY.new(lat: 11.1, long: 22.2),
8
+ name: 'foobar'
9
+ )
10
+ subject.to_digest.must_equal 813052011
11
+ end
12
+ end
13
+
14
+ describe :to_xml do
15
+ it "must build correct XML" do
16
+ subject = AIXM::Component::Geometry::Border.new(
17
+ xy: AIXM::XY.new(lat: 11.1, long: 22.2),
18
+ name: 'foobar'
19
+ )
20
+ subject.to_xml.must_equal <<~END
21
+ <Avx>
22
+ <codeType>FNT</codeType>
23
+ <geoLat>110600.00N</geoLat>
24
+ <geoLong>0221200.00E</geoLong>
25
+ <codeDatum>WGE</codeDatum>
26
+ </Avx>
27
+ END
28
+ end
29
+ end
30
+ end
@@ -1,15 +1,15 @@
1
- require_relative '../../../spec_helper'
1
+ require_relative '../../../../spec_helper'
2
2
 
3
- describe AIXM::Horizontal::Circle do
3
+ describe AIXM::Component::Geometry::Circle do
4
4
  describe :initialize do
5
5
  it "won't accept invalid arguments" do
6
- -> { AIXM::Horizontal::Circle.new(center_xy: 0, radius: 0) }.must_raise ArgumentError
6
+ -> { AIXM::Component::Geometry::Circle.new(center_xy: 0, radius: 0) }.must_raise ArgumentError
7
7
  end
8
8
  end
9
9
 
10
10
  describe :north_xy do
11
11
  it "must calculate approximation of northmost point on the circumference" do
12
- subject = AIXM::Horizontal::Circle.new(
12
+ subject = AIXM::Component::Geometry::Circle.new(
13
13
  center_xy: AIXM::XY.new(lat: 12.12345678, long: -23.12345678),
14
14
  radius: 15
15
15
  )
@@ -19,17 +19,17 @@ describe AIXM::Horizontal::Circle do
19
19
 
20
20
  describe :to_digest do
21
21
  it "must return digest of payload" do
22
- subject = AIXM::Horizontal::Circle.new(
22
+ subject = AIXM::Component::Geometry::Circle.new(
23
23
  center_xy: AIXM::XY.new(lat: 12.12345678, long: -23.12345678),
24
24
  radius: 15
25
25
  )
26
- subject.to_digest.must_equal '914C5F08'
26
+ subject.to_digest.must_equal 386055945
27
27
  end
28
28
  end
29
29
 
30
30
  describe :to_xml do
31
31
  it "must build correct XML for circles not near the equator" do
32
- subject = AIXM::Horizontal::Circle.new(
32
+ subject = AIXM::Component::Geometry::Circle.new(
33
33
  center_xy: AIXM::XY.new(lat: 11.1, long: 22.2),
34
34
  radius: 25
35
35
  )
@@ -46,7 +46,7 @@ describe AIXM::Horizontal::Circle do
46
46
  end
47
47
 
48
48
  it "must build correct XML for circles near the equator" do
49
- subject = AIXM::Horizontal::Circle.new(
49
+ subject = AIXM::Component::Geometry::Circle.new(
50
50
  center_xy: AIXM::XY.new(lat: -0.0005, long: -22.2),
51
51
  radius: 50
52
52
  )
@@ -1,22 +1,22 @@
1
- require_relative '../../../spec_helper'
1
+ require_relative '../../../../spec_helper'
2
2
 
3
- describe AIXM::Horizontal::Point do
3
+ describe AIXM::Component::Geometry::Point do
4
4
  describe :initialize do
5
5
  it "won't accept invalid arguments" do
6
- -> { AIXM::Horizontal::Point.new(xy: 0) }.must_raise ArgumentError
6
+ -> { AIXM::Component::Geometry::Point.new(xy: 0) }.must_raise ArgumentError
7
7
  end
8
8
  end
9
9
 
10
10
  describe :to_digest do
11
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'
12
+ subject = AIXM::Component::Geometry::Point.new(xy: AIXM::XY.new(lat: 11.1, long: 22.2))
13
+ subject.to_digest.must_equal 167706171
14
14
  end
15
15
  end
16
16
 
17
17
  describe :to_xml do
18
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))
19
+ subject = AIXM::Component::Geometry::Point.new(xy: AIXM::XY.new(lat: 11.1, long: 22.2))
20
20
  subject.to_xml.must_equal <<~END
21
21
  <Avx>
22
22
  <codeType>GRC</codeType>
@@ -28,7 +28,7 @@ describe AIXM::Horizontal::Point do
28
28
  end
29
29
 
30
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))
31
+ subject = AIXM::Component::Geometry::Point.new(xy: AIXM::XY.new(lat: -11.1, long: -22.2))
32
32
  subject.to_xml.must_equal <<~END
33
33
  <Avx>
34
34
  <codeType>GRC</codeType>