steflewandowski-geoapi 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.
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
+