ons_openapi 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|