oozou-fusion_tables 0.2.3.dev.20110408163600
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/.document +5 -0
- data/CHANGELOG +6 -0
- data/LICENSE +20 -0
- data/README.textile +148 -0
- data/Rakefile +54 -0
- data/TODO +11 -0
- data/VERSION +1 -0
- data/examples/boris_bikes.rb +159 -0
- data/examples/compare_tweets.rb +143 -0
- data/examples/credentials.example.yml +4 -0
- data/fusion_tables.gemspec +78 -0
- data/lib/fusion_tables.rb +6 -0
- data/lib/fusion_tables/client/fusion_tables.rb +77 -0
- data/lib/fusion_tables/data/data.rb +72 -0
- data/lib/fusion_tables/data/table.rb +144 -0
- data/lib/fusion_tables/ext/fusion_tables.rb +96 -0
- data/pkg/fusion_tables-0.1.0.gem +0 -0
- data/pkg/fusion_tables-0.1.1.gem +0 -0
- data/pkg/fusion_tables-0.1.2.gem +0 -0
- data/pkg/fusion_tables-0.2.0.gem +0 -0
- data/pkg/fusion_tables-0.2.1.gem +0 -0
- data/pkg/fusion_tables-0.2.2.gem +0 -0
- data/test/README +3 -0
- data/test/helper.rb +34 -0
- data/test/test_client.rb +22 -0
- data/test/test_config.yml.sample +3 -0
- data/test/test_ext.rb +58 -0
- data/test/test_table.rb +76 -0
- metadata +110 -0
data/.document
ADDED
data/CHANGELOG
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Tom Verbeure, Simon Tokumine
|
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.textile
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
h1. fusion-tables
|
2
|
+
|
3
|
+
This gem lets you easily interact with Google Fusion Tables from your Ruby application. Here is a "live visualisation of london bike hire availability":http://tables.googlelabs.com/DataSource?snapid=78314 and some "example maps and charts":http://www.tokumine.com/2010/08/10/fusion-tables-gem/.
|
4
|
+
|
5
|
+
h2. Gem Dependencies
|
6
|
+
|
7
|
+
* gdata_19 >= 1.1.2
|
8
|
+
|
9
|
+
h2. Installation
|
10
|
+
|
11
|
+
bc. gem install fusion_tables
|
12
|
+
|
13
|
+
h2. Rubies
|
14
|
+
|
15
|
+
Tested on:
|
16
|
+
|
17
|
+
* 1.8.7
|
18
|
+
* 1.9.2-p0
|
19
|
+
|
20
|
+
h2. To Use
|
21
|
+
|
22
|
+
bc. require 'fusion_tables'
|
23
|
+
|
24
|
+
or in Rails 2.3.x
|
25
|
+
|
26
|
+
bc. config.gem 'fusion_tables'
|
27
|
+
|
28
|
+
h2. API examples
|
29
|
+
|
30
|
+
"Twitter example":http://github.com/tokumine/fusion-tables/blob/master/examples/compare_tweets.rb
|
31
|
+
"Boris bike example":http://github.com/tokumine/fusion-tables/blob/master/examples/boris_bikes.rb
|
32
|
+
"Tests":http://github.com/tokumine/fusion-tables/tree/master/test/
|
33
|
+
|
34
|
+
Here is a brief rundown:
|
35
|
+
|
36
|
+
<pre><code># Connect to service
|
37
|
+
@ft = GData::Client::FusionTables.new
|
38
|
+
@ft.clientlogin(username, password)
|
39
|
+
|
40
|
+
# Browse existing tables
|
41
|
+
@ft.show_tables
|
42
|
+
# => [table_1, table_2]
|
43
|
+
|
44
|
+
# Getting table id suitable for using with google maps (see more below)
|
45
|
+
table_1.id #=> 42342 (the table's google id)
|
46
|
+
|
47
|
+
# Count data
|
48
|
+
table_1.count #=> 1
|
49
|
+
|
50
|
+
# Select data
|
51
|
+
table_1.select
|
52
|
+
#=> data
|
53
|
+
|
54
|
+
# Select data with conditions
|
55
|
+
table_1.select "name", "WHERE x=n"
|
56
|
+
#=> data
|
57
|
+
|
58
|
+
# Select ROWIDs
|
59
|
+
row_ids = table_1.select "ROWID"
|
60
|
+
|
61
|
+
# Drop tables
|
62
|
+
@ft.drop table_1.id # table id
|
63
|
+
@ft.drop [table_1.id, table_2.id] # arrays of table ids
|
64
|
+
@ft.drop /yacht/ # regex on table name
|
65
|
+
|
66
|
+
# Creating a table
|
67
|
+
cols = [{:name => "friend name", :type => 'string' },
|
68
|
+
{:name => "age", :type => 'number' },
|
69
|
+
{:name => "meeting time", :type => 'datetime' },
|
70
|
+
{:name => "where", :type => 'location' }]
|
71
|
+
|
72
|
+
new_table = @ft.create_table "My upcoming meetings", cols
|
73
|
+
|
74
|
+
# Inserting rows (auto chunks every 500)
|
75
|
+
data = [{"friend name" => "Eric Wimp",
|
76
|
+
"age" => 25,
|
77
|
+
"meeting time" => Time.utc(2010,"aug",10,20,15,1),
|
78
|
+
"where" => "29 Acacia Road, Nuttytown"}]
|
79
|
+
new_table.insert data
|
80
|
+
|
81
|
+
# Delete row
|
82
|
+
new_table.delete row_id
|
83
|
+
</code></pre>
|
84
|
+
|
85
|
+
h2. Fusion Tables secret Geospatial Sauce
|
86
|
+
|
87
|
+
*"Geolocated Tweets example":http://tables.googlelabs.com/DataSource?snapid=73106*
|
88
|
+
|
89
|
+
Fusion Tables is a labs product from Google. You can "read more here":http://tables.googlelabs.com/, but the key thing is that it gives you *access to the google tile mill for fast generation of google map layers across large datasets*
|
90
|
+
|
91
|
+
Fusion Tables supports the following geometry types:
|
92
|
+
|
93
|
+
* lat/long
|
94
|
+
* addresses (automatically geocodes them for you)
|
95
|
+
* KML (point, polyline, polygon, multipolygon)
|
96
|
+
|
97
|
+
h2. Integrate with google maps v3
|
98
|
+
|
99
|
+
Adding a fusion tables datalayer with many points/polygons to your v3 map is as simple as:
|
100
|
+
|
101
|
+
bc. layer = new google.maps.FusionTablesLayer(139529);
|
102
|
+
|
103
|
+
That's it
|
104
|
+
|
105
|
+
You can also refine the tiles by SQL, and can even do so dynamically:
|
106
|
+
|
107
|
+
<pre><code>
|
108
|
+
layer = new google.maps.FusionTablesLayer(198945, {
|
109
|
+
query: "SELECT address FROM 198945 WHERE ridership > 5000"}
|
110
|
+
);
|
111
|
+
</code></pre>
|
112
|
+
|
113
|
+
Finally, fusion tables also lets you make Heatmaps
|
114
|
+
|
115
|
+
<pre><code>
|
116
|
+
layer = new google.maps.FusionTablesLayer(136705, {
|
117
|
+
heatmap: true
|
118
|
+
});
|
119
|
+
</code></pre>
|
120
|
+
|
121
|
+
You can also export your data (filtered and geocoded) to KML. As an example, here are "all the Gasoline filling stations in the UK":http://tables.googlelabs.com/exporttable?query=select+col0%2Ccol1%2Ccol2%2Ccol3%2Ccol4%2Ccol5%2Ccol6%2Ccol12%2Ccol13%2Ccol14%2Ccol15%2Ccol16%2Ccol17%2Ccol18%2Ccol19%2Ccol20%2Ccol21+from+214045+&o=kmllink&g=col0
|
122
|
+
|
123
|
+
read "more here":http://code.google.com/apis/maps/documentation/javascript/overlays.html#FusionTables
|
124
|
+
|
125
|
+
h2. Known Issues
|
126
|
+
|
127
|
+
# The gem uses the Google gdata_19 gem which conflicts with the GData2 gem. Uninstall gdata2 to regain sanity.
|
128
|
+
# Currently you have to make a table public before you can display it on a map, unfortunately, this can only be done on the web interface. A suggested workaround is to put all your data in 1 big public table, and then query for the data you want to display based off a key/flag column
|
129
|
+
|
130
|
+
h2. Note on Patches/Pull Requests
|
131
|
+
|
132
|
+
* Fork the project.
|
133
|
+
* Make your feature addition or bug fix.
|
134
|
+
* Add tests for it. This is important so I don't break it in a
|
135
|
+
future version unintentionally.
|
136
|
+
* Commit, do not mess with rakefile, version, or history.
|
137
|
+
(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)
|
138
|
+
* Send me a pull request. Bonus points for topic branches.
|
139
|
+
|
140
|
+
h2. Copyright
|
141
|
+
|
142
|
+
Largely based on Tom Verbeure's work for MTBGuru: http://code.google.com/p/mtbguru-fusiontables/
|
143
|
+
|
144
|
+
Copyright (c) 2010 Tom Verbeure, Simon Tokumine. See LICENSE for details.
|
145
|
+
|
146
|
+
|
147
|
+
|
148
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "oozou-fusion_tables"
|
8
|
+
gem.summary = %Q{Google Fusion Tables API wrapper}
|
9
|
+
gem.description = %Q{A simple Google Fusion Tables API wrapper. Supports bulk inserts and most API functions}
|
10
|
+
gem.email = "simon@tinypla.net"
|
11
|
+
gem.homepage = "http://github.com/oozou/fusion-tables"
|
12
|
+
gem.authors = ["Simon Tokumine", "Tom Verbeure"]
|
13
|
+
gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
|
14
|
+
gem.add_dependency "gdata_19", ">= 1.1.2"
|
15
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
|
+
end
|
17
|
+
Jeweler::GemcutterTasks.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'rake/testtask'
|
23
|
+
Rake::TestTask.new(:test) do |test|
|
24
|
+
test.libs << 'lib' << 'test'
|
25
|
+
test.pattern = 'test/**/test_*.rb'
|
26
|
+
test.verbose = true
|
27
|
+
end
|
28
|
+
|
29
|
+
begin
|
30
|
+
require 'rcov/rcovtask'
|
31
|
+
Rcov::RcovTask.new do |test|
|
32
|
+
test.libs << 'test'
|
33
|
+
test.pattern = 'test/**/test_*.rb'
|
34
|
+
test.verbose = true
|
35
|
+
end
|
36
|
+
rescue LoadError
|
37
|
+
task :rcov do
|
38
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
task :test => :check_dependencies
|
43
|
+
|
44
|
+
task :default => :test
|
45
|
+
|
46
|
+
require 'rake/rdoctask'
|
47
|
+
Rake::RDocTask.new do |rdoc|
|
48
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
49
|
+
|
50
|
+
rdoc.rdoc_dir = 'rdoc'
|
51
|
+
rdoc.title = "fusion_tables #{version}"
|
52
|
+
rdoc.rdoc_files.include('README*')
|
53
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
54
|
+
end
|
data/TODO
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
Areas of improvement:
|
2
|
+
|
3
|
+
* response format json/xml
|
4
|
+
* "create table" should return name
|
5
|
+
* describe table should include name
|
6
|
+
* multiple batch statements for all actions (only insert now). Need on all other commands. Better SQL.
|
7
|
+
* docs need updating, esp on response codes
|
8
|
+
* response codes need to be more standardised
|
9
|
+
* irritating can't include ROWID with * in selects
|
10
|
+
* add columns
|
11
|
+
* better heatmaps - scale is wonky
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.3.dev.20110408163600
|
@@ -0,0 +1,159 @@
|
|
1
|
+
# This library creates a FT and posts data from the Boris Bikes API to it every minute
|
2
|
+
#
|
3
|
+
# Add photos to infowindow
|
4
|
+
# Add fusion table graphs to the inside of the infowindows too
|
5
|
+
|
6
|
+
|
7
|
+
require 'net/http'
|
8
|
+
require 'uri'
|
9
|
+
require 'rubygems'
|
10
|
+
require 'geo_ruby'
|
11
|
+
require 'fusion_tables'
|
12
|
+
require 'time'
|
13
|
+
require 'json'
|
14
|
+
require 'yaml'
|
15
|
+
require 'ap'
|
16
|
+
include GeoRuby
|
17
|
+
include SimpleFeatures
|
18
|
+
|
19
|
+
class Object
|
20
|
+
def try(method, *args, &block)
|
21
|
+
send(method, *args, &block)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def color max, number_to_color, min=0, opacity=80
|
26
|
+
color = ["FFFFB2", "FFFFB2", "FEB24C", "FD8D3C", "F03B20", "BD0026"]
|
27
|
+
#color = %w(FEE0D2 FCBBA1 FC9272 FB6A4A EF3B2C CB181D A50F15 67000D)
|
28
|
+
color.reverse!
|
29
|
+
#color = ["FFFFCC", "D9F0A3", "ADDD8E", "78C679", "31A354", "31A354"] #<- greens
|
30
|
+
chunk = (max-min)/color.size
|
31
|
+
index = (number_to_color/chunk).floor
|
32
|
+
"#{color[index]}#{opacity}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_google(x,y)
|
36
|
+
a = `echo "#{x} #{y}" | cs2cs + +init=epsg:4326 +to +init=epsg:3785 -f "%.12f"`
|
37
|
+
a = a.split(" ")
|
38
|
+
{:x => a[0], :y => a[1]}
|
39
|
+
end
|
40
|
+
|
41
|
+
def from_google(x,y)
|
42
|
+
a = `echo "#{x} #{y}" | cs2cs + +init=epsg:3785 +to +init=epsg:4326 -f "%.12f"`
|
43
|
+
a = a.split(" ")
|
44
|
+
{:x => a[0], :y => a[1]}
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def buffer(center_x, center_y, radius, quality = 4, precision = 12)
|
49
|
+
points = []
|
50
|
+
radians = Math::PI / 180
|
51
|
+
|
52
|
+
coords = to_google(center_x, center_y)
|
53
|
+
center_x = coords[:x].to_f
|
54
|
+
center_y = coords[:y].to_f
|
55
|
+
|
56
|
+
0.step(360, quality) do |i|
|
57
|
+
x = center_x + (radius * Math.cos(i * radians))
|
58
|
+
y = center_y + (radius * Math.sin(i * radians))
|
59
|
+
coords = from_google(x,y)
|
60
|
+
points << Point.from_x_y(round(coords[:x].to_f, precision), round(coords[:y].to_f, precision))
|
61
|
+
end
|
62
|
+
points
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
def round number, precision = 12
|
67
|
+
(number * 10**precision).round.to_f / 10**precision
|
68
|
+
end
|
69
|
+
|
70
|
+
# Configure settings
|
71
|
+
config = YAML::load_file(File.join(File.dirname(__FILE__), 'credentials.yml'))
|
72
|
+
DEFAULT_SRID = 4328
|
73
|
+
|
74
|
+
|
75
|
+
# Configure fusion tables
|
76
|
+
ft = GData::Client::FusionTables.new
|
77
|
+
ft.clientlogin(config["google_username"], config["google_password"])
|
78
|
+
table_name = "Boris Bikes"
|
79
|
+
cols = [
|
80
|
+
{:name => 'name', :type => 'string'},
|
81
|
+
{:name => 'created_at', :type => 'datetime'},
|
82
|
+
{:name => 'updated_at', :type => 'datetime'},
|
83
|
+
{:name => 'boris_id', :type => 'number'},
|
84
|
+
{:name => 'temporary', :type => 'number'},
|
85
|
+
{:name => 'installed', :type => 'number'},
|
86
|
+
{:name => 'locked', :type => 'number'},
|
87
|
+
{:name => 'nb_empty_docs',:type => 'number'},
|
88
|
+
{:name => 'nb_bikes', :type => 'number'},
|
89
|
+
{:name => 'nb_docs', :type => 'number'},
|
90
|
+
{:name => 'image', :type => 'string'},
|
91
|
+
{:name => 'geom', :type => 'location'},
|
92
|
+
{:name => 'geom_fill', :type => 'string'},
|
93
|
+
{:name => 'geom_border', :type => 'string'},
|
94
|
+
]
|
95
|
+
|
96
|
+
# Create FT if it doesn't exist
|
97
|
+
tables = ft.show_tables
|
98
|
+
table = tables.select{|t| t.name == table_name}.first
|
99
|
+
table = ft.create_table(table_name, cols) if !table
|
100
|
+
|
101
|
+
while true do
|
102
|
+
bikes = JSON.parse(Net::HTTP.get(URI.parse('http://borisapi.heroku.com/stations.json')))
|
103
|
+
|
104
|
+
# get largest bike rack to calibrate buffer
|
105
|
+
max = 0
|
106
|
+
bikes.each do |b|
|
107
|
+
slots = b["nb_empty_docks"] + b["nb_bikes"]
|
108
|
+
max = slots if slots > max
|
109
|
+
end
|
110
|
+
|
111
|
+
# loop through data constructing fusion table date
|
112
|
+
data = []
|
113
|
+
max_radius = 150.0 #in meters
|
114
|
+
buffer_chunk = max_radius / max
|
115
|
+
|
116
|
+
bikes.each do |b|
|
117
|
+
if b["lat"].to_f > 50 #ignore non geographic ones
|
118
|
+
docs = (b["nb_bikes"] + b["nb_empty_docks"])
|
119
|
+
geom = Polygon.from_points [buffer(b["long"].to_f, b["lat"].to_f, docs*buffer_chunk)]
|
120
|
+
#geom = Point.from_x_y b["long"].to_f, b["lat"].to_f
|
121
|
+
|
122
|
+
data << {
|
123
|
+
"name" => b["name"],
|
124
|
+
"created_at" => Time::parse(b["created_at"]),
|
125
|
+
"updated_at" => Time::parse(b["updated_at"]),
|
126
|
+
"boris_id" => b["id"],
|
127
|
+
"temporary" => (b["temporary"] ? 1 : 0),
|
128
|
+
"installed" => (b["installed"] ? 1 : 0),
|
129
|
+
"locked" => (b["locked"] ? 1 : 0),
|
130
|
+
"nb_empty_docs" => b["nb_empty_docks"],
|
131
|
+
"nb_bikes" => b["nb_bikes"],
|
132
|
+
"nb_docs" => docs,
|
133
|
+
"image" => "",
|
134
|
+
"geom" => geom.as_kml,
|
135
|
+
"geom_fill" => color(max,b["nb_bikes"]),
|
136
|
+
"geom_border" => color(max,b["nb_bikes"],0,"FF"),
|
137
|
+
}
|
138
|
+
puts "packing data for #{b["name"]}"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# get current number of rows ready to delete
|
143
|
+
row_ids = table.select "ROWID"
|
144
|
+
|
145
|
+
# put new data up
|
146
|
+
puts "sending bikes to fusion tables..."
|
147
|
+
table.insert data
|
148
|
+
|
149
|
+
# remove old data
|
150
|
+
puts "deleting old rows"
|
151
|
+
row_ids.each do |id|
|
152
|
+
table.delete id[:rowid]
|
153
|
+
end
|
154
|
+
|
155
|
+
# Be nice and wait
|
156
|
+
puts "...done! sleeping..."
|
157
|
+
sleep 500
|
158
|
+
end
|
159
|
+
|
@@ -0,0 +1,143 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Twitter Fusion Tables Mashup
|
4
|
+
# S.Tokumine 2010
|
5
|
+
#
|
6
|
+
# looks for tweets in the live stream around certain
|
7
|
+
# cities and posts them to fusion tables with KML attached
|
8
|
+
#
|
9
|
+
# Gem dependencies:
|
10
|
+
#
|
11
|
+
# tweetstream
|
12
|
+
# GeoRuby
|
13
|
+
# fusion_tables
|
14
|
+
#
|
15
|
+
# Output from running this for an evening
|
16
|
+
# http://tables.googlelabs.com/DataSource?snapid=72509
|
17
|
+
#
|
18
|
+
require 'rubygems'
|
19
|
+
require 'tweetstream'
|
20
|
+
require 'geo_ruby'
|
21
|
+
include GeoRuby
|
22
|
+
include SimpleFeatures
|
23
|
+
require 'fusion_tables'
|
24
|
+
require 'time'
|
25
|
+
require 'yaml'
|
26
|
+
|
27
|
+
class Object
|
28
|
+
def try(method, *args, &block)
|
29
|
+
send(method, *args, &block)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Configure settings
|
34
|
+
config = YAML::load_file(File.join(File.dirname(__FILE__), 'credentials.yml'))
|
35
|
+
DEFAULT_SRID = 4328
|
36
|
+
|
37
|
+
# Twitter places
|
38
|
+
places = {
|
39
|
+
:san_francisco => [-122.75,36.8,-121.75,37.8],
|
40
|
+
:new_york => [-74,40,-73,41],
|
41
|
+
:tokyo => [139.3,35,140.3,36],
|
42
|
+
:london => [-0.54,51.2,0.46,52.2],
|
43
|
+
:madrid => [-4.2,40,-3.2,41],
|
44
|
+
:paris => [1.75,48.5,2.75, 49.5],
|
45
|
+
:beijing => [115.9,39,116.9,40],
|
46
|
+
:mumbai => [72.75,18.88,73.75,19.88],
|
47
|
+
}
|
48
|
+
|
49
|
+
# Configure fusion tables
|
50
|
+
ft = GData::Client::FusionTables.new
|
51
|
+
ft.clientlogin(config["google_username"], config["google_password"])
|
52
|
+
table_name = "TwitterFusion"
|
53
|
+
cols = [
|
54
|
+
{:name => 'screen_name', :type => 'string'},
|
55
|
+
{:name => 'avatar', :type => 'string'},
|
56
|
+
{:name => 'text', :type => 'string'},
|
57
|
+
{:name => 'created', :type => 'datetime'},
|
58
|
+
{:name => 'url', :type => 'string'},
|
59
|
+
{:name => 'location', :type => 'location'},
|
60
|
+
{:name => 'iso', :type => 'location'},
|
61
|
+
{:name => 'country_name', :type => 'location'},
|
62
|
+
{:name => 'city', :type => 'string'}
|
63
|
+
]
|
64
|
+
|
65
|
+
# Create FT if it doesn't exist
|
66
|
+
tables = ft.show_tables
|
67
|
+
table = tables.select{|t| t.name == table_name}.first
|
68
|
+
table = ft.create_table(table_name, cols) if !table
|
69
|
+
|
70
|
+
# Configure Twitter stream client
|
71
|
+
data = []
|
72
|
+
tw = TweetStream::Client.new(config["twitter_username"],config["twitter_password"])
|
73
|
+
|
74
|
+
# configure friendly rate limit handling
|
75
|
+
tw.on_limit do |skip_count|
|
76
|
+
sleep 5
|
77
|
+
end
|
78
|
+
|
79
|
+
# start searching twitter stream and posting to FT
|
80
|
+
tw.filter(:locations => places.values.join(",")) do |tweet|
|
81
|
+
begin
|
82
|
+
|
83
|
+
country = "unknown"
|
84
|
+
iso = "unknown"
|
85
|
+
begin
|
86
|
+
country = tweet.try(:[],:place).try(:[], :country)
|
87
|
+
iso = tweet.try(:[],:place).try(:[], :country_code)
|
88
|
+
rescue
|
89
|
+
end
|
90
|
+
|
91
|
+
# Divine the tweets geometry
|
92
|
+
#
|
93
|
+
# overly complex due to
|
94
|
+
# * some US tweets have their lat/longs flipped (but not all...)
|
95
|
+
# * some geo tweets are made using a "place" envelope rather than exact lat/kng
|
96
|
+
if tweet[:geo]
|
97
|
+
if iso == 'US' && tweet[:geo][:coordinates][1] > 0
|
98
|
+
p = Point.from_x_y(tweet[:geo][:coordinates][0],tweet[:geo][:coordinates][1])
|
99
|
+
else
|
100
|
+
p = Point.from_x_y(tweet[:geo][:coordinates][1],tweet[:geo][:coordinates][0])
|
101
|
+
end
|
102
|
+
else
|
103
|
+
p = Polygon.from_coordinates(tweet[:place][:bounding_box][:coordinates]).envelope.center
|
104
|
+
end
|
105
|
+
|
106
|
+
# work out which city the tweet is from by testing with an extended bounding box
|
107
|
+
# BBox extention needed as twitter returns things outside our defined bboxes...
|
108
|
+
city = "unknown"
|
109
|
+
places.each do |key, value|
|
110
|
+
if !(p.x < value[0]-1 || p.x > value[2]+1 || p.y < value[1]-1 || p.y > value[3]+1)
|
111
|
+
city = key.to_s.gsub("_"," ")
|
112
|
+
break
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# pack data
|
117
|
+
data << {
|
118
|
+
"screen_name" => tweet[:user][:screen_name],
|
119
|
+
"avatar" => tweet[:user][:profile_image_url],
|
120
|
+
"text" => tweet[:text],
|
121
|
+
"created" => Time.parse(tweet[:created_at]),
|
122
|
+
"url" => "http://twitter.com/#{tweet[:user][:screen_name]}/status/#{tweet[:id]}",
|
123
|
+
"location" => p.as_kml,
|
124
|
+
"iso" => iso,
|
125
|
+
"country_name" => country,
|
126
|
+
"city" => city
|
127
|
+
}
|
128
|
+
rescue => e
|
129
|
+
puts "ERROR: #{e.inspect}, #{e.backtrace}"
|
130
|
+
#let sleeping dogs lie...
|
131
|
+
end
|
132
|
+
|
133
|
+
# let us know how we're doing
|
134
|
+
puts "#{50-data.size}: #{city}, #{tweet.text}"
|
135
|
+
|
136
|
+
# Post to fusion tables
|
137
|
+
if data.size == 50
|
138
|
+
puts "sending data to fusion tables..."
|
139
|
+
ft_data = data
|
140
|
+
data = []
|
141
|
+
table.insert ft_data
|
142
|
+
end
|
143
|
+
end
|