amanzi-sld 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # This script uses the amanzi-sld DSL to create a sample SLD file
4
+ # containing a few examples. The examples are done in a few different
5
+ # ways to demonstrate the SLD syntax.
6
+
7
+ require 'amanzi/sld'
8
+
9
+ Amanzi::SLD::Config.config[:geometry_property] = 'the_geom'
10
+ #Amanzi::SLD::Config.config[:verbose] = true
11
+
12
+ sld = Amanzi::SLD::Document.new "Example"
13
+
14
+ # Create XML using the underlying pure-XML DSL from Amanzi::XML
15
+ sld.comment "Pure-XML DSL"
16
+ sld.feature_type_style.rule.line_symbolizer.stroke do |stroke|
17
+ stroke.css_parameter(:name => "stroke") << '#dddddd'
18
+ stroke.css_parameter(:name => "stroke-width") << '1'
19
+ end
20
+
21
+ # Now create some SLD XML using the higher level SLD-specific syntax
22
+
23
+ # First a simple example, a LineSymbolizer with no filter
24
+ sld.comment "Simple Line DSL"
25
+ sld.add_line_symbolizer(:stroke_width => 2, :stroke => '#dddddd')
26
+
27
+ # Now try one with a filter, but specified using XML DSL
28
+ sld.comment "Line with Filter, using XML DSL"
29
+ sld.feature_type_style.rule do |rule|
30
+ rule.filter.ns(:ogc).property_is_equal_to do |f|
31
+ f.function(:name => 'geometryType').property_name << 'the_geom'
32
+ f.literal << 'Polygon'
33
+ end
34
+ rule.polygon_symbolizer do |s|
35
+ s.fill do |f|
36
+ f.css_parameter(:name=>"fill") << '#aaaaaa'
37
+ f.css_parameter(:name=>"fill-opacity") << '0.4'
38
+ end
39
+ s.stroke.css_parameter(:name => "stroke") << '#ffe0e0'
40
+ end
41
+ end
42
+
43
+ # And now see how simple these can be with the SLD syntax
44
+ sld.comment "Line with Filter, using SLD DSL"
45
+ sld.add_line_symbolizer(:stroke_width => 2, :stroke => '#dddddd') do |f|
46
+ f.geometry = 'LineString'
47
+ end
48
+ sld.comment "Polygon with Filter, using SLD DSL"
49
+ sld.add_polygon_symbolizer(:stroke_width => 2, :stroke => '#dddddd', :fill => '#aaaaaa', :fill_opacity => 0.4) do |f|
50
+ f.geometry = 'Polygon'
51
+ end
52
+ sld.comment "Polygon with Filter, using SLD DSL, and more compact polygon filtering syntax"
53
+ sld.add_polygon_symbolizer(:stroke_width => 2, :stroke => '#dddddd', :fill => '#aaaaaa', :fill_opacity => 0.4, :geometry => 'Polygon')
54
+
55
+ # Using SLD syntax for Filtering makes it simler to do really complex things
56
+ sld.comment "Polygon with complex Filter"
57
+ sld.add_polygon_symbolizer(:stroke_width => 2, :stroke => '#dddddd', :fill => '#aaaaaa', :fill_opacity => 0.4) do |f|
58
+ f.op(:and) do |f|
59
+ f.geometry = 'Polygon'
60
+ f.op(:or) do |f|
61
+ f.property.not_exists? :highway
62
+ f.property.exists? :waterway
63
+ f.property[:natural] = 'water'
64
+ end
65
+ end
66
+ end
67
+
68
+ # Combining the compact geometry option with other filter settings
69
+ sld.comment "Polygon with combined simple and complex Filter"
70
+ sld.add_polygon_symbolizer(:stroke_width => 2, :stroke => '#dddddd', :fill => '#aaaaaa', :fill_opacity => 0.4, :geometry => 'Polygon') do |f|
71
+ f.op(:or) do |f|
72
+ f.property.not_exists? :highway
73
+ f.property.exists? :waterway
74
+ f.property[:natural] = 'water'
75
+ end
76
+ end
77
+
78
+ # Adding a set of symbolizers within the same rules filter
79
+ sld.comment "Multiple symbolizers and complex filter"
80
+ sld.
81
+ add_line_symbolizer(:stroke_width => 7, :stroke => '#303030').
82
+ add_line_symbolizer(:stroke_width => 5, :stroke => '#e0e0ff') do |f|
83
+ f.op(:and) do |f|
84
+ f.property.exists? :highway
85
+ f.op(:or) do |f|
86
+ f.property[:highway] = 'secondary'
87
+ f.property[:highway] = 'tertiary'
88
+ end
89
+ end
90
+ end
91
+
92
+ puts sld.to_xml(:tab => ' ')
93
+
94
+ #HTML::Document.test
95
+
@@ -0,0 +1,232 @@
1
+ <?xml version="1.0" encoding="ISO-8859-1"?>
2
+ <StyledLayerDescriptor xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd" xmlns:ogc="http://www.opengis.net/ogc" version="1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.opengis.net/sld">
3
+ <NamedLayer>
4
+ <Name>Example</Name>
5
+ <UserStyle>
6
+ <Name>Example</Name>
7
+ <!-- Pure-XML DSL -->
8
+ <FeatureTypeStyle>
9
+ <Rule>
10
+ <LineSymbolizer>
11
+ <Stroke>
12
+ <CssParameter name="stroke">#dddddd</CssParameter>
13
+ <CssParameter name="stroke-width">1</CssParameter>
14
+ </Stroke>
15
+ </LineSymbolizer>
16
+ </Rule>
17
+ </FeatureTypeStyle>
18
+ <!-- Simple Line DSL -->
19
+ <FeatureTypeStyle>
20
+ <Rule>
21
+ <LineSymbolizer>
22
+ <Stroke>
23
+ <CssParameter name="stroke-width">2</CssParameter>
24
+ <CssParameter name="stroke">#dddddd</CssParameter>
25
+ </Stroke>
26
+ </LineSymbolizer>
27
+ </Rule>
28
+ </FeatureTypeStyle>
29
+ <!-- Line with Filter, using XML DSL -->
30
+ <FeatureTypeStyle>
31
+ <Rule>
32
+ <ogc:Filter>
33
+ <ogc:PropertyIsEqualTo>
34
+ <ogc:Function name="geometryType">
35
+ <ogc:PropertyName>the_geom</ogc:PropertyName>
36
+ </ogc:Function>
37
+ <ogc:Literal>Polygon</ogc:Literal>
38
+ </ogc:PropertyIsEqualTo>
39
+ </ogc:Filter>
40
+ <PolygonSymbolizer>
41
+ <Fill>
42
+ <CssParameter name="fill">#aaaaaa</CssParameter>
43
+ <CssParameter name="fill-opacity">0.4</CssParameter>
44
+ </Fill>
45
+ <Stroke>
46
+ <CssParameter name="stroke">#ffe0e0</CssParameter>
47
+ </Stroke>
48
+ </PolygonSymbolizer>
49
+ </Rule>
50
+ </FeatureTypeStyle>
51
+ <!-- Line with Filter, using SLD DSL -->
52
+ <FeatureTypeStyle>
53
+ <Rule>
54
+ <ogc:Filter>
55
+ <ogc:PropertyIsEqualTo>
56
+ <ogc:Function name="geometryType">
57
+ <ogc:PropertyName>the_geom</ogc:PropertyName>
58
+ </ogc:Function>
59
+ <ogc:Literal>LineString</ogc:Literal>
60
+ </ogc:PropertyIsEqualTo>
61
+ </ogc:Filter>
62
+ <LineSymbolizer>
63
+ <Stroke>
64
+ <CssParameter name="stroke-width">2</CssParameter>
65
+ <CssParameter name="stroke">#dddddd</CssParameter>
66
+ </Stroke>
67
+ </LineSymbolizer>
68
+ </Rule>
69
+ </FeatureTypeStyle>
70
+ <!-- Polygon with Filter, using SLD DSL -->
71
+ <FeatureTypeStyle>
72
+ <Rule>
73
+ <ogc:Filter>
74
+ <ogc:PropertyIsEqualTo>
75
+ <ogc:Function name="geometryType">
76
+ <ogc:PropertyName>the_geom</ogc:PropertyName>
77
+ </ogc:Function>
78
+ <ogc:Literal>Polygon</ogc:Literal>
79
+ </ogc:PropertyIsEqualTo>
80
+ </ogc:Filter>
81
+ <PolygonSymbolizer>
82
+ <Fill>
83
+ <CssParameter name="fill-opacity">0.4</CssParameter>
84
+ <CssParameter name="fill">#aaaaaa</CssParameter>
85
+ </Fill>
86
+ <Stroke>
87
+ <CssParameter name="stroke-width">2</CssParameter>
88
+ <CssParameter name="stroke">#dddddd</CssParameter>
89
+ </Stroke>
90
+ </PolygonSymbolizer>
91
+ </Rule>
92
+ </FeatureTypeStyle>
93
+ <!-- Polygon with Filter, using SLD DSL, and more compact polygon filtering syntax -->
94
+ <FeatureTypeStyle>
95
+ <Rule>
96
+ <ogc:Filter>
97
+ <ogc:PropertyIsEqualTo>
98
+ <ogc:Function name="geometryType">
99
+ <ogc:PropertyName>the_geom</ogc:PropertyName>
100
+ </ogc:Function>
101
+ <ogc:Literal>Polygon</ogc:Literal>
102
+ </ogc:PropertyIsEqualTo>
103
+ </ogc:Filter>
104
+ <PolygonSymbolizer>
105
+ <Fill>
106
+ <CssParameter name="fill-opacity">0.4</CssParameter>
107
+ <CssParameter name="fill">#aaaaaa</CssParameter>
108
+ </Fill>
109
+ <Stroke>
110
+ <CssParameter name="stroke-width">2</CssParameter>
111
+ <CssParameter name="stroke">#dddddd</CssParameter>
112
+ </Stroke>
113
+ </PolygonSymbolizer>
114
+ </Rule>
115
+ </FeatureTypeStyle>
116
+ <!-- Polygon with complex Filter -->
117
+ <FeatureTypeStyle>
118
+ <Rule>
119
+ <ogc:Filter>
120
+ <ogc:And>
121
+ <ogc:PropertyIsEqualTo>
122
+ <ogc:Function name="geometryType">
123
+ <ogc:PropertyName>the_geom</ogc:PropertyName>
124
+ </ogc:Function>
125
+ <ogc:Literal>Polygon</ogc:Literal>
126
+ </ogc:PropertyIsEqualTo>
127
+ <ogc:Or>
128
+ <ogc:PropertyIsNull>
129
+ <ogc:PropertyName>highway</ogc:PropertyName>
130
+ </ogc:PropertyIsNull>
131
+ <ogc:Not>
132
+ <ogc:PropertyIsNull>
133
+ <ogc:PropertyName>waterway</ogc:PropertyName>
134
+ </ogc:PropertyIsNull>
135
+ </ogc:Not>
136
+ <ogc:PropertyIsEqualTo>
137
+ <ogc:PropertyName>natural</ogc:PropertyName>
138
+ <ogc:Literal>water</ogc:Literal>
139
+ </ogc:PropertyIsEqualTo>
140
+ </ogc:Or>
141
+ </ogc:And>
142
+ </ogc:Filter>
143
+ <PolygonSymbolizer>
144
+ <Fill>
145
+ <CssParameter name="fill-opacity">0.4</CssParameter>
146
+ <CssParameter name="fill">#aaaaaa</CssParameter>
147
+ </Fill>
148
+ <Stroke>
149
+ <CssParameter name="stroke-width">2</CssParameter>
150
+ <CssParameter name="stroke">#dddddd</CssParameter>
151
+ </Stroke>
152
+ </PolygonSymbolizer>
153
+ </Rule>
154
+ </FeatureTypeStyle>
155
+ <!-- Polygon with combined simple and complex Filter -->
156
+ <FeatureTypeStyle>
157
+ <Rule>
158
+ <ogc:Filter>
159
+ <ogc:And>
160
+ <ogc:PropertyIsEqualTo>
161
+ <ogc:Function name="geometryType">
162
+ <ogc:PropertyName>the_geom</ogc:PropertyName>
163
+ </ogc:Function>
164
+ <ogc:Literal>Polygon</ogc:Literal>
165
+ </ogc:PropertyIsEqualTo>
166
+ <ogc:Or>
167
+ <ogc:PropertyIsNull>
168
+ <ogc:PropertyName>highway</ogc:PropertyName>
169
+ </ogc:PropertyIsNull>
170
+ <ogc:Not>
171
+ <ogc:PropertyIsNull>
172
+ <ogc:PropertyName>waterway</ogc:PropertyName>
173
+ </ogc:PropertyIsNull>
174
+ </ogc:Not>
175
+ <ogc:PropertyIsEqualTo>
176
+ <ogc:PropertyName>natural</ogc:PropertyName>
177
+ <ogc:Literal>water</ogc:Literal>
178
+ </ogc:PropertyIsEqualTo>
179
+ </ogc:Or>
180
+ </ogc:And>
181
+ </ogc:Filter>
182
+ <PolygonSymbolizer>
183
+ <Fill>
184
+ <CssParameter name="fill-opacity">0.4</CssParameter>
185
+ <CssParameter name="fill">#aaaaaa</CssParameter>
186
+ </Fill>
187
+ <Stroke>
188
+ <CssParameter name="stroke-width">2</CssParameter>
189
+ <CssParameter name="stroke">#dddddd</CssParameter>
190
+ </Stroke>
191
+ </PolygonSymbolizer>
192
+ </Rule>
193
+ </FeatureTypeStyle>
194
+ <!-- Multiple symbolizers and complex filter -->
195
+ <FeatureTypeStyle>
196
+ <Rule>
197
+ <ogc:Filter>
198
+ <ogc:And>
199
+ <ogc:Not>
200
+ <ogc:PropertyIsNull>
201
+ <ogc:PropertyName>highway</ogc:PropertyName>
202
+ </ogc:PropertyIsNull>
203
+ </ogc:Not>
204
+ <ogc:Or>
205
+ <ogc:PropertyIsEqualTo>
206
+ <ogc:PropertyName>highway</ogc:PropertyName>
207
+ <ogc:Literal>secondary</ogc:Literal>
208
+ </ogc:PropertyIsEqualTo>
209
+ <ogc:PropertyIsEqualTo>
210
+ <ogc:PropertyName>highway</ogc:PropertyName>
211
+ <ogc:Literal>tertiary</ogc:Literal>
212
+ </ogc:PropertyIsEqualTo>
213
+ </ogc:Or>
214
+ </ogc:And>
215
+ </ogc:Filter>
216
+ <LineSymbolizer>
217
+ <Stroke>
218
+ <CssParameter name="stroke-width">7</CssParameter>
219
+ <CssParameter name="stroke">#303030</CssParameter>
220
+ </Stroke>
221
+ </LineSymbolizer>
222
+ <LineSymbolizer>
223
+ <Stroke>
224
+ <CssParameter name="stroke-width">5</CssParameter>
225
+ <CssParameter name="stroke">#e0e0ff</CssParameter>
226
+ </Stroke>
227
+ </LineSymbolizer>
228
+ </Rule>
229
+ </FeatureTypeStyle>
230
+ </UserStyle>
231
+ </NamedLayer>
232
+ </StyledLayerDescriptor>
@@ -0,0 +1 @@
1
+ require 'amanzi/sld'
@@ -0,0 +1,222 @@
1
+ #/usr/bin/env ruby
2
+
3
+ # The Amanzi:SLD module contains classes for a DSL for specifying
4
+ # StyleLayerDescriptor XML documents in a much simpler syntax than
5
+ # the usual XML syntax. It is based on the Amanzi::XML DSL which
6
+ # by itself already makes the syntax much simpler. You can access
7
+ # the underlying Amanzi::XML syntax as well, should you wish, but
8
+ # The SLD syntax makes common things much easier.
9
+ #
10
+ # For example, specify a PolygonSymbolizer like this:
11
+ # require 'amanzi/sld'
12
+ # sld = Amanzi::SLD::Document.new "Example"
13
+ # sld.add_polygon_symbolizer :stroke_width => 2, :stroke => '#dddddd', :fill => '#aaaaaa', :fill_opacity => 0.4
14
+
15
+ require 'amanzi/xml'
16
+
17
+ module Amanzi
18
+ module SLD
19
+ class Config
20
+ attr_accessor :settings
21
+ def self.config(options={})
22
+ @@instance ||= Config.new(options)
23
+ end
24
+ def initialize(options={})
25
+ @settings = options
26
+ end
27
+ def []=(key,value)
28
+ settings[key] = value
29
+ end
30
+ def [](key)
31
+ settings[key]
32
+ end
33
+ end
34
+ module Logger
35
+ def puts *args
36
+ STDOUT.puts(*args) if(Config.config[:verbose])
37
+ end
38
+ end
39
+ class Document < XML::Document
40
+ include Logger
41
+ attr_reader :sld_name, :style_name, :name, :geometry_property
42
+ def initialize(name,options={})
43
+ super 'StyledLayerDescriptor'
44
+ self['version'] = '1.0.0'
45
+ self['xmlns'] = "http://www.opengis.net/sld"
46
+ self['xmlns:ogc'] ="http://www.opengis.net/ogc"
47
+ self['xmlns:xlink'] = "http://www.w3.org/1999/xlink"
48
+ self['xmlns:xsi'] = "http://www.w3.org/2001/XMLSchema-instance"
49
+ self['xsi:schemaLocation'] = "http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd"
50
+ add_singleton_elements ['named_layer','user_style']
51
+ singleton_method(:named_layer).insert_child(0,'Name') << name
52
+ singleton_method(:user_style).insert_child(0,'Name') << name
53
+ end
54
+ # Pass any unknown methods down to user_style
55
+ def method_missing(symbol,*args,&block)
56
+ puts "Looking for SLD document method: #{symbol}"
57
+ singleton_method(symbol,*args,&block) ||
58
+ singleton_method(:user_style).send(symbol, *args, &block)
59
+ end
60
+ def add_line_symbolizer(options={},*args,&block)
61
+ RuleBuilder.new(self.feature_type_style.rule).add_line_symbolizer(options,*args,&block)
62
+ end
63
+ def add_polygon_symbolizer(options={},*args,&block)
64
+ RuleBuilder.new(self.feature_type_style.rule).add_polygon_symbolizer(options,*args,&block)
65
+ end
66
+ def add_text_symbolizer(options={},*args,&block)
67
+ RuleBuilder.new(self.feature_type_style.rule).add_text_symbolizer(options,*args,&block)
68
+ end
69
+ def add_rule(options={})
70
+ puts "Adding a generic rule: #{options.inspect}"
71
+ self.feature_type_style do |fs|
72
+ rule_builder = RuleBuilder.new(rule)
73
+ yield rule_builder if(block_given?)
74
+ rule_builder
75
+ end
76
+ end
77
+ end
78
+ class ElementWrapper
79
+ include Logger
80
+ attr_reader :element
81
+ def initialize(element)
82
+ @element = element
83
+ end
84
+ def method_missing(symbol,*args,&block)
85
+ puts "Method '#{symbol}' missing on Element Wrapper: #{self.inspect}"
86
+ element.send(symbol,*args,&block)
87
+ end
88
+ end
89
+ class RuleBuilder < ElementWrapper
90
+ alias_method :rule, :element
91
+ def initialize(rule)
92
+ super rule
93
+ end
94
+ def style_it(it,prefix,options={})
95
+ options.each do |k,v|
96
+ if k.to_s =~ /^#{prefix}/
97
+ it.css_parameter(:name => k.to_s.gsub(/_/,'-')) << v
98
+ end
99
+ end
100
+ end
101
+ def style_stroke(stroke,options={})
102
+ style_it(stroke,:stroke,options)
103
+ end
104
+ def style_fill(fill,options={})
105
+ style_it(fill,:fill,options)
106
+ end
107
+ def style_font(font,options={})
108
+ style_it(font,:font,options)
109
+ end
110
+ def update_filter_block(options,&block)
111
+ new_block = block
112
+ self.max_scale_denominator << options[:max_scale].to_f if(options[:max_scale])
113
+ self.min_scale_denominator << options[:min_scale].to_f if(options[:min_scale])
114
+ if(options[:geometry])
115
+ if(block)
116
+ new_block = Proc.new do |f|
117
+ f.op(:and) do |f|
118
+ f.geometry = options[:geometry]
119
+ block.call f
120
+ end
121
+ end
122
+ else
123
+ new_block = Proc.new {|f| f.geometry = options[:geometry]}
124
+ end
125
+ end
126
+ new_block
127
+ end
128
+ def add_line_symbolizer(options={},*args,&block)
129
+ options[:stroke] ||= '#000000'
130
+ options[:stroke_width] ||= 1
131
+ puts "RuleBuilder:Adding line symbolizer with options: #{options.inspect}"
132
+ block = update_filter_block(options,&block)
133
+ block.call FilterBuilder.new(rule.insert_child(0,'Filter').ns(:ogc)) if(block)
134
+ style_stroke(rule.line_symbolizer.stroke,options)
135
+ self
136
+ end
137
+ def add_polygon_symbolizer(options={},*args,&block)
138
+ options[:fill] ||= '#b0b0b0'
139
+ puts "Adding polygon symbolizer with options: #{options.inspect}"
140
+ block = update_filter_block(options,&block)
141
+ block.call FilterBuilder.new(rule.insert_child(0,'Filter').ns(:ogc)) if(block)
142
+ rule.polygon_symbolizer do |p|
143
+ style_fill(p.fill,options)
144
+ style_stroke(p.stroke,options)
145
+ end
146
+ self
147
+ end
148
+ def add_text_symbolizer(options={},*args,&block)
149
+ return self unless options[:text] || options[:property]
150
+ options[:font_family] ||= 'Times New Roman'
151
+ options[:font_style] ||= 'Normal'
152
+ options[:font_size] ||= 14
153
+ options[:font_weight] ||= 'bold'
154
+ options[:font_color] ||= '#101010'
155
+ options[:fill] ||= '#101010'
156
+ options[:halo_style] ||= {:fill => '#ffffbb', :fill_opacity => 0.85}
157
+ puts "Adding text symbolizer with options: #{options.inspect}"
158
+ block = update_filter_block(options,&block)
159
+ block.call FilterBuilder.new(rule.insert_child(0,'Filter').ns(:ogc)) if(block)
160
+ rule.text_symbolizer do |p|
161
+ if options[:property]
162
+ p.label.ogc__property_name << options[:property]
163
+ else
164
+ p.label.ogc__literal << options[:text]
165
+ end
166
+ style_font(p.font,options)
167
+ if options[:halo].to_i > 0
168
+ p.halo do |h|
169
+ h.radius.ogc__literal << options[:halo].to_i
170
+ style_fill(h.fill,options[:halo_style])
171
+ end
172
+ end
173
+ style_fill(p.fill,options)
174
+ end
175
+ self
176
+ end
177
+ end
178
+ class FilterBuilder < ElementWrapper
179
+ def initialize(filter)
180
+ super filter
181
+ puts "Created filter builder: #{element}"
182
+ end
183
+ def geometry=(type)
184
+ element.property_is_equal_to do |f|
185
+ f.function(:name => 'geometryType').property_name << (Config.config[:geometry_property] || 'geom')
186
+ f.literal << type
187
+ end
188
+ end
189
+ def property
190
+ @property ||= PropertyFilterBuilder.new(element)
191
+ end
192
+ def op(op_type)
193
+ op_type = op_type.to_s.intern
194
+ @op ||= {}
195
+ yield(@op[op_type] ||= BooleanFilterBuilder.new(element,op_type)) if(block_given?)
196
+ @op[op_type]
197
+ end
198
+ end
199
+ class PropertyFilterBuilder < ElementWrapper
200
+ def initialize(filter)
201
+ super filter
202
+ end
203
+ def []=(key,value)
204
+ element.property_is_equal_to do |f|
205
+ f.property_name << key.to_s
206
+ f.literal << value.to_s
207
+ end
208
+ end
209
+ def exists? name
210
+ element.send(:not).property_is_null.property_name << name
211
+ end
212
+ def not_exists? name
213
+ element.property_is_null.property_name << name
214
+ end
215
+ end
216
+ class BooleanFilterBuilder < FilterBuilder
217
+ def initialize(filter,op)
218
+ super(filter.send(op))
219
+ end
220
+ end
221
+ end
222
+ end