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
data/.gitignore
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
*.sw[a-p]
|
4
|
+
*.tmproj
|
5
|
+
*.tmproject
|
6
|
+
*.un~
|
7
|
+
*~
|
8
|
+
.DS_Store
|
9
|
+
.Spotlight-V100
|
10
|
+
.Trashes
|
11
|
+
._*
|
12
|
+
.bundle
|
13
|
+
.config
|
14
|
+
.directory
|
15
|
+
.elc
|
16
|
+
.emacs.desktop
|
17
|
+
.emacs.desktop.lock
|
18
|
+
.redcar
|
19
|
+
.yardoc
|
20
|
+
Desktop.ini
|
21
|
+
Gemfile.lock
|
22
|
+
Icon?
|
23
|
+
InstalledFiles
|
24
|
+
Session.vim
|
25
|
+
Thumbs.db
|
26
|
+
\#*\#
|
27
|
+
_yardoc
|
28
|
+
auto-save-list
|
29
|
+
coverage
|
30
|
+
doc
|
31
|
+
lib/bundler/man
|
32
|
+
pkg
|
33
|
+
pkg/*
|
34
|
+
rdoc
|
35
|
+
spec/reports
|
36
|
+
test/tmp
|
37
|
+
test/version_tmp
|
38
|
+
tmp
|
39
|
+
tmtags
|
40
|
+
tramp
|
41
|
+
vendor
|
data/.rbenv-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.9.3-p0
|
data/.rspec
ADDED
data/.rvmrc
ADDED
data/.travis.yml
ADDED
data/CHANGELOG
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
v0.1. First version
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Copyright (c) 2012 Christoph Bünte
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
CHANGELOG
|
2
|
+
LICENSE
|
3
|
+
Manifest
|
4
|
+
README.md
|
5
|
+
Rakefile
|
6
|
+
lib/changeset_callbacks.rb
|
7
|
+
lib/hash.rb
|
8
|
+
lib/rosemary/api.rb
|
9
|
+
lib/rosemary/basic_auth_client.rb
|
10
|
+
lib/rosemary/changeset.rb
|
11
|
+
lib/rosemary/element.rb
|
12
|
+
lib/rosemary/errors.rb
|
13
|
+
lib/rosemary/member.rb
|
14
|
+
lib/rosemary/node.rb
|
15
|
+
lib/rosemary/oauth_client.rb
|
16
|
+
lib/rosemary/parser.rb
|
17
|
+
lib/rosemary/relation.rb
|
18
|
+
lib/rosemary/tags.rb
|
19
|
+
lib/rosemary/user.rb
|
20
|
+
lib/rosemary/way.rb
|
21
|
+
lib/openstreetmap.rb
|
22
|
+
openstreetmap.gemspec
|
23
|
+
spec/api_spec.rb
|
24
|
+
spec/rosemary/changeset_spec.rb
|
25
|
+
spec/rosemary/node_spec.rb
|
26
|
+
spec/rosemary/relation_spec.rb
|
27
|
+
spec/rosemary/way_spec.rb
|
28
|
+
spec/rosemary_changeset_spec.rb
|
29
|
+
spec/rosemary_node_spec.rb
|
30
|
+
spec/rosemary_way_spec.rb
|
31
|
+
|
data/README.md
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# Rosemary: OpenStreetMap for Ruby
|
2
|
+
|
3
|
+
[![alt text][2]][1]
|
4
|
+
|
5
|
+
[1]: http://travis-ci.org/#!/sozialhelden/rosemary
|
6
|
+
[2]: https://secure.travis-ci.org/sozialhelden/rosemary.png
|
7
|
+
|
8
|
+
This ruby gem is an API client for the current OpenStreetMap [API v0.6](http://wiki.openstreetmap.org/wiki/API_v0.6). It provides easy access to OpenStreetMap (OSM) data.
|
9
|
+
|
10
|
+
## What is OpenStreetMap?
|
11
|
+
|
12
|
+
OpenStreetMap (OSM) is a collaborative project to create a free editable map of the world. Two major driving forces behind the establishment and growth of OSM have been restrictions on use or availability of map information across much of the world and the advent of inexpensive portable GPS devices.
|
13
|
+
|
14
|
+
|
15
|
+
## The OpenStreetMap Database
|
16
|
+
|
17
|
+
OpenStreetMap data is published under an open content license, with the intention of promoting free use and re-distribution of the data (both commercial and non-commercial). The license currently used is the [Creative Commons Attribution-Share Alike 2.0 licence](http://creativecommons.org/licenses/by-sa/2.0/); however, legal investigation work and community consultation is underway to relicense the project under the [Open Database License (ODbL)](http://opendatacommons.org/licenses/odbl/) from [Open Data Commons (ODC)](http://opendatacommons.org/), claimed to be more suitable for a map data set.
|
18
|
+
|
19
|
+
## Input Data
|
20
|
+
|
21
|
+
All data added to the project need to have a license compatible with the Creative Commons Attribution-Share Alike license. This can include out of copyright information, public domain or other licenses. All contributors must register with the project and agree to provide data on a Creative Commons CC-BY-SA 2.0 licence, or determine that the licensing of the source data is suitable; this may involve examining licences for government data to establish whether they are compatible.
|
22
|
+
Due to the license switch, data added in future must be compatible with both the Open Database License and the new Contributor Terms in order to be accepted.
|
23
|
+
|
24
|
+
## Installation
|
25
|
+
|
26
|
+
Put this in your Gemfile
|
27
|
+
|
28
|
+
# Gemfile
|
29
|
+
gem 'rosemary', :git => 'git://github.com/sozialhelden/rosemary'
|
30
|
+
|
31
|
+
Then run
|
32
|
+
|
33
|
+
bundle install
|
34
|
+
|
35
|
+
## Getting started
|
36
|
+
|
37
|
+
OK, gimme some code:
|
38
|
+
|
39
|
+
require 'rosemary'
|
40
|
+
api = Rosemary::Api.new
|
41
|
+
node = api.find_node(123)
|
42
|
+
=> #<Rosemary::Node:0x1019268d0 @changeset=7836598, @timestamp=Mon Apr 11 19:40:43 UTC 2011, @user="Turleder'n", @tags={}, @uid=289426, @version=4, @lat=59.9502252, @id=123, @lon=10.7899133>
|
43
|
+
|
44
|
+
Modification of data is supported too. According to the OSM license every modification to the data has to be done by a registered OSM user account. The user can be authenticated with username and password. But see yourself:
|
45
|
+
|
46
|
+
client = Rosemary::BasicAuthClient.new('osm_user_name', 'password')
|
47
|
+
api = Rosemary::Api.new(client)
|
48
|
+
node = Rosemary::Node.new(:lat => 52.0, :lon => 13.4)
|
49
|
+
api.save(node)
|
50
|
+
|
51
|
+
Yeah, i can hear you sayin: 'Seriously, do i have to provide username and password? Is that secure?' Providing username and password is prone to some security issues, especially because the OSM API does not provide an SSL service. But wait, there is some more in store for you: [OAuth](http://oauth.net/) It's much more secure for the user and your OSM app. But it comes with a price: You have to register an application on http://www.openstreetmap.org. After you have your app registered you get an app key and secret. Keep it in a save place.
|
52
|
+
|
53
|
+
consumer = OAuth::Consumer.new( 'osm_app_key', 'osm_app_secret',
|
54
|
+
{ :site => 'http://www.openstreetmap.org',
|
55
|
+
:request_token_path => '/oauth/request_token',
|
56
|
+
:access_token_path => '/oauth/access_token',
|
57
|
+
:authorize_path => '/oauth/authorize'
|
58
|
+
})
|
59
|
+
access_token = OAuth::AccessToken.new(consumer, 'osm_user_token', 'osm_user_key')
|
60
|
+
client = Rosemary::OauthClient.new(access_token)
|
61
|
+
api = Rosemary::Api.new(client)
|
62
|
+
node = Rosemary::Node.new(:lat => 52.0, :lon => 13.4)
|
63
|
+
api.save(node)
|
64
|
+
|
65
|
+
Every request to the API is now handled by the OauthClient.
|
66
|
+
|
67
|
+
|
68
|
+
## Feedback and Contributions
|
69
|
+
|
70
|
+
We appreciate your feedback and contributions. If you find a bug, feel free to to open a GitHub issue. Better yet, add a test that exposes the bug, fix it and send us a pull request.
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rspec/core'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
require 'webmock/rspec'
|
4
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
5
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
6
|
+
end
|
7
|
+
|
8
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
9
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
10
|
+
spec.rcov = true
|
11
|
+
end
|
12
|
+
|
13
|
+
task :default => :spec
|
14
|
+
|
15
|
+
require "bundler/gem_tasks"
|
16
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# According to the OSM API any changes made to the date need an open changeset which belongs to the user
|
2
|
+
# executing the changes.
|
3
|
+
# To keep the code simple the before_save method is included which makes sure and open
|
4
|
+
module ChangesetCallbacks
|
5
|
+
|
6
|
+
def self.included(into)
|
7
|
+
into.instance_methods(false).select{|method_name| [:save, :create, :update, :destroy].include?(method_name.to_sym)}.each do |m|
|
8
|
+
ChangesetCallbacks.before_write(into, m)
|
9
|
+
end
|
10
|
+
|
11
|
+
def into.method_added(m)
|
12
|
+
unless @adding
|
13
|
+
@adding = true
|
14
|
+
if [:save, :create, :update, :destroy].include?(m.to_sym)
|
15
|
+
ChangesetCallbacks.before_write(self, m)
|
16
|
+
end
|
17
|
+
@adding = false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def ChangesetCallbacks.before_write(klass, meth)
|
23
|
+
klass.class_eval do
|
24
|
+
alias_method "old_#{meth}", "#{meth}"
|
25
|
+
define_method(meth) do |*args|
|
26
|
+
find_or_create_open_changeset
|
27
|
+
self.send("old_#{meth}", *args)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/hash.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
class Hash
|
2
|
+
|
3
|
+
define_method(:symbolize_keys!) do
|
4
|
+
self.each do |k,v|
|
5
|
+
self[k.to_sym] = v
|
6
|
+
self.delete(k)
|
7
|
+
end
|
8
|
+
end unless method_defined? :symbolize_keys!
|
9
|
+
|
10
|
+
define_method(:stringify_keys!) do
|
11
|
+
temp_hash = {}
|
12
|
+
self.each do |k,v|
|
13
|
+
temp_hash[k.to_s] = self.delete(k)
|
14
|
+
end
|
15
|
+
temp_hash.each do |k,v|
|
16
|
+
self[k] = v
|
17
|
+
end
|
18
|
+
end unless method_defined? :stringify_keys!
|
19
|
+
end
|
data/lib/rosemary/api.rb
ADDED
@@ -0,0 +1,214 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
module Rosemary
|
3
|
+
class Api
|
4
|
+
include HTTParty
|
5
|
+
include ChangesetCallbacks
|
6
|
+
API_VERSION = "0.6".freeze
|
7
|
+
|
8
|
+
# the default base URI for the API
|
9
|
+
base_uri "http://www.openstreetmap.org"
|
10
|
+
#base_uri "http://api06.dev.openstreetmap.org/api/#{API_VERSION}"
|
11
|
+
|
12
|
+
default_timeout 5
|
13
|
+
|
14
|
+
parser Parser
|
15
|
+
|
16
|
+
attr_accessor :client
|
17
|
+
|
18
|
+
attr_accessor :changeset
|
19
|
+
|
20
|
+
def initialize(client=nil)
|
21
|
+
@client = client
|
22
|
+
end
|
23
|
+
|
24
|
+
def changeset!
|
25
|
+
@changeset ||= create_changeset
|
26
|
+
end
|
27
|
+
|
28
|
+
# Get a Node with specified ID from API.
|
29
|
+
#
|
30
|
+
# call-seq: find_node(id) -> Rosemary::Node
|
31
|
+
#
|
32
|
+
def find_node(id)
|
33
|
+
find_element('node', id)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Get a Way with specified ID from API.
|
37
|
+
#
|
38
|
+
# call-seq: find_way(id) -> Rosemary::Way
|
39
|
+
#
|
40
|
+
def find_way(id)
|
41
|
+
find_element('way', id)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Get a Relation with specified ID from API.
|
45
|
+
#
|
46
|
+
# call-seq: find_relation(id) -> Rosemary::Relation
|
47
|
+
#
|
48
|
+
def find_relation(id)
|
49
|
+
find_element('relation', id)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Get a Changeset with specified ID from API.
|
53
|
+
#
|
54
|
+
# call-seq: find_changeset(id) -> Rosemary::Changeset
|
55
|
+
#
|
56
|
+
def find_changeset(id)
|
57
|
+
find_element('changeset', id)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Get the user which represented by the Rosemary::Client
|
61
|
+
#
|
62
|
+
# call-seq: find_user -> Rosemary::User
|
63
|
+
#
|
64
|
+
def find_user
|
65
|
+
raise CredentialsMissing if client.nil?
|
66
|
+
resp = do_authenticated_request(:get, "/user/details")
|
67
|
+
raise resp if resp.is_a? String
|
68
|
+
resp
|
69
|
+
end
|
70
|
+
|
71
|
+
# Delete an element
|
72
|
+
def destroy(element)
|
73
|
+
raise ChangesetMissing unless changeset.open?
|
74
|
+
element.changeset = changeset.id
|
75
|
+
response = delete("/#{element.type.downcase}/#{element.id}", :body => element.to_xml) unless element.id.nil?
|
76
|
+
response.to_i # New version number
|
77
|
+
end
|
78
|
+
|
79
|
+
# Saves an element to the API.
|
80
|
+
# If it has no id yet, the element will be created, otherwise updated.
|
81
|
+
def save(element)
|
82
|
+
response = if element.id.nil?
|
83
|
+
create(element)
|
84
|
+
else
|
85
|
+
update(element)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def create(element)
|
90
|
+
put("/#{element.type.downcase}/create", :body => element.to_xml)
|
91
|
+
end
|
92
|
+
|
93
|
+
def update(element)
|
94
|
+
raise ChangesetMissing unless changeset.open?
|
95
|
+
element.changeset = changeset.id
|
96
|
+
response = put("/#{element.type.downcase}/#{element.id}", :body => element.to_xml)
|
97
|
+
response.to_i # New Version number
|
98
|
+
end
|
99
|
+
|
100
|
+
def create_changeset
|
101
|
+
changeset = Changeset.new
|
102
|
+
changeset_id = put("/changeset/create", :body => changeset.to_xml).to_i
|
103
|
+
find_changeset(changeset_id) unless changeset_id == 0
|
104
|
+
end
|
105
|
+
|
106
|
+
def close_changeset
|
107
|
+
put("/changeset/#{changeset.id}/close")
|
108
|
+
end
|
109
|
+
|
110
|
+
def find_changesets_for_user(options = {})
|
111
|
+
user_id = find_user.id
|
112
|
+
changesets = get("/changesets", :query => options.merge({:user => user_id}))
|
113
|
+
changesets.nil? ? [] : changesets
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
# Get an object ('node', 'way', or 'relation') with specified ID from API.
|
119
|
+
#
|
120
|
+
# call-seq: find_element('node', id) -> Rosemary::Element
|
121
|
+
#
|
122
|
+
def find_element(type, id)
|
123
|
+
raise ArgumentError.new("type needs to be one of 'node', 'way', and 'relation'") unless type =~ /^(node|way|relation|changeset)$/
|
124
|
+
raise TypeError.new('id needs to be a positive integer') unless(id.kind_of?(Fixnum) && id > 0)
|
125
|
+
response = get("/#{type}/#{id}")
|
126
|
+
response.is_a?(Array ) ? response.first : response
|
127
|
+
end
|
128
|
+
|
129
|
+
# most GET requests are valid without authentication, so this is the standard
|
130
|
+
def get(url, options = {})
|
131
|
+
do_request(:get, url, options)
|
132
|
+
end
|
133
|
+
|
134
|
+
# all PUT requests need authorization, so this is the stanard
|
135
|
+
def put(url, options = {})
|
136
|
+
do_authenticated_request(:put, url, options)
|
137
|
+
end
|
138
|
+
|
139
|
+
# all POST requests need authorization, so this is the stanard
|
140
|
+
def post(url, options = {})
|
141
|
+
do_authenticated_request(:post, url, options)
|
142
|
+
end
|
143
|
+
|
144
|
+
# all DELETE requests need authorization, so this is the stanard
|
145
|
+
def delete(url, options = {})
|
146
|
+
do_authenticated_request(:delete, url, options)
|
147
|
+
end
|
148
|
+
|
149
|
+
def api_url(url)
|
150
|
+
"/api/#{API_VERSION}" + url
|
151
|
+
end
|
152
|
+
|
153
|
+
# Do a API request without authentication
|
154
|
+
def do_request(method, url, options = {})
|
155
|
+
begin
|
156
|
+
response = self.class.send(method, api_url(url), options)
|
157
|
+
check_response_codes(response)
|
158
|
+
response.parsed_response
|
159
|
+
rescue Timeout::Error
|
160
|
+
raise Unavailable.new('Service Unavailable')
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# Do a API request with authentication, using the given client
|
165
|
+
def do_authenticated_request(method, url, options = {})
|
166
|
+
begin
|
167
|
+
response = case client
|
168
|
+
when BasicAuthClient
|
169
|
+
self.class.send(method, api_url(url), options.merge(:basic_auth => client.credentials))
|
170
|
+
when OauthClient
|
171
|
+
# We have to wrap the result of the access_token request into an HTTParty::Response object
|
172
|
+
# to keep duck typing with HTTParty
|
173
|
+
result = client.send(method, api_url(url), options)
|
174
|
+
content_type = Parser.format_from_mimetype(result.content_type)
|
175
|
+
parsed_response = Parser.call(result.body, content_type)
|
176
|
+
|
177
|
+
HTTParty::Response.new(nil, result, parsed_response)
|
178
|
+
else
|
179
|
+
raise CredentialsMissing
|
180
|
+
end
|
181
|
+
check_response_codes(response)
|
182
|
+
response.parsed_response
|
183
|
+
rescue Timeout::Error
|
184
|
+
raise Unavailable.new('Service Unavailable')
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def find_open_changeset
|
189
|
+
find_changesets_for_user(:open => true).first
|
190
|
+
end
|
191
|
+
|
192
|
+
def find_or_create_open_changeset(options = {})
|
193
|
+
@changeset = (find_open_changeset || create_changeset)
|
194
|
+
end
|
195
|
+
|
196
|
+
def check_response_codes(response)
|
197
|
+
body = response.body
|
198
|
+
case response.code.to_i
|
199
|
+
when 200 then return
|
200
|
+
when 400 then raise BadRequest.new(body)
|
201
|
+
when 401 then raise Unauthorized.new(body)
|
202
|
+
when 404 then raise NotFound.new(body)
|
203
|
+
when 405 then raise MethodNotAllowed.new(body)
|
204
|
+
when 409 then raise Conflict.new(body)
|
205
|
+
when 410 then raise Gone.new(body)
|
206
|
+
when 412 then raise Precondition.new(body)
|
207
|
+
when 500 then raise ServerError
|
208
|
+
when 503 then raise Unavailable.new('Service Unavailable')
|
209
|
+
else raise Error
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Rosemary
|
2
|
+
class BasicAuthClient
|
3
|
+
|
4
|
+
attr_reader :username, :password
|
5
|
+
|
6
|
+
def initialize(username, password)
|
7
|
+
@username = username
|
8
|
+
@password = password
|
9
|
+
end
|
10
|
+
|
11
|
+
def credentials
|
12
|
+
{:username => username, :password => password}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'builder'
|
2
|
+
module Rosemary
|
3
|
+
class Changeset
|
4
|
+
# Unique ID
|
5
|
+
attr_reader :id
|
6
|
+
|
7
|
+
# The user who last edited this object (as read from file, it
|
8
|
+
# is not updated by operations to this object)
|
9
|
+
attr_accessor :user
|
10
|
+
|
11
|
+
# The user id of the user who last edited this object (as read from file, it
|
12
|
+
# is not updated by operations to this object)
|
13
|
+
# API 0.6 and above only
|
14
|
+
attr_accessor :uid
|
15
|
+
|
16
|
+
# True if this changeset is still open.
|
17
|
+
attr_accessor :open
|
18
|
+
|
19
|
+
# Creation date of this changeset
|
20
|
+
attr_accessor :created_at
|
21
|
+
|
22
|
+
# When the changeset was closed
|
23
|
+
attr_accessor :closed_at
|
24
|
+
|
25
|
+
# Bounding box surrounding all changes made in this changeset
|
26
|
+
attr_accessor :min_lat, :min_lon, :max_lat, :max_lon
|
27
|
+
|
28
|
+
# Tags for this object
|
29
|
+
attr_reader :tags
|
30
|
+
|
31
|
+
def initialize(attrs = {}) #:nodoc:
|
32
|
+
attrs.stringify_keys!
|
33
|
+
@id = attrs['id'].to_i if attrs['id']
|
34
|
+
@uid = attrs['uid'].to_i
|
35
|
+
@user = attrs['user']
|
36
|
+
@created_at = Time.parse(attrs['created_at']) rescue nil
|
37
|
+
@closed_at = Time.parse(attrs['closed_at']) rescue nil
|
38
|
+
@open = attrs['open']
|
39
|
+
@tags = Tags.new
|
40
|
+
@tags[:created_by] = 'osm for ruby'
|
41
|
+
@min_lat = attrs['min_lat'].to_f
|
42
|
+
@min_lon = attrs['min_lon'].to_f
|
43
|
+
@max_lat = attrs['max_lat'].to_f
|
44
|
+
@max_lon = attrs['max_lon'].to_f
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
# Set timestamp for this object.
|
49
|
+
def created_at=(timestamp)
|
50
|
+
@created_at = Time.parse(timestamp)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Is this changeset still open?
|
54
|
+
def open?
|
55
|
+
["yes", "1", "t", "true"].include?(open)
|
56
|
+
end
|
57
|
+
|
58
|
+
# List of attributes for a Changeset
|
59
|
+
def attribute_list
|
60
|
+
[:id, :user, :uid, :open, :created_at, :closed_at, :min_lat, :max_lat, :min_lon, :max_lon]
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns a hash of all non-nil attributes of this object.
|
64
|
+
#
|
65
|
+
# Keys of this hash are <tt>:id</tt>, <tt>:user</tt>,
|
66
|
+
# and <tt>:timestamp</tt>. For a Node also <tt>:lon</tt>
|
67
|
+
# and <tt>:lat</tt>.
|
68
|
+
#
|
69
|
+
# call-seq: attributes -> Hash
|
70
|
+
#
|
71
|
+
def attributes
|
72
|
+
attrs = Hash.new
|
73
|
+
attribute_list.each do |attribute|
|
74
|
+
value = self.send(attribute)
|
75
|
+
attrs[attribute] = value unless value.nil?
|
76
|
+
end
|
77
|
+
attrs
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_xml(options = {})
|
81
|
+
xml = options[:builder] ||= Builder::XmlMarkup.new
|
82
|
+
xml.instruct! unless options[:skip_instruct]
|
83
|
+
xml.osm do
|
84
|
+
xml.changeset(attributes) do
|
85
|
+
tags.each do |k,v|
|
86
|
+
xml.tag(:k => k, :v => v)
|
87
|
+
end unless tags.empty?
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|