osgb 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +3 -0
- data/Gemfile +2 -0
- data/LICENSE +20 -0
- data/README.md +51 -0
- data/Rakefile +5 -0
- data/init.rb +1 -0
- data/lib/osgb.rb +26 -0
- data/lib/osgb/angle_conversions.rb +9 -0
- data/lib/osgb/ellipsoid.rb +56 -0
- data/lib/osgb/gridref.rb +198 -0
- data/lib/osgb/has_gridref.rb +45 -0
- data/lib/osgb/helmert.rb +33 -0
- data/lib/osgb/projection.rb +21 -0
- data/lib/osgb/railtie.rb +23 -0
- data/lib/osgb/string_conversions.rb +41 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/string_spec.rb +27 -0
- metadata +114 -0
data/CHANGELOG.rdoc
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Ryan Bates
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
= OSGB
|
2
|
+
|
3
|
+
Wiki[https://github.com/spanner/osgb/wiki] RDocs[http://rdoc.info/projects/spanner/osgb]
|
4
|
+
|
5
|
+
Osgb is a library that converts between British (and Irish) grid references and latitude and longitude co-ordinates. It is precise to about 5m, which is to say it's good enough for WGS84 and most GPS use but not local enough for surveying to ETRS89.
|
6
|
+
|
7
|
+
== Installation
|
8
|
+
|
9
|
+
In <b>Rails 3</b>, add this to your Gemfile and run +bundle install+.
|
10
|
+
|
11
|
+
gem "osgb"
|
12
|
+
|
13
|
+
In <b>Rails 2</b>, add this to your environment.rb file.
|
14
|
+
|
15
|
+
config.gem "osgb"
|
16
|
+
|
17
|
+
Alternatively, you can install it as a plugin.
|
18
|
+
|
19
|
+
rails plugin install git://github.com/spanner/osgb.git
|
20
|
+
|
21
|
+
== Usage
|
22
|
+
|
23
|
+
You don't need to make any explicit reference to the gem. It adds conversion methods to the String class:
|
24
|
+
|
25
|
+
"SD12341234".is_gridref? # -> true
|
26
|
+
"SD12341234".to_latlng # ->
|
27
|
+
"SD12341234".to_WGS84 # ->
|
28
|
+
"1.056789, 55.98978607".is_latlng? # -> true
|
29
|
+
"1.056789, 55.98978607".to_gridref # -> true
|
30
|
+
|
31
|
+
and provides some help for your ActiveRecord classes:
|
32
|
+
|
33
|
+
class Checkpoint < ActiveRecord::Base
|
34
|
+
has_gridref :lat => 'lat',
|
35
|
+
:lng => 'lng',
|
36
|
+
:gridref => 'gridref',
|
37
|
+
:validation => false,
|
38
|
+
:converstion => true
|
39
|
+
|
40
|
+
The :lat, :lng and :gridref keys should pass in the names of the relevant columns if they don't match these defaults.
|
41
|
+
|
42
|
+
== Questions or Problems?
|
43
|
+
|
44
|
+
If you have any issues with CanCan which you cannot find the solution to in the documentation[https://github.com/ryanb/cancan/wiki], please add an {issue on GitHub}[https://github.com/ryanb/cancan/issues] or fork the project and send a pull request.
|
45
|
+
|
46
|
+
To get the specs running you should call +bundle+ and then +rake+. See the {spec/README}[https://github.com/ryanb/cancan/blob/master/spec/README.rdoc] for more information.
|
47
|
+
|
48
|
+
|
49
|
+
== Special Thanks
|
50
|
+
|
51
|
+
CanCan was inspired by declarative_authorization[https://github.com/stffn/declarative_authorization/] and aegis[https://github.com/makandra/aegis]. Also many thanks to the CanCan contributors[https://github.com/ryanb/cancan/contributors]. See the CHANGELOG[https://github.com/ryanb/cancan/blob/master/CHANGELOG.rdoc] for the full list.
|
data/Rakefile
ADDED
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'osgb'
|
data/lib/osgb.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'osgb/angle_conversions' # converts degrees to radians and back again
|
2
|
+
require 'osgb/ellipsoid' # standard approximations to the squashed-circle shape of the earth
|
3
|
+
require 'osgb/projection' # the geometrical distortions required by a map projection
|
4
|
+
require 'osgb/helmert' # 3d transformation algorithm for mapping between cartesian and ellipsoidal polar coordinates
|
5
|
+
require 'osgb/gridref' # parse grid references and returns lat/long pairs
|
6
|
+
require 'osgb/string_conversions' # add conversion methods to String
|
7
|
+
require 'osgb/railtie' if defined? Rails # add useful methods to ActiveRecord
|
8
|
+
|
9
|
+
# Define standard ellipsoids
|
10
|
+
|
11
|
+
Osgb::Ellipsoid.new :osgb36, 6377563.396, 6356256.910
|
12
|
+
Osgb::Ellipsoid.new :wgs84, 6378137.000, 6356752.3141
|
13
|
+
Osgb::Ellipsoid.new :ie65, 6377340.189, 6356034.447
|
14
|
+
Osgb::Ellipsoid.new :utm, 6378388.000, 6356911.946
|
15
|
+
|
16
|
+
# Define standard projections
|
17
|
+
|
18
|
+
Osgb::Projection.new :gb, :scale => 0.9996012717, :phi0 => 49, :lambda0 => -2, :e0 => 400000, :n0 => -100000, :ellipsoid => :osgb36
|
19
|
+
Osgb::Projection.new :ie, :scale => 1.000035, :phi0 => 53.5, :lambda0 => -8, :e0 => 250000, :n0 => 250000, :ellipsoid => :ie65
|
20
|
+
Osgb::Projection.new :utm29, :scale => 0.9996, :phi0 => 0, :lambda0 => -9, :e0 => 500000, :n0 => 0, :ellipsoid => :utm
|
21
|
+
Osgb::Projection.new :utm30, :scale => 0.9996, :phi0 => 0, :lambda0 => -3, :e0 => 500000, :n0 => 0, :ellipsoid => :utm
|
22
|
+
Osgb::Projection.new :utm31, :scale => 0.9996, :phi0 => 0, :lambda0 => 3, :e0 => 500000, :n0 => 0, :ellipsoid => :utm
|
23
|
+
|
24
|
+
# the Helmert matrix used to translate to wgs84.
|
25
|
+
|
26
|
+
Osgb::Helmert.new :wgs84, :tx => 446.448, :ty => -125.157, :tz => 542.060, :rx => 0.1502, :ry => 0.2470, :rz => 0.8421, :s => -20.4894
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Osgb
|
2
|
+
class Ellipsoid
|
3
|
+
attr_accessor :name, :a, :b
|
4
|
+
@@instances = {}
|
5
|
+
|
6
|
+
def initialize(name, a, b)
|
7
|
+
@name = name
|
8
|
+
@a = a
|
9
|
+
@b = b
|
10
|
+
@@instances[name] = self
|
11
|
+
end
|
12
|
+
|
13
|
+
def ecc
|
14
|
+
(a**2 - b**2) / (a**2)
|
15
|
+
end
|
16
|
+
|
17
|
+
def nu_for(phi)
|
18
|
+
a / (Math.sqrt(1 - ecc * Math.sin(phi)**2))
|
19
|
+
end
|
20
|
+
|
21
|
+
def precision
|
22
|
+
4 / a
|
23
|
+
end
|
24
|
+
|
25
|
+
def polar_to_cartesian(phi, lambda)
|
26
|
+
h = 0
|
27
|
+
nu = nu_for(phi)
|
28
|
+
x1 = (nu + h) * Math.cos(phi) * Math.cos(lambda)
|
29
|
+
y1 = (nu + h) * Math.cos(phi) * Math.sin(lambda)
|
30
|
+
z1 = ((1 - ecc) * nu + h) * Math.sin(phi)
|
31
|
+
[x1, y1, z1]
|
32
|
+
end
|
33
|
+
|
34
|
+
def cartesian_to_polar(x,y,z)
|
35
|
+
p = Math.sqrt(x**2 + y**2)
|
36
|
+
phi = Math.atan2(z, p*(1-ecc));
|
37
|
+
phip = 2 * Math::PI
|
38
|
+
|
39
|
+
count = 0
|
40
|
+
while (phi-phip).abs > precision do
|
41
|
+
raise RuntimeError "Helmert transformation has not converged. Discrepancy after #{count} cycles is #{phi-phip}" if count >= Osgb::Gridref.iteration_ceiling
|
42
|
+
nu = a / Math.sqrt(1 - ecc * Math.sin(phi)**2)
|
43
|
+
phip = phi
|
44
|
+
phi = Math.atan2(z + ecc * nu * Math.sin(phi), p)
|
45
|
+
count += 1
|
46
|
+
end
|
47
|
+
|
48
|
+
lambda = Math.atan2(y, x)
|
49
|
+
[phi, lambda]
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.[](name)
|
53
|
+
@@instances[name]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/osgb/gridref.rb
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
module Osgb
|
2
|
+
# Implementation derived from the Ordnance Survey guide to coordinate systems in the UK
|
3
|
+
# http://www.ordnancesurvey.co.uk/oswebsite/gps/information/coordinatesystemsinfo/guidecontents/
|
4
|
+
# with help from CPAN module Geography::NationalGrid by and (c) P Kent
|
5
|
+
|
6
|
+
class Gridref
|
7
|
+
OsTiles = {
|
8
|
+
:a => [0,4], :b => [1,4], :c => [2,4], :d => [3,4], :e => [4,4],
|
9
|
+
:f => [0,3], :g => [1,3], :h => [2,3], :j => [3,3], :k => [4,3],
|
10
|
+
:l => [0,2], :m => [1,2], :n => [2,2], :o => [3,2], :p => [4,2],
|
11
|
+
:q => [0,1], :r => [1,1], :s => [2,1], :t => [3,1], :u => [4,1],
|
12
|
+
:v => [0,0], :w => [1,0], :x => [2,0], :y => [3,0], :z => [4,0],
|
13
|
+
}
|
14
|
+
FalseOrigin = {:e => 2, :n => 1}
|
15
|
+
SquareSize = [nil, 10000, 1000, 100, 10, 1] # shorter grid ref = larger square.
|
16
|
+
|
17
|
+
attr_accessor :gridref, :projection, :ellipsoid, :datum, :options, :precision
|
18
|
+
|
19
|
+
@@iteration_ceiling = 1000
|
20
|
+
@@defaults = {
|
21
|
+
:projection => :gb, # mercator projection of input gridref. Can be any projection name: usually :ie or :gb
|
22
|
+
:precision => 6 # decimal places in the output lat/long
|
23
|
+
}
|
24
|
+
|
25
|
+
class << self
|
26
|
+
def iteration_ceiling
|
27
|
+
@@iteration_ceiling
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(string, options={})
|
32
|
+
raise ArgumentError, "invalid grid reference string '#{string}'." unless string.is_gridref?
|
33
|
+
options = @@defaults.merge(options)
|
34
|
+
@gridref = string.upcase
|
35
|
+
@projection = Osgb::Projection[options[:projection]]
|
36
|
+
@precision = options[:precision]
|
37
|
+
@ellipsoid = @projection.ellipsoid
|
38
|
+
@datum = options[:datum]
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
def tile
|
43
|
+
@tile ||= gridref[0,2]
|
44
|
+
end
|
45
|
+
|
46
|
+
def digits
|
47
|
+
@digits ||= gridref[2,10]
|
48
|
+
end
|
49
|
+
|
50
|
+
def resolution
|
51
|
+
digits.length / 2
|
52
|
+
end
|
53
|
+
|
54
|
+
def offsets
|
55
|
+
if tile
|
56
|
+
major = OsTiles[tile[0,1].downcase.to_sym ]
|
57
|
+
minor = OsTiles[tile[1,1].downcase.to_sym]
|
58
|
+
@offset ||= {
|
59
|
+
:e => (500000 * (major[0] - FalseOrigin[:e])) + (100000 * minor[0]),
|
60
|
+
:n => (500000 * (major[1] - FalseOrigin[:n])) + (100000 * minor[1])
|
61
|
+
}
|
62
|
+
else
|
63
|
+
{ :e => 0, :n => 0 }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def easting
|
68
|
+
@east ||= offsets[:e] + digits[0, resolution].to_i * SquareSize[resolution]
|
69
|
+
end
|
70
|
+
|
71
|
+
def northing
|
72
|
+
@north ||= offsets[:n] + digits[resolution, resolution].to_i * SquareSize[resolution]
|
73
|
+
end
|
74
|
+
|
75
|
+
def lat
|
76
|
+
round(coordinates[:lat].to_degrees)
|
77
|
+
end
|
78
|
+
|
79
|
+
def lng
|
80
|
+
round(coordinates[:lng].to_degrees)
|
81
|
+
end
|
82
|
+
|
83
|
+
def round(value)
|
84
|
+
if value.method(:round).arity == 0
|
85
|
+
multiplier = 10**precision
|
86
|
+
(value * multiplier).round.to_f / multiplier
|
87
|
+
else
|
88
|
+
value.round(precision)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def to_s
|
93
|
+
gridref.to_s
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_latlng
|
97
|
+
[lat, lng]
|
98
|
+
end
|
99
|
+
|
100
|
+
def coordinates
|
101
|
+
unless @coordinates
|
102
|
+
# variable names correspond roughly to symbols in the OS algorithm, lowercased:
|
103
|
+
# n0 = northing of true origin
|
104
|
+
# e0 = easting of true origin
|
105
|
+
# f0 = scale factor on central meridian
|
106
|
+
# phi0 = latitude of true origin
|
107
|
+
# lambda0 = longitude of true origin and central meridian.
|
108
|
+
# e2 = eccentricity squared
|
109
|
+
# a = length of polar axis of ellipsoid
|
110
|
+
# b = length of equatorial axis of ellipsoid
|
111
|
+
# ning & eing are the northings and eastings of the supplied gridref
|
112
|
+
# phi and lambda are the discovered latitude and longitude
|
113
|
+
|
114
|
+
ning = northing
|
115
|
+
eing = easting
|
116
|
+
|
117
|
+
n0 = projection.n0
|
118
|
+
e0 = projection.e0
|
119
|
+
phi0 = projection.phi0
|
120
|
+
l0 = projection.lambda0
|
121
|
+
f0 = projection.scale
|
122
|
+
|
123
|
+
a = ellipsoid.a
|
124
|
+
b = ellipsoid.b
|
125
|
+
e2 = ellipsoid.ecc
|
126
|
+
|
127
|
+
# the rest is juste a transliteration of the OS equations
|
128
|
+
|
129
|
+
n = (a - b) / (a + b)
|
130
|
+
m = 0
|
131
|
+
phi = phi0
|
132
|
+
|
133
|
+
# iterate to within acceptable distance of solution
|
134
|
+
|
135
|
+
count = 0
|
136
|
+
while ((ning - n0 - m) >= 0.001) do
|
137
|
+
raise RuntimeError "Demercatorising equation has not converged. Discrepancy after #{count} cycles is #{ning - n0 - m}" if count >= @@iteration_ceiling
|
138
|
+
|
139
|
+
phi = ((ning - n0 - m) / (a * f0)) + phi
|
140
|
+
ma = (1 + n + (1.25 * n**2) + (1.25 * n**3)) * (phi - phi0)
|
141
|
+
mb = ((3 * n) + (3 * n**2) + (2.625 * n**3)) * Math.sin(phi - phi0) * Math.cos(phi + phi0)
|
142
|
+
mc = ((1.875 * n**2) + (1.875 * n**3)) * Math.sin(2 * (phi - phi0)) * Math.cos(2 * (phi + phi0))
|
143
|
+
md = (35/24) * (n**3) * Math.sin(3 * (phi - phi0)) * Math.cos(3 * (phi + phi0))
|
144
|
+
m = b * f0 * (ma - mb + mc - md)
|
145
|
+
count += 1
|
146
|
+
end
|
147
|
+
|
148
|
+
# engage alphabet soup
|
149
|
+
|
150
|
+
nu = a * f0 * ((1-(e2) * ((Math.sin(phi)**2))) ** -0.5)
|
151
|
+
rho = a * f0 * (1-(e2)) * ((1-(e2)*((Math.sin(phi)**2))) ** -1.5)
|
152
|
+
eta2 = (nu/rho - 1)
|
153
|
+
|
154
|
+
# fire
|
155
|
+
|
156
|
+
vii = Math.tan(phi) / (2 * rho * nu)
|
157
|
+
viii = (Math.tan(phi) / (24 * rho * (nu ** 3))) * (5 + (3 * (Math.tan(phi) ** 2)) + eta2 - 9 * eta2 * (Math.tan(phi) ** 2) )
|
158
|
+
ix = (Math.tan(phi) / (720 * rho * (nu ** 5))) * (61 + (90 * (Math.tan(phi) ** 2)) + (45 * (Math.tan(phi) ** 4)) )
|
159
|
+
x = sec(phi) / nu
|
160
|
+
xi = (sec(phi) / (6 * nu ** 3)) * ((nu/rho) + 2 * (Math.tan(phi) ** 2))
|
161
|
+
xii = (sec(phi) / (120 * nu ** 5)) * (5 + (28 * (Math.tan(phi) ** 2)) + (24 * (Math.tan(phi) ** 4)))
|
162
|
+
xiia = (sec(phi) / (5040 * nu ** 7)) * (61 + (662 * (Math.tan(phi) ** 2)) + (1320 * (Math.tan(phi) ** 4)) + (720 * (Math.tan(phi) ** 6)))
|
163
|
+
|
164
|
+
d = eing-e0
|
165
|
+
|
166
|
+
# all of which was just to populate these last two equations:
|
167
|
+
|
168
|
+
phi = phi - vii*(d**2) + viii*(d**4) - ix*(d**6)
|
169
|
+
lambda = l0 + x*d - xi*(d**3) + xii*(d**5) - xiia*(d**7)
|
170
|
+
|
171
|
+
# phi and lambda are lat and long but note that here we are still in radians and osgb36
|
172
|
+
# if a different output datum is required, the helmert transformation remaps the coordinates onto a new globe
|
173
|
+
|
174
|
+
if datum && datum != :osgb36
|
175
|
+
target_ellipsoid = Osgb::Ellipsoid[datum]
|
176
|
+
|
177
|
+
if helmert = Osgb::Helmert[datum]
|
178
|
+
cartesian_coordinates = ellipsoid.polar_to_cartesian(phi, lambda)
|
179
|
+
transformed = helmert.transform(*cartesian_coordinates)
|
180
|
+
phi, lambda = target_ellipsoid.cartesian_to_polar(*transformed)
|
181
|
+
else
|
182
|
+
raise RuntimeError, "Missing ellipsoid or helmert transformation for #{datum}"
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
@coordinates = {:lat => phi, :lng => lambda}
|
187
|
+
end
|
188
|
+
@coordinates
|
189
|
+
end
|
190
|
+
|
191
|
+
private
|
192
|
+
|
193
|
+
def sec(radians)
|
194
|
+
1 / Math.cos(radians)
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Osgb
|
2
|
+
module HasGridref
|
3
|
+
def self.included base #:nodoc:
|
4
|
+
base.class_eval {
|
5
|
+
cattr_accessor :osgb_options
|
6
|
+
@@osgb = {}
|
7
|
+
extend Osgb::ClassMethods
|
8
|
+
}
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def has_gridref name, options = {}
|
14
|
+
include Osgb::InstanceMethods
|
15
|
+
osgb_options = {
|
16
|
+
:lat => 'lat',
|
17
|
+
:lng => 'lng',
|
18
|
+
:gridref => 'gridref',
|
19
|
+
:validation => false,
|
20
|
+
:conversion => true
|
21
|
+
}.merge(options)
|
22
|
+
before_validation :convert_between_gridref_and_latlng if osgb_options[:conversion]
|
23
|
+
validates :must_have_location if osgb_options[:validation]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module InstanceMethods
|
28
|
+
def must_have_location
|
29
|
+
send(cols[:gridref]).is_gridref? || (send("#{cols[:lat]}?") && send("#{cols[:lng]}?"))
|
30
|
+
end
|
31
|
+
|
32
|
+
def convert_between_gridref_and_latlng
|
33
|
+
cols = self.class.osgb_options
|
34
|
+
if columns.include?(cols[:lat], cols[:lng], cols[:gridref])
|
35
|
+
if send("#{cols[:gridref]}_changed?") || !send("#{cols[:lat]}?") || !send("#{cols[:lng]}?")
|
36
|
+
latlng = gridref.coordinates
|
37
|
+
send("#{cols[:lat]}=", latlng[0])
|
38
|
+
send("#{cols[:lng]}=", latlng[1])
|
39
|
+
elsif send("#{cols[:lat]}_changed?") || send("#{cols[:lng]}_changed?") || !send("#{cols[:gridref]}?")
|
40
|
+
send("#{cols[:gridref]}=", Osgb::Gridref.from(send(cols[:lat]), send(cols[:lng])))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/osgb/helmert.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
module Osgb
|
2
|
+
class Helmert
|
3
|
+
attr_accessor :name, :tx, :ty, :tz, :rx, :ry, :rz, :s
|
4
|
+
@@instances = {}
|
5
|
+
|
6
|
+
def initialize(name, attributes)
|
7
|
+
@name = name
|
8
|
+
@tx = attributes[:tx]
|
9
|
+
@ty = attributes[:ty]
|
10
|
+
@tz = attributes[:tz]
|
11
|
+
@rx = (attributes[:rx]/3600).to_radians
|
12
|
+
@ry = (attributes[:ry]/3600).to_radians
|
13
|
+
@rz = (attributes[:rz]/3600).to_radians
|
14
|
+
@s = attributes[:s]
|
15
|
+
@@instances[name] = self
|
16
|
+
end
|
17
|
+
|
18
|
+
def s1
|
19
|
+
s/1e6 + 1
|
20
|
+
end
|
21
|
+
|
22
|
+
def transform(x,y,z)
|
23
|
+
xp = tx + x*s1 - y*rz + z*ry
|
24
|
+
yp = ty + x*rz + y*s1 - z*rx
|
25
|
+
zp = tz - x*ry + y*rx + z*s1
|
26
|
+
[xp, yp, zp]
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.[](name)
|
30
|
+
@@instances[name]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Osgb
|
2
|
+
class Projection
|
3
|
+
attr_accessor :name, :scale, :phi0, :lambda0, :e0, :n0, :ellipsoid
|
4
|
+
@@instances = {}
|
5
|
+
|
6
|
+
def initialize(name, attributes)
|
7
|
+
@name = name
|
8
|
+
@scale = attributes[:scale]
|
9
|
+
@phi0 = attributes[:phi0].to_radians
|
10
|
+
@lambda0 = attributes[:lambda0].to_radians
|
11
|
+
@e0 = attributes[:e0]
|
12
|
+
@n0 = attributes[:n0]
|
13
|
+
@ellipsoid = Osgb::Ellipsoid[attributes[:ellipsoid]]
|
14
|
+
@@instances[name] = self
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.[](name)
|
18
|
+
@@instances[name]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/osgb/railtie.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'osgb'
|
2
|
+
|
3
|
+
module Osgb
|
4
|
+
if defined? Rails::Railtie
|
5
|
+
require 'rails'
|
6
|
+
class Railtie < Rails::Railtie
|
7
|
+
initializer 'osgb.insert_into_active_record' do
|
8
|
+
ActiveSupport.on_load :active_record do
|
9
|
+
Osgb::Railtie.insert
|
10
|
+
end
|
11
|
+
end
|
12
|
+
rake_tasks do
|
13
|
+
load "tasks/osgb.rake"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Railtie
|
19
|
+
def self.insert
|
20
|
+
ActiveRecord::Base.send(:include, Osgb::HasGridref)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class String
|
2
|
+
def is_gridref?
|
3
|
+
!!(self.upcase =~ /^(H(P|T|U|Y|Z)|N(A|B|C|D|F|G|H|J|K|L|M|N|O|R|S|T|U|W|X|Y|Z)|OV|S(C|D|E|G|H|J|K|M|N|O|P|R|S|T|U|W|X|Y|Z)|T(A|F|G|L|M|Q|R|V)){1}\d{4}(NE|NW|SE|SW)?$|((H(P|T|U|Y|Z)|N(A|B|C|D|F|G|H|J|K|L|M|N|O|R|S|T|U|W|X|Y|Z)|OV|S(C|D|E|G|H|J|K|M|N|O|P|R|S|T|U|W|X|Y|Z)|T(A|F|G|L|M|Q|R|V)){1}(\d{4}|\d{6}|\d{8}|\d{10}))$/)
|
4
|
+
end
|
5
|
+
|
6
|
+
def is_latlng?
|
7
|
+
!!coordinates
|
8
|
+
end
|
9
|
+
|
10
|
+
def coordinates
|
11
|
+
if matches = self.match(/(-?\d+\.\d+)[,\s]+(-?\d+\.\d+)/)
|
12
|
+
matches[1,2]
|
13
|
+
else
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_latlng(options={})
|
19
|
+
if is_gridref?
|
20
|
+
Osgb::Gridref.new(self, options).to_latlng
|
21
|
+
else
|
22
|
+
self.coordinates
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_wgs84(options={})
|
27
|
+
if is_gridref?
|
28
|
+
Osgb::Gridref.new(self, options.merge(:datum => :wgs84)).to_latlng
|
29
|
+
else
|
30
|
+
self.coordinates
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def lat(options={})
|
35
|
+
to_latlng({:datum => :wgs84}.merge(options))[0]
|
36
|
+
end
|
37
|
+
|
38
|
+
def lng(options={})
|
39
|
+
to_latlng({:datum => :wgs84}.merge(options))[1]
|
40
|
+
end
|
41
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/spec/string_spec.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe String do
|
4
|
+
it "should know whether or not it looks like a grid reference" do
|
5
|
+
"SD13241324".is_gridref?.should be_true
|
6
|
+
"catapult".is_gridref?.should be_false
|
7
|
+
"54.196915 -3.094684".is_gridref?.should be_false
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should know whether or not it looks like a lat/lng pair" do
|
11
|
+
"SD13241324".is_latlng?.should be_false
|
12
|
+
"catapult".is_latlng?.should be_false
|
13
|
+
"54.196915 -3.094684".is_latlng?.should be_true
|
14
|
+
"54.196915, -3.094684".is_latlng?.should be_true
|
15
|
+
"54.196915|-3.094684".is_latlng?.should be_true
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should turn be able to turn itself into an osgb lat/lng pair" do
|
19
|
+
"SD28687846".to_latlng(:precision => 6).should == [54.196763, -3.093320]
|
20
|
+
"SD28687846".to_latlng(:precision => 2).should == [54.2, -3.09]
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should turn be able to turn itself into a wgs84 lat/lng pair for gps use" do
|
24
|
+
"SD28687846".to_wgs84(:precision => 6).should == [54.196915, -3.094684]
|
25
|
+
"SD28687846".to_wgs84(:precision => 2).should == [54.2, -3.09]
|
26
|
+
end
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: osgb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 0.2.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- William Ross
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-11-24 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 23
|
29
|
+
segments:
|
30
|
+
- 2
|
31
|
+
- 6
|
32
|
+
- 0
|
33
|
+
version: 2.6.0
|
34
|
+
type: :development
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rails
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ~>
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 21
|
45
|
+
segments:
|
46
|
+
- 3
|
47
|
+
- 0
|
48
|
+
- 9
|
49
|
+
version: 3.0.9
|
50
|
+
type: :development
|
51
|
+
version_requirements: *id002
|
52
|
+
description: Supports the use of Ordnance Survey Grid References in place of lat/lng pairs. Includes activerecord plugin.
|
53
|
+
email: will@spanner.org
|
54
|
+
executables: []
|
55
|
+
|
56
|
+
extensions: []
|
57
|
+
|
58
|
+
extra_rdoc_files: []
|
59
|
+
|
60
|
+
files:
|
61
|
+
- lib/osgb/angle_conversions.rb
|
62
|
+
- lib/osgb/ellipsoid.rb
|
63
|
+
- lib/osgb/gridref.rb
|
64
|
+
- lib/osgb/has_gridref.rb
|
65
|
+
- lib/osgb/helmert.rb
|
66
|
+
- lib/osgb/projection.rb
|
67
|
+
- lib/osgb/railtie.rb
|
68
|
+
- lib/osgb/string_conversions.rb
|
69
|
+
- lib/osgb.rb
|
70
|
+
- spec/spec_helper.rb
|
71
|
+
- spec/string_spec.rb
|
72
|
+
- CHANGELOG.rdoc
|
73
|
+
- Gemfile
|
74
|
+
- LICENSE
|
75
|
+
- Rakefile
|
76
|
+
- README.md
|
77
|
+
- init.rb
|
78
|
+
homepage: http://github.com/spanner/osgb
|
79
|
+
licenses: []
|
80
|
+
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options: []
|
83
|
+
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
hash: 3
|
92
|
+
segments:
|
93
|
+
- 0
|
94
|
+
version: "0"
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
hash: 19
|
101
|
+
segments:
|
102
|
+
- 1
|
103
|
+
- 3
|
104
|
+
- 4
|
105
|
+
version: 1.3.4
|
106
|
+
requirements: []
|
107
|
+
|
108
|
+
rubyforge_project:
|
109
|
+
rubygems_version: 1.8.10
|
110
|
+
signing_key:
|
111
|
+
specification_version: 3
|
112
|
+
summary: Grid reference translation to and from lat/long.
|
113
|
+
test_files: []
|
114
|
+
|