osgb_convert 0.0.4

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,3 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in osgb_convert.gemspec
4
+ gemspec
data/README ADDED
@@ -0,0 +1,44 @@
1
+ Some UK Ordnance Survey coordinate conversions in pure Ruby.
2
+
3
+ WGS84 lat/lons -> OSGB36 lat/lons-> OS Eastings & Northings
4
+
5
+ A tidied-into-a-module-and-gem tweak of Harry Wood's port of Chris Veness'
6
+ http://www.movable-type.co.uk/scripts/latlong-convert-coords.html
7
+
8
+
9
+ Originally ported to Ruby by Harry Wood:
10
+ http://www.harrywood.co.uk/blog/2010/06/29/ruby-code-for-converting-to-uk-ordnance-survey-coordinate-systems-from-wgs84/
11
+
12
+ (c) Chris Veness 2005-2010, (c) Harry Wood 2010-2011
13
+ Teeny portions (c) Matt Patterson 2011
14
+
15
+ Released under an LGPL license
16
+ http://www.fsf.org/licensing/licenses/lgpl.html
17
+
18
+
19
+ Examples:
20
+
21
+ WGS84 lat/lon:
22
+
23
+ lon = -0.10322
24
+ lat = 51.52237
25
+ height = 0
26
+
27
+ wgs84_point = OsgbConvert::WGS84.new(lat, lon, height)
28
+
29
+
30
+ osgb36_point = wgs84_point.osgb36
31
+
32
+ osUKgridPoint = OsgbConvert::OSGrid.from_osgb36(osgb36_point)
33
+ # alternatively
34
+ osUKgridPoint = OsgbConvert::OSGrid.from_wgs84(wgs84_point)
35
+ easting = osUKgridPoint.easting
36
+ northing = osUKgridPoint.northing
37
+
38
+ gridrefLetters = osUKgridPoint.grid_ref(8) # 8 is also the default so osUKgridPoint.grid_ref would work just as well
39
+
40
+ puts "wgs84 lat: #{wgs84_point.lat}, wgs84 lon: #{wgs84_point.long}"
41
+ puts "http://www.openstreetmap.org/?mlat=#{wgs84_point.lat}&mlon=#{wgs84_point.long}&zoom=16"
42
+ puts "osgb36 lat: #{osgb36_point.lat}, osgb36 lon: #{osgb36_point.long}"
43
+ puts "easting: #{osUKgridPoint.easting}, northing: #{osUKgridPoint.northing}. As a grid ref: #{osUKgridPoint.grid_ref}"
44
+ puts "http://streetmap.co.uk/grid/#{osUKgridPoint.easting}_#{osUKgridPoint.northing}_106"
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,108 @@
1
+ module OsgbConvert
2
+ def self.degrees_to_rads(degrees)
3
+ degrees * Math::PI / 180
4
+ end
5
+
6
+ def self.rads_to_degrees(rads)
7
+ rads * 180 / Math::PI
8
+ end
9
+
10
+ module Converter
11
+ #ellipse parameters
12
+ ELLIPSE = {
13
+ :wgs84 => { :a=> 6378137, :b=> 6356752.3142, :f=> 1 / 298.257223563 },
14
+ :airy1830 => { :a=> 6377563.396, :b=> 6356256.910, :f=> 1 / 299.3249646 }
15
+ }
16
+
17
+ #helmert transform parameters
18
+ HELMERT = {
19
+ :wgs84toOSGB36 => {
20
+ :tx=> -446.448, :ty=> 125.157, :tz=> -542.060, # m
21
+ :rx=> -0.1502, :ry=> -0.2470, :rz=> -0.8421, # sec
22
+ :s=> 20.4894 # ppm
23
+ },
24
+ :osgb36toWGS84 => {
25
+ :tx=> 446.448, :ty=> -125.157, :tz=> 542.060,
26
+ :rx=> 0.1502, :ry=> 0.2470, :rz=> 0.8421,
27
+ :s=> -20.4894
28
+ }
29
+ }
30
+
31
+ def convert(p1lat, p1lon, p1height, e1, t, e2)
32
+ # -- convert polar to cartesian coordinates (using ellipse 1)
33
+
34
+ p1lat = OsgbConvert.degrees_to_rads(p1lat); p1lon = OsgbConvert.degrees_to_rads(p1lon);
35
+
36
+ a = e1[:a]; b = e1[:b];
37
+
38
+ sinPhi = Math.sin(p1lat); cosPhi = Math.cos(p1lat);
39
+ sinLambda = Math.sin(p1lon); cosLambda = Math.cos(p1lon);
40
+ h = p1height;
41
+
42
+ eSq = (a*a - b*b) / (a*a);
43
+ nu = a / Math.sqrt(1 - eSq*sinPhi*sinPhi);
44
+
45
+ x1 = (nu+h) * cosPhi * cosLambda;
46
+ y1 = (nu+h) * cosPhi * sinLambda;
47
+ z1 = ((1-eSq)*nu + h) * sinPhi;
48
+
49
+ # -- apply helmert transform using appropriate params
50
+
51
+ tx = t[:tx]; ty = t[:ty]; tz = t[:tz];
52
+ rx = t[:rx] / 3600 * Math::PI/180; #normalise seconds to radians
53
+ ry = t[:ry] / 3600 * Math::PI/180;
54
+ rz = t[:rz] / 3600 * Math::PI/180;
55
+ s1 = t[:s] / 1e6 + 1; #normalise ppm to (s+1)
56
+
57
+ #apply transform
58
+ x2 = tx + x1*s1 - y1*rz + z1*ry;
59
+ y2 = ty + x1*rz + y1*s1 - z1*rx;
60
+ z2 = tz - x1*ry + y1*rx + z1*s1;
61
+
62
+ # -- convert cartesian to polar coordinates (using ellipse 2)
63
+
64
+ a = e2[:a]; b = e2[:b];
65
+ precision = 4 / a; # results accurate to around 4 metres
66
+
67
+ eSq = (a*a - b*b) / (a*a);
68
+ p = Math.sqrt(x2*x2 + y2*y2);
69
+ phi = Math.atan2(z2, p*(1-eSq)); phiP = 2 * Math::PI;
70
+ while ( (phi-phiP).abs > precision) do
71
+ nu = a / Math.sqrt(1 - eSq*Math.sin(phi)*Math.sin(phi));
72
+ phiP = phi;
73
+ phi = Math.atan2(z2 + eSq*nu*Math.sin(phi), p);
74
+ end
75
+ lambda = Math.atan2(y2, x2);
76
+ h = p/Math.cos(phi) - nu;
77
+
78
+ #return array [lat,lon,height]
79
+ return [ OsgbConvert.rads_to_degrees(phi), OsgbConvert.rads_to_degrees(lambda), h ];
80
+ end
81
+ end
82
+
83
+ class Coordinate
84
+ include Converter
85
+
86
+ attr_reader :lat, :long, :height
87
+
88
+ def initialize(lat, long, height)
89
+ @lat = lat
90
+ @long = long
91
+ @height = height
92
+ end
93
+ end
94
+
95
+ class WGS84 < Coordinate
96
+ def osgb36
97
+ OSGB36.new(*convert(lat, long, height, ELLIPSE[:wgs84], HELMERT[:wgs84toOSGB36], ELLIPSE[:airy1830]))
98
+ end
99
+ end
100
+
101
+ class OSGB36 < Coordinate
102
+ def wgs84
103
+ WGS84.new(*convert(lat, long, height, ELLIPSE[:airy1830], HELMERT[:osgb36toWGS84], ELLIPSE[:wgs84]))
104
+ end
105
+ end
106
+ end
107
+
108
+ require 'osgb_convert/os_grid'
@@ -0,0 +1,200 @@
1
+ module OsgbConvert
2
+ class OSGrid
3
+ GRID_LETTERS = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K',
4
+ 'L', 'M', 'N', 'O', 'P', 'Q', 'S', 'T', 'U', 'V',
5
+ 'W', 'X', 'Y', 'Z']
6
+
7
+ attr_reader :easting, :northing
8
+
9
+ def self.from_wgs84(wgs84)
10
+ from_osgb36(wgs84.osgb36)
11
+ end
12
+
13
+ # http://www.movable-type.co.uk/scripts/latlong-gridref.html
14
+ # (c) Chris Veness 2005-2010 Released under an LGPL license
15
+ # http://www.fsf.org/licensing/licenses/lgpl.html
16
+ # Ported to ruby by Harry Wood
17
+
18
+ # OSGB36 coordinates to OS UK grid eastings & northings
19
+ def self.from_osgb36(osgb36)
20
+ lat = OsgbConvert.degrees_to_rads(osgb36.lat);
21
+ lon = OsgbConvert.degrees_to_rads(osgb36.long);
22
+
23
+ a = 6377563.396; b = 6356256.910 # Airy 1830 major & minor semi-axes
24
+ f0 = 0.9996012717 # NatGrid scale factor on central meridian
25
+ lat0 = OsgbConvert.degrees_to_rads(49); lon0 = OsgbConvert.degrees_to_rads(-2) # NatGrid true origin
26
+ n0 = -100000; e0 = 400000; # northing & easting of true origin, metres
27
+ e2 = 1 - (b*b) / (a*a); # eccentricity squared
28
+ n = (a-b) / (a+b); n2 = n*n; n3 = n*n*n;
29
+
30
+ cos_lat = Math.cos(lat); sinLat = Math.sin(lat);
31
+ nu = a*f0/Math.sqrt(1-e2*sinLat*sinLat); # transverse radius of curvature
32
+ rho = a*f0*(1-e2) / ( (1-e2*sinLat*sinLat) ** 1.5); # meridional radius of curvature
33
+ eta2 = nu/rho-1;
34
+
35
+ ma = (1 + n + (5/4)*n2 + (5/4)*n3) * (lat-lat0);
36
+ mb = (3*n + 3*n*n + (21/8)*n3) * Math.sin(lat-lat0) * Math.cos(lat+lat0);
37
+ mc = ((15/8)*n2 + (15/8)*n3) * Math.sin(2*(lat-lat0)) * Math.cos(2*(lat+lat0));
38
+ md = (35/24)*n3 * Math.sin(3*(lat-lat0)) * Math.cos(3*(lat+lat0));
39
+ m = b * f0 * (ma - mb + mc - md) # meridional arc
40
+
41
+ cos3lat = cos_lat*cos_lat*cos_lat
42
+ cos5lat = cos3lat*cos_lat*cos_lat
43
+ tan2lat = Math.tan(lat)*Math.tan(lat);
44
+ tan4lat = tan2lat*tan2lat;
45
+
46
+ i = m + n0
47
+ ii = (nu/2)*sinLat*cos_lat;
48
+ iii = (nu/24)*sinLat*cos3lat*(5-tan2lat+9*eta2);
49
+ iiiA = (nu/720)*sinLat*cos5lat*(61-58*tan2lat+tan4lat);
50
+ iv = nu*cos_lat;
51
+ v = (nu/6)*cos3lat*(nu/rho-tan2lat);
52
+ vi = (nu/120) * cos5lat * (5 - 18*tan2lat + tan4lat + 14*eta2 - 58*tan2lat*eta2);
53
+
54
+ dLon = lon-lon0;
55
+ dLon2 = dLon*dLon
56
+ dLon3 = dLon2*dLon
57
+ dLon4 = dLon3*dLon
58
+ dLon5 = dLon4*dLon
59
+ dLon6 = dLon5*dLon
60
+
61
+ n = i + ii*dLon2 + iii*dLon4 + iiiA*dLon6;
62
+ e = e0 + iv*dLon + v*dLon3 + vi*dLon5;
63
+
64
+ new(e, n) # return new OSGrid instance using the raw easting and northings
65
+ end
66
+
67
+ # convert standard grid reference ('SU387148') to fully numeric ref ([438700,114800])
68
+ # returned co-ordinates are in metres, centred on grid square for conversion to lat/long
69
+ #
70
+ # note that northern-most grid squares will give 7-digit northings
71
+ # no error-checking is done on gridref (bad input will give bad results or NaN)
72
+ def self.from_standard_ref(grid_ref)
73
+ # get numeric values of letter references, mapping A->0, B->1, C->2, etc:
74
+ first_letter = GRID_LETTERS.index(grid_ref[0..0].upcase)
75
+ second_letter = GRID_LETTERS.index(grid_ref[1..1].upcase)
76
+
77
+ # convert grid letters into 100km-square indexes from false origin (grid square SV):
78
+ easting = ((first_letter - 2) % 5) * 5 + (second_letter % 5)
79
+ northing = (19 - (first_letter / 5).floor * 5) - (second_letter / 5).floor
80
+
81
+ # skip grid letters to get numeric part of ref, stripping any spaces:
82
+ numeric_ref = grid_ref[2..-1].gsub(/\s/, '')
83
+
84
+ # append numeric part of references to grid index:
85
+ easting += numeric_ref[0..(numeric_ref.length / 2)]
86
+ northing += numeric_ref[(numeric_ref.length / 2)..-1]
87
+
88
+ # normalise to 1m grid, rounding up to centre of grid square:
89
+ case numeric_ref.length
90
+ when 6
91
+ easting += '50'
92
+ northing += '50'
93
+ when 8
94
+ easting += '5'
95
+ northing += '5'
96
+ end
97
+ # 10-digit refs are already 1m
98
+
99
+ new(e, n)
100
+ end
101
+
102
+ def initialize(easting, northing)
103
+ @easting = easting
104
+ @northing = northing
105
+ end
106
+
107
+ #
108
+ # convert OS grid reference to geodesic co-ordinates
109
+ #
110
+ def osgb36
111
+ # Airy 1830 major & minor semi-axes
112
+ a = Converter::ELLIPSE[:airy1830][:a]
113
+ b = Converter::ELLIPSE[:airy1830][:b]
114
+ f0 = 0.9996012717; # NatGrid scale factor on central meridian
115
+ lat0 = 49 * Math::PI / 180 # NatGrid true origin
116
+ lon0 = -2 * Math::PI / 180
117
+ n0 = -100000 # northing of true origin, metres
118
+ e0 = 400000 # easting of true origin, metres
119
+ e2 = 1 - (b * b) / (a * a) # eccentricity squared
120
+ n = (a - b) / (a + b)
121
+ n2 = n * n
122
+ n3 = n * n * n
123
+
124
+ lat = lat0
125
+ m = 0
126
+
127
+ while (northing - n0 - m) >= 0.00001 # ie until < 0.01mm
128
+ lat = (northing - n0 - m) / (a * f0) + lat
129
+
130
+ ma = (1 + n + ((5 / 4) * n2) + ((5 / 4) * n3)) * (lat - lat0)
131
+ mb = (3 * n + 3 * n * n + (21 / 8) * n3) * Math.sin(lat - lat0) * Math.cos(lat + lat0)
132
+ mc = ((15 / 8) * n2 + (15 / 8) * n3) * Math.sin(2 * (lat - lat0)) * Math.cos(2 * (lat + lat0))
133
+ md = (35 / 24) * n3 * Math.sin(3 * (lat - lat0)) * Math.cos(3 * (lat + lat0))
134
+ m = b * f0 * (ma - mb + mc - md) # meridional arc
135
+ end
136
+
137
+ cos_lat = Math.cos(lat)
138
+ sin_lat = Math.sin(lat)
139
+ nu = a * f0 / Math.sqrt(1 - e2 * sin_lat * sin_lat) # transverse radius of curvature
140
+ rho = a * f0 * (1 - e2) / (1 - e2 * sin_lat * sin_lat) ** 1.5 # meridional radius of curvature
141
+ eta2 = nu / rho - 1
142
+
143
+ tan_lat = Math.tan(lat)
144
+ tan2lat = tan_lat*tan_lat
145
+ tan4lat = tan2lat*tan2lat
146
+ tan6lat = tan4lat*tan2lat
147
+ sec_lat = 1/cos_lat
148
+ nu3 = nu*nu*nu
149
+ nu5 = nu3*nu*nu
150
+ nu7 = nu5*nu*nu
151
+ vii = tan_lat/(2*rho*nu)
152
+ viii = tan_lat/(24*rho*nu3)*(5+3*tan2lat+eta2-9*tan2lat*eta2)
153
+ ix = tan_lat/(720*rho*nu5)*(61+90*tan2lat+45*tan4lat)
154
+ x = sec_lat/nu
155
+ xi = sec_lat/(6*nu3)*(nu/rho+2*tan2lat)
156
+ xii = sec_lat/(120*nu5)*(5+28*tan2lat+24*tan4lat)
157
+ xiia = sec_lat/(5040*nu7)*(61+662*tan2lat+1320*tan4lat+720*tan6lat)
158
+
159
+ dE = (easting - e0)
160
+ dE2 = dE*dE
161
+ dE3 = dE2*dE
162
+ dE4 = dE2*dE2
163
+ dE5 = dE3*dE2
164
+ dE6 = dE4*dE2
165
+ dE7 = dE5*dE2
166
+ lat = OsgbConvert.rads_to_degrees(lat - vii*dE2 + viii*dE4 - ix*dE6)
167
+ lon = OsgbConvert.rads_to_degrees(lon0 + x*dE - xi*dE3 + xii*dE5 - xiia*dE7)
168
+
169
+ OSGB36.new(lat, lon, 0)
170
+ end
171
+
172
+ def wgs84
173
+ osgb36.wgs84
174
+ end
175
+
176
+ # convert numeric grid reference (in metres) to standard-form grid ref
177
+ # [defaults to 8 digits (because this was in the example, not because I know better)]
178
+ def grid_ref(digits = 8)
179
+ return @grid_ref if @grid_ref
180
+ #get the 100km-grid indices
181
+ e100k = (easting / 100000).floor
182
+ n100k = (northing / 100000).floor
183
+
184
+ return '' if (e100k < 0 or e100k > 6 or n100k < 0 or n100k > 12)
185
+
186
+ #translate those into numeric equivalents of the grid letters
187
+ first_letter = (19 - n100k) - (19 - n100k) % 5 + ((e100k + 10) / 5).floor;
188
+ second_letter = (19 - n100k) * 5 % 25 + e100k % 5;
189
+
190
+ # letter - 1 to ensure we have 0-indexed the array and aren't off-by-one
191
+ grid_name = GRID_LETTERS[first_letter - 1] + GRID_LETTERS[second_letter - 1]
192
+
193
+ # strip 100km-grid indices from easting & northing, and reduce precision
194
+ reduced_precision_easting = ( (easting % 100000) / (10 ** (5 - digits / 2)) ).floor
195
+ reduced_precision_northing = ( (northing % 100000) / (10 ** (5 - digits / 2)) ).floor
196
+
197
+ @grid_ref = grid_name + reduced_precision_easting.to_s.rjust(digits / 2) + reduced_precision_northing.to_s.rjust(digits / 2)
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,3 @@
1
+ module OsgbConvert
2
+ VERSION = "0.0.4"
3
+ end
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "osgb_convert/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "osgb_convert"
7
+ s.version = OsgbConvert::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Matt Patterson"]
10
+ s.email = ["matt@reprocessed.org"]
11
+ s.homepage = "http://github.com/fidothe/osgb_convert"
12
+ s.summary = %q{Geo coordinate transformation between WGS84 (GPS) and OSGB36 (UK Ordnance Survey mapping)}
13
+ s.description = %q{Provides a simple interface to transform Geographic coordinates between WGS84 (GPS) and OSGB36 (UK Ordnance Survey mapping)}
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: osgb_convert
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 4
9
+ version: 0.0.4
10
+ platform: ruby
11
+ authors:
12
+ - Matt Patterson
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-02-03 00:00:00 +00:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: Provides a simple interface to transform Geographic coordinates between WGS84 (GPS) and OSGB36 (UK Ordnance Survey mapping)
22
+ email:
23
+ - matt@reprocessed.org
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files: []
29
+
30
+ files:
31
+ - .gitignore
32
+ - Gemfile
33
+ - README
34
+ - Rakefile
35
+ - lib/osgb_convert.rb
36
+ - lib/osgb_convert/os_grid.rb
37
+ - lib/osgb_convert/version.rb
38
+ - osgb_convert.gemspec
39
+ has_rdoc: true
40
+ homepage: http://github.com/fidothe/osgb_convert
41
+ licenses: []
42
+
43
+ post_install_message:
44
+ rdoc_options: []
45
+
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ segments:
53
+ - 0
54
+ version: "0"
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ segments:
60
+ - 0
61
+ version: "0"
62
+ requirements: []
63
+
64
+ rubyforge_project:
65
+ rubygems_version: 1.3.6
66
+ signing_key:
67
+ specification_version: 3
68
+ summary: Geo coordinate transformation between WGS84 (GPS) and OSGB36 (UK Ordnance Survey mapping)
69
+ test_files: []
70
+