rosemary 0.2.2
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.
- data/.gitignore +41 -0
- data/.rbenv-version +1 -0
- data/.rspec +2 -0
- data/.rvmrc +2 -0
- data/.travis.yml +7 -0
- data/CHANGELOG +1 -0
- data/Gemfile +3 -0
- data/LICENSE +7 -0
- data/Manifest +31 -0
- data/README.md +70 -0
- data/Rakefile +16 -0
- data/lib/changeset_callbacks.rb +31 -0
- data/lib/hash.rb +19 -0
- data/lib/rosemary/api.rb +214 -0
- data/lib/rosemary/basic_auth_client.rb +15 -0
- data/lib/rosemary/changeset.rb +93 -0
- data/lib/rosemary/element.rb +280 -0
- data/lib/rosemary/errors.rb +55 -0
- data/lib/rosemary/member.rb +39 -0
- data/lib/rosemary/node.rb +51 -0
- data/lib/rosemary/oauth_client.rb +31 -0
- data/lib/rosemary/parser.rb +123 -0
- data/lib/rosemary/relation.rb +52 -0
- data/lib/rosemary/tags.rb +26 -0
- data/lib/rosemary/user.rb +34 -0
- data/lib/rosemary/version.rb +3 -0
- data/lib/rosemary/way.rb +84 -0
- data/lib/rosemary.rb +33 -0
- data/rosemary.gemspec +58 -0
- data/spec/integration/changeset_spec.rb +132 -0
- data/spec/integration/node_spec.rb +384 -0
- data/spec/integration/way_spec.rb +52 -0
- data/spec/models/changeset_spec.rb +90 -0
- data/spec/models/node_spec.rb +87 -0
- data/spec/models/relation_spec.rb +26 -0
- data/spec/models/way_spec.rb +77 -0
- data/spec/spec_helper.rb +3 -0
- metadata +244 -0
@@ -0,0 +1,280 @@
|
|
1
|
+
module Rosemary
|
2
|
+
# This is a virtual parent class for the OSM objects Node, Way and Relation.
|
3
|
+
class Element
|
4
|
+
include ActiveModel::Validations
|
5
|
+
|
6
|
+
# Unique ID
|
7
|
+
attr_reader :id
|
8
|
+
|
9
|
+
# The version of this object (as read from file, it
|
10
|
+
# is not updated by operations to this object)
|
11
|
+
# API 0.6 and above only
|
12
|
+
attr_accessor :version
|
13
|
+
|
14
|
+
# The user who last edited this object (as read from file, it
|
15
|
+
# is not updated by operations to this object)
|
16
|
+
attr_accessor :user
|
17
|
+
|
18
|
+
# The user id of the user who last edited this object (as read from file, it
|
19
|
+
# is not updated by operations to this object)
|
20
|
+
# API 0.6 and above only
|
21
|
+
attr_accessor :uid
|
22
|
+
|
23
|
+
# Last change of this object (as read from file, it is not
|
24
|
+
# updated by operations to this object)
|
25
|
+
attr_reader :timestamp
|
26
|
+
|
27
|
+
# The changeset the last change of this object was made with.
|
28
|
+
attr_accessor :changeset
|
29
|
+
|
30
|
+
# Tags for this object
|
31
|
+
attr_reader :tags
|
32
|
+
|
33
|
+
# Get Rosemary::Element from API
|
34
|
+
def self.from_api(id, api=Rosemary::API.new) #:nodoc:
|
35
|
+
raise NotImplementedError.new('Element is a virtual base class for the Node, Way, and Relation classes') if self.class == Rosemary::Element
|
36
|
+
api.get_object(type, id)
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(attrs = {}) #:nodoc:
|
40
|
+
raise NotImplementedError.new('Element is a virtual base class for the Node, Way, and Relation classes') if self.class == Rosemary::Element
|
41
|
+
attrs = {'version' => 1, 'uid' => 1}.merge(attrs.stringify_keys!)
|
42
|
+
@id = attrs['id'].to_i if attrs['id']
|
43
|
+
@version = attrs['version'].to_i
|
44
|
+
@uid = attrs['uid'].to_i
|
45
|
+
@user = attrs['user']
|
46
|
+
@timestamp = Time.parse(attrs['timestamp']) rescue nil
|
47
|
+
@changeset = attrs['changeset'].to_i
|
48
|
+
@tags = Tags.new
|
49
|
+
add_tags(attrs['tag']) if attrs['tag']
|
50
|
+
end
|
51
|
+
|
52
|
+
# Create an error when somebody tries to set the ID.
|
53
|
+
# (We need this here because otherwise method_missing will be called.)
|
54
|
+
def id=(id) # :nodoc:
|
55
|
+
raise NotImplementedError.new('id can not be changed once the object was created')
|
56
|
+
end
|
57
|
+
|
58
|
+
# Set timestamp for this object.
|
59
|
+
def timestamp=(timestamp)
|
60
|
+
@timestamp = _check_timestamp(timestamp)
|
61
|
+
end
|
62
|
+
|
63
|
+
# The list of attributes for this object
|
64
|
+
def attribute_list # :nodoc:
|
65
|
+
[:id, :version, :uid, :user, :timestamp, :tags]
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns a hash of all non-nil attributes of this object.
|
69
|
+
#
|
70
|
+
# Keys of this hash are <tt>:id</tt>, <tt>:user</tt>,
|
71
|
+
# and <tt>:timestamp</tt>. For a Node also <tt>:lon</tt>
|
72
|
+
# and <tt>:lat</tt>.
|
73
|
+
#
|
74
|
+
# call-seq: attributes -> Hash
|
75
|
+
#
|
76
|
+
def attributes
|
77
|
+
attrs = Hash.new
|
78
|
+
attribute_list.each do |attribute|
|
79
|
+
value = self.send(attribute)
|
80
|
+
attrs[attribute] = value unless value.nil?
|
81
|
+
end
|
82
|
+
attrs
|
83
|
+
end
|
84
|
+
|
85
|
+
# Get tag value
|
86
|
+
def [](key)
|
87
|
+
tags[key]
|
88
|
+
end
|
89
|
+
|
90
|
+
# Set tag
|
91
|
+
def []=(key, value)
|
92
|
+
tags[key] = value
|
93
|
+
end
|
94
|
+
|
95
|
+
# Add one or more tags to this object.
|
96
|
+
#
|
97
|
+
# call-seq: add_tags(Hash) -> OsmObject
|
98
|
+
#
|
99
|
+
def add_tags(new_tags)
|
100
|
+
case new_tags
|
101
|
+
when Array # Called with an array
|
102
|
+
# Call recursively for each entry
|
103
|
+
new_tags.each do |tag_hash|
|
104
|
+
add_tags(tag_hash)
|
105
|
+
end
|
106
|
+
when Hash # Called with a hash
|
107
|
+
#check if it is weird {'k' => 'key', 'v' => 'value'} syntax
|
108
|
+
if (new_tags.size == 2 && new_tags.keys.include?('k') && new_tags.keys.include?('v'))
|
109
|
+
# call recursively with values from k and v keys.
|
110
|
+
add_tags({new_tags['k'] => new_tags['v']})
|
111
|
+
else
|
112
|
+
# OK, this seems to be a proper ruby hash with a single entry
|
113
|
+
new_tags.each do |k,v|
|
114
|
+
self.tags[k] = v
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
self # return self so calls can be chained
|
119
|
+
end
|
120
|
+
|
121
|
+
def update_attributes(attribute_hash)
|
122
|
+
dirty = false
|
123
|
+
attribute_hash.each do |key,value|
|
124
|
+
if self.send(key).to_s != value.to_s
|
125
|
+
self.send("#{key}=", value.to_s)
|
126
|
+
dirty = true
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
puts "returning #{dirty}"
|
131
|
+
dirty
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
# Has this object any tags?
|
136
|
+
#
|
137
|
+
# call-seq: is_tagged?
|
138
|
+
#
|
139
|
+
def is_tagged?
|
140
|
+
! @tags.empty?
|
141
|
+
end
|
142
|
+
|
143
|
+
# Create a new GeoRuby::Shp4r::ShpRecord with the geometry of
|
144
|
+
# this object and the given attributes.
|
145
|
+
#
|
146
|
+
# This only works if the GeoRuby library is included.
|
147
|
+
#
|
148
|
+
# geom:: Geometry
|
149
|
+
# attributes:: Hash with attributes
|
150
|
+
#
|
151
|
+
# call-seq: shape(attributes) -> GeoRuby::Shp4r::ShpRecord
|
152
|
+
#
|
153
|
+
# Example:
|
154
|
+
# require 'rubygems'
|
155
|
+
# require 'geo_ruby'
|
156
|
+
# node = Node(nil, nil, nil, 7.84, 54.34)
|
157
|
+
# g = node.point
|
158
|
+
# node.shape(g, :type => 'Pharmacy', :name => 'Hyde Park Pharmacy')
|
159
|
+
#
|
160
|
+
def shape(geom, attributes)
|
161
|
+
fields = Hash.new
|
162
|
+
attributes.each do |key, value|
|
163
|
+
fields[key.to_s] = value
|
164
|
+
end
|
165
|
+
GeoRuby::Shp4r::ShpRecord.new(geom, fields)
|
166
|
+
end
|
167
|
+
|
168
|
+
# Get all relations from the API that have his object as members.
|
169
|
+
#
|
170
|
+
# The optional parameter is an Rosemary::API object. If none is specified
|
171
|
+
# the default OSM API is used.
|
172
|
+
#
|
173
|
+
# Returns an array of Relation objects or an empty array.
|
174
|
+
#
|
175
|
+
def get_relations_from_api(api=Rosemary::API.new)
|
176
|
+
api.get_relations_referring_to_object(type, self.id.to_i)
|
177
|
+
end
|
178
|
+
|
179
|
+
# Get the history of this object from the API.
|
180
|
+
#
|
181
|
+
# The optional parameter is an Rosemary::API object. If none is specified
|
182
|
+
# the default OSM API is used.
|
183
|
+
#
|
184
|
+
# Returns an array of Rosemary::Node, Rosemary::Way, or Rosemary::Relation objects
|
185
|
+
# with all the versions.
|
186
|
+
def get_history_from_api(api=Rosemary::API.new)
|
187
|
+
api.get_history(type, self.id.to_i)
|
188
|
+
end
|
189
|
+
|
190
|
+
# All other methods are mapped so its easy to access tags: For
|
191
|
+
# instance obj.name is the same as obj.tags['name']. This works
|
192
|
+
# for getting and setting tags.
|
193
|
+
#
|
194
|
+
# node = Rosemary::Node.new
|
195
|
+
# node.add_tags( 'highway' => 'residential', 'name' => 'Main Street' )
|
196
|
+
# node.highway #=> 'residential'
|
197
|
+
# node.highway = 'unclassified' #=> 'unclassified'
|
198
|
+
# node.name #=> 'Main Street'
|
199
|
+
#
|
200
|
+
# In addition methods of the form <tt>key?</tt> are used to
|
201
|
+
# check boolean tags. For instance +oneway+ can be 'true' or
|
202
|
+
# 'yes' or '1', all meaning the same.
|
203
|
+
#
|
204
|
+
# way.oneway?
|
205
|
+
#
|
206
|
+
# will check this. It returns true if the value of this key is
|
207
|
+
# either 'true', 'yes' or '1'.
|
208
|
+
def method_missing(method, *args)
|
209
|
+
methodname = method.to_s
|
210
|
+
if methodname.slice(-1, 1) == '='
|
211
|
+
if args.size != 1
|
212
|
+
raise ArgumentError.new("wrong number of arguments (#{args.size} for 1)")
|
213
|
+
end
|
214
|
+
tags[methodname.chop] = args[0]
|
215
|
+
elsif methodname.slice(-1, 1) == '?'
|
216
|
+
if args.size != 0
|
217
|
+
raise ArgumentError.new("wrong number of arguments (#{args.size} for 0)")
|
218
|
+
end
|
219
|
+
tags[methodname.chop] =~ /^(true|yes|1)$/
|
220
|
+
else
|
221
|
+
if args.size != 0
|
222
|
+
raise ArgumentError.new("wrong number of arguments (#{args.size} for 0)")
|
223
|
+
end
|
224
|
+
tags[methodname]
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def initialize_copy(from)
|
229
|
+
super
|
230
|
+
@tags = from.tags.dup
|
231
|
+
end
|
232
|
+
|
233
|
+
private
|
234
|
+
|
235
|
+
# Return next free ID
|
236
|
+
def _next_id
|
237
|
+
@@id -= 1
|
238
|
+
@@id
|
239
|
+
end
|
240
|
+
|
241
|
+
def _check_id(id)
|
242
|
+
if id.kind_of?(Integer)
|
243
|
+
return id
|
244
|
+
elsif id.kind_of?(String)
|
245
|
+
raise ArgumentError, "ID must be an integer" unless id =~ /^-?[0-9]+$/
|
246
|
+
return id.to_i
|
247
|
+
else
|
248
|
+
raise ArgumentError, "ID must be integer or string with integer"
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def _check_timestamp(timestamp)
|
253
|
+
if timestamp !~ /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(Z|([+-][0-9]{2}:[0-9]{2}))$/
|
254
|
+
raise ArgumentError, "Timestamp is in wrong format (must be 'yyyy-mm-ddThh:mm:ss(Z|[+-]mm:ss)')"
|
255
|
+
end
|
256
|
+
timestamp
|
257
|
+
end
|
258
|
+
|
259
|
+
def _check_lon(lon)
|
260
|
+
if lon.kind_of?(Numeric)
|
261
|
+
return lon.to_s
|
262
|
+
elsif lon.kind_of?(String)
|
263
|
+
return lon
|
264
|
+
else
|
265
|
+
raise ArgumentError, "'lon' must be number or string containing number"
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def _check_lat(lat)
|
270
|
+
if lat.kind_of?(Numeric)
|
271
|
+
return lat.to_s
|
272
|
+
elsif lat.kind_of?(String)
|
273
|
+
return lat
|
274
|
+
else
|
275
|
+
raise ArgumentError, "'lat' must be number or string containing number"
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
end
|
280
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Rosemary
|
2
|
+
# Unspecified OSM API error.
|
3
|
+
class Error < StandardError
|
4
|
+
attr_reader :data
|
5
|
+
|
6
|
+
def initialize(data)
|
7
|
+
@data = data
|
8
|
+
super
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# This error occurs when Rosemary is instantiated without a client
|
13
|
+
class CredentialsMissing < StandardError; end
|
14
|
+
|
15
|
+
# This error occurs when Rosemary has no changeset.
|
16
|
+
class ChangesetMissing < StandardError; end
|
17
|
+
|
18
|
+
# An object was not found in the database.
|
19
|
+
class NotFound < Error; end
|
20
|
+
|
21
|
+
# The API returned HTTP 400 (Bad Request).
|
22
|
+
class BadRequest < Error; end # 400
|
23
|
+
|
24
|
+
# The API operation wasn't authorized. This happens if you didn't set the user and
|
25
|
+
# password for a write operation.
|
26
|
+
class Unauthorized < Error; end # 401
|
27
|
+
|
28
|
+
# The object was not found (HTTP 404). Generally means that the object doesn't exist
|
29
|
+
# and never has.
|
30
|
+
class NotFound < Error; end # 404
|
31
|
+
|
32
|
+
# If the request is not a HTTP PUT request
|
33
|
+
class MethodNotAllowed < Error; end # 405
|
34
|
+
|
35
|
+
# If the changeset in question has already been closed
|
36
|
+
class Conflict < Error; end # 409
|
37
|
+
|
38
|
+
# The object was not found (HTTP 410), but it used to exist. This generally means
|
39
|
+
# that the object existed at some point, but was deleted.
|
40
|
+
class Gone < Error; end # 410
|
41
|
+
|
42
|
+
# When a node is still used by a way
|
43
|
+
# When a node is still member of a relation
|
44
|
+
# When a way is still member of a relation
|
45
|
+
# When a relation is still member of another relation
|
46
|
+
class Precondition < Error; end # 412
|
47
|
+
|
48
|
+
# Unspecified API server error.
|
49
|
+
class ServerError < Error; end # 500
|
50
|
+
|
51
|
+
class Unavailable < Error; end # 503
|
52
|
+
|
53
|
+
class NotImplemented < Error; end # This method is not implemented yet.
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'builder'
|
2
|
+
module Rosemary
|
3
|
+
# A member of an OpenStreetMap Relation.
|
4
|
+
class Member
|
5
|
+
|
6
|
+
# Role this member has in the relationship
|
7
|
+
attr_accessor :role
|
8
|
+
|
9
|
+
# Type of referenced object (can be 'node', 'way', or 'relation')
|
10
|
+
attr_reader :type
|
11
|
+
|
12
|
+
# ID of referenced object
|
13
|
+
attr_reader :ref
|
14
|
+
|
15
|
+
# Create a new Member object. Type can be one of 'node', 'way' or
|
16
|
+
# 'relation'. Ref is the ID of the corresponding Node, Way, or
|
17
|
+
# Relation. Role is a freeform string and can be empty.
|
18
|
+
def initialize(type, ref, role='')
|
19
|
+
if type !~ /^(node|way|relation)$/
|
20
|
+
raise ArgumentError.new("type must be 'node', 'way', or 'relation'")
|
21
|
+
end
|
22
|
+
if ref.to_s !~ /^[0-9]+$/
|
23
|
+
raise ArgumentError
|
24
|
+
end
|
25
|
+
@type = type
|
26
|
+
@ref = ref.to_i
|
27
|
+
@role = role
|
28
|
+
end
|
29
|
+
|
30
|
+
# Return XML for this way. This method uses the Builder library.
|
31
|
+
# The only parameter ist the builder object.
|
32
|
+
def to_xml(options = {})
|
33
|
+
xml = options[:builder] ||= Builder::XmlMarkup.new
|
34
|
+
xml.instruct! unless options[:skip_instruct]
|
35
|
+
xml.member(:type => type, :ref => ref, :role => role)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'builder'
|
2
|
+
module Rosemary
|
3
|
+
# OpenStreetMap Node.
|
4
|
+
#
|
5
|
+
# To create a new Rosemary::Node object:
|
6
|
+
# node = Rosemary::Node.new(:id => "123", :lat => "52.2", :lon => "13.4", :changeset => "12", :user => "fred", :uid => "123", :visible => true, :timestamp => "2005-07-30T14:27:12+01:00")
|
7
|
+
#
|
8
|
+
# To get a node from the API:
|
9
|
+
# node = Rosemary::Node.find(17)
|
10
|
+
#
|
11
|
+
class Node < Element
|
12
|
+
# Longitude in decimal degrees
|
13
|
+
attr_accessor :lon
|
14
|
+
|
15
|
+
# Latitude in decimal degrees
|
16
|
+
attr_accessor :lat
|
17
|
+
|
18
|
+
validates :lat, :presence => true, :numericality => {:greater_than_or_equal_to => -180, :less_than_or_equal_to => 180}
|
19
|
+
validates :lon, :presence => true, :numericality => {:greater_than_or_equal_to => -90, :less_than_or_equal_to => 90}
|
20
|
+
|
21
|
+
# Create new Node object.
|
22
|
+
#
|
23
|
+
# If +id+ is +nil+ a new unique negative ID will be allocated.
|
24
|
+
def initialize(attrs = {})
|
25
|
+
attrs.stringify_keys!
|
26
|
+
@lon = attrs['lon'].to_f rescue nil
|
27
|
+
@lat = attrs['lat'].to_f rescue nil
|
28
|
+
super(attrs)
|
29
|
+
end
|
30
|
+
|
31
|
+
def type
|
32
|
+
'Node'
|
33
|
+
end
|
34
|
+
|
35
|
+
# List of attributes for a Node
|
36
|
+
def attribute_list
|
37
|
+
[:id, :version, :uid, :user, :timestamp, :lon, :lat, :changeset]
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_xml(options = {})
|
41
|
+
xml = options[:builder] ||= Builder::XmlMarkup.new
|
42
|
+
xml.instruct! unless options[:skip_instruct]
|
43
|
+
xml.osm do
|
44
|
+
xml.node(attributes) do
|
45
|
+
tags.to_xml(:builder => xml, :skip_instruct => true)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Rosemary
|
2
|
+
class OauthClient
|
3
|
+
|
4
|
+
attr_reader :access_token
|
5
|
+
|
6
|
+
def initialize(access_token)
|
7
|
+
@access_token = access_token
|
8
|
+
end
|
9
|
+
|
10
|
+
def get(url, header={})
|
11
|
+
access_token.get(url, {'Content-Type' => 'application/xml' })
|
12
|
+
end
|
13
|
+
|
14
|
+
def put(url, options={}, header={})
|
15
|
+
body = options[:body]
|
16
|
+
access_token.put(url, body, {'Content-Type' => 'application/xml' })
|
17
|
+
end
|
18
|
+
|
19
|
+
def delete(url, options={}, header={})
|
20
|
+
raise NotImplemented.new("Delete with Oauth and OSM is not supported")
|
21
|
+
# body = options[:body]
|
22
|
+
# access_token.delete(url, {'Content-Type' => 'application/xml' })
|
23
|
+
end
|
24
|
+
|
25
|
+
def post(url, options={}, header={})
|
26
|
+
body = options[:body]
|
27
|
+
access_token.post(url, body, {'Content-Type' => 'application/xml' })
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'xml/libxml'
|
3
|
+
class Rosemary::Parser < HTTParty::Parser
|
4
|
+
include LibXML::XML::SaxParser::Callbacks
|
5
|
+
|
6
|
+
attr_accessor :context, :description, :lang, :collection
|
7
|
+
|
8
|
+
def parse
|
9
|
+
return nil if body.nil? || body.empty?
|
10
|
+
if supports_format?
|
11
|
+
self.send(format) # This is a hack, cause the xml format would not be recognized ways, but for nodes and relations
|
12
|
+
else
|
13
|
+
body
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def xml
|
18
|
+
@parser = LibXML::XML::SaxParser.string(body)
|
19
|
+
@parser.callbacks = self
|
20
|
+
@parser.parse
|
21
|
+
@collection.empty? ? @context : @collection
|
22
|
+
end
|
23
|
+
|
24
|
+
def plain
|
25
|
+
body
|
26
|
+
end
|
27
|
+
|
28
|
+
def on_start_document # :nodoc:
|
29
|
+
@collection = []
|
30
|
+
start_document if respond_to?(:start_document)
|
31
|
+
end
|
32
|
+
|
33
|
+
def on_end_document # :nodoc:
|
34
|
+
end_document if respond_to?(:end_document)
|
35
|
+
end
|
36
|
+
|
37
|
+
def on_start_element(name, attr_hash) # :nodoc:
|
38
|
+
case name
|
39
|
+
when 'node' then _start_node(attr_hash)
|
40
|
+
when 'way' then _start_way(attr_hash)
|
41
|
+
when 'relation' then _start_relation(attr_hash)
|
42
|
+
when 'changeset' then _start_changeset(attr_hash)
|
43
|
+
when 'user' then _start_user(attr_hash)
|
44
|
+
when 'tag' then _tag(attr_hash)
|
45
|
+
when 'nd' then _nd(attr_hash)
|
46
|
+
when 'member' then _member(attr_hash)
|
47
|
+
when 'home' then _home(attr_hash)
|
48
|
+
when 'description' then @description = true
|
49
|
+
when 'lang' then @lang = true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def on_end_element(name) # :nodoc:
|
54
|
+
case name
|
55
|
+
when 'description' then @description = false
|
56
|
+
when 'lang' then @lang = false
|
57
|
+
when 'changeset' then _end_changeset
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def on_characters(chars)
|
62
|
+
if @context.class.name == 'Rosemary::User'
|
63
|
+
if @description
|
64
|
+
@context.description = chars
|
65
|
+
end
|
66
|
+
if @lang
|
67
|
+
@context.languages << chars
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def _start_node(attr_hash)
|
75
|
+
@context = Rosemary::Node.new(attr_hash)
|
76
|
+
end
|
77
|
+
|
78
|
+
def _start_way(attr_hash)
|
79
|
+
@context = Rosemary::Way.new(attr_hash)
|
80
|
+
end
|
81
|
+
|
82
|
+
def _start_relation(attr_hash)
|
83
|
+
@context = Rosemary::Relation.new(attr_hash)
|
84
|
+
end
|
85
|
+
|
86
|
+
def _start_changeset(attr_hash)
|
87
|
+
@context = Rosemary::Changeset.new(attr_hash)
|
88
|
+
end
|
89
|
+
|
90
|
+
def _end_changeset
|
91
|
+
@collection << @context
|
92
|
+
end
|
93
|
+
|
94
|
+
def _start_user(attr_hash)
|
95
|
+
@context = Rosemary::User.new(attr_hash)
|
96
|
+
end
|
97
|
+
|
98
|
+
def _nd(attr_hash)
|
99
|
+
@context << attr_hash['ref']
|
100
|
+
end
|
101
|
+
|
102
|
+
def _tag(attr_hash)
|
103
|
+
if respond_to?(:tag)
|
104
|
+
return unless tag(@context, attr_hash['k'], attr_value['v'])
|
105
|
+
end
|
106
|
+
@context.tags.merge!(attr_hash['k'] => attr_hash['v'])
|
107
|
+
end
|
108
|
+
|
109
|
+
def _member(attr_hash)
|
110
|
+
new_member = Rosemary::Member.new(attr_hash['type'], attr_hash['ref'], attr_hash['role'])
|
111
|
+
if respond_to?(:member)
|
112
|
+
return unless member(@context, new_member)
|
113
|
+
end
|
114
|
+
@context.members << new_member
|
115
|
+
end
|
116
|
+
|
117
|
+
def _home(attr_hash)
|
118
|
+
@context.lat = attr_hash['lat'] if attr_hash['lat']
|
119
|
+
@context.lon = attr_hash['lon'] if attr_hash['lon']
|
120
|
+
@context.lon = attr_hash['zoom'] if attr_hash['zoom']
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Rosemary
|
2
|
+
# OpenStreetMap Relation.
|
3
|
+
#
|
4
|
+
# To create a new Rosemary::Relation object:
|
5
|
+
# relation = Rosemary::Relation.new()
|
6
|
+
#
|
7
|
+
# To get a relation from the API:
|
8
|
+
# relation = Rosemary::Relation.find(17)
|
9
|
+
#
|
10
|
+
class Relation < Element
|
11
|
+
# Array of Member objects
|
12
|
+
attr_reader :members
|
13
|
+
|
14
|
+
# Create new Relation object.
|
15
|
+
#
|
16
|
+
# If +id+ is +nil+ a new unique negative ID will be allocated.
|
17
|
+
def initialize(attrs)
|
18
|
+
attrs.stringify_keys!
|
19
|
+
@members = extract_member(attrs['member'])
|
20
|
+
super(attrs)
|
21
|
+
end
|
22
|
+
|
23
|
+
def type
|
24
|
+
'relation'
|
25
|
+
end
|
26
|
+
|
27
|
+
# Return XML for this relation. This method uses the Builder library.
|
28
|
+
# The only parameter ist the builder object.
|
29
|
+
def to_xml(option = {})
|
30
|
+
xml = options[:builder] ||= Builder::XmlMarkup.new
|
31
|
+
xml.instruct! unless options[:skip_instruct]
|
32
|
+
xml.relation(attributes) do
|
33
|
+
members.each do |member|
|
34
|
+
member.to_xml(:builder => xml, :skip_instruct => true)
|
35
|
+
end
|
36
|
+
tags.to_xml(:builder => xml, :skip_instruct => true)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
|
42
|
+
def extract_member(member_array)
|
43
|
+
return [] unless member_array && member_array.size > 0
|
44
|
+
|
45
|
+
member_array.inject([]) do |memo, member|
|
46
|
+
class_to_instantize = "Rosemary::#{member['type'].classify}".constantize
|
47
|
+
memo << class_to_instantize.new(:id => member['ref'])
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Rosemary
|
2
|
+
# A collection of OSM tags which can be attached to a Node, Way,
|
3
|
+
# or Relation.
|
4
|
+
# It is a subclass of Hash.
|
5
|
+
class Tags < Hash
|
6
|
+
|
7
|
+
# Return XML for these tags. This method uses the Builder library.
|
8
|
+
# The only parameter ist the builder object.
|
9
|
+
def to_xml(options = {})
|
10
|
+
xml = options[:builder] ||= Builder::XmlMarkup.new
|
11
|
+
xml.instruct! unless options[:skip_instruct]
|
12
|
+
each do |key, value|
|
13
|
+
xml.tag(:k => key, :v => value)
|
14
|
+
end unless empty?
|
15
|
+
end
|
16
|
+
|
17
|
+
# Return string with comma separated key=value pairs.
|
18
|
+
#
|
19
|
+
# call-seq: to_s -> String
|
20
|
+
#
|
21
|
+
def to_s
|
22
|
+
sort.collect{ |k, v| "#{k}=#{v}" }.join(', ')
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'builder'
|
2
|
+
module Rosemary
|
3
|
+
class User
|
4
|
+
# Unique ID
|
5
|
+
attr_reader :id
|
6
|
+
|
7
|
+
# Display name
|
8
|
+
attr_reader :display_name
|
9
|
+
|
10
|
+
# When this user was created
|
11
|
+
attr_reader :account_created
|
12
|
+
|
13
|
+
# A little prosa about this user
|
14
|
+
attr_accessor :description
|
15
|
+
|
16
|
+
# All languages the user can speak
|
17
|
+
attr_accessor :languages
|
18
|
+
|
19
|
+
# Lat/Lon Coordinates of the users home.
|
20
|
+
attr_accessor :lat, :lon, :zoom
|
21
|
+
|
22
|
+
# A picture from this user
|
23
|
+
attr_accessor :img
|
24
|
+
|
25
|
+
def initialize(attrs = {})
|
26
|
+
attrs.stringify_keys!
|
27
|
+
@id = attrs['id'].to_i if attrs['id']
|
28
|
+
@display_name = attrs['display_name']
|
29
|
+
@account_created = Time.parse(attrs['account_created']) rescue nil
|
30
|
+
@languages = []
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|