rcap 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,2 @@
1
+ 0.1 - 5th November 2009
2
+ * Initial release
data/README ADDED
@@ -0,0 +1,151 @@
1
+ = RCAP - Common Alerting Protocol for Ruby
2
+
3
+ == Overview
4
+
5
+ The Common Alerting Protocol is a lightweight standard to facilitate the distribution of alerting data. RCAP is an implementation of the CAP in Ruby. It allows for the creation of RCAP messages from Ruby applications and the parsing of external messages.
6
+
7
+ RCAP supports CAP Version 1.1.
8
+
9
+ == Usage
10
+
11
+ To include RCAP into your application add the following require
12
+
13
+ require 'rcap/rcap'
14
+
15
+ === Creating an Alert
16
+
17
+ ==== From XML
18
+
19
+ RCAP allows for the parsing of a CAP XML string into a collection of RCAP objects
20
+
21
+ alert = RCAP::Alert.from_xml( xml_string )
22
+
23
+ Currently RCAP only supports version 1.1 of the CAP standard and the parser is as strict as possible when parsing data.
24
+
25
+ ==== Programatically
26
+
27
+ Alerts can also be created programatically by initialising the various RCAP classes manually
28
+
29
+ include RCAP # Include RCAP module into namespace
30
+
31
+ alert = Alert.new( :sender => 'cape_town_disaster_relief@capetown.municipal.za',
32
+ :status => Alert::STATUS_ACTUAL,
33
+ :msg_type => Alert::MSG_TYPE_ALERT,
34
+ :scope => Alert::SCOPE_PUBLIC,
35
+ :infos => Info.new( :event => 'Liquid Petroleoum Tanker Fire',
36
+ :language => 'en-ZA',
37
+ :categories => [ Info::CATEGORY_TRANSPORT, Info::CATEGORY_FIRE ],
38
+ :urgency => Info::URGENCY_IMMEDIATE,
39
+ :severity => Info::SEVERITY_SEVERE,
40
+ :certainty => Info::CERTAINTY_OBSERVED,
41
+ :headline => 'LIQUID PETROLEOUM TANKER FIRE ON N2 INCOMING FREEWAY',
42
+ :description => 'A liquid petroleoum tanker has caught fire on the N2 incoming freeway 1km
43
+ after the R300 interchange. Municipal fire fighting crews have been dispatched.
44
+ Traffic control officers are on the scene and have diverted traffic onto
45
+ alternate routes.' ))
46
+
47
+ # Accessing attributes
48
+ puts alert.status # Print out "Actual"
49
+ puts alert.infos[0].language # Print out "en-ZA"
50
+ puts alert.infos[0].categories.join( ' ' ) # Print out "Transport Fire"
51
+
52
+ puts alert.to_xml # Print out CAP XML message
53
+
54
+ Will print the following CAP XML
55
+
56
+ <?xml version='1.0'?>
57
+ <alert xmlns='urn:oasis:names:tc:emergency:cap:1.1'>
58
+ <identifier>494207a7-f86b-4060-8318-a4b2a3ce565e</identifier>
59
+ <sender>cape_town_disaster_relief@capetown.municipal.za</sender>
60
+ <sent>2009-10-26T21:04:51+2:00</sent>
61
+ <status>Actual</status>
62
+ <msgType>Alert</msgType>
63
+ <scope>Public</scope>
64
+ <info>
65
+ <language>en-ZA</language>
66
+ <category>Transport</category>
67
+ <category>Fire</category>
68
+ <event>Liquid Petroleoum Tanker Fire</event>
69
+ <urgency>Immediate</urgency>
70
+ <severity>Severe</severity>
71
+ <certainty>Observed</certainty>
72
+ <headline>LIQUID PETROLEOUM TANKER FIRE ON N2 INCOMING FREEWAY</headline>
73
+ <description>
74
+ A liquid petroleoum tanker has caught fire on the N2 incoming freeway 1km
75
+ after the R300 interchange. Municipal fire fighting crews have been
76
+ dispatched. Traffic control officers are on the scene and have diverted
77
+ traffic onto alternate routes.
78
+ </description>
79
+ </info>
80
+ </alert>
81
+
82
+
83
+ You can also create the various message elements seperately and then combine them later:
84
+
85
+ # Setting attributes at initialisation
86
+ alert = Alert.new( :sender => 'cape_town_disaster_relief@capetown.municipal.za',
87
+ :status => Alert::STATUS_ACTUAL,
88
+ :msg_type => Alert::MSG_TYPE_ALERT,
89
+ :scope => Alert::SCOPE_PUBLIC )
90
+
91
+ # Setting attributes after initialisation
92
+ info = Info.new
93
+ info.event = 'Liquid Petroleoum Tanker Fire'
94
+ info.language = 'en-ZA'
95
+ info.categories = [ Info::CATEGORY_TRANSPORT, Info::CATEGORY_FIRE ]
96
+ info.urgency = Info::URGENCY_IMMEDIATE
97
+ info.severity = Info::SEVERITY_SEVERE
98
+ info.certainty = Info::CERTAINTY_OBSERVED
99
+
100
+
101
+ resource = Resource.new( :resource_desc => 'Cape Town Municipal Traffic Management Guidelines',
102
+ :uri => 'http://capetown.municipal.za/traffic/management_guidelines.pdf',
103
+ :mime_type => 'application/pdf')
104
+
105
+ # Combining all elements
106
+ info.resources << resource
107
+ alert.infos << info
108
+
109
+ === Validating an alert
110
+
111
+ The RCAP API aims to codify as many of the rules of the CAP XML format into validation rules that can be checked using the Assistance API (http://assistance.rubyforge.org). The following Info object has two attributes ('severity' and 'certainty') set to incorrect values.
112
+
113
+ info = Info.new( :event => 'Liquid Petroleoum Tanker Fire',
114
+ :language => 'en-ZA',
115
+ :categories => [ Info::CATEGORY_TRANSPORT, Info::CATEGORY_FIRE ],
116
+ :urgency => Info::URGENCY_IMMEDIATE,
117
+ :severity => nil, # Severity is not assigned
118
+ :certainty => 'Unknown Certainty' ) # Certainty is assigned in incorrect value
119
+ puts info.valid?
120
+ puts info.errors.full_messages
121
+
122
+ Will produce the folling output:
123
+
124
+ false
125
+ severity is not present
126
+ certainty can only be assigned the following values: Observed, Likely, Possible, Unlikely, Unknown
127
+
128
+ A full spec suite using RSpec (http://www.rspec.info) was used to test the validations and currently numbers over 140 tests.
129
+
130
+ == Version
131
+
132
+ 0.1
133
+
134
+ == Dependencies
135
+
136
+ RCAP depends on the following gems
137
+
138
+ * Assistance - http://assistance.rubyforge.org
139
+ * UUIDTools - http://uuidtools.rubyforge.org
140
+
141
+ == Authors
142
+
143
+ Farrel Lifson - farrel.lifson@aimred.com
144
+
145
+ == License
146
+
147
+ RCAP is released under the BSD License.
148
+
149
+ == Copyright
150
+
151
+ 2009 Aimred CC
data/lib/rcap.rb ADDED
@@ -0,0 +1,21 @@
1
+ require 'date'
2
+ require 'assistance'
3
+ require 'uuidtools'
4
+ require 'rexml/document'
5
+ require 'rcap/utilities'
6
+ require 'rcap/validations'
7
+ require 'rcap/alert'
8
+ require 'rcap/parameter'
9
+ require 'rcap/event_code'
10
+ require 'rcap/info'
11
+ require 'rcap/resource'
12
+ require 'rcap/point'
13
+ require 'rcap/circle'
14
+ require 'rcap/polygon'
15
+ require 'rcap/geocode'
16
+ require 'rcap/area'
17
+
18
+ module RCAP
19
+ XMLNS = "urn:oasis:names:tc:emergency:cap:1.1"
20
+ VERSION = "0.1"
21
+ end
data/lib/rcap/alert.rb ADDED
@@ -0,0 +1,186 @@
1
+ module RCAP
2
+ # An Alert object is valid if
3
+ # * it has an identifier
4
+ # * it has a sender
5
+ # * it has a sent time
6
+ # * it has a valid status value
7
+ # * it has a valid messge type value
8
+ # * it has a valid scope value
9
+ # * all Info objects contained in infos are valid
10
+ class Alert
11
+ include Validation
12
+
13
+ STATUS_ACTUAL = "Actual"
14
+ STATUS_EXERCISE = "Exercise"
15
+ STATUS_SYSTEM = "System"
16
+ STATUS_TEST = "Test"
17
+ STATUS_DRAFT = "Draft"
18
+ ALL_STATUSES = [ STATUS_ACTUAL, STATUS_EXERCISE, STATUS_SYSTEM, STATUS_TEST, STATUS_DRAFT ] #:nodoc:
19
+
20
+ MSG_TYPE_ALERT = "Alert"
21
+ MSG_TYPE_UPDATE = "Update"
22
+ MSG_TYPE_CANCEL = "Cancel"
23
+ MSG_TYPE_ACK = "Ack"
24
+ MSG_TYPE_ERROR = "Error"
25
+ ALL_MSG_TYPES = [ MSG_TYPE_ALERT, MSG_TYPE_UPDATE, MSG_TYPE_CANCEL, MSG_TYPE_ACK, MSG_TYPE_ERROR ] #:nodoc:
26
+
27
+ SCOPE_PUBLIC = "Public"
28
+ SCOPE_RESTRICTED = "Restricted"
29
+ SCOPE_PRIVATE = "Private"
30
+ ALL_SCOPES = [ SCOPE_PUBLIC, SCOPE_PRIVATE, SCOPE_RESTRICTED ] #:nodoc:
31
+
32
+ XML_ELEMENT_NAME = 'alert' # :nodoc:
33
+ IDENTIFIER_ELEMENT_NAME = 'identifier' # :nodoc:
34
+ SENDER_ELEMENT_NAME = 'sender' # :nodoc:
35
+ SENT_ELEMENT_NAME = 'sent' # :nodoc:
36
+ STATUS_ELEMENT_NAME = 'status' # :nodoc:
37
+ MSG_TYPE_ELEMENT_NAME = 'msgType' # :nodoc:
38
+ SOURCE_ELEMENT_NAME = 'source' # :nodoc:
39
+ SCOPE_ELEMENT_NAME = 'scope' # :nodoc:
40
+ RESTRICTION_ELEMENT_NAME = 'restriction' # :nodoc:
41
+ ADDRESSES_ELEMENT_NAME = 'addresses' # :nodoc:
42
+ CODE_ELEMENT_NAME = 'code' # :nodoc:
43
+ NOTE_ELEMENT_NAME = 'note' # :nodoc:
44
+ REFERENCES_ELEMENT_NAME = 'references' # :nodoc:
45
+ INCIDENTS_ELEMENT_NAME = 'incidents' # :nodoc:
46
+
47
+ XPATH = 'cap:alert' # :nodoc:
48
+ IDENTIFIER_XPATH = "cap:#{ IDENTIFIER_ELEMENT_NAME }" # :nodoc:
49
+ SENDER_XPATH = "cap:#{ SENDER_ELEMENT_NAME }" # :nodoc:
50
+ SENT_XPATH = "cap:#{ SENT_ELEMENT_NAME }" # :nodoc:
51
+ STATUS_XPATH = "cap:#{ STATUS_ELEMENT_NAME }" # :nodoc:
52
+ MSG_TYPE_XPATH = "cap:#{ MSG_TYPE_ELEMENT_NAME }" # :nodoc:
53
+ SOURCE_XPATH = "cap:#{ SOURCE_ELEMENT_NAME }" # :nodoc:
54
+ SCOPE_XPATH = "cap:#{ SCOPE_ELEMENT_NAME }" # :nodoc:
55
+ RESTRICTION_XPATH = "cap:#{ RESTRICTION_ELEMENT_NAME }" # :nodoc:
56
+ ADDRESSES_XPATH = "cap:#{ ADDRESSES_ELEMENT_NAME }" # :nodoc:
57
+ CODE_XPATH = "cap:#{ CODE_ELEMENT_NAME }" # :nodoc:
58
+ NOTE_XPATH = "cap:#{ NOTE_ELEMENT_NAME }" # :nodoc:
59
+ REFERENCES_XPATH = "cap:#{ REFERENCES_ELEMENT_NAME }" # :nodoc:
60
+ INCIDENTS_XPATH = "cap:#{ INCIDENTS_ELEMENT_NAME }" # :nodoc:
61
+
62
+ # If not set a UUID will be set by default
63
+ attr_accessor( :identifier)
64
+ attr_accessor( :sender )
65
+ # Sent Time - If not set will value will be time of creation.
66
+ attr_accessor( :sent )
67
+ attr_accessor( :status )
68
+ # Message Type
69
+ attr_accessor( :msg_type )
70
+ attr_accessor( :scope )
71
+ attr_accessor( :source )
72
+ # Depends on scope being SCOPE_RESTRICTED.
73
+ attr_accessor( :restriction )
74
+ attr_accessor( :code )
75
+ attr_accessor( :note )
76
+
77
+ # Collection of address strings. Depends on scope being SCOPE_PRIVATE.
78
+ attr_reader( :addresses )
79
+ # Collection of reference strings - see Alert#to_reference
80
+ attr_reader( :references)
81
+ # Collection of incident strings
82
+ attr_reader( :incidents )
83
+ # Collection of Info objects
84
+ attr_reader( :infos )
85
+
86
+ validates_presence_of( :identifier, :sender, :sent, :status, :msg_type, :scope )
87
+
88
+ validates_inclusion_of( :status, :in => ALL_STATUSES )
89
+ validates_inclusion_of( :msg_type, :in => ALL_MSG_TYPES )
90
+ validates_inclusion_of( :scope, :in => ALL_SCOPES )
91
+
92
+ validates_format_of( :identifier, :with => ALLOWED_CHARACTERS )
93
+ validates_format_of( :sender , :with => ALLOWED_CHARACTERS )
94
+
95
+ validates_dependency_of( :addresses, :on => :scope, :with_value => SCOPE_PRIVATE )
96
+ validates_dependency_of( :restriction, :on => :scope, :with_value => SCOPE_RESTRICTED )
97
+
98
+ validates_collection_of( :infos )
99
+
100
+ def initialize( attributes = {})
101
+ @identifier = attributes[ :identifier ] || UUIDTools::UUID.random_create.to_s
102
+ @sender = attributes[ :sender ]
103
+ @sent = attributes[ :sent ] || DateTime.now
104
+ @status = attributes[ :status ]
105
+ @msg_type = attributes[ :msg_type ]
106
+ @scope = attributes[ :scope ]
107
+ @source = attributes[ :source ]
108
+ @restriction = attributes[ :restriction ]
109
+ @addresses = Array( attributes[ :addresses ])
110
+ @references = Array( attributes[ :references ])
111
+ @incidents = Array( attributes[ :incidents ])
112
+ @infos = Array( attributes[ :infos ])
113
+ end
114
+
115
+ def to_xml_element #:nodoc:
116
+ xml_element = REXML::Element.new( XML_ELEMENT_NAME )
117
+ xml_element.add_namespace( RCAP::XMLNS )
118
+ xml_element.add_element( IDENTIFIER_ELEMENT_NAME ).add_text( self.identifier )
119
+ xml_element.add_element( SENDER_ELEMENT_NAME ).add_text( self.sender )
120
+ xml_element.add_element( SENT_ELEMENT_NAME ).add_text( self.sent.to_s )
121
+ xml_element.add_element( STATUS_ELEMENT_NAME ).add_text( self.status )
122
+ xml_element.add_element( MSG_TYPE_ELEMENT_NAME ).add_text( self.msg_type )
123
+ xml_element.add_element( SOURCE_ELEMENT_NAME ).add_text( self.source ) if self.source
124
+ xml_element.add_element( SCOPE_ELEMENT_NAME ).add_text( self.scope )
125
+ xml_element.add_element( RESTRICTION_ELEMENT_NAME ).add_text( self.restriction ) if self.restriction
126
+ unless self.addresses.empty?
127
+ xml_element.add_element( ADDRESSES_ELEMENT_NAME ).add_text( self.addresses.to_s_for_cap )
128
+ end
129
+ xml_element.add_element( CODE_ELEMENT_NAME ).add_text( self.code ) if self.code
130
+ xml_element.add_element( NOTE_ELEMENT_NAME ).add_text( self.note ) if self.note
131
+ unless self.references.empty?
132
+ xml_element.add_element( REFERENCES_ELEMENT_NAME ).add_text( self.references.join( ' ' ))
133
+ end
134
+ unless self.incidents.empty?
135
+ xml_element.add_element( INCIDENTS_ELEMENT_NAME ).add_text( self.incidents.join( ' ' ))
136
+ end
137
+ self.infos.each do |info|
138
+ xml_element.add_element( info.to_xml_element )
139
+ end
140
+ xml_element
141
+ end
142
+
143
+ def to_xml_document #:nodoc:
144
+ xml_document = REXML::Document.new
145
+ xml_document.add( REXML::XMLDecl.new )
146
+ xml_document.add( self.to_xml_element )
147
+ xml_document
148
+ end
149
+
150
+ # Returns a string containing the XML representation of the alert.
151
+ def to_xml
152
+ self.to_xml_document.to_s
153
+ end
154
+
155
+ # Returns a string of the format 'sender,identifier,sent' suitable for usage as a reference in a CAP message.
156
+ def to_reference
157
+ "#{ self.sender },#{ self.identifier },#{ self.sent }"
158
+ end
159
+
160
+ def self.from_xml_element( alert_xml_element ) # :nodoc:
161
+ alert = RCAP::Alert.new( :identifier => RCAP.xpath_text( alert_xml_element, RCAP::Alert::IDENTIFIER_XPATH ),
162
+ :sender => RCAP.xpath_text( alert_xml_element, SENDER_XPATH ),
163
+ :sent => (( sent = RCAP.xpath_first( alert_xml_element, SENT_XPATH )) ? DateTime.parse( sent.text ) : nil ),
164
+ :status => RCAP.xpath_text( alert_xml_element, STATUS_XPATH ),
165
+ :msg_type => RCAP.xpath_text( alert_xml_element, MSG_TYPE_XPATH ),
166
+ :source => RCAP.xpath_text( alert_xml_element, SOURCE_XPATH ),
167
+ :scope => RCAP.xpath_text( alert_xml_element, SCOPE_XPATH ),
168
+ :restriction => RCAP.xpath_text( alert_xml_element, RESTRICTION_XPATH ),
169
+ :addresses => (( address = RCAP.xpath_text( alert_xml_element, ADDRESSES_XPATH )) ? address.unpack_cap_list : nil ),
170
+ :code => RCAP.xpath_text( alert_xml_element, CODE_XPATH ),
171
+ :note => RCAP.xpath_text( alert_xml_element, NOTE_XPATH ),
172
+ :references => (( references = RCAP.xpath_text( alert_xml_element, REFERENCES_XPATH )) ? references.split( ' ' ) : nil ),
173
+ :incidents => (( incidents = RCAP.xpath_text( alert_xml_element, INCIDENTS_XPATH )) ? incidents.split( ' ' ) : nil ),
174
+ :infos => RCAP.xpath_match( alert_xml_element, RCAP::Info::XPATH ).map{ |element| RCAP::Info.from_xml_element( element )})
175
+ end
176
+
177
+ def self.from_xml_document( xml_document ) # :nodoc:
178
+ self.from_xml_element( xml_document.root )
179
+ end
180
+
181
+ # Initialised an Alert object from the XML string.
182
+ def self.from_xml( xml_string )
183
+ self.from_xml_document( REXML::Document.new( xml_string ))
184
+ end
185
+ end
186
+ end
data/lib/rcap/area.rb ADDED
@@ -0,0 +1,82 @@
1
+ module RCAP
2
+ # An Area object is valid if
3
+ # * it has an area description
4
+ # * all Circle objects contained in circles are valid
5
+ # * all Geocode objects contained in geocodes are valid
6
+ # * all Polygon objects contained in polygons are valid
7
+ # * altitude has a value if ceiling is set
8
+ class Area
9
+ include Validation
10
+
11
+ # Area Description - Textual description of area.
12
+ attr_accessor( :area_desc )
13
+ # Expressed in feet above sea level
14
+ attr_accessor( :altitude )
15
+ # Expressed in feet above sea level.
16
+ attr_accessor( :ceiling )
17
+ # Collection of Circle objects
18
+ attr_reader( :circles )
19
+ # Collection of Geocode objects
20
+ attr_reader( :geocodes )
21
+ # Collection of Polygon objects
22
+ attr_reader( :polygons )
23
+
24
+ validates_presence_of( :area_desc )
25
+ validates_collection_of( :circles, :geocodes, :polygons )
26
+ validates_dependency_of( :ceiling, :on => :altitude )
27
+
28
+ XML_ELEMENT_NAME = 'area' # :nodoc:
29
+ AREA_DESC_ELEMENT_NAME = 'areaDesc' # :nodoc:
30
+ ALTITUDE_ELEMENT_NAME = 'altitude' # :nodoc:
31
+ CEILING_ELEMENT_NAME = 'ceiling' # :nodoc:
32
+
33
+ XPATH = "cap:#{ XML_ELEMENT_NAME }" # :nodoc:
34
+ AREA_DESC_XPATH = "cap:#{ AREA_DESC_ELEMENT_NAME }" # :nodoc:
35
+ ALTITUDE_XPATH = "cap:#{ ALTITUDE_ELEMENT_NAME }" # :nodoc:
36
+ CEILING_XPATH = "cap:#{ CEILING_ELEMENT_NAME }" # :nodoc:
37
+
38
+ def initialize( attributes = {})
39
+ @area_desc = attributes[ :area_desc ]
40
+ @altitude = attributes[ :altitude ]
41
+ @ceiling = attributes[ :ceiling ]
42
+ @circles = Array( attributes[ :circles ])
43
+ @geocodes = Array( attributes[ :geocodes ])
44
+ @polygons = Array( attributes[ :polygons ])
45
+ end
46
+
47
+ def to_xml_element # :nodoc:
48
+ xml_element = REXML::Element.new( XML_ELEMENT_NAME )
49
+ xml_element.add_element( AREA_DESC_ELEMENT_NAME ).add_text( @area_desc.to_s )
50
+ add_to_xml_element = lambda do |element, object|
51
+ element.add_element( object.to_xml_element )
52
+ element
53
+ end
54
+ @polygons.inject( xml_element, &add_to_xml_element )
55
+ @circles.inject( xml_element, &add_to_xml_element )
56
+ @geocodes.inject( xml_element, &add_to_xml_element )
57
+ xml_element.add_element( ALTITUDE_ELEMENT_NAME ).add_text( @altitude.to_s ) unless self.altitude.blank?
58
+ xml_element.add_element( CEILING_ELEMENT_NAME ).add_text( @ceiling.to_s ) unless self.altitude.blank?
59
+ xml_element
60
+ end
61
+
62
+ def to_xml # :nodoc:
63
+ self.to_xml_element.to_s
64
+ end
65
+
66
+ # Implements an equality operator for the Area object. Two Area objects are equal if all their attributes are equal.
67
+ def ==( other )
68
+ comparison_attributes = lambda{ |area| [ area.area_desc, area.altitude, area.ceiling, area.circles, area.geocodes, area.polygons ]}
69
+ comparison_attributes.call( self ) == comparison_attributes.call( other )
70
+ end
71
+
72
+ def self.from_xml_element( area_xml_element ) # :nodoc:
73
+ area = RCAP::Area.new( :area_desc => RCAP.xpath_text( area_xml_element, AREA_DESC_XPATH ),
74
+ :altitude => (( alt = RCAP.xpath_text( area_xml_element, ALTITUDE_XPATH )) ? alt.to_f : nil ),
75
+ :ceiling => (( ceil = RCAP.xpath_text( area_xml_element, CEILING_XPATH )) ? ceil.to_f : nil ),
76
+ :circles => RCAP.xpath_match( area_xml_element, RCAP::Circle::XPATH ).map{ |circle_element| RCAP::Circle.from_xml_element( circle_element )},
77
+ :geocodes => RCAP.xpath_match( area_xml_element, RCAP::Geocode::XPATH ).map{ |geocode_element| RCAP::Geocode.from_xml_element( geocode_element )},
78
+ :polygons => RCAP.xpath_match( area_xml_element, RCAP::Polygon::XPATH ).map{ |polygon_element| RCAP::Polygon.from_xml_element( polygon_element )})
79
+ area
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,62 @@
1
+ module RCAP
2
+ # A Circle object is valid if
3
+ # * it has a point which is a valid Point object
4
+ # * it has a radius with a value greater than zero
5
+ class Circle
6
+ include Validation
7
+
8
+ # Instance of Point class
9
+ attr_accessor( :point )
10
+ # Expresed in kilometers
11
+ attr_accessor( :radius )
12
+
13
+ validates_presence_of( :point, :radius )
14
+ validates_numericality_of( :radius , :greater_than => 0 )
15
+ validates_validity_of( :point )
16
+
17
+ XML_ELEMENT_NAME = 'circle' # :nodoc:
18
+
19
+ XPATH = 'cap:circle' # :nodoc:
20
+
21
+ def initialize( attributes = {} )
22
+ @point = attributes[ :point ]
23
+ @radius = attributes[ :radius ]
24
+ end
25
+
26
+ def to_s # :nodoc:
27
+ "#{ self.point.to_s } #{ self.radius }"
28
+ end
29
+
30
+ def inspect # :nodoc:
31
+ "(#{ self.point.lattitude},#{ self.point.longitude } #{ self.radius })"
32
+ end
33
+
34
+ def to_xml_element # :nodoc:
35
+ xml_element = REXML::Element.new( XML_ELEMENT_NAME )
36
+ xml_element.add_text( self.to_s )
37
+ xml_element
38
+ end
39
+
40
+ def to_xml # :nodoc:
41
+ self.to_xml_element.to_s
42
+ end
43
+
44
+ def self.parse_circle_string( circle_string ) # :nodoc:
45
+ coordinates, radius = circle_string.split( ' ' )
46
+ lattitude, longitude = coordinates.split( ',' )
47
+ [ lattitude, longitude, radius ].map{ |e| e.to_f }
48
+ end
49
+
50
+ def self.from_xml_element( circle_xml_element ) # :nodoc:
51
+ lattitude, longitude, radius = self.parse_circle_string( circle_xml_element.text )
52
+ point = RCAP::Point.new( :lattitude => lattitude, :longitude => longitude )
53
+ circle = self.new( :point => point,
54
+ :radius => radius )
55
+ end
56
+
57
+ # Two circles are equivalent if their point and radius are equal.
58
+ def ==( other )
59
+ self.point == other.point && self.radius == other.radius
60
+ end
61
+ end
62
+ end