govkit-ca 0.0.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.
@@ -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