openstreetmap 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1 @@
1
+ v0.1. First version
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.
@@ -0,0 +1,29 @@
1
+ CHANGELOG
2
+ LICENSE
3
+ Manifest
4
+ README.md
5
+ Rakefile
6
+ lib/changeset_callbacks.rb
7
+ lib/hash.rb
8
+ lib/open_street_map/api.rb
9
+ lib/open_street_map/basic_auth_client.rb
10
+ lib/open_street_map/changeset.rb
11
+ lib/open_street_map/element.rb
12
+ lib/open_street_map/errors.rb
13
+ lib/open_street_map/member.rb
14
+ lib/open_street_map/node.rb
15
+ lib/open_street_map/oauth_client.rb
16
+ lib/open_street_map/parser.rb
17
+ lib/open_street_map/relation.rb
18
+ lib/open_street_map/tags.rb
19
+ lib/open_street_map/user.rb
20
+ lib/open_street_map/way.rb
21
+ lib/openstreetmap.rb
22
+ openstreetmap.gemspec
23
+ spec/open_street_map/changeset_spec.rb
24
+ spec/open_street_map/node_spec.rb
25
+ spec/open_street_map/relation_spec.rb
26
+ spec/open_street_map/way_spec.rb
27
+ spec/open_street_map_changeset_spec.rb
28
+ spec/open_street_map_node_spec.rb
29
+ spec/open_street_map_way_spec.rb
@@ -0,0 +1,70 @@
1
+ # OpenStreetMap for ruby
2
+
3
+ [![alt text][2]][1]
4
+
5
+ [1]: http://travis-ci.org/#!/sozialhelden/openstreetmap
6
+ [2]: https://secure.travis-ci.org/sozialhelden/openstreetmap.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 'openstreetmap', :git => 'git://github.com/sozialhelden/openstreetmap'
30
+
31
+ Then run
32
+
33
+ bundle install
34
+
35
+ ## Getting started
36
+
37
+ OK, gimme some code:
38
+
39
+ require 'openstreetmap'
40
+ api = OpenStreetMap::Api.new
41
+ node = api.find_node(123)
42
+ => #<OpenStreetMap::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 = OpenStreetMap::BasicAuthClient.new('osm_user_name', 'password')
47
+ api = OpenStreetMap::Api.new(client)
48
+ node = OpenStreetMap::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 = OpenStreetMap::OauthClient.new(access_token)
61
+ api = OpenStreetMap::Api.new(client)
62
+ node = OpenStreetMap::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.
@@ -0,0 +1,39 @@
1
+ #coding:utf-8
2
+ require 'rake'
3
+ require 'echoe'
4
+
5
+ Echoe.new('openstreetmap', '0.2.1') do |p|
6
+
7
+ p.description = "OpenStreetMap API client for ruby"
8
+ p.url = "https://github.com/sozialhelden/openstreetmap"
9
+ p.author = ["Christoph B\303\274nte", "Enno Brehm"]
10
+ p.email = ["info@christophbuente.de", "guildenstern@gmx.de"]
11
+
12
+ p.retain_gemspec = true
13
+
14
+ p.ignore_pattern = %w{
15
+ Gemfile
16
+ Gemfile.lock
17
+ vendor/**/*
18
+ tmp/*
19
+ log/*
20
+ *.tmproj
21
+ }
22
+
23
+ p.runtime_dependencies = [ "httparty", "libxml-ruby", "builder", "oauth", "activemodel" ]
24
+ p.development_dependencies = [ "echoe", "rspec", "webmock" ]
25
+ end
26
+
27
+ require 'rspec/core'
28
+ require 'rspec/core/rake_task'
29
+ require 'webmock/rspec'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ task :default => :spec
@@ -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
@@ -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
@@ -0,0 +1,214 @@
1
+ require 'httparty'
2
+ module OpenStreetMap
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) -> OpenStreetMap::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) -> OpenStreetMap::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) -> OpenStreetMap::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) -> OpenStreetMap::Changeset
55
+ #
56
+ def find_changeset(id)
57
+ find_element('changeset', id)
58
+ end
59
+
60
+ # Get the user which represented by the OpenStreetMap::Client
61
+ #
62
+ # call-seq: find_user -> OpenStreetMap::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) -> OpenStreetMap::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 OpenStreetMap
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 OpenStreetMap
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