openstreetmap 0.2.1

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.
@@ -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