osgb 0.2.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.
- 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
|
+
|