osgb_convert 0.0.4

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