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