aixm 0.1.0 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
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>