govkit-ca 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in govkit-ca.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 James McKinney
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.
@@ -0,0 +1,44 @@
1
+ # GovKit-CA
2
+
3
+ GovKit-CA is a Ruby gem that provides easy access to Canadian civic data around the web. It currently provides an API for free postal code to electoral district lookups, using the following sources:
4
+
5
+ * [elections.ca](http://elections.ca/)
6
+ * [cbc.ca](http://www.cbc.ca/)
7
+ * [ndp.ca](http://www.ndp.ca/)
8
+ * [digital-copyright.ca](http://www.digital-copyright.ca/)
9
+ * [liberal.ca](http://www.liberal.ca/)
10
+
11
+ GovKit-CA follows from [Participatory Politics Foundation](http://www.participatorypolitics.org/)'s [GovKit](https://github.com/opengovernment/govkit) gem. GovKit-CA is not affiliated with the Participatory Politics Foundation or GovKit.
12
+
13
+ # Installation
14
+
15
+ gem install govkit
16
+
17
+ # Examples
18
+
19
+ >> require 'govkit-ca'
20
+
21
+ >> GovKit::CA::PostalCode.find_electoral_districts_by_postal_code('A1A1A1')
22
+ => [10007]
23
+ >> GovKit::CA::PostalCode.find_electoral_districts_by_postal_code('K0A1K0')
24
+ => [35087, 35025, 35052, 35012, 35040, 35063, 35064]
25
+
26
+ >> GovKit::CA::PostalCode.find_province_by_postal_code('A1A1A1')
27
+ => "Newfoundland and Labrador"
28
+ >> GovKit::CA::PostalCode.find_province_by_postal_code('K0A1K0')
29
+ => "Ontario"
30
+
31
+ Postal codes may contain lowercase letters. Spaces and non-alphanumeric characters are removed before processing.
32
+
33
+ GovKit-CA will raise GovKit::CA::ResourceNotFound if the electoral districts within a postal code cannot be determined.
34
+
35
+ # Bugs? Questions?
36
+
37
+ Govkit-CA's main repository is on GitHub: [http://github.com/jpmckinney/govkit-ca](http://github.com/jpmckinney/govkit-ca), where your contributions, forks, bug reports, feature requests, and feedback are greatly welcomed.
38
+
39
+ # TODOs
40
+
41
+ * Write tests
42
+ * Add a `ElectoralDistrict` class to convert IDs to English and french names.
43
+
44
+ Copyright (c) 2011 James McKinney, released under the MIT license
@@ -0,0 +1,9 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
8
+
9
+ require File.expand_path('../tasks/rid_to_edid', __FILE__)
data/USAGE ADDED
@@ -0,0 +1 @@
1
+ See README.md for full usage details.
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "gov_kit-ca/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "govkit-ca"
7
+ s.version = GovKit::CA::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["James McKinney"]
10
+ s.email = ["james@slashpoundbang.com"]
11
+ s.homepage = "http://github.com/jpmckinney/govkit-ca"
12
+ s.summary = %q{Easy access to Canadian civic data around the web}
13
+ s.description = %q{GovKit-CA lets you quickly get encapsulated Ruby objects for Canadian civic data.}
14
+
15
+ s.rubyforge_project = "govkit-ca"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ #s.add_runtime_dependency('graticule', '>= 0.2.8')
23
+ s.add_runtime_dependency('httparty', '>= 0.6.1')
24
+ s.add_runtime_dependency('nokogiri', '>= 1.4.4')
25
+ s.add_runtime_dependency('yajl-ruby', '>= 0.7.8')
26
+ s.add_development_dependency('rspec', '>= 2')
27
+ end
@@ -0,0 +1,309 @@
1
+ ---
2
+ 1: 10001
3
+ 2: 10002
4
+ 3: 10003
5
+ 4: 10004
6
+ 5: 10005
7
+ 6: 10006
8
+ 7: 10007
9
+ 8: 11001
10
+ 9: 11002
11
+ 10: 11003
12
+ 11: 11004
13
+ 12: 12001
14
+ 13: 12002
15
+ 14: 12007
16
+ 15: 12003
17
+ 16: 12004
18
+ 17: 12005
19
+ 18: 12006
20
+ 19: 12008
21
+ 20: 12009
22
+ 21: 12010
23
+ 22: 12011
24
+ 23: 13001
25
+ 24: 13002
26
+ 25: 13003
27
+ 26: 13004
28
+ 27: 13005
29
+ 28: 13006
30
+ 29: 13007
31
+ 30: 13008
32
+ 31: 13009
33
+ 32: 13010
34
+ 33: 24046
35
+ 34: 24001
36
+ 35: 24002
37
+ 36: 24003
38
+ 37: 24004
39
+ 38: 24054
40
+ 39: 24005
41
+ 40: 24006
42
+ 41: 24007
43
+ 42: 24008
44
+ 43: 24009
45
+ 44: 24010
46
+ 45: 24011
47
+ 46: 24012
48
+ 47: 24013
49
+ 48: 24015
50
+ 49: 24016
51
+ 50: 24017
52
+ 51: 24018
53
+ 52: 24019
54
+ 53: 24020
55
+ 54: 24041
56
+ 55: 24021
57
+ 56: 24022
58
+ 57: 24023
59
+ 58: 24024
60
+ 59: 24025
61
+ 60: 24026
62
+ 61: 24027
63
+ 62: 24028
64
+ 63: 24029
65
+ 64: 24030
66
+ 65: 24031
67
+ 66: 24032
68
+ 67: 24033
69
+ 68: 24034
70
+ 69: 24035
71
+ 70: 24036
72
+ 71: 24037
73
+ 72: 24038
74
+ 73: 24039
75
+ 74: 24040
76
+ 75: 24042
77
+ 76: 24043
78
+ 77: 24058
79
+ 78: 24014
80
+ 79: 24044
81
+ 80: 24045
82
+ 81: 24047
83
+ 82: 24048
84
+ 83: 24049
85
+ 84: 24050
86
+ 85: 24051
87
+ 86: 24052
88
+ 87: 24053
89
+ 88: 24055
90
+ 89: 24056
91
+ 90: 24057
92
+ 91: 24059
93
+ 92: 24060
94
+ 93: 24061
95
+ 94: 24062
96
+ 95: 24063
97
+ 96: 24064
98
+ 97: 24065
99
+ 98: 24066
100
+ 99: 24067
101
+ 100: 24068
102
+ 101: 24069
103
+ 102: 24070
104
+ 103: 24071
105
+ 104: 24072
106
+ 105: 24073
107
+ 106: 24074
108
+ 107: 24075
109
+ 108: 35001
110
+ 109: 35002
111
+ 110: 35003
112
+ 111: 35004
113
+ 112: 35005
114
+ 113: 35006
115
+ 114: 35007
116
+ 115: 35008
117
+ 116: 35009
118
+ 117: 35026
119
+ 118: 35010
120
+ 119: 35011
121
+ 120: 35012
122
+ 121: 35013
123
+ 122: 35015
124
+ 123: 35016
125
+ 124: 35017
126
+ 125: 35018
127
+ 126: 35014
128
+ 127: 35019
129
+ 128: 35020
130
+ 129: 35021
131
+ 130: 35022
132
+ 131: 35023
133
+ 132: 35024
134
+ 133: 35025
135
+ 134: 35027
136
+ 135: 35028
137
+ 136: 35029
138
+ 137: 35030
139
+ 138: 35031
140
+ 139: 35032
141
+ 140: 35033
142
+ 141: 35034
143
+ 142: 35035
144
+ 143: 35036
145
+ 144: 35037
146
+ 145: 35038
147
+ 146: 35039
148
+ 147: 35046
149
+ 148: 35040
150
+ 149: 35041
151
+ 150: 35042
152
+ 151: 35043
153
+ 152: 35044
154
+ 153: 35045
155
+ 154: 35047
156
+ 155: 35048
157
+ 156: 35049
158
+ 157: 35050
159
+ 158: 35051
160
+ 159: 35052
161
+ 160: 35053
162
+ 161: 35054
163
+ 162: 35055
164
+ 163: 35056
165
+ 164: 35057
166
+ 165: 35058
167
+ 166: 35059
168
+ 167: 35060
169
+ 168: 35061
170
+ 169: 35062
171
+ 170: 35063
172
+ 171: 35064
173
+ 172: 35065
174
+ 173: 35066
175
+ 174: 35067
176
+ 175: 35068
177
+ 176: 35069
178
+ 177: 35070
179
+ 178: 35071
180
+ 179: 35072
181
+ 180: 35073
182
+ 181: 35074
183
+ 182: 35075
184
+ 183: 35076
185
+ 184: 35077
186
+ 185: 35078
187
+ 186: 35079
188
+ 187: 35080
189
+ 188: 35081
190
+ 189: 35082
191
+ 190: 35083
192
+ 191: 35084
193
+ 192: 35085
194
+ 193: 35086
195
+ 194: 35087
196
+ 195: 35088
197
+ 196: 35089
198
+ 197: 35090
199
+ 198: 35091
200
+ 199: 35092
201
+ 200: 35093
202
+ 201: 35094
203
+ 202: 35095
204
+ 203: 35096
205
+ 204: 35097
206
+ 205: 35098
207
+ 206: 35099
208
+ 207: 35100
209
+ 208: 35101
210
+ 209: 35102
211
+ 210: 35103
212
+ 211: 35104
213
+ 212: 35105
214
+ 213: 35106
215
+ 214: 46001
216
+ 215: 46002
217
+ 216: 46003
218
+ 217: 46004
219
+ 218: 46005
220
+ 219: 46006
221
+ 220: 46007
222
+ 221: 46008
223
+ 222: 46009
224
+ 223: 46010
225
+ 224: 46011
226
+ 225: 46012
227
+ 226: 46013
228
+ 227: 46014
229
+ 228: 47001
230
+ 229: 47002
231
+ 230: 47004
232
+ 231: 47003
233
+ 232: 47005
234
+ 233: 47006
235
+ 234: 47007
236
+ 235: 47008
237
+ 236: 47009
238
+ 237: 47010
239
+ 238: 47011
240
+ 239: 47012
241
+ 240: 47013
242
+ 241: 47014
243
+ 242: 48006
244
+ 243: 48003
245
+ 244: 48002
246
+ 245: 48004
247
+ 246: 48005
248
+ 247: 48007
249
+ 248: 48008
250
+ 249: 48009
251
+ 250: 48010
252
+ 251: 48012
253
+ 252: 48013
254
+ 253: 48014
255
+ 254: 48011
256
+ 255: 48015
257
+ 256: 48016
258
+ 257: 48017
259
+ 258: 48018
260
+ 259: 48001
261
+ 260: 48019
262
+ 261: 48020
263
+ 262: 48021
264
+ 263: 48022
265
+ 264: 48023
266
+ 265: 48024
267
+ 266: 48025
268
+ 267: 48026
269
+ 268: 48027
270
+ 269: 48028
271
+ 270: 59001
272
+ 271: 59026
273
+ 272: 59002
274
+ 273: 59003
275
+ 274: 59004
276
+ 275: 59005
277
+ 276: 59006
278
+ 277: 59008
279
+ 278: 59009
280
+ 279: 59010
281
+ 280: 59011
282
+ 281: 59012
283
+ 282: 59013
284
+ 283: 59014
285
+ 284: 59015
286
+ 285: 59016
287
+ 286: 59017
288
+ 287: 59019
289
+ 288: 59020
290
+ 289: 59018
291
+ 290: 59007
292
+ 291: 59021
293
+ 292: 59022
294
+ 293: 59023
295
+ 294: 59024
296
+ 295: 59025
297
+ 296: 59027
298
+ 297: 59028
299
+ 298: 59029
300
+ 299: 59030
301
+ 300: 59031
302
+ 301: 59032
303
+ 302: 59033
304
+ 303: 59034
305
+ 304: 59035
306
+ 305: 59036
307
+ 306: 60001
308
+ 307: 61001
309
+ 308: 62001
@@ -0,0 +1,18 @@
1
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__))) unless $LOAD_PATH.include?(File.expand_path(File.dirname(__FILE__)))
2
+
3
+ require 'json'
4
+ require 'yaml'
5
+
6
+ #require 'graticule'
7
+ require 'httparty'
8
+ require 'nokogiri'
9
+ require 'yajl'
10
+
11
+ module GovKit
12
+ module CA
13
+ autoload :PostalCode, 'gov_kit-ca/postal_code'
14
+
15
+ class GovKitError < StandardError; end
16
+ class ResourceNotFound < GovKitError; end
17
+ end
18
+ end
@@ -0,0 +1,68 @@
1
+ require 'gov_kit-ca/postal_code/strategy'
2
+
3
+ module GovKit
4
+ module CA
5
+ # A collection of postal code helpers.
6
+ # @see http://en.wikipedia.org/wiki/Postal_codes_in_Canada Postal codes in Canada
7
+ module PostalCode
8
+ # Returns the electoral districts within a postal code.
9
+ #
10
+ # Statistics Canada charges for its Postal Codes by Federal Ridings File
11
+ # (PCFRF). A free alternative requires scraping data from other sources.
12
+ #
13
+ # @param [String] postal_code a postal code
14
+ # @return [Array<Fixnum>] the electoral districts within the postal code
15
+ # @raise [ResourceNotFound] if the electoral districts cannot be determined
16
+ # @see http://www.statcan.gc.ca/bsolc/olc-cel/olc-cel?lang=eng&catno=92F0193X Statistics Canada's product page for the Postal Codes by Federal Ridings File (PCFRF)
17
+ def self.find_electoral_districts_by_postal_code(postal_code)
18
+ StrategySet.run format_postal_code(postal_code)
19
+ end
20
+
21
+ # Returns the province that a postal code belongs to.
22
+ # @param [String] postal_code a postal code
23
+ # @return [String] the province that the postal code belongs to
24
+ # @raise [ResourceNotFound] if the province cannot be determined
25
+ # @see http://en.wikipedia.org/wiki/List_of_postal_codes_in_Canada List of postal codes in Canada
26
+ def self.find_province_by_postal_code(postal_code)
27
+ case format_postal_code(postal_code)
28
+ when /\AA/
29
+ 'Newfoundland and Labrador'
30
+ when /\AB/
31
+ 'Nova Scotia'
32
+ when /\AC/
33
+ 'Prince Edward Island'
34
+ when /\AE/
35
+ 'New Brunswick'
36
+ when /\A[GHJ]/
37
+ 'Quebec'
38
+ when /\A[KLMNP]/
39
+ 'Ontario'
40
+ when /\AR/
41
+ 'Manitoba'
42
+ when /\AS/
43
+ 'Saskatchewan'
44
+ when /\AT/
45
+ 'Alberta'
46
+ when /\AV/
47
+ 'British Columbia'
48
+ # http://en.wikipedia.org/wiki/List_of_X_postal_codes_of_Canada
49
+ when /\AX0[ABC]/
50
+ 'Nunavut'
51
+ when /\AX0[EG]/, /\AX1A/
52
+ 'Northwest Territories'
53
+ when /\AY/
54
+ 'Yukon'
55
+ else
56
+ raise ResourceNotFound, "The province cannot be determined from the postal code"
57
+ end
58
+ end
59
+
60
+ # Formats a postal code as A1A1A1. Removes non-alphanumeric characters.
61
+ # @param [String] postal_code a postal code
62
+ # @return [String] a formatted postal code
63
+ def self.format_postal_code(postal_code)
64
+ postal_code.upcase.gsub(/[^A-Z0-9]/, '')
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,45 @@
1
+ module GovKit
2
+ module CA
3
+ module PostalCode
4
+ # The set of postal code to electoral district strategies.
5
+ class StrategySet
6
+ # Stores the strategy set.
7
+ # @return [Array<Strategy::Base>] the strategy set
8
+ def self.strategies
9
+ @@strategies ||= []
10
+ end
11
+
12
+ # Adds a strategy to the strategy set.
13
+ # @param [Strategy::Base] strategy a strategy
14
+ # @return [Array<Strategy::Base>] the strategy set
15
+ def self.register(strategy)
16
+ strategies << strategy
17
+ end
18
+
19
+ # Runs through the strategies in order of registration. Returns the
20
+ # output of the first strategy to successfully determine electoral
21
+ # districts from a postal code.
22
+ # @param [String] postal_code a postal code
23
+ # @return [Array<Fixnum>] the electoral districts within the postal code
24
+ # @raise [ResourceNotFound] if no strategy succeeds
25
+ def self.run(postal_code)
26
+ strategies.each do |strategy|
27
+ electoral_districts = strategy.new(postal_code).electoral_districts
28
+ return electoral_districts if electoral_districts
29
+ end
30
+
31
+ raise ResourceNotFound
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ require 'gov_kit-ca/postal_code/strategy/base'
39
+ require 'gov_kit-ca/postal_code/strategy/elections-ca'
40
+ require 'gov_kit-ca/postal_code/strategy/cbc-ca'
41
+ require 'gov_kit-ca/postal_code/strategy/ndp-ca'
42
+ require 'gov_kit-ca/postal_code/strategy/digital_copyright-ca'
43
+ require 'gov_kit-ca/postal_code/strategy/liberal-ca'
44
+ #require 'gov_kit-ca/postal_code/strategy/parl-gc-ca'
45
+ #require 'gov_kit-ca/postal_code/strategy/conservative-ca'
@@ -0,0 +1,45 @@
1
+ module GovKit
2
+ module CA
3
+ module PostalCode
4
+ module Strategy
5
+ # Abstract class for implementing postal code to electoral district
6
+ # strategies.
7
+ #
8
+ # The following methods must be implemented in sub-classes:
9
+ #
10
+ # * `electoral_districts!`
11
+ class Base
12
+ include HTTParty
13
+ follow_redirects false
14
+ headers 'User-Agent' => 'GovKit-CA +http://govkit.org'
15
+
16
+ # Creates a new postal code to electoral district strategy.
17
+ # @param [String] postal_code a postal code
18
+ def initialize(postal_code)
19
+ @postal_code = postal_code
20
+ end
21
+
22
+ # Returns the electoral districts within a postal code.
23
+ # @return [Array<Fixnum>] the electoral districts within the postal code
24
+ def electoral_districts
25
+ valid? && electoral_districts!.map(&:to_i)
26
+ end
27
+
28
+ private
29
+
30
+ # Returns the electoral districts within a postal code, without
31
+ # passing validation first.
32
+ # @return [Array<Fixnum>] the electoral districts within the postal code
33
+ def electoral_districts!
34
+ raise NotImplementedError
35
+ end
36
+
37
+ # @return [Boolean] whether the electoral districts can be determined
38
+ def valid?
39
+ true
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,46 @@
1
+ module GovKit
2
+ module CA
3
+ module PostalCode
4
+ module Strategy
5
+ # cbc.ca ought to be a reliable source. It is unknown if its database
6
+ # is kept up-to-date between elections, however.
7
+ # @see https://github.com/danielharan/pc_scraper
8
+ class CbcCa < Base
9
+ base_uri 'www.cbc.ca'
10
+
11
+ def initialize(postal_code)
12
+ @fsa, @letter, @ldu = postal_code.downcase.match(/\A((.).{2})(.{3})\Z/)[1..3]
13
+ super
14
+ end
15
+
16
+ def json_response # Yajl barfs on bad encoding
17
+ Yajl::Parser.parse(response.parsed_response) rescue JSON.parse(response.parsed_response)
18
+ end
19
+
20
+ private
21
+
22
+ def electoral_districts!
23
+ json_response.map{|x| self.class.rid_to_edid[x['rid']]}
24
+ end
25
+
26
+ def valid?
27
+ !!response.headers['expires']
28
+ end
29
+
30
+ def response
31
+ @response ||= self.class.get "/news/canadavotes/myriding/postalcodes/#{@letter}/#{@fsa}/#{@ldu}.html"
32
+ end
33
+
34
+ # cbc.ca uses an internal riding ID, which must be matched to a
35
+ # canonical electoral district ID.
36
+ # @return [Hash] a map of cbc.ca riding ID to electoral district ID
37
+ def self.rid_to_edid
38
+ @@yml ||= YAML.load_file(File.expand_path('../../../../data/rid_to_edid.yml', __FILE__))
39
+ end
40
+ end
41
+
42
+ StrategySet.register CbcCa
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,29 @@
1
+ module GovKit
2
+ module CA
3
+ module PostalCode
4
+ module Strategy
5
+ # conservative.ca seems unreliable. In the case of B0J2L0, for example,
6
+ # it does not return three ridings like other sources.
7
+ class ConservativeCa < Base
8
+ base_uri 'www.conservative.ca'
9
+
10
+ private
11
+
12
+ def electoral_districts!
13
+ # TODO returns HTML with electoral district names only
14
+ end
15
+
16
+ def valid?
17
+ # TODO
18
+ end
19
+
20
+ def response
21
+ @response ||= self.class.get "/?section_id=1051&postal_code=#{@postal_code}"
22
+ end
23
+ end
24
+
25
+ StrategySet.register ConservativeCa
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,27 @@
1
+ module GovKit
2
+ module CA
3
+ module PostalCode
4
+ module Strategy
5
+ class DigitalCopyrightCa < Base
6
+ base_uri 'www.digital-copyright.ca'
7
+
8
+ private
9
+
10
+ def electoral_districts!
11
+ Nokogiri::HTML(response.parsed_response).css('.node .content a').map{|a| a[:href][/\d+\Z/]}
12
+ end
13
+
14
+ def valid?
15
+ !response.parsed_response.match /\binvalid postal code\b/
16
+ end
17
+
18
+ def response
19
+ @response ||= self.class.get "/edid/postal?postalcode=#{@postal_code}"
20
+ end
21
+ end
22
+
23
+ StrategySet.register DigitalCopyrightCa
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,30 @@
1
+ module GovKit
2
+ module CA
3
+ module PostalCode
4
+ module Strategy
5
+ # elections.ca is a reliable source, but it does not return electoral
6
+ # districts for postal codes that contain multiple electoral districts.
7
+ # @see https://github.com/danielharan/postal_code_to_edid_webservice
8
+ class ElectionsCa < Base
9
+ base_uri 'elections.ca'
10
+
11
+ private
12
+
13
+ def electoral_districts!
14
+ [ response.headers['location'][/&ED=(\d{5})&/, 1] ]
15
+ end
16
+
17
+ def valid?
18
+ !!response.headers['location']
19
+ end
20
+
21
+ def response
22
+ @response ||= self.class.head "/scripts/pss/FindED.aspx?PC=#{@postal_code}"
23
+ end
24
+ end
25
+
26
+ StrategySet.register ElectionsCa
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,27 @@
1
+ module GovKit
2
+ module CA
3
+ module PostalCode
4
+ module Strategy
5
+ class LiberalCa < Base
6
+ base_uri 'www.liberal.ca'
7
+
8
+ private
9
+
10
+ def electoral_districts!
11
+ Nokogiri::HTML(response.parsed_response).css('img.RidingListImage').map{|img| img[:src][/\d{5}/]}
12
+ end
13
+
14
+ def valid?
15
+ !response.parsed_response.match /\bOopsies!/
16
+ end
17
+
18
+ def response
19
+ @response ||= self.class.get "/riding/postal/#{@postal_code}/"
20
+ end
21
+ end
22
+
23
+ StrategySet.register LiberalCa
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,29 @@
1
+ module GovKit
2
+ module CA
3
+ module PostalCode
4
+ module Strategy
5
+ # ndp.ca does not return electoral districts for postal codes that
6
+ # contain multiple electoral districts.
7
+ class NDPCa < Base
8
+ base_uri 'www.ndp.ca'
9
+
10
+ private
11
+
12
+ def electoral_districts!
13
+ [ response.headers['location'][/\d+\Z/] ]
14
+ end
15
+
16
+ def valid?
17
+ response.headers['location'] != 'http://www.ndp.ca/'
18
+ end
19
+
20
+ def response
21
+ @response ||= self.class.head "http://www.ndp.ca/riding/#{@postal_code}"
22
+ end
23
+ end
24
+
25
+ StrategySet.register NDPCa
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ module GovKit
2
+ module CA
3
+ module PostalCode
4
+ module Strategy
5
+ # parl.gc.ca seems unreliable. In the case of K0A1K0, for example, it
6
+ # does not return seven ridings like other sources.
7
+ class ParlGcCa < Base
8
+ base_uri 'www2.parl.gc.ca'
9
+
10
+ private
11
+
12
+ def electoral_districts!
13
+ # TODO returns HTML with electoral district names only
14
+ end
15
+
16
+ def valid?
17
+ # TODO
18
+ end
19
+
20
+ def response
21
+ @response ||= self.class.get "/parlinfo/Compilations/HouseOfCommons/MemberByPostalCode.aspx?PostalCode=#{@postal_code}"
22
+ end
23
+ end
24
+
25
+ StrategySet.register ParlGcCa
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,5 @@
1
+ module GovKit
2
+ module CA
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1 @@
1
+ require 'gov_kit-ca'
@@ -0,0 +1,3 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ # TODO
@@ -0,0 +1,5 @@
1
+ --colour
2
+ --format nested
3
+ --loadby mtime
4
+ --reverse
5
+ --backtrace
@@ -0,0 +1,3 @@
1
+ require 'rubygems'
2
+ require 'rspec'
3
+ require File.dirname(__FILE__) + '/../lib/gov_kit-ca'
@@ -0,0 +1,136 @@
1
+ require 'csv'
2
+ require 'open-uri'
3
+ require File.expand_path('../../lib/gov_kit-ca', __FILE__)
4
+
5
+ # http://www.digital-copyright.ca/pcfrf/pcfrf.tgz contains
6
+ # `postal-code-for-districts.csv`, "which is 308 postal codes that should
7
+ # map to each of the 308 different electoral districts." However, six of the
8
+ # postal codes are invalid (G0A2C0, J8M1R8, J0W1B0, J0B1H0, L0J1B0, N2A1A3),
9
+ # 14 are duplicate, and the remaining 294 map to 246 electoral districts.
10
+ desc "Picks the set cover for postal codes to cbc.ca riding IDs"
11
+ task :trim_postal_codes do |t,args|
12
+ abort "Usage: rake #{t.name} file=postal-codes-for-districts.csv" unless args[:file]
13
+
14
+ # Get the riding IDs that each postal code covers
15
+ postal_to_rid = {}
16
+ File.read(args[:file]).split("\n").uniq.each do |postal_code| # Remove duplicate postal codes
17
+ begin
18
+ postal_to_rid[postal_code] = GovKit::CA::PostalCode::Strategy::CbcCa.new(postal_code).json_response.map{|hash| hash['rid'].to_i}
19
+ rescue JSON::ParserError => e # Ignore invalid postal codes
20
+ puts "ERROR: #{postal_code} is invalid"
21
+ end
22
+ end
23
+
24
+ # Get the minimum number of postal codes to cover all riding IDs. This is an
25
+ # instance of the set cover problem, which is NP-complete. We use the greedy
26
+ # algorithm, which is the best-possible polynomial time approximation
27
+ # algorithm for set cover. http://en.wikipedia.org/wiki/Set_cover_problem
28
+ postal_codes = []
29
+ until postal_to_rid.empty?
30
+ postal_code, rids = postal_to_rid.find{|k,v| v.size == postal_to_rid.map{|k,v| v.size}.max}
31
+ postal_to_rid.each{|k,v| postal_to_rid[k] -= rids}
32
+ postal_to_rid.reject!{|k,v| v.empty?}
33
+ postal_codes << postal_code
34
+ end
35
+
36
+ puts postal_codes.sort
37
+ end
38
+
39
+ # Use https://github.com/danielharan/pc_scraper/raw/master/data/index.csv and
40
+ # https://github.com/danielharan/canadian-postal-code-to-electoral-districts/raw/master/pc_edid.yml
41
+ # The YAML file may contain invalid postal codes, e.g. M5V1L6, and does not
42
+ # contain every electoral district, e.g. 35061. 35061 corresponds to riding ID
43
+ # 168, which contains the postal code L1H1X8.
44
+ desc "Generates postal codes within the given list of cbc.ca riding IDs"
45
+ task :riding_id_to_postal_code do |t,args|
46
+ abort "Usage: rake #{t.name} file=riding-ids.csv csv=index.csv yml=pc_edid.yml" unless args[:file] && args[:csv] && args[:yml]
47
+
48
+ # The YML file maps postal codes to electoral districts
49
+ yml = YAML.load_file(args[:yml])
50
+
51
+ # Get an old map of riding IDs to electoral districts
52
+ rid_to_edid = {}
53
+ CSV.foreach(args[:csv]) do |row|
54
+ rid_to_edid[row[1]] = row[0]
55
+ end
56
+
57
+ # Map riding IDs to postal codes
58
+ rid_to_postal = {}
59
+ File.read(args[:file]).split("\n").each do |riding_id|
60
+ match = yml.find{|k,v| v == rid_to_edid[riding_id]}
61
+ if match
62
+ rid_to_postal[riding_id] = match[0]
63
+ else
64
+ puts "ERROR: No postal code for riding ID #{riding_id}, electoral district #{rid_to_edid[riding_id]}"
65
+ end
66
+ end
67
+
68
+ puts rid_to_postal.values.sort
69
+ end
70
+
71
+ # Pass postal-code-for-districts.csv from http://www.digital-copyright.ca/pcfrf/pcfrf.tgz
72
+ # through `rake trim_postal_codes`. This rake task will print a list of riding
73
+ # IDs which none of the given postal codes belong to. Pass this list through
74
+ # `rake riding_id_to_postal_code` to get a list of postal codes belonging to
75
+ # those riding IDs. Manually add postal codes for any remaining riding IDs.
76
+ desc "Generates YAML mapping cbc.ca riding IDs to electoral districts"
77
+ task :riding_id_to_electoral_district do |t,args|
78
+ abort "Usage: rake #{t.name} file=postal-codes-for-districts.csv" unless args[:file]
79
+
80
+ def transliterate_elections_ca(string)
81
+ {
82
+ /\342\200\223/ => '-',
83
+ /\303\242/ => 'a',
84
+ /\303[\251\250]/ => 'e',
85
+ /\303\264/ => 'o',
86
+ /\303\211/ => 'E',
87
+ /\303\216/ => 'I',
88
+ }.reduce(string) do |string,map|
89
+ string.gsub(map[0], map[1])
90
+ end
91
+ end
92
+
93
+ # Map electoral district names to electoral district IDs
94
+ name_to_edid = {}
95
+ doc = Nokogiri::HTML(open("http://www.elections.ca/content.aspx?section=res&dir=cir/list&document=index&lang=e").read)
96
+ doc.css('sup').remove
97
+ edids = doc.css('table[summary^="List"] th.normal').map{|x| x.text.to_i}
98
+ doc.css('table[summary^="List"] th.normal + td').map{|x| x.text.gsub(/\s+/, ' ').strip}.each_with_index do |name,index|
99
+ name_to_edid[transliterate_elections_ca(name)] = edids[index]
100
+ end
101
+
102
+ def transliterate_cbc_ca(string)
103
+ {
104
+ / - / => '-',
105
+ /\342/ => 'a',
106
+ /[\350\351]/ => 'e',
107
+ /\364/ => 'o',
108
+ /\311/ => 'E',
109
+ /\316/ => 'I',
110
+ }.reduce(string) do |string,map|
111
+ string.gsub(map[0], map[1])
112
+ end
113
+ end
114
+
115
+ # Map electoral district names to riding IDs
116
+ name_to_rid = {}
117
+ File.read(args[:file]).split("\n").each do |postal_code|
118
+ GovKit::CA::PostalCode::Strategy::CbcCa.new(postal_code).json_response.each do |hash|
119
+ name_to_rid[transliterate_cbc_ca(hash['name'])] = hash['rid'].to_i
120
+ end
121
+ end
122
+
123
+ # Can we generate a complete mapping?
124
+ missing = name_to_edid.keys - name_to_rid.keys
125
+ unless missing.empty?
126
+ abort "The postal codes in #{args[:file]} do not cover #{missing.size} electoral districts:\n#{missing.sort.join("\n")}\n\nThe missing cbc.ca riding IDs are:\n#{([*1..308] - name_to_rid.map{|k,v| v}.flatten).sort.join("\n")}"
127
+ end
128
+
129
+ # Map riding IDs to electoral districts
130
+ rid_to_edid = {}
131
+ name_to_edid.keys.each do |name|
132
+ rid_to_edid[name_to_rid[name]] = name_to_edid[name]
133
+ end
134
+
135
+ puts rid_to_edid.to_yaml
136
+ end
metadata ADDED
@@ -0,0 +1,148 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: govkit-ca
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - James McKinney
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-03-01 00:00:00 -05:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: httparty
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ - 6
31
+ - 1
32
+ version: 0.6.1
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: nokogiri
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ segments:
44
+ - 1
45
+ - 4
46
+ - 4
47
+ version: 1.4.4
48
+ type: :runtime
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: yajl-ruby
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ segments:
59
+ - 0
60
+ - 7
61
+ - 8
62
+ version: 0.7.8
63
+ type: :runtime
64
+ version_requirements: *id003
65
+ - !ruby/object:Gem::Dependency
66
+ name: rspec
67
+ prerelease: false
68
+ requirement: &id004 !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ segments:
74
+ - 2
75
+ version: "2"
76
+ type: :development
77
+ version_requirements: *id004
78
+ description: GovKit-CA lets you quickly get encapsulated Ruby objects for Canadian civic data.
79
+ email:
80
+ - james@slashpoundbang.com
81
+ executables: []
82
+
83
+ extensions: []
84
+
85
+ extra_rdoc_files: []
86
+
87
+ files:
88
+ - .gitignore
89
+ - Gemfile
90
+ - LICENSE
91
+ - README.md
92
+ - Rakefile
93
+ - USAGE
94
+ - govkit-ca.gemspec
95
+ - lib/data/rid_to_edid.yml
96
+ - lib/gov_kit-ca.rb
97
+ - lib/gov_kit-ca/postal_code.rb
98
+ - lib/gov_kit-ca/postal_code/strategy.rb
99
+ - lib/gov_kit-ca/postal_code/strategy/base.rb
100
+ - lib/gov_kit-ca/postal_code/strategy/cbc-ca.rb
101
+ - lib/gov_kit-ca/postal_code/strategy/conservative-ca.rb
102
+ - lib/gov_kit-ca/postal_code/strategy/digital_copyright-ca.rb
103
+ - lib/gov_kit-ca/postal_code/strategy/elections-ca.rb
104
+ - lib/gov_kit-ca/postal_code/strategy/liberal-ca.rb
105
+ - lib/gov_kit-ca/postal_code/strategy/ndp-ca.rb
106
+ - lib/gov_kit-ca/postal_code/strategy/parl-gc-ca.rb
107
+ - lib/gov_kit-ca/version.rb
108
+ - lib/govkit-ca.rb
109
+ - spec/postal_code_spec.rb
110
+ - spec/spec.opts
111
+ - spec/spec_helper.rb
112
+ - tasks/rid_to_edid.rb
113
+ has_rdoc: true
114
+ homepage: http://github.com/jpmckinney/govkit-ca
115
+ licenses: []
116
+
117
+ post_install_message:
118
+ rdoc_options: []
119
+
120
+ require_paths:
121
+ - lib
122
+ required_ruby_version: !ruby/object:Gem::Requirement
123
+ none: false
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ segments:
128
+ - 0
129
+ version: "0"
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ none: false
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ segments:
136
+ - 0
137
+ version: "0"
138
+ requirements: []
139
+
140
+ rubyforge_project: govkit-ca
141
+ rubygems_version: 1.3.7
142
+ signing_key:
143
+ specification_version: 3
144
+ summary: Easy access to Canadian civic data around the web
145
+ test_files:
146
+ - spec/postal_code_spec.rb
147
+ - spec/spec.opts
148
+ - spec/spec_helper.rb