openstreetmap 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,280 @@
1
+ module OpenStreetMap
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 OpenStreetMap::Element from API
34
+ def self.from_api(id, api=OpenStreetMap::API.new) #:nodoc:
35
+ raise NotImplementedError.new('Element is a virtual base class for the Node, Way, and Relation classes') if self.class == OpenStreetMap::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 == OpenStreetMap::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 OpenStreetMap::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=OpenStreetMap::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 OpenStreetMap::API object. If none is specified
182
+ # the default OSM API is used.
183
+ #
184
+ # Returns an array of OpenStreetMap::Node, OpenStreetMap::Way, or OpenStreetMap::Relation objects
185
+ # with all the versions.
186
+ def get_history_from_api(api=OpenStreetMap::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 = OpenStreetMap::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 OpenStreetMap
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 OpenStreetMap is instantiated without a client
13
+ class CredentialsMissing < StandardError; end
14
+
15
+ # This error occurs when OpenStreetMap 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 OpenStreetMap
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 OpenStreetMap
3
+ # OpenStreetMap Node.
4
+ #
5
+ # To create a new OpenStreetMap::Node object:
6
+ # node = OpenStreetMap::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 = OpenStreetMap::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 OpenStreetMap
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