pangel-sg-ruby 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +120 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/lib/simple_geo.rb +23 -0
- data/lib/simple_geo/client.rb +166 -0
- data/lib/simple_geo/connection.rb +109 -0
- data/lib/simple_geo/endpoint.rb +67 -0
- data/lib/simple_geo/hash_utils.rb +24 -0
- data/lib/simple_geo/record.rb +56 -0
- data/lib/simplegeo.rb +2 -0
- data/sg-ruby.gemspec +81 -0
- data/spec/client_spec.rb +1180 -0
- data/spec/fixtures/contains.json +127 -0
- data/spec/fixtures/get_density_by_day.json +833 -0
- data/spec/fixtures/get_density_by_hour.json +36 -0
- data/spec/fixtures/get_history.json +13 -0
- data/spec/fixtures/get_nearby.json +84 -0
- data/spec/fixtures/get_record.json +38 -0
- data/spec/fixtures/get_records.json +197 -0
- data/spec/fixtures/layer_info.json +8 -0
- data/spec/fixtures/nearby_address.json +21 -0
- data/spec/fixtures/no_such_record.json +4 -0
- data/spec/fixtures/nonetype_not_iterable.json +4 -0
- data/spec/fixtures/overlaps.json +41 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +29 -0
- metadata +158 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Dan Dofter
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
= sg-ruby
|
2
|
+
|
3
|
+
A SimpleGeo Ruby client.
|
4
|
+
|
5
|
+
For the specific documentation on APIs (and the full list of parameters) see:
|
6
|
+
|
7
|
+
http://help.simplegeo.com/faqs/api-documentation/endpoints
|
8
|
+
|
9
|
+
== Examples
|
10
|
+
|
11
|
+
Start by requiring SimpleGeo and setting your authentication credentials:
|
12
|
+
|
13
|
+
require 'simplegeo'
|
14
|
+
SimpleGeo::Client.set_credentials('token', 'secret')
|
15
|
+
|
16
|
+
=== Getting a record
|
17
|
+
|
18
|
+
record = SimpleGeo::Client.get_record('com.simplegeo.global.geonames', '5373629')
|
19
|
+
|
20
|
+
=== Getting multiple records
|
21
|
+
|
22
|
+
records = SimpleGeo::Client.get_records('com.simplegeo.global.geonames', ['1234', '5678'])
|
23
|
+
|
24
|
+
=== Adding a record
|
25
|
+
|
26
|
+
record = SimpleGeo::Record.new({
|
27
|
+
:id => '1234',
|
28
|
+
:created => Time.now,
|
29
|
+
:lat => 37.759650000000001,
|
30
|
+
:lon => -122.42608,
|
31
|
+
:layer => 'com.example.testlayer',
|
32
|
+
:properties => {
|
33
|
+
:test_property => 'foobar'
|
34
|
+
}
|
35
|
+
})
|
36
|
+
SimpleGeo::Client.add_record(record)
|
37
|
+
|
38
|
+
=== Updating a record
|
39
|
+
|
40
|
+
record = SimpleGeo::Client.get_record('com.example.testlayer', '1234')
|
41
|
+
record.lat = 40.714269
|
42
|
+
record.lon = -74.005973
|
43
|
+
SimpleGeo::Client.add_record(record)
|
44
|
+
|
45
|
+
=== Adding / updating multiple records
|
46
|
+
|
47
|
+
records = [
|
48
|
+
SimpleGeo::Record.new({
|
49
|
+
:id => '1234',
|
50
|
+
:created => Time.now,
|
51
|
+
:lat => 37.759650000000001,
|
52
|
+
:lon => -122.42608,
|
53
|
+
:layer => 'com.example.testlayer',
|
54
|
+
:properties => {
|
55
|
+
:test_property => 'foobar'
|
56
|
+
}
|
57
|
+
}),
|
58
|
+
SimpleGeo::Record.new({
|
59
|
+
:id => '5678',
|
60
|
+
:created => Time.now,
|
61
|
+
:lat => 37.755470000000003,
|
62
|
+
:lon => -122.420646,
|
63
|
+
:layer => 'com.example.testlayer',
|
64
|
+
:properties => {
|
65
|
+
:mad_prop => 'baz'
|
66
|
+
}
|
67
|
+
})
|
68
|
+
]
|
69
|
+
SimpleGeo::Client.add_records('com.example.testlayer', records)
|
70
|
+
|
71
|
+
=== Deleting a record
|
72
|
+
|
73
|
+
SimpleGeo::Client.delete_record('1234')
|
74
|
+
|
75
|
+
=== Getting a record's history
|
76
|
+
|
77
|
+
history = SimpleGeo::Client.get_history('com.example.testlayer', '1234')
|
78
|
+
|
79
|
+
=== Getting nearby records
|
80
|
+
|
81
|
+
See http://help.simplegeo.com/faqs/api-documentation/endpoints for other optional params
|
82
|
+
|
83
|
+
# by lat, lon
|
84
|
+
records = SimpleGeo::Client.get_nearby_records('com.example.testlayer',
|
85
|
+
:lat => 37.759650000000001,
|
86
|
+
:lon => -122.42608)
|
87
|
+
|
88
|
+
# by geohash
|
89
|
+
records = SimpleGeo::Client.get_nearby_records('com.example.testlayer',
|
90
|
+
:geohash => '9q8yy1ujcsfm')
|
91
|
+
|
92
|
+
=== Getting a nearby address for a lat and lon
|
93
|
+
|
94
|
+
nearby_address = SimpleGeo::Client.get_nearby_address(37.759650000000001, -122.42608)
|
95
|
+
|
96
|
+
=== Getting SpotRank density information
|
97
|
+
|
98
|
+
# by day
|
99
|
+
density_info = SimpleGeo::Client.get_density(37.75965, -122.42608, 'sat')
|
100
|
+
|
101
|
+
# by hour
|
102
|
+
density_info = SimpleGeo::Client.get_density(37.75965, -122.42608, 'sat', '16' )
|
103
|
+
|
104
|
+
=== Other APIs
|
105
|
+
|
106
|
+
For more examples see: spec/client_spec.rb
|
107
|
+
|
108
|
+
== Note on Patches/Pull Requests
|
109
|
+
|
110
|
+
* Fork the project.
|
111
|
+
* Make your feature addition or bug fix.
|
112
|
+
* Add tests for it. This is important so I don't break it in a
|
113
|
+
future version unintentionally.
|
114
|
+
* Commit, do not mess with rakefile, version, or history.
|
115
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
116
|
+
* Send me a pull request. Bonus points for topic branches.
|
117
|
+
|
118
|
+
== Copyright
|
119
|
+
|
120
|
+
Copyright (c) 2010 Dan Dofter. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "sg-ruby"
|
8
|
+
gem.summary = %Q{A SimpleGeo Ruby Client}
|
9
|
+
gem.email = "dan@dofter.com"
|
10
|
+
gem.homepage = "http://github.com/archfear/sg-ruby"
|
11
|
+
gem.authors = ["Dan Dofter"]
|
12
|
+
|
13
|
+
gem.add_dependency("oauth", ">= 0.4.0")
|
14
|
+
gem.add_dependency("json_pure")
|
15
|
+
|
16
|
+
gem.add_development_dependency "rspec", ">= 1.2.0"
|
17
|
+
gem.add_development_dependency("fakeweb", ">= 1.2.0")
|
18
|
+
end
|
19
|
+
Jeweler::GemcutterTasks.new
|
20
|
+
rescue LoadError
|
21
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
22
|
+
end
|
23
|
+
|
24
|
+
require 'spec/rake/spectask'
|
25
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
26
|
+
spec.libs << 'lib' << 'spec'
|
27
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
28
|
+
end
|
29
|
+
|
30
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
31
|
+
spec.libs << 'lib' << 'spec'
|
32
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
33
|
+
spec.rcov = true
|
34
|
+
spec.rcov_opts << "--sort coverage"
|
35
|
+
spec.rcov_opts << "--exclude gems,spec"
|
36
|
+
end
|
37
|
+
|
38
|
+
task :spec => :check_dependencies
|
39
|
+
|
40
|
+
task :default => :spec
|
41
|
+
|
42
|
+
require 'rake/rdoctask'
|
43
|
+
Rake::RDocTask.new do |rdoc|
|
44
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
45
|
+
|
46
|
+
rdoc.rdoc_dir = 'rdoc'
|
47
|
+
rdoc.title = "sg-ruby #{version}"
|
48
|
+
rdoc.rdoc_files.include('README*')
|
49
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
50
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/lib/simple_geo.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'json'
|
3
|
+
require 'oauth'
|
4
|
+
|
5
|
+
require 'simple_geo/hash_utils'
|
6
|
+
require 'simple_geo/connection'
|
7
|
+
require 'simple_geo/endpoint'
|
8
|
+
require 'simple_geo/client'
|
9
|
+
require 'simple_geo/record'
|
10
|
+
|
11
|
+
module SimpleGeo
|
12
|
+
API_VERSION = '0.1'.freeze
|
13
|
+
REALM = "http://api.simplegeo.com"
|
14
|
+
VERSION = File.read(File.join(File.dirname(__FILE__), '..', 'VERSION'))
|
15
|
+
|
16
|
+
class SimpleGeoError < StandardError; end
|
17
|
+
class Unauthorized < SimpleGeoError; end
|
18
|
+
class NotFound < SimpleGeoError; end
|
19
|
+
class ServerError < SimpleGeoError; end
|
20
|
+
class Unavailable < SimpleGeoError; end
|
21
|
+
class DecodeError < SimpleGeoError; end
|
22
|
+
class NoConnectionEstablished < SimpleGeoError; end
|
23
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
module SimpleGeo
|
2
|
+
|
3
|
+
class Client
|
4
|
+
|
5
|
+
@@connection = nil
|
6
|
+
@@debug = false
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
def set_credentials(token, secret)
|
11
|
+
@@connection = Connection.new(token, secret)
|
12
|
+
@@connection.debug = @@debug
|
13
|
+
end
|
14
|
+
|
15
|
+
def debug=(debug_flag)
|
16
|
+
@@debug = debug_flag
|
17
|
+
@@connection.debug = @@debug if @@connection
|
18
|
+
end
|
19
|
+
|
20
|
+
def debug
|
21
|
+
@@debug
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_record(record)
|
25
|
+
raise SimpleGeoError, "Record has no layer" if record.layer.nil?
|
26
|
+
put Endpoint.record(record.layer, record.id), record
|
27
|
+
end
|
28
|
+
|
29
|
+
def delete_record(layer, id)
|
30
|
+
delete Endpoint.record(layer, id)
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_record(layer, id)
|
34
|
+
record_hash = get Endpoint.record(layer, id)
|
35
|
+
record = Record.parse_geojson_hash(record_hash)
|
36
|
+
record.layer = layer
|
37
|
+
record
|
38
|
+
end
|
39
|
+
|
40
|
+
def add_records(layer, records)
|
41
|
+
features = {
|
42
|
+
:type => 'FeatureCollection',
|
43
|
+
:features => records.collect { |record| record.to_hash }
|
44
|
+
}
|
45
|
+
post Endpoint.add_records(layer), features
|
46
|
+
end
|
47
|
+
|
48
|
+
# This request currently generates a 500 error if an unknown id is passed in.
|
49
|
+
def get_records(layer, ids)
|
50
|
+
features_hash = get Endpoint.records(layer, ids)
|
51
|
+
records = []
|
52
|
+
features_hash['features'].each do |feature_hash|
|
53
|
+
record = Record.parse_geojson_hash(feature_hash)
|
54
|
+
record.layer = layer
|
55
|
+
records << record
|
56
|
+
end
|
57
|
+
records
|
58
|
+
end
|
59
|
+
|
60
|
+
def get_history(layer, id)
|
61
|
+
history_geojson = get Endpoint.history(layer, id)
|
62
|
+
history = []
|
63
|
+
history_geojson['geometries'].each do |point|
|
64
|
+
history << {
|
65
|
+
:created => Time.at(point['created']),
|
66
|
+
:lat => point['coordinates'][1],
|
67
|
+
:lon => point['coordinates'][0]
|
68
|
+
}
|
69
|
+
end
|
70
|
+
history
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_nearby_records(layer, options)
|
74
|
+
if options[:geohash]
|
75
|
+
endpoint = Endpoint.nearby_geohash(layer, options.delete(:geohash))
|
76
|
+
elsif options[:lat] && options[:lon]
|
77
|
+
endpoint = Endpoint.nearby_coordinates(layer,
|
78
|
+
options.delete(:lat), options.delete(:lon))
|
79
|
+
else
|
80
|
+
raise SimpleGeoError, "Either geohash or lat and lon is required"
|
81
|
+
end
|
82
|
+
|
83
|
+
options = nil if options.empty?
|
84
|
+
features_hash = get(endpoint, options)
|
85
|
+
nearby_records = {
|
86
|
+
:next_cursor => features_hash['next_cursor'],
|
87
|
+
:records => []
|
88
|
+
}
|
89
|
+
features_hash['features'].each do |feature_hash|
|
90
|
+
record = Record.parse_geojson_hash(feature_hash)
|
91
|
+
record.layer = layer
|
92
|
+
record_info = {
|
93
|
+
:distance => feature_hash['distance'],
|
94
|
+
:record => record
|
95
|
+
}
|
96
|
+
nearby_records[:records] << record_info
|
97
|
+
end
|
98
|
+
nearby_records
|
99
|
+
end
|
100
|
+
|
101
|
+
def get_nearby_address(lat, lon)
|
102
|
+
geojson_hash = get Endpoint.nearby_address(lat, lon)
|
103
|
+
HashUtils.symbolize_keys geojson_hash['properties']
|
104
|
+
end
|
105
|
+
|
106
|
+
def get_layer_information(layer)
|
107
|
+
layer_info = get Endpoint.layer(layer)
|
108
|
+
layer_info.delete('selfLink')
|
109
|
+
HashUtils.symbolize_keys(layer_info)
|
110
|
+
end
|
111
|
+
|
112
|
+
def get_density(lat, lon, day, hour=nil)
|
113
|
+
geojson_hash = get Endpoint.density(lat, lon, day, hour)
|
114
|
+
geojson_hash = HashUtils.recursively_symbolize_keys(geojson_hash)
|
115
|
+
if hour.nil?
|
116
|
+
density_info = []
|
117
|
+
geojson_hash[:features].each do |hour_geojson_hash|
|
118
|
+
density_info << hour_geojson_hash[:properties].merge(
|
119
|
+
{:geometry => hour_geojson_hash[:geometry]})
|
120
|
+
end
|
121
|
+
density_info
|
122
|
+
else
|
123
|
+
geojson_hash[:properties].merge({:geometry => geojson_hash[:geometry]})
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def get_overlaps(south, west, north, east, options=nil)
|
128
|
+
info = get Endpoint.overlaps(south, west, north, east), options
|
129
|
+
HashUtils.recursively_symbolize_keys(info)
|
130
|
+
end
|
131
|
+
|
132
|
+
# this API call seems to always return a 404
|
133
|
+
def get_boundary(id)
|
134
|
+
info = get Endpoint.boundary(id)
|
135
|
+
HashUtils.recursively_symbolize_keys(info)
|
136
|
+
end
|
137
|
+
|
138
|
+
def get_contains(lat, lon)
|
139
|
+
info = get Endpoint.contains(lat, lon)
|
140
|
+
HashUtils.recursively_symbolize_keys(info)
|
141
|
+
end
|
142
|
+
|
143
|
+
def get(endpoint, data=nil)
|
144
|
+
raise NoConnectionEstablished if @@connection.nil?
|
145
|
+
@@connection.get endpoint, data
|
146
|
+
end
|
147
|
+
|
148
|
+
def delete(endpoint, data=nil)
|
149
|
+
raise NoConnectionEstablished if @@connection.nil?
|
150
|
+
@@connection.delete endpoint, data
|
151
|
+
end
|
152
|
+
|
153
|
+
def post(endpoint, data=nil)
|
154
|
+
raise NoConnectionEstablished if @@connection.nil?
|
155
|
+
@@connection.post endpoint, data
|
156
|
+
end
|
157
|
+
|
158
|
+
def put(endpoint, data=nil)
|
159
|
+
raise NoConnectionEstablished if @@connection.nil?
|
160
|
+
@@connection.put endpoint, data
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module SimpleGeo
|
2
|
+
class Connection
|
3
|
+
|
4
|
+
attr_accessor :debug
|
5
|
+
|
6
|
+
def initialize(token, secret)
|
7
|
+
consumer = OAuth::Consumer.new(token, secret, :site => REALM)
|
8
|
+
@access_token = OAuth::AccessToken.new(consumer)
|
9
|
+
debug = false
|
10
|
+
end
|
11
|
+
|
12
|
+
def get(endpoint, data=nil)
|
13
|
+
request :get, endpoint, data
|
14
|
+
end
|
15
|
+
|
16
|
+
def delete(endpoint, data=nil)
|
17
|
+
request :delete, endpoint, data
|
18
|
+
end
|
19
|
+
|
20
|
+
def post(endpoint, data=nil)
|
21
|
+
request :post, endpoint, data
|
22
|
+
end
|
23
|
+
|
24
|
+
def put(endpoint, data=nil)
|
25
|
+
request :put, endpoint, data
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def request(method, endpoint, data)
|
31
|
+
headers = {'User-Agent' => "SimpleGeo Ruby Client v#{VERSION}"}
|
32
|
+
|
33
|
+
if [:get, :delete].include?(method) && !data.nil?
|
34
|
+
endpoint = endpoint + '?' + build_query(data)
|
35
|
+
end
|
36
|
+
|
37
|
+
if debug
|
38
|
+
puts "request: #{method.to_s.upcase} #{endpoint}"
|
39
|
+
puts "headers:"
|
40
|
+
headers.each do |key, value|
|
41
|
+
puts "#{key}=#{value}"
|
42
|
+
end
|
43
|
+
if [:post, :put].include?(method) && !data.nil?
|
44
|
+
puts "data:"
|
45
|
+
puts data.to_json
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
case method
|
50
|
+
when :get, :delete
|
51
|
+
response = @access_token.request(method, endpoint, headers)
|
52
|
+
when :post, :put
|
53
|
+
data = data.to_json unless data.nil?
|
54
|
+
response = @access_token.request(method, endpoint, data, headers)
|
55
|
+
end
|
56
|
+
|
57
|
+
if debug
|
58
|
+
puts "\nresponse: #{response.code}"
|
59
|
+
puts "headers:"
|
60
|
+
response.header.each do |key, value|
|
61
|
+
puts "#{key}=#{value}"
|
62
|
+
end
|
63
|
+
puts "body:"
|
64
|
+
puts response.body
|
65
|
+
end
|
66
|
+
|
67
|
+
raise_errors(response)
|
68
|
+
|
69
|
+
if response.body.empty?
|
70
|
+
content = nil
|
71
|
+
else
|
72
|
+
begin
|
73
|
+
content = JSON.parse(response.body)
|
74
|
+
rescue JSON::ParserError
|
75
|
+
raise DecodeError, "content: <#{response.body}>"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
content
|
80
|
+
end
|
81
|
+
|
82
|
+
def build_query(data)
|
83
|
+
data.map do |key, value|
|
84
|
+
[key.to_s, URI.escape(value.to_s)].join('=')
|
85
|
+
end.join('&')
|
86
|
+
end
|
87
|
+
|
88
|
+
def raise_errors(response)
|
89
|
+
response_description = "(#{response.code}): #{response.message}"
|
90
|
+
response_description += " - #{response.body}" unless response.body.empty?
|
91
|
+
|
92
|
+
case response.code.to_i
|
93
|
+
when 401
|
94
|
+
raise Unauthorized
|
95
|
+
when 404
|
96
|
+
raise NotFound
|
97
|
+
when 500
|
98
|
+
raise ServerError, "SimpleGeo had an internal error. Please let them know. #{response_description}"
|
99
|
+
when 502..503
|
100
|
+
raise Unavailable, response_description
|
101
|
+
else
|
102
|
+
unless response.is_a? Net::HTTPSuccess
|
103
|
+
raise SimpleGeoError, response_description
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|