ons_openapi 0.1.0
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.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/README.md +218 -0
- data/lib/ons_openapi/classification.rb +10 -0
- data/lib/ons_openapi/collection.rb +151 -0
- data/lib/ons_openapi/concept.rb +6 -0
- data/lib/ons_openapi/connection.rb +65 -0
- data/lib/ons_openapi/context.rb +106 -0
- data/lib/ons_openapi/data_helper.rb +5 -0
- data/lib/ons_openapi/dimension.rb +46 -0
- data/lib/ons_openapi/geographical_hierarchy.rb +6 -0
- data/lib/ons_openapi/item.rb +13 -0
- data/lib/ons_openapi/name_helper.rb +7 -0
- data/lib/ons_openapi/url_helper.rb +12 -0
- data/lib/ons_openapi/value.rb +17 -0
- data/lib/ons_openapi.rb +65 -0
- data/vendor/json-stat.max.js +697 -0
- metadata +120 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b1a1edf4c26311e209664f2c74d01601f169c77d
|
4
|
+
data.tar.gz: d25742c9874d3bd14602b0cd1a0bafc89ea83ed4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 211e09eae5a302907f2a1f06256fbe88e592c48d85609bfc901b3802d3a960b0cd14bc12b1ea18f75aa89688c1dcbbeaaad61bd2b295277786cd0a72c04d1694
|
7
|
+
data.tar.gz: e172c11cf23355fcc0db0519b37bbde324beb677023cf7e4e05df9434f5ea666b823705e7d2724b2f2338a93c6b3f1280422bf03a585dac2c65ec880cc124966
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Rob McKinnon
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
22
|
+
|
data/README.md
ADDED
@@ -0,0 +1,218 @@
|
|
1
|
+
ons-openapi
|
2
|
+
===========
|
3
|
+
|
4
|
+
Ruby wrapper around the [ONS OpenAPI](https://www.ons.gov.uk/ons/apiservice/web/apiservice/home) - the UK Office of National Statistics's data API. The intention is to make it easy to quickly retrieve data. It may not expose the full functionality of the ONS OpenAPI.
|
5
|
+
|
6
|
+
This gem was partially written at [Accountability Hack 2014](https://twitter.com/search?q=%23AccHack14), an event organised by Parliament, ONS, and NAO.
|
7
|
+
|
8
|
+
Please provide feedback via [GitHub issues](https://github.com/robmckinnon/ons-openapi/issues).
|
9
|
+
|
10
|
+
# Installation
|
11
|
+
|
12
|
+
```
|
13
|
+
gem install ons_openapi
|
14
|
+
```
|
15
|
+
|
16
|
+
then in Ruby code
|
17
|
+
|
18
|
+
```
|
19
|
+
require 'ons_openapi'
|
20
|
+
```
|
21
|
+
|
22
|
+
or if using bundler (as with Rails), add to the Gemfile
|
23
|
+
|
24
|
+
```
|
25
|
+
gem 'ons_openapi'
|
26
|
+
```
|
27
|
+
|
28
|
+
You must have an ExecJS supported runtime when running this gem. If you are using Mac OS X or Windows, you already have a JavaScript runtime installed in your operating system. Check [ExecJS documentation](https://github.com/sstephenson/execjs#readme) to know all supported JavaScript runtimes.
|
29
|
+
|
30
|
+
The gem includes code from the [JSON-stat Javascript Toolkit](https://github.com/badosa/JSON-stat) to parse JSON-stat formatted results from the ONS OpenAPI. ExecJS is used to run JSON-stat JavaScript code from within Ruby.
|
31
|
+
|
32
|
+
# Register for API key
|
33
|
+
|
34
|
+
The ONS OpenAPI requires you [register for an API key](https://www.ons.gov.uk/ons/apiservice/web/apiservice/home#reg-main-content).
|
35
|
+
|
36
|
+
When using the gem, set an environment variable named `ONS_APIKEY` with your API key value.
|
37
|
+
|
38
|
+
```
|
39
|
+
$ ONS_APIKEY=<your_ons_openapi_key>
|
40
|
+
```
|
41
|
+
|
42
|
+
# Getting started
|
43
|
+
|
44
|
+
Running in irb:
|
45
|
+
|
46
|
+
$ ONS_APIKEY=<your_ons_openapi_key> irb
|
47
|
+
|
48
|
+
require 'ons_openapi'
|
49
|
+
|
50
|
+
census = OnsOpenApi.context('Census')
|
51
|
+
|
52
|
+
religion_detailed = census.collection('Religion (detailed)')
|
53
|
+
|
54
|
+
religion_detailed.data_for('Islington')
|
55
|
+
#=> [["2011 Administrative Hierarchy", "Religion (Flat) (T059A)", "Time Dimension", "Value"],
|
56
|
+
# ["Islington", "All categories: Religion", "2011", 206125],
|
57
|
+
# ["Islington", "Christian", "2011", 82879],
|
58
|
+
# ["Islington", "Buddhist", "2011", 2117],
|
59
|
+
# ...
|
60
|
+
|
61
|
+
religion_detailed.data_for('Islington S')
|
62
|
+
#=> [["2011 Westminster Parliamentary Constituency Hierarchy", "Religion (Flat) (T059A)", "Time", "Value"],
|
63
|
+
# ["Islington South and Finsbury", "All categories: Religion", "2011", 102656],
|
64
|
+
# ["Islington South and Finsbury", "Christian", "2011", 42222],
|
65
|
+
# ["Islington South and Finsbury", "Buddhist", "2011", 1126],
|
66
|
+
# ...
|
67
|
+
|
68
|
+
religion_detailed.data_for('England')
|
69
|
+
#=> [["2011 Administrative Hierarchy", "Religion (Flat) (T059A)", "Time Dimension", "Value"],
|
70
|
+
# ["England", "All categories: Religion", "2011", 53012456],
|
71
|
+
# ["England", "Christian", "2011", 31479876],
|
72
|
+
# ["England", "Buddhist", "2011", 238626],
|
73
|
+
# ...
|
74
|
+
|
75
|
+
religion_detailed.data_for('North West')
|
76
|
+
#=> [["2011 Administrative Hierarchy", "Religion (Flat) (T059A)", "Time Dimension", "Value"],
|
77
|
+
# ["North West", "All categories: Religion", "2011", 7052177],
|
78
|
+
# ["North West", "Christian", "2011", 4742860],
|
79
|
+
# ["North West", "Buddhist", "2011", 20695]
|
80
|
+
|
81
|
+
religion_detailed.data_for('Scotland')
|
82
|
+
# RuntimeError: ONS Exception: 404 INTERNAL ERROR: Invalid dimension item code S92000003
|
83
|
+
|
84
|
+
religion_detailed.data_for('Woodlands')
|
85
|
+
# RuntimeError: more than one match, try one of:
|
86
|
+
#
|
87
|
+
# data_for('E05006341 Woodlands') or data_for('E05006341') see http://statistics.data.gov.uk/doc/statistical-geography/E05006341
|
88
|
+
#
|
89
|
+
# data_for('E05008891 Woodlands') or data_for('E05008891') see http://statistics.data.gov.uk/doc/statistical-geography/E05008891
|
90
|
+
#
|
91
|
+
# data_for('E05001234 Woodlands') or data_for('E05001234') see http://statistics.data.gov.uk/doc/statistical-geography/E05001234
|
92
|
+
#
|
93
|
+
# data_for('E05004981 Woodlands') or data_for('E05004981') see http://statistics.data.gov.uk/doc/statistical-geography/E05004981
|
94
|
+
|
95
|
+
|
96
|
+
# Contexts
|
97
|
+
|
98
|
+
The datastore underneath the ONS OpenAPI is divided into four sections called contexts.
|
99
|
+
|
100
|
+
OnsOpenApi.context_names
|
101
|
+
#=> ["Census", "Socio-Economic", "Economic", "Social"]
|
102
|
+
|
103
|
+
OnsOpenApi.contexts.size
|
104
|
+
#=> 4
|
105
|
+
|
106
|
+
The Census context contains data from the 2011 Census in England and Wales.
|
107
|
+
|
108
|
+
# Collections
|
109
|
+
|
110
|
+
Each context consists of several dataset collections.
|
111
|
+
|
112
|
+
economic = OnsOpenApi.context('Economic')
|
113
|
+
economic.collections.size
|
114
|
+
#=> 26
|
115
|
+
|
116
|
+
census = OnsOpenApi.context('Census')
|
117
|
+
census.collections.size
|
118
|
+
#=> 340
|
119
|
+
|
120
|
+
Use `collection_names()` to view a list of collection names:
|
121
|
+
|
122
|
+
puts census.collection_names.select {|n| n[/religion/i]}
|
123
|
+
# DC1202EW Household composition by religion of Household Reference Person (HRP)
|
124
|
+
# DC2107EW Religion by sex by age
|
125
|
+
# DC2201EW Ethnic group by religion
|
126
|
+
# ...
|
127
|
+
# QS208EW Religion
|
128
|
+
# QS210EW Religion (detailed)
|
129
|
+
# ST210EWla Religion (non-UK born short-term residents)
|
130
|
+
|
131
|
+
Retrieve a collection using `collection()`. Pass in the collection code, collection name, or collection code and name:
|
132
|
+
|
133
|
+
census.collection('QS210EW')
|
134
|
+
census.collection('Religion (detailed)')
|
135
|
+
census.collection('QS210EW Religion (detailed)')
|
136
|
+
|
137
|
+
# Geographical hierarchies
|
138
|
+
|
139
|
+
Each collection can be filtered with different geographical hierarchies.
|
140
|
+
|
141
|
+
census = OnsOpenApi.context('Census')
|
142
|
+
religion = census.collection('Religion')
|
143
|
+
|
144
|
+
religion.geography_codes
|
145
|
+
# [
|
146
|
+
# ["2011STATH", "2011 Statistical Geography Hierarchy"],
|
147
|
+
# ["2011WARDH", "2011 Administrative Hierarchy"],
|
148
|
+
# ["2011PCONH", "2011 Westminster Parliamentary Constituency Hierarchy"]
|
149
|
+
# ]
|
150
|
+
|
151
|
+
# Listing areas from geography hierarchy
|
152
|
+
|
153
|
+
Use `geographies()` to get a hash of geography hierarchies. To get a list of geography items for a hierarchy, say '2011PCONH', do:
|
154
|
+
|
155
|
+
census = OnsOpenApi.context('Census')
|
156
|
+
religion = census.collection('Religion')
|
157
|
+
constituencies = religion.geographies['2011PCONH']
|
158
|
+
|
159
|
+
constituencies.map(&:label)
|
160
|
+
# ["Aberavon", "Aberconwy", "Aldershot", "Aldridge-Brownhills", ...
|
161
|
+
|
162
|
+
constituencies.map(&:item_code)
|
163
|
+
# ["E14000759", "E14000531", "E14000967", "E14000904", ...
|
164
|
+
|
165
|
+
To see `area_types` in the hierarchy:
|
166
|
+
|
167
|
+
constituencies.map(&:area_type).map {|a| [a.codename, a.level]}.uniq
|
168
|
+
# [["Westminster Parliamentary Constituency", 3],
|
169
|
+
# ["Region", 2],
|
170
|
+
# ["Country", 1],
|
171
|
+
# ["England and Wales", 0]]
|
172
|
+
|
173
|
+
Or for hierarchy '2011WARDH', do:
|
174
|
+
|
175
|
+
areas = religion.geographies['2011WARDH']
|
176
|
+
|
177
|
+
areas.map(&:label)
|
178
|
+
# ["Aldwick East", "West Mersea", "Stowmarket Central", ...
|
179
|
+
|
180
|
+
areas.map {|a| [a.item_code, a.label, a.area_type.codename, a.area_type.level]}
|
181
|
+
# [["E05007576", "Aldwick East", "Electoral Ward/Division ", 7],
|
182
|
+
# ["E05004144", "West Mersea", "Electoral Ward/Division ", 7],
|
183
|
+
# ["E05007153", "Stowmarket Central", "Electoral Ward/Division ", 7]], ...
|
184
|
+
|
185
|
+
To see `area_types` in the hierarchy:
|
186
|
+
|
187
|
+
areas.map(&:area_type).map {|a| [a.codename, a.level]}.uniq
|
188
|
+
# [["Electoral Ward/Division ", 7],
|
189
|
+
# ["Electoral Division", 7],
|
190
|
+
# ["Metropolitan District ", 6],
|
191
|
+
# ["Council Area", 4],
|
192
|
+
# ["County", 5],
|
193
|
+
# ["Non-metropolitan District", 6],
|
194
|
+
# ["London Borough ", 6],
|
195
|
+
# ["Unitary Authority", 5],
|
196
|
+
# ["Metropolitan County", 5],
|
197
|
+
# ["Country", 3],
|
198
|
+
# ["Great Britain", 1],
|
199
|
+
# ["United Kingdom", 0],
|
200
|
+
# ["Region", 4],
|
201
|
+
# ["England and Wales", 2],
|
202
|
+
# ["Inner and Outer London", 5]]
|
203
|
+
|
204
|
+
# Data for a geographic area
|
205
|
+
|
206
|
+
Use `data_for()` to retrieve data for a geography that matches given label_or_code.
|
207
|
+
|
208
|
+
census = OnsOpenApi.context('Census')
|
209
|
+
religion = census.collection('Religion')
|
210
|
+
|
211
|
+
religion.data_for('England')
|
212
|
+
religion.data_for('Islington S')
|
213
|
+
religion.data_for('E05002040')
|
214
|
+
|
215
|
+
# Feedback
|
216
|
+
|
217
|
+
Please provide feedback via [GitHub issues](https://github.com/robmckinnon/ons-openapi/issues).
|
218
|
+
|
@@ -0,0 +1,151 @@
|
|
1
|
+
class OnsOpenApi::Collection
|
2
|
+
|
3
|
+
include Morph
|
4
|
+
include OnsOpenApi::DataHelper
|
5
|
+
|
6
|
+
# Returns title, i.e. "id name"
|
7
|
+
def title
|
8
|
+
[id, name].join(' ')
|
9
|
+
end
|
10
|
+
|
11
|
+
# Returns data as array of arrays, for a geography that matches label_or_code.
|
12
|
+
# e.g. data_for('England'), data_for('Islington S'), data_for('E05002040')
|
13
|
+
# Raises exception if no match or more than one match.
|
14
|
+
def data_for label_or_code
|
15
|
+
if geographies = geography(label_or_code)
|
16
|
+
if geographies.size > 1
|
17
|
+
cmds = geographies.map {|g| "data_for('#{g.title}') or data_for('#{g.item_code}') see http://statistics.data.gov.uk/doc/statistical-geography/#{g.item_code}"}
|
18
|
+
raise "more than one match, try one of:\n\n #{cmds.join(" \n\n ") }\n\n"
|
19
|
+
else
|
20
|
+
geo = geographies.first
|
21
|
+
data geo.item_code, geo.geography_code
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns data for given geog_code, geog, and optional JSON-stat command.
|
27
|
+
def data geog_code, geog, stat='.toTable()'
|
28
|
+
@json_stats ||={}
|
29
|
+
@json_stats[geog_code + geog] ||= OnsOpenApi::request("dataset/#{id}",
|
30
|
+
context: context_name,
|
31
|
+
geog: geog,
|
32
|
+
"dm/#{geog}" => geog_code,
|
33
|
+
totals: 'false',
|
34
|
+
jsontype: 'json-stat')
|
35
|
+
|
36
|
+
raw_json_stat = @json_stats[geog_code + geog].gsub("\n", ' ').gsub("'", "").squeeze(' ')
|
37
|
+
|
38
|
+
if raw_json_stat.include?('ns1faultstring')
|
39
|
+
raise "ONS Exception: #{raw_json_stat.gsub(/(<.?ns1XMLFault>)|(<.?ns1faultstring>)/,'')}"
|
40
|
+
elsif raw_json_stat.include?('errorMessage')
|
41
|
+
raise "ONS Error: #{raw_json_stat}"
|
42
|
+
end
|
43
|
+
|
44
|
+
begin
|
45
|
+
table = js_context.eval( %Q| JSONstat( JSON.parse(' #{raw_json_stat} ') ).Dataset(0)#{stat} | )
|
46
|
+
rescue Encoding::UndefinedConversionError => e
|
47
|
+
if e.to_s[/ASCII-8BIT to UTF-8/]
|
48
|
+
raw_json_stat = raw_json_stat.force_encoding('ASCII-8BIT').encode('UTF-8', :invalid => :replace, :undef => :replace, :replace => '?')
|
49
|
+
table = js_context.eval( %Q| JSONstat( JSON.parse(' #{raw_json_stat} ') ).Dataset(0)#{stat} | )
|
50
|
+
else
|
51
|
+
raise "#{e.to_s}: #{raw_json_stat}"
|
52
|
+
end
|
53
|
+
rescue ExecJS::RuntimeError, ExecJS::ProgramError => e
|
54
|
+
raise "#{e.to_s}: #{raw_json_stat}"
|
55
|
+
end
|
56
|
+
|
57
|
+
if table.is_a?(Array) && (index = table[1].index('Segment_1'))
|
58
|
+
table.each {|row| row.delete_at(index)}
|
59
|
+
end
|
60
|
+
table
|
61
|
+
end
|
62
|
+
|
63
|
+
#
|
64
|
+
# Returns array of [geography_code, description] that are supported by this
|
65
|
+
# collection.
|
66
|
+
#
|
67
|
+
# e.g.
|
68
|
+
# 2011WARDH - 2011 Administrative Hierarchy
|
69
|
+
# 2011STATH - 2011 Statistical Geography Hierarchy
|
70
|
+
# 2011PCONH - 2011 Westminster Parliamentary Constituency Hierarchy
|
71
|
+
# 2011HTWARDH - 2011 Census Merged Ward Hierarchy
|
72
|
+
# 2011CMLADH - 2011 Census merged local authority district hierarchy
|
73
|
+
# 2011PARISH - 2011 Parish Hierarchy
|
74
|
+
#
|
75
|
+
def geography_codes
|
76
|
+
gh ||= geographical_hierarchies
|
77
|
+
@geographies_data ||= if gh.respond_to?(:geographical_hierarchy) && gh.geographical_hierarchy
|
78
|
+
url, args = gh.geographical_hierarchy.url
|
79
|
+
OnsOpenApi::get(url, args)
|
80
|
+
elsif gh.respond_to?(:geographical_hierarchies) && gh.geographical_hierarchies
|
81
|
+
url, args = gh.geographical_hierarchies.first.url
|
82
|
+
OnsOpenApi::get(url, args)
|
83
|
+
end
|
84
|
+
|
85
|
+
if @geographies_data
|
86
|
+
list = @geographies_data.geographical_hierarchy_list
|
87
|
+
if list.respond_to?(:geographical_hierarchy) && list.geographical_hierarchy
|
88
|
+
[ [list.geographical_hierarchy.id, list.geographical_hierarchy.name] ]
|
89
|
+
elsif list.respond_to?(:geographical_hierarchies) && list.geographical_hierarchies
|
90
|
+
list.geographical_hierarchies.map{|x| [x.id, x.name]}
|
91
|
+
else
|
92
|
+
[]
|
93
|
+
end
|
94
|
+
else
|
95
|
+
[]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Returns geography item that matches given label_or_code.
|
100
|
+
# Raises exception if there is no match.
|
101
|
+
def geography label_or_code
|
102
|
+
if matches = geography_exact_match(label_or_code) || geography_partial_match(label_or_code)
|
103
|
+
matches
|
104
|
+
else
|
105
|
+
raise "no geography match found for: #{label_or_code}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns collection_detail object.
|
110
|
+
def collection_detail
|
111
|
+
OnsOpenApi::get(url.first, url.last).collection_detail
|
112
|
+
end
|
113
|
+
|
114
|
+
# Returns hash of geography code to list of geography items.
|
115
|
+
def geographies
|
116
|
+
codes = geography_codes.map(&:first)
|
117
|
+
codes.each_with_object({}) do |code, hash|
|
118
|
+
hash[code] = OnsOpenApi.context(context_name).geographies(code)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def geography_exact_match label_or_code
|
125
|
+
matches = []
|
126
|
+
geographies.values.each do |list|
|
127
|
+
found = list.select{|c| (c.label == label_or_code) || (c.item_code == label_or_code) || (c.title == label_or_code) }
|
128
|
+
matches += found unless found.empty?
|
129
|
+
end
|
130
|
+
matches.empty? ? nil : matches.uniq {|m| m.item_code}
|
131
|
+
end
|
132
|
+
|
133
|
+
def geography_partial_match label_or_code
|
134
|
+
matches = []
|
135
|
+
geographies.values.each do |list|
|
136
|
+
found = list.select{|c| c.label[label_or_code] }
|
137
|
+
matches += found unless found.empty?
|
138
|
+
end
|
139
|
+
matches.empty? ? nil : matches.uniq {|m| m.item_code}
|
140
|
+
end
|
141
|
+
|
142
|
+
def js_context
|
143
|
+
unless @js_context
|
144
|
+
jsonstatjs = File.expand_path('../../../vendor/json-stat.max.js', __FILE__)
|
145
|
+
source = open(jsonstatjs).read
|
146
|
+
@js_context = ExecJS.compile(source)
|
147
|
+
end
|
148
|
+
@js_context
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'net/https'
|
2
|
+
|
3
|
+
module OnsOpenApi::Connection
|
4
|
+
|
5
|
+
BASE_URI = 'http://data.ons.gov.uk/ons/api/data/'
|
6
|
+
API_KEY = ENV['ONS_APIKEY']
|
7
|
+
|
8
|
+
def get(resource, args={})
|
9
|
+
to_object request(resource, "get", args)
|
10
|
+
end
|
11
|
+
|
12
|
+
def post(resource, args={})
|
13
|
+
to_object request(resource, "post", args)
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_object json
|
17
|
+
json.gsub!('"@', '"')
|
18
|
+
json.gsub!('xml.lang','xml_lang')
|
19
|
+
json.gsub!('"$":','"text":')
|
20
|
+
json.gsub!('"2011WARDH" : {', '"X2011WARDH" : {')
|
21
|
+
json.gsub!('"2011HTWARDH" : {', '"X2011HTWARDH" : {')
|
22
|
+
hash = JSON.parse json
|
23
|
+
Morph.from_hash hash, OnsOpenApi
|
24
|
+
end
|
25
|
+
|
26
|
+
def request_uri(resource, args)
|
27
|
+
unless API_KEY
|
28
|
+
raise 'No ONS OpenAPI key found. Set this environment variable: ONS_APIKEY=<your_ons_openapi_key>'
|
29
|
+
end
|
30
|
+
uri = URI.join(BASE_URI, (resource+'.json').sub('.json.json','.json') )
|
31
|
+
|
32
|
+
args ||= {}
|
33
|
+
args.delete('apikey')
|
34
|
+
args.merge!( apikey: API_KEY )
|
35
|
+
uri.query = args.map { |k,v| "%s=%s" % [URI.encode(k.to_s), URI.encode(v.to_s)] }.join("&") if args
|
36
|
+
|
37
|
+
puts uri.to_s
|
38
|
+
uri
|
39
|
+
end
|
40
|
+
|
41
|
+
def request(resource, method="get", args)
|
42
|
+
uri = request_uri resource, args
|
43
|
+
case method
|
44
|
+
when "get"
|
45
|
+
req = Net::HTTP::Get.new(uri.request_uri)
|
46
|
+
when "post"
|
47
|
+
req = Net::HTTP::Post.new(uri.request_uri)
|
48
|
+
end
|
49
|
+
|
50
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
51
|
+
http.use_ssl = (uri.port == 443)
|
52
|
+
|
53
|
+
res = http.start() { |conn| conn.request(req) }
|
54
|
+
res.body
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
module OnsOpenApi
|
60
|
+
|
61
|
+
class << self
|
62
|
+
include OnsOpenApi::Connection
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
class OnsOpenApi::Context
|
2
|
+
|
3
|
+
include Morph
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def all
|
7
|
+
@contexts ||= OnsOpenApi::get('contexts').context_list.statistical_contexts.map {|x| new x}
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize x
|
12
|
+
self.id = x.context_id
|
13
|
+
self.name = x.context_name
|
14
|
+
end
|
15
|
+
|
16
|
+
def concepts
|
17
|
+
@concepts ||= OnsOpenApi::get('concepts', context: @name).concept_list.concepts
|
18
|
+
end
|
19
|
+
|
20
|
+
def collections
|
21
|
+
@collections ||= OnsOpenApi::get('collections', context: @name).collection_list.collections
|
22
|
+
unless @collections.first.respond_to?(:context_name) && @collections.first.context_name
|
23
|
+
@collections.each {|c| c.context_name = @name}
|
24
|
+
end
|
25
|
+
@collections
|
26
|
+
end
|
27
|
+
|
28
|
+
def classifications
|
29
|
+
@classifications ||= OnsOpenApi::get('classifications', context: @name).classification_list.classifications
|
30
|
+
end
|
31
|
+
|
32
|
+
def collection id_or_name
|
33
|
+
collection = collections.detect{|c| (c.id == id_or_name || c.title == id_or_name) }
|
34
|
+
|
35
|
+
unless collection
|
36
|
+
list = collections.select{|c| c.name == id_or_name}
|
37
|
+
if list.size > 1
|
38
|
+
cmds = list.map{|c| [c.id,c.title]}.flatten.map{|n| "collection('#{n}')"}
|
39
|
+
raise "more than one match, try one of:\n\n #{cmds.join(" \n\n ") }\n\n"
|
40
|
+
else
|
41
|
+
collection = list.first
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
collection
|
46
|
+
end
|
47
|
+
|
48
|
+
def concept_names
|
49
|
+
names_for concepts
|
50
|
+
end
|
51
|
+
|
52
|
+
def collection_names
|
53
|
+
names_for collections
|
54
|
+
end
|
55
|
+
|
56
|
+
def classification_names
|
57
|
+
names_for classifications
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns geography objects for given geography code.
|
61
|
+
#
|
62
|
+
# Parameter +code+ defaults to +'2011WARDH'+ for the 2011 Administrative
|
63
|
+
# Hierarchy, if no +code+ supplied.
|
64
|
+
#
|
65
|
+
# Codes include:
|
66
|
+
# +2011WARDH+ - 2011 Administrative Hierarchy
|
67
|
+
# +2011STATH+ - 2011 Statistical Geography Hierarchy
|
68
|
+
# +2011PCONH+ - 2011 Westminster Parliamentary Constituency Hierarchy
|
69
|
+
# +2011HTWARDH+ - 2011 Census Merged Ward Hierarchy
|
70
|
+
# +2011CMLADH+ - 2011 Census merged local authority district hierarchy
|
71
|
+
# +2011PARISH+ - 2011 Parish Hierarchy
|
72
|
+
def geographies code='2011WARDH'
|
73
|
+
@geographies ||= {}
|
74
|
+
unless @geographies[code]
|
75
|
+
params = { context: @name }
|
76
|
+
|
77
|
+
if code == '2011STATH'
|
78
|
+
params.merge!({ levels: '0,1,2,3,4,5' }) # restrict levels to reduce delay
|
79
|
+
end
|
80
|
+
|
81
|
+
result = OnsOpenApi::get "hierarchies/hierarchy/#{code}", params
|
82
|
+
@geographies[code] = result.geography_list.items.items
|
83
|
+
end
|
84
|
+
@geographies[code].each {|g| g.geography_code = code }
|
85
|
+
@geographies[code]
|
86
|
+
end
|
87
|
+
|
88
|
+
# Returns geography objects from the 2011 Administrative Hierarchy
|
89
|
+
# with area type 'Electoral Division'
|
90
|
+
def electoral_divisions
|
91
|
+
geographies('2011WARDH').select {|z| z.area_type.codename['Electoral Division']}
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns geography objects from the 2011 Administrative Hierarchy
|
95
|
+
# with area type 'Electoral Ward/Division'
|
96
|
+
def electoral_wards
|
97
|
+
geographies('2011WARDH').select {|z| z.area_type.codename['Electoral Ward/Division']}
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def names_for list
|
103
|
+
list.map(&:title)
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class OnsOpenApi::Dimension
|
2
|
+
|
3
|
+
include Morph
|
4
|
+
|
5
|
+
def values
|
6
|
+
ids.each_with_object({}) do |x, h|
|
7
|
+
h[alt_label(x)] = send(method(x))
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def labels
|
12
|
+
data = values
|
13
|
+
non_data_keys = role.morph_attributes.values.flatten.map{|x| alt_label x}
|
14
|
+
non_data_keys.each do |k|
|
15
|
+
data.delete(k)
|
16
|
+
end
|
17
|
+
data = data.values.first.category
|
18
|
+
|
19
|
+
key_to_index = data.index.morph_attributes
|
20
|
+
key_to_label = data.label.morph_attributes
|
21
|
+
|
22
|
+
labels = key_to_index.to_a.each_with_object([]) do |k_i, a|
|
23
|
+
if index = k_i[1]
|
24
|
+
key = k_i[0]
|
25
|
+
a[index] = key_to_label[key]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# label = data.label
|
30
|
+
# items = data.category.index
|
31
|
+
# labels = data.category.label
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def alt_label x
|
37
|
+
x['2011WARDH'] ? 'X2011_WARDH' : x
|
38
|
+
x['2011HTWARDH'] ? 'X2011_HTWARDH' : x
|
39
|
+
end
|
40
|
+
|
41
|
+
def method x
|
42
|
+
x = alt_label x
|
43
|
+
method = Morph::InstanceMethods::Helper.convert_to_morph_method_name x
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module OnsOpenApi::UrlHelper
|
2
|
+
|
3
|
+
def url
|
4
|
+
require 'cgi'
|
5
|
+
if respond_to?(:urls) && urls
|
6
|
+
uri = URI.parse urls.urls.detect{|x| x.representation['json'] }.href
|
7
|
+
params = Rack::Utils.parse_query(uri.query)
|
8
|
+
params.delete('apikey')
|
9
|
+
return [uri.path, params]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|