aixm 0.1.0 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +29 -1
- data/Guardfile +1 -1
- data/README.md +146 -17
- data/aixm.gemspec +3 -1
- data/lib/aixm.rb +12 -10
- data/lib/aixm/component/base.rb +6 -0
- data/lib/aixm/component/class_layer.rb +49 -0
- data/lib/aixm/component/geometry.rb +73 -0
- data/lib/aixm/component/geometry/arc.rb +53 -0
- data/lib/aixm/component/geometry/border.rb +49 -0
- data/lib/aixm/component/geometry/circle.rb +56 -0
- data/lib/aixm/component/geometry/point.rb +42 -0
- data/lib/aixm/component/schedule.rb +45 -0
- data/lib/aixm/{vertical/limits.rb → component/vertical_limits.rb} +9 -14
- data/lib/aixm/document.rb +30 -19
- data/lib/aixm/feature/airspace.rb +60 -29
- data/lib/aixm/refinements.rb +49 -2
- data/lib/aixm/shortcuts.rb +30 -0
- data/lib/aixm/version.rb +1 -1
- data/spec/factory.rb +42 -25
- data/spec/lib/aixm/component/class_layer_spec.rb +74 -0
- data/spec/lib/aixm/{horizontal → component/geometry}/arc_spec.rb +11 -11
- data/spec/lib/aixm/component/geometry/border_spec.rb +30 -0
- data/spec/lib/aixm/{horizontal → component/geometry}/circle_spec.rb +8 -8
- data/spec/lib/aixm/{horizontal → component/geometry}/point_spec.rb +7 -7
- data/spec/lib/aixm/{geometry_spec.rb → component/geometry_spec.rb} +39 -40
- data/spec/lib/aixm/component/schedule_spec.rb +33 -0
- data/spec/lib/aixm/{vertical/limits_spec.rb → component/vertical_limits_spec.rb} +10 -10
- data/spec/lib/aixm/document_spec.rb +97 -36
- data/spec/lib/aixm/feature/airspace_spec.rb +230 -71
- data/spec/lib/aixm/refinements_spec.rb +52 -12
- metadata +30 -23
- data/lib/aixm/constants.rb +0 -6
- data/lib/aixm/geometry.rb +0 -71
- data/lib/aixm/horizontal/arc.rb +0 -50
- data/lib/aixm/horizontal/border.rb +0 -45
- data/lib/aixm/horizontal/circle.rb +0 -53
- data/lib/aixm/horizontal/point.rb +0 -39
- data/spec/lib/aixm/horizontal/border_spec.rb +0 -47
@@ -0,0 +1,53 @@
|
|
1
|
+
module AIXM
|
2
|
+
module Component
|
3
|
+
class Geometry
|
4
|
+
|
5
|
+
##
|
6
|
+
# Arcs are +clockwise+ (true/false) circle sectors around +center_xy+ and
|
7
|
+
# starting at +xy+.
|
8
|
+
class Arc < Point
|
9
|
+
using AIXM::Refinements
|
10
|
+
|
11
|
+
attr_reader :center_xy
|
12
|
+
|
13
|
+
def initialize(xy:, center_xy:, clockwise:)
|
14
|
+
super(xy: xy)
|
15
|
+
fail(ArgumentError, "invalid center xy") unless center_xy.is_a? AIXM::XY
|
16
|
+
fail(ArgumentError, "clockwise must be true or false") unless [true, false].include? clockwise
|
17
|
+
@center_xy, @clockwise = center_xy, clockwise
|
18
|
+
end
|
19
|
+
|
20
|
+
##
|
21
|
+
# Whether the arc is going clockwise (true) or not (false)
|
22
|
+
def clockwise?
|
23
|
+
@clockwise
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# Digest to identify the payload
|
28
|
+
def to_digest
|
29
|
+
[xy.lat, xy.long, center_xy.lat, center_xy.long, clockwise?].to_digest
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Render AIXM
|
34
|
+
#
|
35
|
+
# Extensions:
|
36
|
+
# * +:OFM+ - Open Flightmaps
|
37
|
+
def to_xml(*extensions)
|
38
|
+
format = extensions >> :OFM ? :OFM : :AIXM
|
39
|
+
builder = Builder::XmlMarkup.new(indent: 2)
|
40
|
+
builder.Avx do |avx|
|
41
|
+
avx.codeType(clockwise? ? 'CWA' : 'CCA')
|
42
|
+
avx.geoLat(xy.lat(format))
|
43
|
+
avx.geoLong(xy.long(format))
|
44
|
+
avx.codeDatum('WGE')
|
45
|
+
avx.geoLatArc(center_xy.lat(format))
|
46
|
+
avx.geoLongArc(center_xy.long(format))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module AIXM
|
2
|
+
module Component
|
3
|
+
class Geometry
|
4
|
+
|
5
|
+
##
|
6
|
+
# Borders are following natural or artifical border lines referenced by
|
7
|
+
# +name+ and starting at +xy+.
|
8
|
+
class Border < Point
|
9
|
+
using AIXM::Refinements
|
10
|
+
|
11
|
+
attr_reader :name
|
12
|
+
|
13
|
+
def initialize(xy:, name:)
|
14
|
+
super(xy: xy)
|
15
|
+
@name = name
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# Digest to identify the payload
|
20
|
+
def to_digest
|
21
|
+
[xy.lat, xy.long, name].to_digest
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
# Render AIXM
|
26
|
+
#
|
27
|
+
# Extensions:
|
28
|
+
# * +:OFM+ - Open Flightmaps
|
29
|
+
def to_xml(*extensions)
|
30
|
+
format = extensions >> :OFM ? :OFM : :AIXM
|
31
|
+
builder = Builder::XmlMarkup.new(indent: 2)
|
32
|
+
builder.Avx do |avx|
|
33
|
+
avx.codeType('FNT')
|
34
|
+
avx.geoLat(xy.lat(format))
|
35
|
+
avx.geoLong(xy.long(format))
|
36
|
+
avx.codeDatum('WGE')
|
37
|
+
# TODO: Find examples how to do this with vanilla AIXM
|
38
|
+
if extensions >> :OFM
|
39
|
+
avx.GbrUid do |gbruid|
|
40
|
+
gbruid.txtName(name.to_s)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module AIXM
|
2
|
+
module Component
|
3
|
+
class Geometry
|
4
|
+
|
5
|
+
##
|
6
|
+
# Circles are defined by a +center_xy+ and a +radius+ in kilometers.
|
7
|
+
class Circle
|
8
|
+
using AIXM::Refinements
|
9
|
+
|
10
|
+
attr_reader :center_xy, :radius
|
11
|
+
|
12
|
+
def initialize(center_xy:, radius:)
|
13
|
+
fail(ArgumentError, "invalid center xy") unless center_xy.is_a? AIXM::XY
|
14
|
+
@center_xy, @radius = center_xy, radius
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# Digest to identify the payload
|
19
|
+
def to_digest
|
20
|
+
[center_xy.lat, center_xy.long, radius].to_digest
|
21
|
+
end
|
22
|
+
|
23
|
+
##
|
24
|
+
# Render AIXM
|
25
|
+
#
|
26
|
+
# Extensions:
|
27
|
+
# * +:OFM+ - Open Flightmaps
|
28
|
+
def to_xml(*extensions)
|
29
|
+
format = extensions >> :OFM ? :OFM : :AIXM
|
30
|
+
builder = Builder::XmlMarkup.new(indent: 2)
|
31
|
+
builder.Avx do |avx|
|
32
|
+
avx.codeType('CWA')
|
33
|
+
avx.geoLat(north_xy.lat(format))
|
34
|
+
avx.geoLong(north_xy.long(format))
|
35
|
+
avx.codeDatum('WGE')
|
36
|
+
avx.geoLatArc(center_xy.lat(format))
|
37
|
+
avx.geoLongArc(center_xy.long(format))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
##
|
44
|
+
# Coordinates of the point which is both strictly north of the center
|
45
|
+
# and on the circumference of the circle
|
46
|
+
def north_xy
|
47
|
+
AIXM.xy(
|
48
|
+
lat: center_xy.lat + radius.to_f / 6371 * 180 / Math::PI,
|
49
|
+
long: center_xy.long
|
50
|
+
)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module AIXM
|
2
|
+
module Component
|
3
|
+
class Geometry
|
4
|
+
|
5
|
+
##
|
6
|
+
# Points are defined by +xy+ coordinates.
|
7
|
+
class Point
|
8
|
+
using AIXM::Refinements
|
9
|
+
|
10
|
+
attr_reader :xy
|
11
|
+
|
12
|
+
def initialize(xy:)
|
13
|
+
fail(ArgumentError, "invalid xy") unless xy.is_a? AIXM::XY
|
14
|
+
@xy = xy
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# Digest to identify the payload
|
19
|
+
def to_digest
|
20
|
+
[xy.lat, xy.long].to_digest
|
21
|
+
end
|
22
|
+
|
23
|
+
##
|
24
|
+
# Render AIXM
|
25
|
+
#
|
26
|
+
# Extensions:
|
27
|
+
# * +:OFM+ - Open Flightmaps
|
28
|
+
def to_xml(*extensions)
|
29
|
+
format = extensions >> :OFM ? :OFM : :AIXM
|
30
|
+
builder = Builder::XmlMarkup.new(indent: 2)
|
31
|
+
builder.Avx do |avx|
|
32
|
+
avx.codeType('GRC')
|
33
|
+
avx.geoLat(xy.lat(format))
|
34
|
+
avx.geoLong(xy.long(format))
|
35
|
+
avx.codeDatum('WGE')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module AIXM
|
2
|
+
module Component
|
3
|
+
|
4
|
+
##
|
5
|
+
# Schedules define activity time windows. As of now, only predefined
|
6
|
+
# schedules are imlemented either by use of explicit (e.g. +:continuous+)
|
7
|
+
# or short codes (e.g. +:H24+) as listed by the +CODES+ constant.
|
8
|
+
#
|
9
|
+
# Shortcuts:
|
10
|
+
# * +AIXM::H24+ - continuous 24/7
|
11
|
+
class Schedule < Base
|
12
|
+
using AIXM::Refinements
|
13
|
+
|
14
|
+
CODES = {
|
15
|
+
continuous: :H24,
|
16
|
+
sunrise_to_sunset: :HJ,
|
17
|
+
sunset_to_sunrise: :HN,
|
18
|
+
unspecified: :HX,
|
19
|
+
operational_request: :HO,
|
20
|
+
notam: :NOTAM
|
21
|
+
}.freeze
|
22
|
+
|
23
|
+
attr_reader :code
|
24
|
+
|
25
|
+
def initialize(code:)
|
26
|
+
@code = code&.to_sym
|
27
|
+
@code = CODES[code] unless CODES.has_value? code
|
28
|
+
fail(ArgumentError, "code `#{code}' not recognized") unless @code
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Digest to identify the payload
|
33
|
+
def to_digest
|
34
|
+
[code].to_digest
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Render AIXM
|
39
|
+
def to_xml(*extensions)
|
40
|
+
Builder::XmlMarkup.new(indent: 2).codeWorkHr(code.to_s)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -1,18 +1,18 @@
|
|
1
1
|
module AIXM
|
2
|
-
module
|
2
|
+
module Component
|
3
3
|
|
4
4
|
##
|
5
|
-
# Vertical limits
|
6
|
-
#
|
7
|
-
# Normally noted as:
|
5
|
+
# Vertical limits define a 3D airspace vertically. They are normally noted
|
6
|
+
# as follows:
|
8
7
|
#
|
9
8
|
# +upper z+ (or +max_z+ whichever is higher)
|
10
9
|
# ---------
|
11
10
|
# +lower_z+ (or +min_z+ whichever is lower)
|
12
11
|
#
|
13
|
-
#
|
14
|
-
|
15
|
-
|
12
|
+
# Shortcuts:
|
13
|
+
# * +AIXM::GROUND+ - surface (aka: 0ft QFE)
|
14
|
+
# * +AIXM::UNLIMITED+ - no upper limit (aka: FL 999)
|
15
|
+
class VerticalLimits < Base
|
16
16
|
using AIXM::Refinements
|
17
17
|
|
18
18
|
TAGS = { upper: :Upper, lower: :Lower, max: :Max, min: :Mnm }.freeze
|
@@ -20,13 +20,7 @@ module AIXM
|
|
20
20
|
|
21
21
|
attr_reader :upper_z, :lower_z, :max_z, :min_z
|
22
22
|
|
23
|
-
|
24
|
-
# Defines vertical limits +upper_z+ and +lower_z+
|
25
|
-
#
|
26
|
-
# Options:
|
27
|
-
# * +max_z+ - alternative upper limit "whichever is higher"
|
28
|
-
# * +min_z+ - alternative lower limit "whichever is lower"
|
29
|
-
def initialize(upper_z:, lower_z:, max_z: nil, min_z: nil)
|
23
|
+
def initialize(max_z: nil, upper_z:, lower_z:, min_z: nil)
|
30
24
|
fail(ArgumentError, "invalid upper_z") unless upper_z.is_a? AIXM::Z
|
31
25
|
fail(ArgumentError, "invalid lower_z") unless lower_z.is_a? AIXM::Z
|
32
26
|
fail(ArgumentError, "invalid max_z") unless max_z.nil? || max_z.is_a?(AIXM::Z)
|
@@ -55,5 +49,6 @@ module AIXM
|
|
55
49
|
end.target! # see https://github.com/jimweirich/builder/issues/42
|
56
50
|
end
|
57
51
|
end
|
52
|
+
|
58
53
|
end
|
59
54
|
end
|
data/lib/aixm/document.rb
CHANGED
@@ -1,29 +1,32 @@
|
|
1
1
|
module AIXM
|
2
2
|
class Document
|
3
3
|
|
4
|
-
include Enumerable
|
5
|
-
extend Forwardable
|
6
4
|
using AIXM::Refinements
|
7
5
|
|
8
|
-
def_delegators :@result_array, :each, :<<
|
9
|
-
|
10
6
|
attr_reader :created_at, :effective_at
|
7
|
+
attr_accessor :features
|
11
8
|
|
12
9
|
##
|
13
10
|
# Define a AIXM-Snapshot document
|
14
11
|
#
|
15
12
|
# Options:
|
16
|
-
# * +created_at+ - creation date (default: now)
|
17
|
-
# * +effective_at+ - snapshot effective after date (default: now)
|
13
|
+
# * +created_at+ - creation date and time (default: now)
|
14
|
+
# * +effective_at+ - snapshot effective after date and time (default: now)
|
18
15
|
def initialize(created_at: nil, effective_at: nil)
|
19
|
-
@created_at, @effective_at = created_at, effective_at
|
20
|
-
@
|
16
|
+
@created_at, @effective_at = parse_time(created_at), parse_time(effective_at)
|
17
|
+
@features = []
|
21
18
|
end
|
22
19
|
|
23
20
|
##
|
24
|
-
#
|
25
|
-
def
|
26
|
-
|
21
|
+
# Check whether the document is complete (extensions excluded)
|
22
|
+
def complete?
|
23
|
+
features.any? && features.none? { |f| !f.complete? }
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# Validate atainst the XSD and return +true+ if no errors were found
|
28
|
+
def valid?
|
29
|
+
errors.none?
|
27
30
|
end
|
28
31
|
|
29
32
|
##
|
@@ -33,12 +36,6 @@ module AIXM
|
|
33
36
|
xsd.validate(Nokogiri::XML(to_xml))
|
34
37
|
end
|
35
38
|
|
36
|
-
##
|
37
|
-
# Check whether the document is valid (extensions excluded)
|
38
|
-
def valid?
|
39
|
-
any? && reduce(true) { |b, f| b && f.valid? } && errors.none?
|
40
|
-
end
|
41
|
-
|
42
39
|
##
|
43
40
|
# Render AIXM
|
44
41
|
#
|
@@ -53,12 +50,26 @@ module AIXM
|
|
53
50
|
created: @created_at&.xmlschema || now,
|
54
51
|
effective: @effective_at&.xmlschema || now
|
55
52
|
}
|
56
|
-
meta[:version] += ' + OFM extensions of version 0.1' if extensions
|
53
|
+
meta[:version] += ' + OFM extensions of version 0.1' if extensions >> :OFM
|
57
54
|
builder = Builder::XmlMarkup.new(indent: 2)
|
58
55
|
builder.instruct!
|
59
56
|
builder.tag!('AIXM-Snapshot', meta) do |aixm_snapshot|
|
60
|
-
aixm_snapshot <<
|
57
|
+
aixm_snapshot << features.map { |f| f.to_xml(*extensions) }.join.indent(2)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def parse_time(value)
|
64
|
+
case value
|
65
|
+
when String then Time.parse(value)
|
66
|
+
when Date then value.to_time
|
67
|
+
when Time then value
|
68
|
+
when nil then nil
|
69
|
+
else fail ArgumentError
|
61
70
|
end
|
71
|
+
rescue ArgumentError
|
72
|
+
raise(ArgumentError, "`#{value}' is not a valid date and time")
|
62
73
|
end
|
63
74
|
|
64
75
|
end
|
@@ -4,38 +4,43 @@ module AIXM
|
|
4
4
|
|
5
5
|
using AIXM::Refinements
|
6
6
|
|
7
|
-
attr_reader :name, :type
|
8
|
-
attr_reader :
|
9
|
-
attr_accessor :geometry, :remarks
|
7
|
+
attr_reader :name, :short_name, :type
|
8
|
+
attr_reader :schedule
|
9
|
+
attr_accessor :geometry, :class_layers, :remarks
|
10
10
|
|
11
11
|
##
|
12
12
|
# Airspace feature
|
13
13
|
#
|
14
14
|
# Options:
|
15
|
-
# * +name+ - name of the airspace (will be converted to uppercase
|
15
|
+
# * +name+ - full name of the airspace (will be converted to uppercase,
|
16
|
+
# e.g. +LF P 81+)
|
17
|
+
# * +short_name+ - short name of the airspace (will be converted to
|
18
|
+
# uppercase, e.g. +LF P 81 CHERBOURG+)
|
16
19
|
# * +type+ - airspace type (e.g. +TMA+ or +P+)
|
17
|
-
def initialize(name:, type:)
|
18
|
-
@
|
19
|
-
@
|
20
|
+
def initialize(name:, short_name: nil, type:)
|
21
|
+
@name, @short_name, @type = name.uptrans, short_name&.uptrans, type
|
22
|
+
@schedule = nil
|
23
|
+
@geometry = AIXM.geometry
|
24
|
+
@class_layers = []
|
20
25
|
end
|
21
26
|
|
22
27
|
##
|
23
|
-
# Assign a +
|
24
|
-
def
|
25
|
-
fail(ArgumentError, "invalid
|
26
|
-
@
|
28
|
+
# Assign a +Schedule+ object or +nil+
|
29
|
+
def schedule=(value)
|
30
|
+
fail(ArgumentError, "invalid schedule") unless value.nil? || value.is_a?(AIXM::Component::Schedule)
|
31
|
+
@schedule = value
|
27
32
|
end
|
28
33
|
|
29
34
|
##
|
30
|
-
# Check whether the airspace is
|
31
|
-
def
|
32
|
-
name && type &&
|
35
|
+
# Check whether the airspace is complete
|
36
|
+
def complete?
|
37
|
+
!!name && !!type && class_layers.any? && geometry.complete?
|
33
38
|
end
|
34
39
|
|
35
40
|
##
|
36
41
|
# Digest to identify the payload
|
37
42
|
def to_digest
|
38
|
-
[name, type,
|
43
|
+
[name, short_name, type, schedule&.to_digest, class_layers.map(&:to_digest), geometry.to_digest, remarks].to_digest
|
39
44
|
end
|
40
45
|
|
41
46
|
##
|
@@ -46,28 +51,54 @@ module AIXM
|
|
46
51
|
def to_xml(*extensions)
|
47
52
|
mid = to_digest
|
48
53
|
builder = Builder::XmlMarkup.new(indent: 2)
|
49
|
-
builder.
|
50
|
-
|
51
|
-
|
52
|
-
aseuid.
|
54
|
+
builder.comment! "Airspace: [#{type}] #{name}"
|
55
|
+
builder.Ase({ xt_classLayersAvail: ((class_layers.count > 1) if extensions >> :OFM) }.compact) do |ase|
|
56
|
+
ase.AseUid({ mid: mid, newEntity: (true if extensions >> :OFM) }.compact) do |aseuid|
|
57
|
+
aseuid.codeType(type.to_s)
|
58
|
+
aseuid.codeId(mid)
|
53
59
|
end
|
54
|
-
ase.
|
55
|
-
ase
|
56
|
-
ase.
|
57
|
-
if
|
58
|
-
ase.
|
59
|
-
|
60
|
+
ase.txtLocalType(short_name.to_s) if short_name && short_name != name
|
61
|
+
ase.txtName(name.to_s)
|
62
|
+
ase << class_layers.first.to_xml(*extensions).indent(2)
|
63
|
+
if schedule
|
64
|
+
ase.Att do |att|
|
65
|
+
att << schedule.to_xml(*extensions).indent(4)
|
66
|
+
end
|
60
67
|
end
|
68
|
+
ase.txtRmk(remarks.to_s) if remarks
|
69
|
+
ase.xt_selAvail(false) if extensions >> :OFM
|
61
70
|
end
|
62
71
|
builder.Abd do |abd|
|
63
72
|
abd.AbdUid do |abduid|
|
64
|
-
abduid.AseUid(
|
65
|
-
aseuid.codeType(type)
|
66
|
-
aseuid.codeId(mid)
|
73
|
+
abduid.AseUid({ mid: mid, newEntity: (true if extensions >> :OFM) }.compact) do |aseuid|
|
74
|
+
aseuid.codeType(type.to_s)
|
75
|
+
aseuid.codeId(mid)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
abd << geometry.to_xml(*extensions).indent(2)
|
79
|
+
end
|
80
|
+
if class_layers.count > 1
|
81
|
+
builder.Adg do |adg|
|
82
|
+
class_layers.each.with_index do |class_layer, index|
|
83
|
+
adg.AdgUid do |adguid|
|
84
|
+
adguid.AseUid(mid: "#{mid}.#{index + 1}") do |aseuid|
|
85
|
+
aseuid.codeType("CLASS")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
adg.AseUidSameExtent(mid: mid)
|
90
|
+
end
|
91
|
+
class_layers.each.with_index do |class_layer, index|
|
92
|
+
builder.Ase do |ase|
|
93
|
+
ase.AseUid(mid: "#{mid}.#{index + 1}") do |aseuid|
|
94
|
+
aseuid.codeType("CLASS")
|
95
|
+
end
|
96
|
+
ase.txtName(name.to_s)
|
97
|
+
ase << class_layers[index].to_xml(*extensions).indent(2)
|
67
98
|
end
|
68
99
|
end
|
69
|
-
abd << geometry.to_xml(extensions).indent(2)
|
70
100
|
end
|
101
|
+
builder.target! # see https://github.com/jimweirich/builder/issues/42
|
71
102
|
end
|
72
103
|
end
|
73
104
|
end
|