barton 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/lib/barton.rb CHANGED
@@ -2,12 +2,21 @@ require "barton/core"
2
2
 
3
3
  module Barton
4
4
  class << self
5
- def data_loaded
6
- @@data_loaded ||= false
5
+ def environment
6
+ @@environment = ENV['BARTON'] || 'development'
7
7
  end
8
-
9
- def data_loaded=(bool)
10
- @@data_loaded = bool
8
+
9
+ def environment=(env)
10
+ ENV['BARTON'] = env
11
+ @@environment = env
12
+ end
13
+
14
+ def api_url
15
+ @@api_url ||= 'http://localhost:4567'
16
+ end
17
+
18
+ def api_url=(env)
19
+ @@api_url = env
11
20
  end
12
21
  end
13
22
  end
data/lib/barton/app.rb CHANGED
@@ -1,20 +1,22 @@
1
1
  require 'sinatra'
2
2
  require 'json'
3
+ require 'uri'
3
4
  require 'barton'
4
5
  require 'barton/core'
5
6
 
6
- module Barton
7
+ module Barton
7
8
  class App < Sinatra::Base
8
-
9
- # routes
9
+
10
+ # routes
10
11
  allowed_formats = ['', 'json']
11
12
 
12
- get '/' do
13
- "this is the Barton home page"
13
+ # helpers
14
+ before do
15
+ Barton.api_url = "#{request.base_url }"
14
16
  end
15
17
 
16
- get '/docs' do
17
- "this is the docs page"
18
+ get '/' do
19
+ File.read( 'lib/barton/docs.html' )
18
20
  end
19
21
 
20
22
  # electorate resource
@@ -23,10 +25,12 @@ module Barton
23
25
  geo, tags, address = params[:geo], params[:tags], params[:address]
24
26
  tags = tags.split(',') unless tags.nil?
25
27
  raise Sinatra::NotFound unless allowed_formats.include?( format )
26
- geo = Barton::Data.address( address ) if address
27
28
  if not id.empty?
28
29
  results = Barton.electorates( {:id => id} )
29
30
  prepare_response( results )
31
+ elsif address
32
+ results = Barton.electorates( {:tags => tags, :address => address} )
33
+ prepare_response( results )
30
34
  elsif geo or tags
31
35
  results = Barton.electorates( {:tags => tags, :geo => geo} )
32
36
  prepare_response( results )
@@ -35,31 +39,53 @@ module Barton
35
39
  end
36
40
  end
37
41
 
42
+ # electorate resource
43
+ get %r{^/api/members/?(\w*)/?.?([A-z]*)} do
44
+ id, format = params[:captures]
45
+ geo, tags, address = params[:geo], params[:tags], params[:address]
46
+ tags = tags.split(',') unless tags.nil?
47
+ raise Sinatra::NotFound unless allowed_formats.include?( format )
48
+ if not id.empty?
49
+ results = Barton.members( {:id => id} )
50
+ prepare_response( results )
51
+ elsif address
52
+ results = Barton.members( {:tags => tags, :address => address} )
53
+ prepare_response( results )
54
+ elsif geo or tags
55
+ results = Barton.members( {:tags => tags, :geo => geo} )
56
+ prepare_response( results )
57
+ else
58
+ prepare_response
59
+ end
60
+ end
61
+
62
+
38
63
  # base api
39
64
  get %r{^/api/?.?([A-z]*)$} do
40
65
  prepare_response
41
66
  end
42
-
67
+
43
68
  # views
44
-
69
+
45
70
  # Prepares and array of results for the API response
46
71
  def prepare_response( args=nil )
47
72
  # add generic API meta data
48
- app_url = 'http://barton.experimentsindemocracy.org'
49
73
  response = {}
50
74
  response[:name] = "Barton API"
51
- response[:disclaimer] = "This data is crowded sourced and provided free of charge for informational purposes only. No guarantees whatsoever regarding data quality are made excluding its sheer awesomeness."
75
+ response[:disclaimer] = "This data is crowded sourced and provided free of charge for informational purposes only. No guarantees regarding data quality are made whatsoever apart from it's rather considerable coolness."
52
76
  response[:license] = "MIT License http://www.opensource.org/licenses/mit-license.php"
53
77
  unless args.nil?
54
- response[:result_count] = args.length
78
+ response[:result_count] = args.length
55
79
  response[:results] = args
80
+ status 404 if args.length == 0
56
81
  end
57
- response[:resources] = { :home => app_url, :api => "#{app_url}/api", :electorates => "#{app_url}/api/electorates" }
58
- response[:examples] = { :electorates => {
59
- :resource_id => "#{app_url}/api/electorates/ccbfd1",
60
- :geo => "#{app_url}/api/electorates?geo=151.2054563,-33.8438383",
61
- :tags => "#{app_url}/api/electorates?tags=sydney,jurisidction:local",
62
- :mixed => "#{app_url}/api/electorates?geo=151.2054563,-33.8438383&tags=federal", }}
82
+ response[:resources] = { :home => Barton.api_url, :api => "#{Barton.api_url}/api", :electorates => "#{Barton.api_url}/api/electorates", :members => "#{Barton.api_url}/api/members" }
83
+ response[:examples] = {
84
+ :id => "#{Barton.api_url}/api/members/0db2ec",
85
+ :geo => "#{Barton.api_url}/api/electorates?geo=151.2054563,-33.8438383",
86
+ :tags => "#{Barton.api_url}/api/members?tags=sydney,local",
87
+ :mixed => "#{Barton.api_url}/api/electorates?geo=151.2054563,-33.8438383&tags=federal",
88
+ }
63
89
  # format
64
90
  content_type 'application/json;charset=utf-8'
65
91
  JSON.pretty_generate( response )
data/lib/barton/core.rb CHANGED
@@ -5,7 +5,7 @@ require 'json'
5
5
  require 'barton/app'
6
6
 
7
7
  module Barton
8
-
8
+
9
9
  # Returns an array of electorates matching the search criteria
10
10
  # Accepts a hash of criteria
11
11
  # :id
@@ -13,50 +13,75 @@ module Barton
13
13
  # :geo
14
14
  # :address
15
15
  # Returns an array of hashes
16
- def Barton.electorates( query = {} )
16
+ def self.electorates( query = {} )
17
17
  Find.electorates( query )
18
18
  end
19
-
19
+
20
20
  # Returns an array of member matching the search criteria
21
21
  # Accepts a hash of criteria
22
- # Returns an array of hashes
23
- def members
24
- # TBA
22
+ # Returns an array of hashes
23
+ def self.members( query={} )
24
+ Find.members( query )
25
25
  end
26
-
26
+
27
27
  # Loads electoral data from the yaml files into elasticsearch
28
28
  def self.setup
29
- Data.config
30
29
  Data.purge_es
31
30
  Dir['data/*.yaml'].each do |f|
32
31
  Setup.load_file( f )
33
32
  end
34
- Barton.data_loaded = true
35
33
  end
36
-
37
- # Configuration options for the Gem
34
+
38
35
  def self.config
39
- #puts ENV['RAKE_ENV']
40
- #Data.config
36
+ Data.config
41
37
  end
42
-
38
+
43
39
  module Find
44
- def self.electorates( query={} )
40
+ def self.electorates( query )
41
+ docs = Find.documents( query, 'electorate' )
42
+ # remove unwanted fields
43
+ docs.each do |e|
44
+ e.keys.each { |k| e.delete( k ) if k.match( /_|geobox|boundaries|highlight|sort/ ) }
45
+ e[:url] = "#{Barton.api_url}/api/electorates/#{e[:id]}"
46
+ members = Array.new
47
+ if e.has_key?( :members )
48
+ e[:members].each do |m|
49
+ m = m.to_hash
50
+ m[:url] = "#{Barton.api_url}/api/members/#{m[:id]}"
51
+ m.delete( :id )
52
+ members.push( m )
53
+ end
54
+ end
55
+ e[:members] = members if members.length > 0
56
+ end
57
+ docs
58
+ end
59
+
60
+ def self.members( query )
61
+ results = Array.new
62
+ docs = Find.documents( query, 'member' )
63
+ docs.each do |m|
64
+ m.keys.each { |k| m.delete( k ) if k.match( /_|highlight|sort/ ) }
65
+ m[:url] = "#{Barton.api_url}/api/members/#{m[:id]}"
66
+ e = m[:electorate].to_hash
67
+ e[:url] = "#{Barton.api_url}/api/electorates/#{e[:id]}"
68
+ e.delete( :id )
69
+ m[:electorate] = e
70
+ end
71
+ docs
72
+ end
73
+
74
+ def self.documents( query={}, type=nil )
45
75
  results = Array.new
46
76
  terms = ["id:#{query[:id]}"] if query.has_key?( :id )
47
77
  terms = query[:tags] if query.has_key?( :tags )
48
78
  geo = query[:geo] if query.has_key?( :geo )
49
79
  geo = self.address( query[:address] ) if query.has_key?( :address ) and not query.has_key?( :geo )
50
- s = Data.search( terms, geo )
51
- s.results.each do |e|
52
- e = e.to_hash
53
- e.keys.each { |k| e.delete( k ) if k.match( /_|geobox|boundaries|highlight|sort/ ) }
54
- e[:url] = "#{@api_url}/electorates/#{e[:id]}"
55
- results.push( e )
56
- end
80
+ s = Data.search( terms, geo, type )
81
+ s.results.each { |e| results.push( e.to_hash ) }
57
82
  results
58
- end
59
-
83
+ end
84
+
60
85
  # Geocode lookup to google
61
86
  def self.address( address )
62
87
  # http://maps.googleapis.com/maps/api/geocode/json?address=address&sensor=false&region=au
@@ -73,7 +98,7 @@ module Barton
73
98
  puts e
74
99
  end
75
100
  end
76
-
101
+
77
102
  # Ray casting algorithm to find if point is in polygon
78
103
  def self.point_in_poly?( geo, boundaries )
79
104
  # get pairs of points of boundaries
@@ -102,19 +127,93 @@ module Barton
102
127
  ax, ay, bx, by = ax.to_f, ay.to_f, bx.to_f, by.to_f
103
128
  return 0 if ay < long and by < long
104
129
  return ( ( ax < lat and lat < bx ) or ( bx < lat and lat < ax ) ) ? 1 : 0
105
- end
130
+ end
106
131
  end
107
-
132
+
133
+ module Data
134
+ @index_name = 'electorates'
135
+
136
+ # Set elasticsearch config
137
+ def self.config
138
+ case Barton.environment
139
+ when 'test'
140
+ @index_name = 'test_electorates'
141
+ when ENV['FOUNDELASTICSEARCH_URL']
142
+ Tire::Configuration.url ENV['FOUNDELASTICSEARCH_URL']
143
+ @index_name = 'electorates'
144
+ else
145
+ @index_name = 'electorates'
146
+ end
147
+ #if ENV['BONSAI_INDEX_URL']
148
+ # Tire.configure do
149
+ # url "http://index.bonsai.io"
150
+ # end
151
+ # @index_name = ENV['BONSAI_INDEX_URL'][/[^\/]+$/]
152
+ #end
153
+ return Barton.environment
154
+ end
155
+
156
+ # Purge electorate data from elasticsearch
157
+ def self.purge_es
158
+ self.query_es( 'purge' )
159
+ end
160
+
161
+ # Load electorate data to elasticsearch
162
+ def self.update_es( data )
163
+ self.query_es( 'update', data )
164
+ end
165
+
166
+ # Query elasticsearch
167
+ def self.query_es( action, data=nil )
168
+ self.config
169
+ begin
170
+ Tire.index "#{@index_name}" do
171
+ delete if action == 'purge'
172
+ create if action == 'purge'
173
+ import data if action == 'update' and not data.nil?
174
+ end
175
+ rescue Exception => e
176
+ puts "Elasticsearch error: #{e}"
177
+ end
178
+ end
179
+
180
+ def self.search( terms, geo, type=nil )
181
+ self.config
182
+ s = Tire.search "#{@index_name}" do
183
+ query do
184
+ boolean do
185
+ if geo
186
+ long, lat = geo.split( ',' )
187
+ must { range :north, { :gte => lat } }
188
+ must { range :south, { :lte => lat } }
189
+ must { range :east, { :gte => long } }
190
+ must { range :west, { :lte => long } }
191
+ elsif terms
192
+ terms.each { |t| must { string t } }
193
+ end
194
+ must { string "type:#{type}" } if type
195
+ end
196
+ end
197
+ # apply filters only with geo search
198
+ filter :terms, :_all => terms if geo and terms
199
+ size 100
200
+ end
201
+ s
202
+ end
203
+ end
204
+
108
205
  module Setup
109
206
  # Parses a electorate yaml file, merges it with a geo yaml file,
110
207
  # loads them to the datastore and resaves the yaml files
111
208
  def self.parse_yaml( filename )
112
- data = YAML::load_file( filename ) if File.exist?( filename )
209
+ data = Array.new
210
+ electorates = YAML::load_file( filename ) if File.exist?( filename )
113
211
  geofile = "#{File.dirname( filename )}/geo/#{File.basename( filename )}"
114
212
  geo = YAML::load_file( geofile ) if File.exist?( geofile )
115
- if data.respond_to?( 'each' )
116
- data.each do |e|
117
- e['id'] = Digest::SHA1.hexdigest( [e['name'], e['tags'].join].join )[0..5] unless e['id'] or not e['name'] or not e['tags']
213
+ if electorates.respond_to?( 'each' )
214
+ electorates.each do |e|
215
+ # id is the first 6 digits of a hash of object name with file name EG BulimbaQLD-State.yaml
216
+ e['id'] = Digest::SHA1.hexdigest( [e['name'], filename].join )[0..5].force_encoding('utf-8') unless e['id'] or not e['name']
118
217
  # make geobox and update geo.yaml if boundaries present
119
218
  if e.has_key?( 'boundaries')
120
219
  e['geobox'] = self.create_geobox( e['boundaries'] )
@@ -123,14 +222,37 @@ module Barton
123
222
  e['boundaries'] = geo[e['id']]['boundaries']
124
223
  e['geobox'] = geo[e['id']]['geobox']
125
224
  end
225
+
226
+ ei = e.clone # this will be the electorate that is indexed
227
+ ei['type'] = 'electorate'
228
+
229
+ if e.has_key?( 'members' )
230
+ # create seperate types for member details
231
+ members = Array.new
232
+ e['members'].each do |m|
233
+ m['role'] = "Member for #{e['name']}" unless m['role']
234
+ m['id'] = Digest::SHA1.hexdigest( [m['role'], filename].join )[0..5].force_encoding('utf-8') unless m['id'] or not m['role']
235
+ # only add this for indexing
236
+ mi = m.clone
237
+ mi['type'] = 'member'
238
+ mi['tags'] = [] unless m.has_key?( 'tags' )
239
+ mi['tags'] |= e['tags'] if e.has_key?( 'tags' )
240
+ mi['electorate'] = {'name' => e['name'], 'id' => e['id']}
241
+ data.push( mi )
242
+ members.push( {'role' => m['role'], 'name' => m['name'], 'id' => m['id']})
243
+ end
244
+ # only index limited member data
245
+ ei['members'] = members
246
+ end
247
+ data.push( ei )
126
248
  end
127
249
  # save parsed yaml
128
250
  data_yaml = Array.new
129
251
  geo_yaml = Hash.new
130
- data.each do |e|
252
+ electorates.each do |e|
131
253
  # remove geo from data
132
254
  geo_yaml[e['id']] = Hash['boundaries' => e['boundaries'], 'geobox' => e['geobox']] if e.has_key?( 'boundaries' )
133
- data_yaml.push( e.select { |k,v| not ['boundaries', 'geobox'].include? k } )
255
+ data_yaml.push( e.select { |k,v| not ['boundaries', 'geobox', 'type'].include? k } )
134
256
  end
135
257
  File.open( filename, 'w+' ) { |f| f.puts( YAML.dump( data_yaml ) ) }
136
258
  File.open( geofile, 'w+' ) { |f| f.puts( YAML.dump( geo_yaml ) ) }
@@ -139,7 +261,7 @@ module Barton
139
261
  nil
140
262
  end
141
263
  end
142
-
264
+
143
265
  # Creates a minimum bounded box encapsulating the boundary polygons
144
266
  def self.create_geobox( boundaries )
145
267
  return nil unless boundaries.instance_of? Array
@@ -155,79 +277,14 @@ module Barton
155
277
  end
156
278
  box
157
279
  end
158
-
280
+
159
281
  # Load data from yaml source file
160
282
  def self.load_file( filename )
161
- puts "Loading data from #{filename}....."
283
+ puts "Loading data from #{filename} to #{Barton.environment}....."
162
284
  electorates = self.parse_yaml( filename )
163
285
  puts "Failed to load #{filename}" if electorates.nil?
164
286
  connected = Data.update_es( electorates )
165
287
  abort "Connection to Elasticsearch failed" if connected.nil?
166
288
  end
167
289
  end
168
-
169
-
170
- module Data
171
-
172
- @index_name = ENV['RAKE_ENV']
173
-
174
- # Set elasticsearch config
175
- def self.config
176
- @index_name = ENV['RAKE_ENV']
177
- if ENV['BONSAI_INDEX_URL']
178
- Tire.configure do
179
- url "http://index.bonsai.io"
180
- end
181
- @index_name = ENV['BONSAI_INDEX_URL'][/[^\/]+$/]
182
- end
183
- end
184
-
185
- # Purge electorate data from elasticsearch
186
- def self.purge_es
187
- self.query_es( 'purge' )
188
- end
189
-
190
- # Load electorate data to elasticsearch
191
- def self.update_es( data )
192
- self.query_es( 'update', data )
193
- end
194
-
195
- # Query elasticsearch
196
- def self.query_es( action, data=nil )
197
- begin
198
- Tire.index "#{@index_name}-electorates" do
199
- delete if action == 'purge'
200
- create if action == 'purge'
201
- import data if action == 'update' and not data.nil?
202
- end
203
- rescue => e
204
- puts e
205
- nil
206
- end
207
- end
208
-
209
- def self.search( terms, geo )
210
- s = Tire.search "#{@index_name}-electorates" do
211
- query do
212
- boolean do
213
- if geo
214
- long, lat = geo.split( ',' )
215
- must { range :north, { :gte => lat } }
216
- must { range :south, { :lte => lat } }
217
- must { range :east, { :gte => long } }
218
- must { range :west, { :lte => long } }
219
- elsif terms
220
- terms.each { |t| must { string t } }
221
- end
222
- end
223
- end
224
- # apply filters only with geo search
225
- if geo and terms
226
- filter :terms, :_all => terms
227
- end
228
- size 100
229
- end
230
- s
231
- end
232
- end
233
290
  end