steflewandowski-geoapi 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2009 Chris Bruce
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,156 @@
1
+ = OVERVIEW
2
+
3
+ Unfortunately I've decided to 'fail fast' and am giving up on developing this gem:
4
+ The API isn't yet reliable enough to be able to develop with it - I'm consistently seeing the following:
5
+
6
+ * 504 bad gateway
7
+ * Connections that remain open
8
+ * Every other request giving a response, others just time out
9
+ * Search for user-entities doesn't appear to function (the reason for the app I am working on)
10
+
11
+ Sorry - if anyone wants to pick up where I left off, feel free.
12
+
13
+
14
+
15
+ == Code examples:
16
+
17
+ === Setting the API KEY
18
+
19
+ There are three methods for setting the apikey variable which must be sent with all requests. They are:
20
+
21
+ Defining it. Easiest: sets the API Key for use globally. Use this method unless you need multiple API Keys:
22
+
23
+ GEOAPI_KEY = "<my-apikey>"
24
+
25
+ For Rails, put this in environment.rb
26
+
27
+ Using an Environment variable in your shell.
28
+
29
+ ENV["GEOAPI_KEY"] = <my-apikey>
30
+
31
+ Using a 'Client' so you can use more than one API Key per application:
32
+
33
+ @client = GeoAPI::Client.new("<my-apikey>")
34
+ @entity = GeoAPI::Entity.find_by_id('12345', :client=>@client)
35
+
36
+ @other_client = GeoAPI::Client.new("<my-other-apikey>")
37
+ @other_entity = GeoAPI::Entity.find_by_id('abcde', :client=>@other_client)
38
+
39
+ You can also send :apikey=>"<my-apikey>" in options for Entity methods:
40
+
41
+ @entity = GeoAPI::Entity.find_by_id('12345', :apikey=>"<my-apikey>")
42
+
43
+ === Creating new Entities
44
+
45
+ @entity = Entity.create_at_lat_lng(:id=>"moseley",:name=>"Moseley", :lat=>52.446506, :lng=>1.888213)
46
+
47
+ Creates a new Entity object at the given latitude/longitude point.
48
+
49
+ Other ways of creating entities are not yet supported, but there are Polygon and Multipoint models which need building.
50
+
51
+ == Finding Entities
52
+
53
+ Entity.search(options)
54
+ Performs and Entity search on the API. Takes a hash of options as documented on GeoAPI.com
55
+
56
+ Entity.find(options)
57
+ Finds a specific entity - you must pass :guid or :id
58
+
59
+ == Standard CRUD methods
60
+
61
+ Entity.create_at_lat_lng(:id=>"moseley",:name=>"Moseley", :lat=>52.446506, :lng=>1.888213)
62
+ The easiest way to create a new User Entity - requires an ID and a name.
63
+
64
+ Entity.find_by_id("moseley")
65
+ Gets an entity and returns the result.
66
+
67
+ Entity.find_by_guid("user-abc123-moseley")
68
+ An ID is useful for your application, but its actual GUID can also be used to retrieve it.
69
+
70
+ Entity.delete({:id=>"moseley"}) and
71
+ @entity.delete
72
+ Destroys an entity, either via guid or id.
73
+
74
+ @entity.update
75
+
76
+ == Eager loading and caching
77
+
78
+ This gem does _not_ eager load all results of a search, nor does it cache them, In order to use an item retrieved from a request, you must first load it. Eg.:
79
+
80
+ @entities = Entity.find(options)
81
+
82
+ @entities.each do |e|
83
+ e.load
84
+ puts "#{e.name} is at #{e.lat},#{e.lng}"
85
+ end
86
+
87
+ === Automatically generated accessor functions for all Views and UserViews
88
+
89
+ @entity.twitter_view
90
+ @entity.twitter_view_entries
91
+
92
+ @entity.flickr_view
93
+ @entity.flickr_view_entries
94
+ @entity.<my_application>_view_entries
95
+
96
+
97
+ = NOTE
98
+
99
+ This is still actively being developed and is very alpha. You can currently conduct a simple search and an MQL query. The results are returned as ruby hash.
100
+
101
+ == TODO
102
+
103
+ - Allow updates to views.
104
+
105
+
106
+ = GeoAPI
107
+
108
+ A Ruby wrapper for the GeoAPI.com APIs. This gem was almost entirely inspired by the various geoplanet gems.
109
+
110
+ == Usage
111
+
112
+ === Reverse Geocoding:
113
+
114
+ require 'geoapi'
115
+ GeoAPI.apikey = [Your App ID Here]
116
+
117
+ # Location
118
+ latitude = -27.000
119
+ longitude = -131.000
120
+
121
+ # Non Required Options
122
+ optional_parameters = {:radius => '500m', :type => 'POI', "include-parents" => true, :limit => 5, :pretty => true}
123
+
124
+ # Simple Search
125
+ result = GeoAPI::Query.simple_search(latitude, longitude, optional_parameters)
126
+
127
+ # MQL Query
128
+ q = {:lat => 37.75629, :lon => -122.4213, :radius => "1km", :entity => [{:type => "business", :guid => nil}]}
129
+ results = GeoAPI::Query.query(q)
130
+
131
+
132
+
133
+ == REQUIREMENTS:
134
+
135
+ To use this library, you must have a valid GeoAPI.com API Key.
136
+ You can get one at http://api.geoapi.com
137
+
138
+ Additionally, geoapi has the following gem dependencies:
139
+
140
+ * rest-client >= 0.9
141
+ * json >= 1.1.3
142
+
143
+ Please note that if you have ActiveSupport::JSON defined (either by
144
+ manually having loaded it or when you use geoapi within a Rails
145
+ application) the json dependency will be ignored and geoapi uses
146
+ ActiveSupport::JSON instead.
147
+
148
+ == INSTALL:
149
+
150
+ This gem is hosted on Gemcutter. To install gemcutter:
151
+ gem install gemcutter
152
+ gem tumble
153
+
154
+ To install geoapi after gemcutter:
155
+ gem install geoapi
156
+
data/geoapi.gemspec ADDED
@@ -0,0 +1,36 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "steflewandowski-geoapi"
3
+ s.version = "0.2.1"
4
+ s.date = "2009-11-10"
5
+ s.summary = "A Ruby wrapper for the GeoAPI.com API."
6
+ s.email = "stef@stef.io"
7
+ s.homepage = "http://github.com/steflewandowski/GeoAPI/"
8
+ s.description = "A Ruby wrapper for the GeoAPI.com API based on the work of Chris Bruce. See http://api.geoapi.com for more information about the API."
9
+ s.authors = ["Stef Lewandowski","Chris Bruce"]
10
+
11
+ s.files = [
12
+ "README",
13
+ "LICENSE",
14
+ "geoapi.gemspec",
15
+ "lib/geoapi.rb",
16
+ "lib/geoapi/geo_object.rb",
17
+ "lib/geoapi/geometry.rb",
18
+ "lib/geoapi/query.rb",
19
+ "lib/geoapi/entity.rb",
20
+ "lib/geoapi/entry.rb",
21
+ "lib/geoapi/view.rb",
22
+ "lib/geoapi/user_view.rb",
23
+ "lib/geoapi/version.rb",
24
+ "lib/geoapi/client",
25
+ "lib/geoapi/neighborhood"
26
+ ]
27
+
28
+ s.add_dependency("rest-client", [">= 0.9"])
29
+ s.add_dependency("crack")
30
+ s.add_dependency("httparty")
31
+ s.add_dependency("uuidtools")
32
+
33
+
34
+ s.has_rdoc = false
35
+ s.rdoc_options = ["--main", "README.rdoc"]
36
+ end
@@ -0,0 +1,32 @@
1
+ module GeoAPI
2
+ class Client
3
+
4
+ # To connect to GeoAPI, either:
5
+ #
6
+ # Add export GEOAPI_KEY=<your-api-key> in your .bashrc file
7
+ #
8
+ # @client = GeoAPI::Client.new
9
+ #
10
+ # OR
11
+ #
12
+ # @client = GeoAPI::Client.new('<your-api-key>')
13
+ #
14
+ # OR set GEOAPI_KEY='<your-api-key>' before including GeoAPI
15
+ #
16
+ # @client = GeoAPI::Client.new
17
+
18
+ def initialize(api_key=nil)
19
+ @api_key = api_key || ENV['GEOAPI_KEY'] || GeoAPI::GEOAPI_KEY
20
+ end
21
+
22
+ def api_key
23
+ @api_key
24
+ end
25
+
26
+ def self.id_from_guid(guid,apikey)
27
+ id = guid.sub('user-','')
28
+ id = id.sub("#{apikey}-",'')
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,308 @@
1
+ module GeoAPI
2
+ class Entity < GeoAPI::GeoObject
3
+
4
+ attr_accessor :guid, :id, :name, :entity_type, :geom, :url, :views, :userviews, :raw_json, :errors, :shorturl, :raw_json
5
+
6
+
7
+ alias_method :geometry, :geom
8
+
9
+ def latitude
10
+ @latitude ||= geometry.latitude unless geometry.blank?
11
+ end
12
+
13
+ def longitude
14
+ @longitude ||= geometry.longitude unless geometry.blank?
15
+ end
16
+
17
+ alias_method :lat, :latitude
18
+ alias_method :lon, :longitude
19
+ alias_method :lng, :longitude
20
+
21
+ # Class methods
22
+
23
+
24
+ def self.create(params)
25
+ puts "GEOAPI::Entity.create #{params.to_json}"
26
+ #required: name, geom
27
+ #optional: pass id and it will create a new guid for you as user-<apikey>-<id>
28
+
29
+ raise ArgumentError, "A name is required (pass :name in parameters)" unless params.has_key?(:name)
30
+ raise ArgumentError, "A geometry is required (pass :geom in parameters)" unless params.has_key?(:geom)
31
+
32
+ api_key = self.api_key_from_parameters(params)
33
+
34
+ raise ArgumentError, "An API Key is required" if api_key.blank?
35
+
36
+ id = params[:id] || UUIDTools::UUID.timestamp_create().to_s
37
+
38
+ post_url = "/e"
39
+ post_url = "/e/user-#{api_key}-#{id}?apikey=#{api_key}" unless id.blank?
40
+ post_url = "/e/#{params[:guid]}?apikey=#{api_key}" unless params[:guid].blank?
41
+
42
+ puts post_url
43
+
44
+ params.delete(:id) if params.has_key?(:id)
45
+ params.delete(:guid) if params.has_key?(:guid)
46
+
47
+ begin
48
+ results = Entity.post(post_url, {:body=> params.to_json})
49
+ rescue
50
+ raise BadRequest, "There was a problem communicating with the API"
51
+ end
52
+
53
+ raise BadRequest, results['error'] unless results['error'].blank?
54
+
55
+ #todo the result does not contain the guid, so merge it back in. Possible point of failure here?
56
+
57
+ guid = results['query']['params']['guid']
58
+ Entity.new(results['result'].merge({'guid'=>guid, 'id'=>GeoAPI::Client.id_from_guid(guid,api_key)}))
59
+ end
60
+
61
+ def self.destroy(params)
62
+
63
+ puts "GEOAPI::Entity.destroy #{params.to_json}"
64
+
65
+ raise ArgumentError, "An id or guid is required (pass :id or :guid in parameters)" unless params.has_key?(:id) || params.has_key?(:guid)
66
+
67
+ raise ArgumentError, "An API Key is required" if api_key.blank?
68
+
69
+ begin
70
+ unless params[:guid].blank?
71
+ delete("/e/#{params[:guid]}?apikey=#{api_key}")
72
+ else
73
+ delete("/e/user-#{api_key}-#{params[:id]}?apikey=#{api_key}") unless params[:id].blank?
74
+ end
75
+
76
+ rescue
77
+ raise BadRequest, "There was a problem communicating with the API"
78
+ end
79
+ end
80
+
81
+ def self.create_at_lat_lng(*args)
82
+
83
+ params = args.extract_options!
84
+ params = params == {} ? nil : params
85
+
86
+ puts "GEOAPI::Entity.create_at_lat_lng #{params.to_json}"
87
+
88
+ raise ArgumentError, ":lat must be sent as a parameter" unless params.has_key?(:lat)
89
+ raise ArgumentError, ":lng must be sent as a parameter" unless params.has_key?(:lng)
90
+ puts "test API key #{api_key}"
91
+ raise ArgumentError, "An API Key is required" if api_key.blank?
92
+
93
+ p = GeoAPI::Point.new(:lat => params[:lat],:lng => params[:lng])
94
+
95
+ params.delete(:lat)
96
+ params.delete(:lng)
97
+
98
+ self.create(params.merge({:geom=>p}))
99
+ end
100
+
101
+ def self.find(*args)
102
+
103
+ puts "GEOAPI::Entity.find #{args.to_s}"
104
+
105
+ raise ArgumentError, "First argument must be symbol (:all or :get)" unless args.first.kind_of?(Symbol)
106
+
107
+ params = args.extract_options!
108
+ params = params == {} ? nil : params
109
+
110
+ api_key = self.api_key_from_parameters(params)
111
+
112
+ raise ArgumentError, "An API Key is required" if api_key.blank?
113
+
114
+ case args.first
115
+
116
+ when :all
117
+ results = []
118
+ else
119
+ results = nil
120
+
121
+ raise ArgumentError, "Arguments should include a :guid or :id" if params[:guid].blank? && params[:id].blank?
122
+
123
+ params[:guid] = "user-#{api_key}-#{the_id}" unless params[:id].blank?
124
+
125
+ response = get("/e/#{params[:guid]}?apikey=#{api_key}")
126
+
127
+ begin
128
+
129
+ rescue
130
+ raise BadRequest, "There was a problem communicating with the API"
131
+ end
132
+
133
+ results = Entity.new(response['result'].merge({'guid'=>params[:guid]})) unless response['result'].blank? #the api doesn't return a guid in json?!
134
+ end
135
+
136
+ results
137
+
138
+ end
139
+
140
+ def self.find_by_id(the_id, options={})
141
+ puts "GEOAPI::Entity.find_by_id #{the_id}"
142
+
143
+ api_key = self.api_key_from_parameters(options)
144
+
145
+ raise ArgumentError, "An API Key is required" if api_key.blank?
146
+
147
+ self.find(:get, :guid=>"user-#{api_key}-#{the_id}")
148
+ end
149
+
150
+ def self.find_by_guid(the_guid, options={})
151
+ puts "GEOAPI::Entity.find_by_guid #{the_guid}"
152
+
153
+ api_key = self.api_key_from_parameters(options)
154
+
155
+ raise ArgumentError, "An API Key is required" if api_key.blank?
156
+
157
+ self.find(:get, :guid=>the_guid)
158
+ end
159
+
160
+
161
+ # Instance methods
162
+ def initialize(attrs)
163
+ super attrs
164
+ self.setup(attrs) unless attrs.blank?
165
+ end
166
+
167
+ def setup(attrs)
168
+ puts "Setup Entity with attributes: #{attrs.to_json}"
169
+ api_key = @api_key
170
+ api_key ||= self.api_key_from_parameters(params)
171
+
172
+ self.guid = attrs['guid'] if attrs.has_key?('guid')
173
+ self.guid = "user-#{@api_key}-#{attrs['id']}" if attrs.has_key?('id')
174
+ puts "GEOAPI::Entity.setup #{self.guid}"
175
+ self.id = attrs['id'] if attrs.has_key?('id')
176
+ self.id = GeoAPI::Client.id_from_guid(self.guid,api_key) if self.id.blank?
177
+ self.errors = attrs['error']
178
+ self.name = attrs['name']
179
+ self.name ||= attrs['meta']['name'] unless attrs['meta'].blank?
180
+ self.entity_type = attrs['type']
181
+ self.shorturl = attrs['shorturl']
182
+
183
+ self.geom = GeoAPI::Geometry.from_hash(attrs['geom']) unless attrs['geom'].blank?
184
+ self.geom ||= GeoAPI::Geometry.from_hash(attrs['meta']['geom']) unless attrs['meta'].blank?
185
+
186
+
187
+ self.views = []
188
+ unless attrs['views'].blank?
189
+ if attrs['views'].size > 0
190
+ attrs['views'].each do |view|
191
+ self.views << GeoAPI::View.new({'name'=>view, 'guid'=>self.guid})
192
+
193
+ # Dynamically create methods like twitter_view
194
+
195
+ (class <<self; self; end).send :define_method, :"#{view}_view" do
196
+ find_view("#{view}")
197
+ end
198
+
199
+ (class <<self; self; end).send :define_method, :"#{view}_view_entries" do
200
+ find_view_entries("#{view}")
201
+ end
202
+ end
203
+ end
204
+ end
205
+
206
+ self.userviews = []
207
+ unless attrs['userviews'].blank?
208
+ if attrs['userviews'].size > 0
209
+ attrs['userviews'].each do |view|
210
+ self.userviews << GeoAPI::UserView.new({'name'=>view, 'guid'=>self.guid})
211
+
212
+ # Dynamically create methods like myapp_userview
213
+ class << self
214
+ define_method "#{view}_userview" do
215
+ find_view("#{view}")
216
+ end
217
+
218
+ define_method :"#{view}_entries" do
219
+ #todo needs caching here
220
+ find_view("#{view}").entries
221
+ end
222
+ end
223
+
224
+ end
225
+ end
226
+ end
227
+
228
+ self
229
+
230
+ end
231
+
232
+ def type #type is a reserved word
233
+ self.entity_type
234
+ end
235
+
236
+ def update
237
+ puts "GEOAPI::Entity.update #{self.guid}"
238
+
239
+ raise ArgumentError, "An API Key is required" if api_key.blank?
240
+
241
+ self.setup(post("/e/#{guid}?apikey=#{api_key}", {:body=>self.to_json}))
242
+ end
243
+
244
+ def load
245
+ puts "GEOAPI::Entity.load #{self.guid}"
246
+
247
+ raise ArgumentError, "An API Key is required" if api_key.blank?
248
+
249
+ raise ArgumentError, "Properties should include a .guid or .id" if self.guid.blank? && self.id.blank?
250
+
251
+ the_guid = self.guid
252
+ the_guid ||= "user-#{api_key}-#{self.id}"
253
+
254
+ begin
255
+ response = self.class.get("/e/#{the_guid}?apikey=#{api_key}")
256
+ rescue
257
+ raise BadRequest, "There was a problem communicating with the API"
258
+ end
259
+
260
+ self.setup(response['result'].merge({'guid'=>self.guid }))
261
+
262
+ self
263
+ end
264
+
265
+ def delete
266
+ puts "GEOAPI::Entity.delete #{self.guid}"
267
+
268
+ raise ArgumentError, "An API Key is required" if api_key.blank?
269
+
270
+ raise ArgumentError, "Object has no :guid" if self.guid.blank?
271
+ begin
272
+ Entity.destroy(:guid=>self.guid)
273
+ rescue
274
+ raise BadRequest, "There was a problem communicating with the API"
275
+ end
276
+
277
+ end
278
+
279
+ def destroy
280
+ self.delete
281
+ end
282
+
283
+ def save
284
+ update
285
+ end
286
+
287
+ def to_s
288
+ self.name
289
+ end
290
+
291
+ def to_json options=nil
292
+ {:name=>name, :guid=>guid, :type=>entity_type, :geom=>geom, :views=>views, :userviews=>userviews, :shorturl=>shorturl}.to_json
293
+ end
294
+
295
+ # Common facility methods
296
+
297
+ def find_view view_name
298
+ views.each do |view|
299
+ return view if view.name == view_name
300
+ end
301
+ end
302
+
303
+ def find_view_entries view_name
304
+ find_view(view_name).load.entries
305
+ end
306
+
307
+ end
308
+ end
@@ -0,0 +1,12 @@
1
+ module GeoAPI
2
+ class Entry < GeoAPI::GeoObject
3
+
4
+ attr_accessor :properties
5
+
6
+ def initialize attrs
7
+ super(attrs)
8
+ self.properties = attrs['properties']
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,126 @@
1
+ module GeoAPI
2
+ class GeoObject
3
+
4
+ include HTTParty
5
+
6
+ format :json
7
+
8
+ class << self
9
+ attr_accessor :api_key
10
+ end
11
+
12
+ #default_params Proc.new { :apikey => api_key} }
13
+
14
+ base_uri GeoAPI::API_URL
15
+
16
+ # Allow users to set the API key explicitly, or gain it from other sources.
17
+ # Makes for more readable code, and allows multiple keys to be used in the one application.
18
+
19
+ def initialize(attrs)
20
+ #raise ArgumentError, "A :client must be passed to when creating a GeoObject, unless unless ENV['GEOAPI_KEY'] or is set"
21
+
22
+ @api_key = self.class.api_key_from_parameters(attrs)
23
+
24
+ end
25
+
26
+ def self.api_key
27
+ @api_key ||= GeoAPI::GEOAPI_KEY
28
+ @api_key ||= ENV["GEOAPI_KEY"]
29
+ puts "API KEY: #{@api_key}"
30
+ @api_key
31
+ end
32
+
33
+ def self.api_key_from_parameters(attrs)
34
+ unless attrs.blank?
35
+ the_api_key = attrs[:client].api_key if attrs.has_key?(:client)
36
+ the_api_key ||= attrs[:apikey] if attrs.has_key?(:apikey)
37
+ the_api_key ||= attrs[:api_key] if attrs.has_key?(:api_key)
38
+ the_api_key ||= attrs[:api_key] if attrs.has_key?(:api_key)
39
+ end
40
+ the_api_key ||= self.api_key
41
+ puts "API Key from parameters: #{the_api_key}"
42
+ @api_key = the_api_key
43
+ the_api_key
44
+ end
45
+
46
+ def self.post path, options={}
47
+ puts "Post: #{path} : #{options.to_s}"
48
+ timeout = options[:timeout] || 5
49
+ options.delete(:timeout)
50
+ retryable( :tries => 2 ) do
51
+ Timeout::timeout(timeout) do |t|
52
+ super path, options
53
+ end
54
+ end
55
+ end
56
+
57
+ def self.get path, options={}
58
+ puts "Get: #{path} : #{options.to_s}"
59
+ timeout = options[:timeout] || 5
60
+ options.delete(:timeout)
61
+ puts options.to_s
62
+ puts "Timeout #{timeout}"
63
+ retryable( :tries => 2 ) do
64
+ Timeout::timeout(timeout) do |t|
65
+ super path, options
66
+ end
67
+ end
68
+ end
69
+
70
+ def self.delete path, options={}
71
+ puts "Delete: #{path} : #{options.to_s}"
72
+ timeout = options[:timeout] || 5
73
+ options.delete(:timeout)
74
+ retryable( :tries => 2 ) do
75
+ Timeout::timeout(timeout) do |t|
76
+ super path, options
77
+ end
78
+ end
79
+ end
80
+
81
+ def self.search(conditions, options={})
82
+ puts "GEOAPI::Entity.search #{conditions.to_s}"
83
+
84
+ raise ArgumentError, ":lat and :lng are required for search" unless conditions.has_key?(:lat) && conditions.has_key?(:lng)
85
+
86
+ api_key = self.api_key_from_parameters(options)
87
+
88
+ raise ArgumentError, "An API Key is required" if api_key.blank?
89
+
90
+ # Accepts all conditions from the API and passes them through - http://docs.geoapi.com/Simple-Search
91
+
92
+ conditions.merge!({:lat=>conditions[:lat],:lon=>conditions[:lng],:apikey=>api_key})
93
+ conditions.delete(:lng)
94
+
95
+ response = get("/search", {:timeout=>60, :query=>conditions})
96
+
97
+ begin
98
+
99
+ rescue
100
+ raise BadRequest, "There was a problem communicating with the API"
101
+ end
102
+
103
+ results = []
104
+ unless response['result'].blank?
105
+ response['result'].each do |result|
106
+ results << self.new(result)
107
+ end
108
+ results.reverse!
109
+ end
110
+ end
111
+
112
+ def self.find params
113
+ # Find a given object by location passed as :lat, :lng
114
+ raise ArgumentError, ":lat must be sent as a parameter" unless params.has_key?(:lat)
115
+ raise ArgumentError, ":lng must be sent as a parameter" unless params.has_key?(:lng)
116
+
117
+ self.search({:lat=>params[:lat], :lng=>params[:lng],:type=>self.entity_type})
118
+ end
119
+
120
+ def self.entity_type
121
+ "user-entity"
122
+ end
123
+
124
+ end
125
+
126
+ end
@@ -0,0 +1,138 @@
1
+ module GeoAPI
2
+ class Geometry
3
+
4
+ class << self
5
+ attr_accessor :coords, :geometry_type
6
+ end
7
+
8
+
9
+ # Class methods
10
+
11
+ def self.new_from_class_name class_name
12
+ geom = case class_name.downcase.gsub('_','')
13
+
14
+ when "point" then GeoAPI::Point.new
15
+
16
+ when "polygon" then GeoAPI::Polygon.new
17
+
18
+ when "multipoint" then GeoAPI::Polygon.new
19
+
20
+ end
21
+
22
+ end
23
+
24
+ def self.from_json json
25
+ unless json.blank?
26
+ attrs = Crack::JSON.parse(json)
27
+
28
+ geom = Geometry.new_from_class_name json['type']
29
+
30
+ unless geom.blank?
31
+
32
+ geom.geometry_type = attrs['type']
33
+
34
+ geom.coords = attrs['coordinates']
35
+
36
+ geom
37
+
38
+ end
39
+ end
40
+ end
41
+
42
+ def self.from_hash hash
43
+
44
+ unless hash.blank?
45
+ geom = Geometry.new_from_class_name hash['type']
46
+
47
+ unless geom.blank?
48
+
49
+ geom.geometry_type = hash['type']
50
+
51
+ geom.coords = hash['coordinates']
52
+
53
+ puts "Got geometry: #{geom}"
54
+
55
+ geom
56
+
57
+ end
58
+ end
59
+ end
60
+
61
+
62
+ # Instance methods
63
+
64
+ def coordinates
65
+ coords
66
+ end
67
+
68
+ def type
69
+ geometry_type
70
+ end
71
+
72
+ def to_json options=nil
73
+ {:type=>self.geometry_type, :coordinates=>self.coords}.to_json
74
+ end
75
+
76
+ def initialize attrs
77
+ @geometry_type = self.class.name.split('::').last
78
+ end
79
+
80
+ end
81
+
82
+ class Point < GeoAPI::Geometry
83
+
84
+ attr_accessor :geometry_type, :coords
85
+
86
+ def latitude
87
+ coords[0]
88
+ end
89
+
90
+ def longitude
91
+ coords[1]
92
+ end
93
+
94
+ def initialize *args
95
+
96
+ params = args.extract_options!
97
+ params = params == {} ? nil : params
98
+
99
+ puts "GEOAPI::Point.new #{params.to_json}"
100
+
101
+ #raise ArgumentError, ":lat (latitude) must be sent as a parameter to the GeoAPI::Point constructor" unless params.has_key?(:lat)
102
+ #raise ArgumentError, ":lng (longitude) must be sent as a parameter to the GeoAPI::Point constructor" unless params.has_key?(:lng)
103
+
104
+ self.coords = [params[:lng].to_f, params[:lat].to_f] unless params.blank? || params[:lat].blank?
105
+ super args
106
+ end
107
+
108
+
109
+ end
110
+
111
+ class Multipoint < GeoAPI::Geometry
112
+
113
+ attr_accessor :geometry_type, :coords
114
+
115
+ def initialize attrs
116
+
117
+ self.coords = []
118
+ super attrs
119
+ end
120
+
121
+
122
+ end
123
+
124
+ class Polygon < GeoAPI::Geometry
125
+
126
+ attr_accessor :geometry_type, :coords
127
+
128
+ def initialize attrs
129
+ self.coords = []
130
+ super attrs
131
+ end
132
+
133
+
134
+ end
135
+
136
+ #todo add additional classes
137
+
138
+ end
@@ -0,0 +1,17 @@
1
+ module GeoAPI
2
+ class Neighborhood < GeoAPI::GeoObject
3
+
4
+ attr_accessor :properties
5
+
6
+ def self.entity_type
7
+ "neighborhood"
8
+ end
9
+
10
+ def initialize attrs
11
+ super(attrs)
12
+ self.properties = attrs['properties']
13
+
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,61 @@
1
+ module GeoAPI
2
+ class Query
3
+
4
+ class << self
5
+ # Uses GeoAPI's simple search method
6
+ def simple_search(lat, lon, options = {})
7
+ options[:lat] = lat
8
+ options[:lon] = lon
9
+ url = build_url('search', options)
10
+ get_then_parse(url)
11
+ end
12
+
13
+ # Uses GeoAPI's MQL query method
14
+ def query(query)
15
+ q = JSON.generate(query)
16
+ url = build_url('q', {:q => URI.escape(q)})
17
+ get_then_parse(url)
18
+ end
19
+
20
+
21
+ def get_then_parse(url)
22
+ JSON.parse(get(url))
23
+ end
24
+
25
+ def build_url(resource_path, options = {})
26
+
27
+ options[:apikey] ||= GeoAPI.apikey
28
+ query_string = build_query_params(options)
29
+
30
+ "#{GeoAPI::API_URL}#{resource_path}#{query_string}"
31
+ end
32
+
33
+ def get(url)
34
+ RestClient.get(url)
35
+ rescue RestClient::RequestFailed
36
+ raise BadRequest, "Parameter invalid"
37
+ rescue RestClient::ResourceNotFound
38
+ raise NotFound, "GUID invalid"
39
+ end
40
+
41
+ protected
42
+
43
+ # Take options and build query string
44
+ def build_query_params(options)
45
+ query = {}
46
+
47
+ #convert True/False
48
+ options.each_pair do |key, value|
49
+ new_key = key.to_s
50
+ new_val = case value
51
+ when TrueClass then 1
52
+ when FalseClass then 0
53
+ else value
54
+ end
55
+ query[new_key] = new_val
56
+ end
57
+ "?" + query.map{|k,v| "#{k}=#{v}"}.join('&')
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,11 @@
1
+ module GeoAPI
2
+ class UserView < GeoAPI::View
3
+ class << self
4
+ @@path_prefix = GeoAPI::USERVIEW_PATH_PREFIX
5
+ end
6
+
7
+ def initialize attrs
8
+ super attrs
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ module GeoAPI
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ TINY = 1
6
+ STRING = [MAJOR, MINOR, TINY].join('.')
7
+ end
8
+ end
@@ -0,0 +1,100 @@
1
+ module GeoAPI
2
+ class View < GeoAPI::GeoObject
3
+
4
+ attr_accessor :name, :guid, :view_type, :id, :entries
5
+
6
+ class << self
7
+ attr_accessor :path_prefix
8
+ end
9
+
10
+ GeoAPI::VIEW_PATH_PREFIX = "view"
11
+ GeoAPI::USERVIEW_PATH_PREFIX = "userview"
12
+
13
+
14
+ # Class methods
15
+
16
+ def self.path_prefix
17
+ case self.to_s
18
+ when "GeoAPI::View" then VIEW_PATH_PREFIX
19
+
20
+ when "GeoAPI::UserView" then USERVIEW_PATH_PREFIX
21
+ end
22
+ end
23
+
24
+ def self.find(*args)
25
+
26
+ #raise ArgumentError, "First argument must be symbol (:all or :get)" unless args.first.kind_of?(Symbol)
27
+
28
+ params = args.extract_options!
29
+ params = params == {} ? nil : params
30
+
31
+ results = nil
32
+ params[:guid] = "user-#{GeoAPI::API_KEY}-#{params[:id]}" unless params[:id].blank?
33
+ raise ArgumentError, "Arguments should include a entity :guid or an :id" if params[:guid].blank? && params[:id].blank?
34
+ raise ArgumentError, "Arguments should include a view :name" if params[:name].blank?
35
+
36
+ begin
37
+ debugger
38
+
39
+ response = get("/e/#{params[:guid]}/#{self.class.path_prefix}/#{params[:name]}")
40
+ rescue
41
+ raise BadRequest, "There was a problem communicating with the API"
42
+ end
43
+
44
+ entries = {'entries' => response['result']} # There are no entries in this view
45
+
46
+ results = View.new(entries.merge({'guid'=>params[:guid], 'name'=>params[:name]})) unless response['result'].blank? #the api doesn't return a guid in json?!
47
+
48
+ results
49
+ end
50
+
51
+
52
+ # Instance methods
53
+ def initialize attrs
54
+ setup(attrs)
55
+ end
56
+
57
+ def setup attrs
58
+ self.name = attrs['name']
59
+ self.guid = attrs['guid']
60
+ self.view_type = attrs['type']
61
+
62
+ self.entries = []
63
+ unless attrs['entries'].blank?
64
+ if attrs['entries'].size > 0
65
+ attrs['entries'].each do |entry|
66
+ self.entries << GeoAPI::Entry.new({'properties'=>entry})
67
+ end
68
+ self.entries.reverse!
69
+ end
70
+ end
71
+ end
72
+
73
+
74
+ def load
75
+ raise ArgumentError, "Properties should include a .guid or .id" if self.guid.blank? && self.id.blank?
76
+ raise ArgumentError, "Properties should include a .name" if self.name.blank?
77
+
78
+ the_guid = self.guid
79
+
80
+ the_guid ||= "user-#{GeoAPI::API_KEY}-#{self.id}"
81
+
82
+ begin
83
+ response = self.class.get("/e/#{the_guid}/#{self.class.path_prefix}/#{self.name}")
84
+ rescue
85
+ raise BadRequest, "There was a problem communicating with the API"
86
+ end
87
+
88
+ entries = {'entries' => response['result']} # There are no entries in this view
89
+
90
+ self.setup(entries.merge({'guid'=>self.guid, 'name'=>self.name }))
91
+
92
+ self
93
+ end
94
+
95
+ def to_json options=nil
96
+ {:name=>name, :guid=>guid, :type=>view_type}.to_json
97
+ end
98
+ end
99
+
100
+ end
data/lib/geoapi.rb ADDED
@@ -0,0 +1,89 @@
1
+
2
+ %w{rubygems rest_client httparty json crack/json uuidtools}.each { |x| require x }
3
+
4
+ # Patch HTTParty to debug
5
+ HTTParty::Request.class_eval do
6
+ class << self
7
+ attr_accessor :debug
8
+ end
9
+ self.debug = true
10
+
11
+ def perform_with_debug
12
+ if self.class.debug
13
+ puts "HTTParty making #{http_method::METHOD} request to:"
14
+ puts uri
15
+ puts "With Body: #{body}"
16
+ end
17
+ perform_without_debug
18
+ end
19
+
20
+ alias_method :perform_without_debug, :perform
21
+ alias_method :perform, :perform_with_debug
22
+ end
23
+
24
+ # monkey patch HTTParty to use a configurable timeout
25
+ module HTTParty
26
+ class Request
27
+ private
28
+ def http
29
+ http = Net::HTTP.new(uri.host, uri.port, options[:http_proxyaddr], options[:http_proxyport])
30
+ http.use_ssl = (uri.port == 443)
31
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
32
+ http.open_timeout = http.read_timeout = options[:timeout].to_i if options[:timeout].to_i > 0
33
+ http
34
+ end
35
+ end
36
+ end
37
+
38
+ def retryable(options = {}, &block)
39
+ opts = { :tries => 1, :on => Exception }.merge(options)
40
+
41
+ retry_exception, retries = opts[:on], opts[:tries]
42
+
43
+ begin
44
+ return yield
45
+ rescue retry_exception
46
+ retry if (retries -= 1) > 0
47
+ end
48
+
49
+ yield
50
+ end
51
+
52
+ class Array
53
+ # Extract options from a set of arguments. Removes and returns the last element in the array if it's a hash, otherwise returns a blank hash.
54
+ #
55
+ # def options(*args)
56
+ # args.extract_options!
57
+ # end
58
+ #
59
+ # options(1, 2) # => {}
60
+ # options(1, 2, :a => :b) # => {:a=>:b}
61
+ def extract_options!
62
+ last.is_a?(::Hash) ? pop : {}
63
+ end
64
+ end
65
+
66
+ module GeoAPI
67
+ API_VERSION = "v1"
68
+ API_URL = "http://api.geoapi.com/#{API_VERSION}/"
69
+
70
+ class ArgumentError < StandardError; end
71
+ class BadRequest < StandardError; end
72
+ class NotFound < StandardError; end
73
+ class NotAcceptable < StandardError; end
74
+ end
75
+
76
+
77
+ $:.unshift(File.dirname(__FILE__)) unless
78
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
79
+
80
+ require 'geoapi/geo_object'
81
+ require 'geoapi/geometry'
82
+ require 'geoapi/version'
83
+ require 'geoapi/entity'
84
+ require 'geoapi/entry'
85
+ require 'geoapi/view'
86
+ require 'geoapi/user_view'
87
+ require 'geoapi/query'
88
+ require 'geoapi/client'
89
+ require 'geoapi/neighborhood'
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: steflewandowski-geoapi
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ platform: ruby
6
+ authors:
7
+ - Stef Lewandowski
8
+ - Chris Bruce
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-11-10 00:00:00 +00:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: rest-client
18
+ type: :runtime
19
+ version_requirement:
20
+ version_requirements: !ruby/object:Gem::Requirement
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: "0.9"
25
+ version:
26
+ - !ruby/object:Gem::Dependency
27
+ name: crack
28
+ type: :runtime
29
+ version_requirement:
30
+ version_requirements: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: "0"
35
+ version:
36
+ - !ruby/object:Gem::Dependency
37
+ name: httparty
38
+ type: :runtime
39
+ version_requirement:
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: "0"
45
+ version:
46
+ - !ruby/object:Gem::Dependency
47
+ name: uuidtools
48
+ type: :runtime
49
+ version_requirement:
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ description: A Ruby wrapper for the GeoAPI.com API based on the work of Chris Bruce. See http://api.geoapi.com for more information about the API.
57
+ email: stef@stef.io
58
+ executables: []
59
+
60
+ extensions: []
61
+
62
+ extra_rdoc_files: []
63
+
64
+ files:
65
+ - README
66
+ - LICENSE
67
+ - geoapi.gemspec
68
+ - lib/geoapi.rb
69
+ - lib/geoapi/geo_object.rb
70
+ - lib/geoapi/geometry.rb
71
+ - lib/geoapi/query.rb
72
+ - lib/geoapi/entity.rb
73
+ - lib/geoapi/entry.rb
74
+ - lib/geoapi/view.rb
75
+ - lib/geoapi/user_view.rb
76
+ - lib/geoapi/version.rb
77
+ - lib/geoapi/client.rb
78
+ - lib/geoapi/neighborhood.rb
79
+ has_rdoc: true
80
+ homepage: http://github.com/steflewandowski/GeoAPI/
81
+ licenses: []
82
+
83
+ post_install_message:
84
+ rdoc_options:
85
+ - --main
86
+ - README.rdoc
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: "0"
94
+ version:
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: "0"
100
+ version:
101
+ requirements: []
102
+
103
+ rubyforge_project:
104
+ rubygems_version: 1.3.5
105
+ signing_key:
106
+ specification_version: 3
107
+ summary: A Ruby wrapper for the GeoAPI.com API.
108
+ test_files: []
109
+