defra_ruby_address 0.1.0 → 0.4.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 +5 -5
- data/README.md +82 -6
- data/Rakefile +2 -2
- data/bin/console +2 -6
- data/lib/defra_ruby/address/helmert_transformation.rb +102 -0
- data/lib/defra_ruby/address/response.rb +1 -2
- data/lib/defra_ruby/address/services/base_service.rb +2 -2
- data/lib/defra_ruby/address/services/easting_northing_to_lat_lon_service.rb +18 -0
- data/lib/defra_ruby/address/services/lat_lon_to_easting_northing_service.rb +20 -0
- data/lib/defra_ruby/address/services/os_api_address_lookup_v1_service.rb +100 -0
- data/lib/defra_ruby/address/services/os_places_address_lookup_service.rb +11 -14
- data/lib/defra_ruby/address/transverse_mercator_projection.rb +151 -0
- data/lib/defra_ruby/address/version.rb +1 -1
- data/lib/defra_ruby/address.rb +5 -0
- metadata +11 -181
- data/spec/defra_ruby/address/configuration_spec.rb +0 -18
- data/spec/defra_ruby/address/response_spec.rb +0 -70
- data/spec/defra_ruby/address/services/ea_address_facade_v1_1_service_spec.rb +0 -73
- data/spec/defra_ruby/address/services/ea_address_facade_v1_service_spec.rb +0 -73
- data/spec/defra_ruby/address/services/os_places_address_lookup_service_spec.rb +0 -72
- data/spec/defra_ruby/address_spec.rb +0 -30
- data/spec/examples.txt +0 -27
- data/spec/fixtures/ea_address_facade_v1_1_blank.json +0 -8
- data/spec/fixtures/ea_address_facade_v1_1_not_found.json +0 -14
- data/spec/fixtures/ea_address_facade_v1_1_valid.json +0 -83
- data/spec/fixtures/ea_address_facade_v1_blank.json +0 -13
- data/spec/fixtures/ea_address_facade_v1_not_found.json +0 -8
- data/spec/fixtures/ea_address_facade_v1_valid.json +0 -45
- data/spec/fixtures/os_places_address_lookup_blank.json +0 -5
- data/spec/fixtures/os_places_address_lookup_not_found.json +0 -6
- data/spec/fixtures/os_places_address_lookup_valid.json +0 -56
- data/spec/spec_helper.rb +0 -83
- data/spec/support/defra_ruby_address.rb +0 -4
- data/spec/support/dotenv.rb +0 -4
- data/spec/support/pry.rb +0 -7
- data/spec/support/shared_examples/handle_request_errors.rb +0 -32
- data/spec/support/simplecov.rb +0 -17
- data/spec/support/webmock.rb +0 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: a5d4b459711e84d2d0caac1f48600cb928c1cede9b4ca85e5d23e53b12f4a881
|
|
4
|
+
data.tar.gz: 0c6b7e465734d6f4cc4cbea1df20b40a2d93d9e0d38035dc7d2d8ffbe639855e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c80f7606cbfffc7dc4f03c06b106c2df3d23ad5c4fd93e29849c3ff035e6b5d53836b8bad16816f40164674de1d9437b07792d13410d99b8df4775cb72c10c2c
|
|
7
|
+
data.tar.gz: 7dc7df096183d0422971325ba71e4f5607b9e279833f10d8bd86249ef808af6c758be28a610e16bfb6f2604489992fbfa3eba0c089543fabbe2d6bec9197029b
|
data/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Defra Ruby Address
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[
|
|
4
|
+
[](https://sonarcloud.io/dashboard?id=DEFRA_defra-ruby-address)
|
|
5
|
+
[](https://sonarcloud.io/dashboard?id=DEFRA_defra-ruby-address)
|
|
6
|
+
[](https://hakiri.io/github/DEFRA/defra-ruby-address/main)
|
|
7
7
|
[](https://badge.fury.io/rb/defra_ruby_address)
|
|
8
8
|
[](http://www.nationalarchives.gov.uk/doc/open-government-licence/version/3)
|
|
9
9
|
|
|
@@ -47,14 +47,23 @@ DefraRuby::Address.configure do |config|
|
|
|
47
47
|
end
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
To use the OS API service, you need to configure your API key from the [OS Data Hub](https://osdatahub.os.uk/). The host defaults to `https://api.os.uk/search/places/v1`:
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
DefraRuby::Address.configure do |config|
|
|
54
|
+
config.key = ENV["OS_API_KEY"]
|
|
55
|
+
end
|
|
56
|
+
```
|
|
57
|
+
|
|
50
58
|
## Usage
|
|
51
59
|
|
|
52
|
-
The gem interfaces with
|
|
60
|
+
The gem interfaces with 3 address lookups
|
|
53
61
|
|
|
54
62
|
- [OS Places Address Lookup](https://github.com/DEFRA/os-places-address-lookup)
|
|
55
63
|
- [EA Address Facade](https://github.com/DEFRA/ea-address-facade) (repo is private)
|
|
64
|
+
- [OS API](https://docs.os.uk/os-apis/accessing-os-apis/os-places-api) (Ordnance Survey Places API)
|
|
56
65
|
|
|
57
|
-
The **EA Address Facade** has 2 versions,
|
|
66
|
+
The **EA Address Facade** has 2 versions, and combined with the other lookups, the gem provides 4 separate services a host app can use.
|
|
58
67
|
|
|
59
68
|
### Response object
|
|
60
69
|
|
|
@@ -210,6 +219,73 @@ The expected format of each result is
|
|
|
210
219
|
}
|
|
211
220
|
```
|
|
212
221
|
|
|
222
|
+
### OS API Address Lookup v1
|
|
223
|
+
|
|
224
|
+
This service calls the [Ordnance Survey Places API](https://docs.os.uk/os-apis/accessing-os-apis/os-places-api) directly, without going through an intermediary service. It returns results mapped to the same format as the EA Address Facade v1.1, making it a drop-in replacement.
|
|
225
|
+
|
|
226
|
+
> **Note:** The OS API is subject to a 600 transactions-per-minute throttle. Applications using this service should implement their own caching and/or rate limiting to stay within this limit.
|
|
227
|
+
|
|
228
|
+
```ruby
|
|
229
|
+
response = DefraRuby::Address::OsApiAddressLookupV1Service.run("BS1 5AH")
|
|
230
|
+
|
|
231
|
+
puts response.results.first["uprn"] # 340116
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
The expected format of each result matches the EA Address Facade v1.1 format:
|
|
235
|
+
|
|
236
|
+
```ruby
|
|
237
|
+
{
|
|
238
|
+
"uprn"=>340116,
|
|
239
|
+
"address"=>"ENVIRONMENT AGENCY, HORIZON HOUSE, DEANERY ROAD, BRISTOL, BS1 5AH",
|
|
240
|
+
"organisation"=>"ENVIRONMENT AGENCY",
|
|
241
|
+
"premises"=>"HORIZON HOUSE",
|
|
242
|
+
"street_address"=>"DEANERY ROAD",
|
|
243
|
+
"locality"=>nil,
|
|
244
|
+
"city"=>"BRISTOL",
|
|
245
|
+
"postcode"=>"BS1 5AH",
|
|
246
|
+
"country"=>"United Kingdom",
|
|
247
|
+
"x"=>358205.03,
|
|
248
|
+
"y"=>172708.07,
|
|
249
|
+
"coordinate_system"=>nil,
|
|
250
|
+
"blpu_state_date"=>"12/10/2009",
|
|
251
|
+
"blpu_state_code"=>"2",
|
|
252
|
+
"postal_address_code"=>"D",
|
|
253
|
+
"logical_status_code"=>"1",
|
|
254
|
+
"source_data_type"=>"dpa",
|
|
255
|
+
"blpu_state_code_description"=>"In use",
|
|
256
|
+
"classification_code"=>"CO01",
|
|
257
|
+
"classification_code_description"=>"Office / Work Studio",
|
|
258
|
+
"lpi_logical_status_code"=>nil,
|
|
259
|
+
"lpi_logical_status_code_description"=>nil,
|
|
260
|
+
"match"=>1.0,
|
|
261
|
+
"match_description"=>"EXACT",
|
|
262
|
+
"topography_layer_toid"=>"osgb1000002529079737",
|
|
263
|
+
"parent_uprn"=>nil,
|
|
264
|
+
"last_update_date"=>"10/02/2016",
|
|
265
|
+
"status"=>"APPROVED",
|
|
266
|
+
"entry_date"=>"12/10/2009",
|
|
267
|
+
"postal_address_code_description"=>"A record which is linked to PAF",
|
|
268
|
+
"usrn"=>nil,
|
|
269
|
+
"language"=>"EN"
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Coordinate conversion
|
|
274
|
+
|
|
275
|
+
The gem also provides a pair of services for converting British National Grid easting and northing coordinates (OSGB36) to WGS84 latitude and longitude, and back again, for example to support geospatial queries.
|
|
276
|
+
|
|
277
|
+
The conversion is pure Ruby, using the 7-parameter Helmert transformation and Transverse Mercator formulae from the OS guide [A guide to coordinate systems in Great Britain](https://www.ordnancesurvey.co.uk/documents/resources/guide-coordinate-systems-great-britain.pdf), and is accurate to around 5 metres.
|
|
278
|
+
|
|
279
|
+
Unlike the address lookup services these do not call an external API, and they return a plain hash rather than a `Response` object. String arguments are coerced to floats.
|
|
280
|
+
|
|
281
|
+
```ruby
|
|
282
|
+
DefraRuby::Address::EastingNorthingToLatLonService.run(358_130, 172_688)
|
|
283
|
+
# => { latitude: 51.45161..., longitude: -2.60394... }
|
|
284
|
+
|
|
285
|
+
DefraRuby::Address::LatLonToEastingNorthingService.run(51.451616, -2.603943)
|
|
286
|
+
# => { easting: 358129.8..., northing: 172687.8... }
|
|
287
|
+
```
|
|
288
|
+
|
|
213
289
|
## Contributing to this project
|
|
214
290
|
|
|
215
291
|
If you have an idea you'd like to contribute please log an issue.
|
data/Rakefile
CHANGED
|
@@ -11,7 +11,6 @@ Bundler::GemHelper.install_tasks
|
|
|
11
11
|
# This is wrapped to prevent an error when rake is called in environments where
|
|
12
12
|
# rspec may not be available, e.g. production. As such we don't need to handle
|
|
13
13
|
# the error.
|
|
14
|
-
# rubocop:disable Lint/HandleExceptions
|
|
15
14
|
begin
|
|
16
15
|
require "rspec/core/rake_task"
|
|
17
16
|
|
|
@@ -26,8 +25,9 @@ begin
|
|
|
26
25
|
require "github_changelog_generator/task"
|
|
27
26
|
|
|
28
27
|
GitHubChangelogGenerator::RakeTask.new :changelog do |config|
|
|
28
|
+
config.user = "defra"
|
|
29
|
+
config.project = "defra-ruby-address"
|
|
29
30
|
end
|
|
30
31
|
rescue LoadError
|
|
31
32
|
# no changelog available
|
|
32
33
|
end
|
|
33
|
-
# rubocop:enable Lint/HandleExceptions
|
data/bin/console
CHANGED
|
@@ -6,9 +6,5 @@ require "defra_ruby/address"
|
|
|
6
6
|
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
7
|
# with your gem easier. You can also use a different console, if you like.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
# Pry.start
|
|
12
|
-
|
|
13
|
-
require "irb"
|
|
14
|
-
IRB.start(__FILE__)
|
|
9
|
+
require "pry"
|
|
10
|
+
Pry.start
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DefraRuby
|
|
4
|
+
module Address
|
|
5
|
+
# Shifts latitude/longitude between the OSGB36 and WGS84 datums using the
|
|
6
|
+
# 7-parameter Helmert transformation from the OS guide "A guide to
|
|
7
|
+
# coordinate systems in Great Britain". Accurate to around 5 metres.
|
|
8
|
+
#
|
|
9
|
+
# The OSGB36 to WGS84 direction is ported from the breasal gem
|
|
10
|
+
# (https://github.com/theodi/breasal, MIT licence, copyright 2013
|
|
11
|
+
# pezholio), corrected to the published OS guide parameters. The reverse
|
|
12
|
+
# direction just negates them.
|
|
13
|
+
class HelmertTransformation
|
|
14
|
+
AIRY_1830 = { a: 6_377_563.396, b: 6_356_256.909 }.freeze
|
|
15
|
+
WGS84 = { a: 6_378_137.0, b: 6_356_752.3141 }.freeze
|
|
16
|
+
|
|
17
|
+
ARC_SECOND_IN_RADIANS = Math::PI / 648_000
|
|
18
|
+
|
|
19
|
+
# Translations (tx, ty, tz) in metres, scale change (s) in parts per
|
|
20
|
+
# unit and rotations (rx, ry, rz) in radians, for the OSGB36 to WGS84
|
|
21
|
+
# direction; negated they transform in the opposite direction.
|
|
22
|
+
PARAMETERS = {
|
|
23
|
+
tx: 446.448,
|
|
24
|
+
ty: -125.157,
|
|
25
|
+
tz: 542.060,
|
|
26
|
+
s: -0.0000204894,
|
|
27
|
+
rx: 0.1502 * ARC_SECOND_IN_RADIANS,
|
|
28
|
+
ry: 0.2470 * ARC_SECOND_IN_RADIANS,
|
|
29
|
+
rz: 0.8421 * ARC_SECOND_IN_RADIANS
|
|
30
|
+
}.freeze
|
|
31
|
+
|
|
32
|
+
def self.osgb36_to_wgs84(latitude, longitude)
|
|
33
|
+
new(AIRY_1830, WGS84, 1).transform(latitude, longitude)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.wgs84_to_osgb36(latitude, longitude)
|
|
37
|
+
new(WGS84, AIRY_1830, -1).transform(latitude, longitude)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def initialize(source_ellipsoid, target_ellipsoid, direction)
|
|
41
|
+
@source_ellipsoid = source_ellipsoid
|
|
42
|
+
@target_ellipsoid = target_ellipsoid
|
|
43
|
+
@direction = direction
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def transform(latitude, longitude)
|
|
47
|
+
to_geodetic(apply_helmert(to_cartesian(latitude, longitude)))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
attr_reader :source_ellipsoid, :target_ellipsoid, :direction
|
|
53
|
+
|
|
54
|
+
def to_cartesian(latitude, longitude)
|
|
55
|
+
e_squared = eccentricity_squared(source_ellipsoid)
|
|
56
|
+
phi = latitude * Math::PI / 180
|
|
57
|
+
lambda = longitude * Math::PI / 180
|
|
58
|
+
nu = source_ellipsoid[:a] / Math.sqrt(1.0 - (e_squared * (Math.sin(phi)**2)))
|
|
59
|
+
|
|
60
|
+
[nu * Math.cos(phi) * Math.cos(lambda),
|
|
61
|
+
nu * Math.cos(phi) * Math.sin(lambda),
|
|
62
|
+
(1.0 - e_squared) * nu * Math.sin(phi)]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def apply_helmert(cartesian)
|
|
66
|
+
x, y, z = cartesian
|
|
67
|
+
scale = 1.0 + parameter(:s)
|
|
68
|
+
rx = parameter(:rx)
|
|
69
|
+
ry = parameter(:ry)
|
|
70
|
+
rz = parameter(:rz)
|
|
71
|
+
|
|
72
|
+
[parameter(:tx) + (scale * x) - (rz * y) + (ry * z),
|
|
73
|
+
parameter(:ty) + (rz * x) + (scale * y) - (rx * z),
|
|
74
|
+
parameter(:tz) - (ry * x) + (rx * y) + (scale * z)]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def to_geodetic(cartesian)
|
|
78
|
+
x, y, z = cartesian
|
|
79
|
+
e_squared = eccentricity_squared(target_ellipsoid)
|
|
80
|
+
p = Math.hypot(x, y)
|
|
81
|
+
phi = Math.atan(z / (p * (1.0 - e_squared)))
|
|
82
|
+
|
|
83
|
+
10.times do
|
|
84
|
+
nu = target_ellipsoid[:a] / Math.sqrt(1.0 - (e_squared * (Math.sin(phi)**2)))
|
|
85
|
+
phi = Math.atan((z + (e_squared * nu * Math.sin(phi))) / p)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
[phi * 180 / Math::PI, Math.atan2(y, x) * 180 / Math::PI]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def parameter(key)
|
|
92
|
+
direction * PARAMETERS[key]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def eccentricity_squared(ellipsoid)
|
|
96
|
+
a = ellipsoid[:a]
|
|
97
|
+
b = ellipsoid[:b]
|
|
98
|
+
((a * a) - (b * b)) / (a * a)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DefraRuby
|
|
4
|
+
module Address
|
|
5
|
+
# Converts a British National Grid easting and northing to a WGS84
|
|
6
|
+
# latitude and longitude. Accurate to around 5 metres.
|
|
7
|
+
class EastingNorthingToLatLonService < BaseService
|
|
8
|
+
def run(easting, northing)
|
|
9
|
+
osgb36_latitude, osgb36_longitude = TransverseMercatorProjection.easting_northing_to_lat_lon(
|
|
10
|
+
Float(easting), Float(northing)
|
|
11
|
+
)
|
|
12
|
+
latitude, longitude = HelmertTransformation.osgb36_to_wgs84(osgb36_latitude, osgb36_longitude)
|
|
13
|
+
|
|
14
|
+
{ latitude: latitude, longitude: longitude }
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DefraRuby
|
|
4
|
+
module Address
|
|
5
|
+
# Converts a WGS84 latitude and longitude to a British National Grid
|
|
6
|
+
# easting and northing. Accurate to around 5 metres.
|
|
7
|
+
class LatLonToEastingNorthingService < BaseService
|
|
8
|
+
def run(latitude, longitude)
|
|
9
|
+
osgb36_latitude, osgb36_longitude = HelmertTransformation.wgs84_to_osgb36(
|
|
10
|
+
Float(latitude), Float(longitude)
|
|
11
|
+
)
|
|
12
|
+
easting, northing = TransverseMercatorProjection.lat_lon_to_easting_northing(
|
|
13
|
+
osgb36_latitude, osgb36_longitude
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
{ easting: easting, northing: northing }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DefraRuby
|
|
4
|
+
module Address
|
|
5
|
+
class OsApiAddressLookupV1Service < BaseService
|
|
6
|
+
|
|
7
|
+
# Mapping from OS API DPA field names to EaAddressFacade field names
|
|
8
|
+
# rubocop:disable Layout/HashAlignment
|
|
9
|
+
FIELD_MAPPING = {
|
|
10
|
+
"UPRN" => "uprn",
|
|
11
|
+
"ADDRESS" => "address",
|
|
12
|
+
"ORGANISATION_NAME" => "organisation",
|
|
13
|
+
"BUILDING_NAME" => "premises",
|
|
14
|
+
"THOROUGHFARE_NAME" => "street_address",
|
|
15
|
+
"DEPENDENT_LOCALITY" => "locality",
|
|
16
|
+
"POST_TOWN" => "city",
|
|
17
|
+
"POSTCODE" => "postcode",
|
|
18
|
+
"X_COORDINATE" => "x",
|
|
19
|
+
"Y_COORDINATE" => "y",
|
|
20
|
+
"BLPU_STATE_DATE" => "blpu_state_date",
|
|
21
|
+
"BLPU_STATE_CODE" => "blpu_state_code",
|
|
22
|
+
"POSTAL_ADDRESS_CODE" => "postal_address_code",
|
|
23
|
+
"LOGICAL_STATUS_CODE" => "logical_status_code",
|
|
24
|
+
"BLPU_STATE_CODE_DESCRIPTION" => "blpu_state_code_description",
|
|
25
|
+
"CLASSIFICATION_CODE" => "classification_code",
|
|
26
|
+
"CLASSIFICATION_CODE_DESCRIPTION" => "classification_code_description",
|
|
27
|
+
"MATCH" => "match",
|
|
28
|
+
"MATCH_DESCRIPTION" => "match_description",
|
|
29
|
+
"TOPOGRAPHY_LAYER_TOID" => "topography_layer_toid",
|
|
30
|
+
"PARENT_UPRN" => "parent_uprn",
|
|
31
|
+
"LAST_UPDATE_DATE" => "last_update_date",
|
|
32
|
+
"STATUS" => "status",
|
|
33
|
+
"ENTRY_DATE" => "entry_date",
|
|
34
|
+
"POSTAL_ADDRESS_CODE_DESCRIPTION" => "postal_address_code_description",
|
|
35
|
+
"USRN" => "usrn",
|
|
36
|
+
"LANGUAGE" => "language"
|
|
37
|
+
}.freeze
|
|
38
|
+
# rubocop:enable Layout/HashAlignment
|
|
39
|
+
|
|
40
|
+
DEFAULT_HOST = "https://api.os.uk/search/places/v1"
|
|
41
|
+
|
|
42
|
+
def run(postcode)
|
|
43
|
+
@postcode = postcode
|
|
44
|
+
Response.new(response_exe)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
attr_reader :postcode
|
|
50
|
+
|
|
51
|
+
def url
|
|
52
|
+
host = DefraRuby::Address.configuration.host || DEFAULT_HOST
|
|
53
|
+
|
|
54
|
+
File.join(
|
|
55
|
+
host,
|
|
56
|
+
"postcode?postcode=#{postcode}&key=#{DefraRuby::Address.configuration.key}"
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def response_exe
|
|
61
|
+
lambda do
|
|
62
|
+
response = RestClient::Request.execute(
|
|
63
|
+
method: :get,
|
|
64
|
+
url: url,
|
|
65
|
+
timeout: DefraRuby::Address.configuration.timeout
|
|
66
|
+
)
|
|
67
|
+
parsed = JSON.parse(response)
|
|
68
|
+
results = parsed["results"] || []
|
|
69
|
+
|
|
70
|
+
raise DefraRuby::Address::NoMatchError if results.empty?
|
|
71
|
+
|
|
72
|
+
results.map { |r| map_dpa_to_result(r["DPA"]) }
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def map_dpa_to_result(dpa)
|
|
77
|
+
result = map_fields(dpa)
|
|
78
|
+
result["uprn"] = result["uprn"]&.to_i
|
|
79
|
+
result.merge(hardcoded_fields)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def map_fields(dpa)
|
|
83
|
+
FIELD_MAPPING.each_with_object({}) do |(os_key, facade_key), hash|
|
|
84
|
+
hash[facade_key] = dpa[os_key]
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def hardcoded_fields
|
|
89
|
+
{
|
|
90
|
+
"country" => "United Kingdom",
|
|
91
|
+
"coordinate_system" => nil,
|
|
92
|
+
"source_data_type" => "dpa",
|
|
93
|
+
"lpi_logical_status_code" => nil,
|
|
94
|
+
"lpi_logical_status_code_description" => nil
|
|
95
|
+
}
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
module DefraRuby
|
|
4
4
|
module Address
|
|
5
5
|
class OsPlacesAddressLookupService < BaseService
|
|
6
|
+
OS_PLACES_ADDRESS_LOOKUP_NO_MATCH_ERROR_MSG = "Parameters are not valid"
|
|
6
7
|
|
|
7
8
|
def run(postcode)
|
|
8
9
|
@postcode = postcode
|
|
@@ -11,8 +12,6 @@ module DefraRuby
|
|
|
11
12
|
|
|
12
13
|
private
|
|
13
14
|
|
|
14
|
-
OS_PLACES_ADDRESS_LOOKUP_NO_MATCH_ERROR_MSG = "Parameters are not valid"
|
|
15
|
-
|
|
16
15
|
attr_reader :postcode
|
|
17
16
|
|
|
18
17
|
def url
|
|
@@ -24,16 +23,16 @@ module DefraRuby
|
|
|
24
23
|
|
|
25
24
|
def response_exe
|
|
26
25
|
lambda do
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
26
|
+
|
|
27
|
+
response = RestClient::Request.execute(
|
|
28
|
+
method: :get,
|
|
29
|
+
url: url,
|
|
30
|
+
timeout: DefraRuby::Address.configuration.timeout
|
|
31
|
+
)
|
|
32
|
+
JSON.parse(response)
|
|
33
|
+
rescue RestClient::BadRequest => e
|
|
34
|
+
raise process_error(e)
|
|
35
|
+
|
|
37
36
|
end
|
|
38
37
|
end
|
|
39
38
|
|
|
@@ -43,8 +42,6 @@ module DefraRuby
|
|
|
43
42
|
end
|
|
44
43
|
rescue StandardError
|
|
45
44
|
error
|
|
46
|
-
ensure
|
|
47
|
-
error
|
|
48
45
|
end
|
|
49
46
|
end
|
|
50
47
|
end
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DefraRuby
|
|
4
|
+
module Address
|
|
5
|
+
# Converts between British National Grid easting/northing and OSGB36
|
|
6
|
+
# latitude/longitude, a Transverse Mercator projection on the Airy 1830
|
|
7
|
+
# ellipsoid. Variable names follow the terms in the OS guide "A guide to
|
|
8
|
+
# coordinate systems in Great Britain".
|
|
9
|
+
#
|
|
10
|
+
# The easting/northing to lat/lon direction is ported from the breasal
|
|
11
|
+
# gem (https://github.com/theodi/breasal, MIT licence, copyright 2013
|
|
12
|
+
# pezholio). Breasal has no reverse conversion, so that direction comes
|
|
13
|
+
# straight from the OS guide's forward projection formulae.
|
|
14
|
+
class TransverseMercatorProjection
|
|
15
|
+
A = HelmertTransformation::AIRY_1830[:a]
|
|
16
|
+
B = HelmertTransformation::AIRY_1830[:b]
|
|
17
|
+
E_SQUARED = ((A * A) - (B * B)) / (A * A)
|
|
18
|
+
N = (A - B) / (A + B)
|
|
19
|
+
|
|
20
|
+
F0 = 0.9996012717
|
|
21
|
+
PHI0 = 49.0 * Math::PI / 180
|
|
22
|
+
LAMBDA0 = -2.0 * Math::PI / 180
|
|
23
|
+
E0 = 400_000.0
|
|
24
|
+
N0 = -100_000.0
|
|
25
|
+
|
|
26
|
+
def self.easting_northing_to_lat_lon(easting, northing)
|
|
27
|
+
new.easting_northing_to_lat_lon(easting, northing)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.lat_lon_to_easting_northing(latitude, longitude)
|
|
31
|
+
new.lat_lon_to_easting_northing(latitude, longitude)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def easting_northing_to_lat_lon(easting, northing)
|
|
35
|
+
phi_prime = converged_phi_prime(northing)
|
|
36
|
+
delta_e = easting - E0
|
|
37
|
+
|
|
38
|
+
[latitude_from(phi_prime, delta_e) * 180 / Math::PI,
|
|
39
|
+
longitude_from(phi_prime, delta_e) * 180 / Math::PI]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def lat_lon_to_easting_northing(latitude, longitude)
|
|
43
|
+
phi = latitude * Math::PI / 180
|
|
44
|
+
delta_lambda = (longitude * Math::PI / 180) - LAMBDA0
|
|
45
|
+
|
|
46
|
+
[easting_from(phi, delta_lambda), northing_from(phi, delta_lambda)]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
# Converges within a few iterations for any real coordinates; the cap
|
|
52
|
+
# guards against non-finite input looping forever
|
|
53
|
+
def converged_phi_prime(northing)
|
|
54
|
+
phi_prime = ((northing - N0) / (A * F0)) + PHI0
|
|
55
|
+
|
|
56
|
+
100.times do
|
|
57
|
+
delta = northing - N0 - meridional_arc(phi_prime)
|
|
58
|
+
break if delta < 0.001
|
|
59
|
+
|
|
60
|
+
phi_prime += delta / (A * F0)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
phi_prime
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# The methods below are a straight port of the OS guide formulae; the
|
|
67
|
+
# ABC metric counts every arithmetic operator, so is disabled for them.
|
|
68
|
+
# rubocop:disable Metrics/AbcSize
|
|
69
|
+
|
|
70
|
+
# M in the OS guide
|
|
71
|
+
def meridional_arc(phi)
|
|
72
|
+
delta_phi = phi - PHI0
|
|
73
|
+
sum_phi = phi + PHI0
|
|
74
|
+
|
|
75
|
+
B * F0 * (((1 + N + ((5.0 / 4) * (N**2)) + ((5.0 / 4) * (N**3))) * delta_phi) -
|
|
76
|
+
(((3 * N) + (3 * (N**2)) + ((21.0 / 8) * (N**3))) * Math.sin(delta_phi) * Math.cos(sum_phi)) +
|
|
77
|
+
((((15.0 / 8) * (N**2)) + ((15.0 / 8) * (N**3))) * Math.sin(2 * delta_phi) * Math.cos(2 * sum_phi)) -
|
|
78
|
+
(((35.0 / 24) * (N**3)) * Math.sin(3 * delta_phi) * Math.cos(3 * sum_phi)))
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Terms VII, VIII and IX in the OS guide
|
|
82
|
+
def latitude_from(phi_prime, delta_e)
|
|
83
|
+
tan = Math.tan(phi_prime)
|
|
84
|
+
tan2 = tan**2
|
|
85
|
+
nu = nu(phi_prime)
|
|
86
|
+
rho = rho(phi_prime)
|
|
87
|
+
eta_squared = (nu / rho) - 1.0
|
|
88
|
+
|
|
89
|
+
vii = tan / (2 * rho * nu)
|
|
90
|
+
viii = (tan / (24 * rho * (nu**3))) * (5 + (3 * tan2) + eta_squared - (9 * tan2 * eta_squared))
|
|
91
|
+
ix = (tan / (720 * rho * (nu**5))) * (61 + (90 * tan2) + (45 * (tan2**2)))
|
|
92
|
+
|
|
93
|
+
phi_prime - (vii * (delta_e**2)) + (viii * (delta_e**4)) - (ix * (delta_e**6))
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Terms X, XI, XII and XIIA in the OS guide
|
|
97
|
+
def longitude_from(phi_prime, delta_e)
|
|
98
|
+
sec = 1.0 / Math.cos(phi_prime)
|
|
99
|
+
tan2 = Math.tan(phi_prime)**2
|
|
100
|
+
nu = nu(phi_prime)
|
|
101
|
+
|
|
102
|
+
x = sec / nu
|
|
103
|
+
xi = (sec / (6 * (nu**3))) * ((nu / rho(phi_prime)) + (2 * tan2))
|
|
104
|
+
xii = (sec / (120 * (nu**5))) * (5 + (28 * tan2) + (24 * (tan2**2)))
|
|
105
|
+
xiia = (sec / (5040 * (nu**7))) * (61 + (662 * tan2) + (1320 * (tan2**2)) + (720 * (tan2**3)))
|
|
106
|
+
|
|
107
|
+
LAMBDA0 + (x * delta_e) - (xi * (delta_e**3)) + (xii * (delta_e**5)) - (xiia * (delta_e**7))
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Terms IV, V and VI in the OS guide
|
|
111
|
+
def easting_from(phi, delta_lambda)
|
|
112
|
+
nu = nu(phi)
|
|
113
|
+
rho = rho(phi)
|
|
114
|
+
eta_squared = (nu / rho) - 1.0
|
|
115
|
+
cos_phi = Math.cos(phi)
|
|
116
|
+
tan2 = Math.tan(phi)**2
|
|
117
|
+
|
|
118
|
+
iv = nu * cos_phi
|
|
119
|
+
v = (nu / 6) * (cos_phi**3) * ((nu / rho) - tan2)
|
|
120
|
+
vi = (nu / 120) * (cos_phi**5) * (5 - (18 * tan2) + (tan2**2) + (14 * eta_squared) - (58 * tan2 * eta_squared))
|
|
121
|
+
|
|
122
|
+
E0 + (iv * delta_lambda) + (v * (delta_lambda**3)) + (vi * (delta_lambda**5))
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Terms I, II, III and IIIA in the OS guide
|
|
126
|
+
def northing_from(phi, delta_lambda)
|
|
127
|
+
nu = nu(phi)
|
|
128
|
+
eta_squared = (nu / rho(phi)) - 1.0
|
|
129
|
+
sin_phi = Math.sin(phi)
|
|
130
|
+
cos_phi = Math.cos(phi)
|
|
131
|
+
tan2 = Math.tan(phi)**2
|
|
132
|
+
|
|
133
|
+
i = meridional_arc(phi) + N0
|
|
134
|
+
ii = (nu / 2) * sin_phi * cos_phi
|
|
135
|
+
iii = (nu / 24) * sin_phi * (cos_phi**3) * (5 - tan2 + (9 * eta_squared))
|
|
136
|
+
iiia = (nu / 720) * sin_phi * (cos_phi**5) * (61 - (58 * tan2) + (tan2**2))
|
|
137
|
+
|
|
138
|
+
i + (ii * (delta_lambda**2)) + (iii * (delta_lambda**4)) + (iiia * (delta_lambda**6))
|
|
139
|
+
end
|
|
140
|
+
# rubocop:enable Metrics/AbcSize
|
|
141
|
+
|
|
142
|
+
def nu(phi)
|
|
143
|
+
A * F0 * ((1.0 - (E_SQUARED * (Math.sin(phi)**2)))**-0.5)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def rho(phi)
|
|
147
|
+
A * F0 * (1.0 - E_SQUARED) * ((1.0 - (E_SQUARED * (Math.sin(phi)**2)))**-1.5)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
data/lib/defra_ruby/address.rb
CHANGED
|
@@ -2,12 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "address/configuration"
|
|
4
4
|
require_relative "address/ea_address_facade_request"
|
|
5
|
+
require_relative "address/helmert_transformation"
|
|
5
6
|
require_relative "address/no_match_error"
|
|
6
7
|
require_relative "address/response"
|
|
8
|
+
require_relative "address/transverse_mercator_projection"
|
|
7
9
|
|
|
8
10
|
require_relative "address/services/base_service"
|
|
9
11
|
require_relative "address/services/ea_address_facade_v1_1_service"
|
|
10
12
|
require_relative "address/services/ea_address_facade_v1_service"
|
|
13
|
+
require_relative "address/services/easting_northing_to_lat_lon_service"
|
|
14
|
+
require_relative "address/services/lat_lon_to_easting_northing_service"
|
|
15
|
+
require_relative "address/services/os_api_address_lookup_v1_service"
|
|
11
16
|
require_relative "address/services/os_places_address_lookup_service"
|
|
12
17
|
|
|
13
18
|
module DefraRuby
|