Ruby code for converting to UK Ordnance Survey coordinate systems from WGS84?

Ruby code for converting to UK Ordnance Survey coordinate systems from WGS84? It’s one of those things I’d assumed would be a five minute search -> cut-n-paste job. But no. Well now you can cut & paste from here.

Using Proj4 and the proj4rb bindings

You can do all manner of coordinate projecting using Proj, and this is available within ruby code via proj4rb. To get set-up, install proj. On ubuntu you can do:

sudo apt-get install proj

download the proj4rb gem file (the ruby bindings), and install the gem:

sudo gem install proj4rb-0.3.1.gem

To use in a plain ruby script:

require 'rubygems'

require 'proj4'

To use in a rails app, add a line to your environment.rb file

config.gem "proj4rb", :lib => "proj4"

Probably all obvious to any pro-rubyist, but I got stuck at various stages of that. Anyway once you’re set up…

This code will do a conversion from WGS84 lat/lon to eastings and northings:

lon = -0.10322
lat = 51.52237

srcPoint = Proj4::Point.new(Math::PI * lon.to_f / 180,
                            Math::PI * lat.to_f / 180)

srcPrj  = Proj4::Projection.new("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs")
destPrj = Proj4::Projection.new("+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 "+
                                      "+ellps=airy +datum=OSGB36 +units=m +no_defs")

point = srcPrj.transform(destPrj, srcPoint)

puts "http://www.openstreetmap.org/?mlat=" + lat.to_s + "&mlon=" + lon.to_s + "&zoom=16"
puts "Converts to:";
puts "http://streetmap.co.uk/grid/" + point.x.round.to_s + "_" + point.y.round.to_s + "_106"

Originally I was trying just the ‘destPrj’ string, and calling the ‘forward’ method, but this seemed to be skipping the datum conversion, resulting in everything being 100m out. It seemed to be necessary to use the ‘srcPrj’ string and the ‘transform’ method, to get the datum conversion happening.

You can look up the mystical proj strings on spatialreference.org

(UPDATE)  If you’re interested in converting in the other direction, using the proj4 gem, see Peter Hicks blog: Converting OSGB36 (Eastings/Northings) to WGS84 (Longitude/Latitude) in Ruby

A pure ruby implementation

Like many naive developers who have gone before me, I started out thinking that these coordinate transformations should be acheivable with a few simple formulea, and was expecting to be able to copy & paste a block of ruby code from somewhere to do it without installing any gems.

I didn’t find any such code, but frustration with proj problems (before I got that working) led me to create a pure ruby solution, by porting this pure javascript code by Chris Veness into ruby. So there we go:

osgbconvert.rb – Conversion to Ordnance Survey coordinates in pure ruby

You can run it to see an example conversion with the output:

wgs84 lat:51.52237, wgs84 lon:-0.10322
http://www.openstreetmap.org/?mlat=51.52237&mlon=-0.10322&zoom=16
Converts to:osgb36 lat:51.5218609279112, osgb36 lon:-0.101610123646581

Converts to:easting:531691, northing:182090  As a grid ref:TQ31698209

http://streetmap.co.uk/grid/531691_182090_106

This includes ported code for Convert co-ordinates between WGS-84 and OSGB36 and ported code in one direction for OS Latitude/Longitude to OS National Grid (coordianate systems explained below) Also I haven’t faithfully stuck to what Chris’ functions return.

There is a bunch of maths, but it was quite a straightforward syntax conversion, and it comes in at about 150 lines of code. Somebody should do this for other languages. I’m sure other languages offer more beefy libraries equivalent to proj4. The main benefit of this is for quick copy & paste coding.

Mind you, if you are copying and pasting this, be sure to worry about the fact that it is LGPL licensed by Chris Veness [Update: It’s now shown under the more permissive CC-BY license on his website. Take your pick!]  For my part, you can credit me too if you like, but I don’t care.

I also came across another implementation, license unknown.

About Ordnance Survery coordinate systems

Chris Veness’s page is well linked because the code comes with a comprehensive description of what’s going on. However I misunderstood a few things even after reading it several times. Here’s my noddy guide to Ordnance Survey coordinate systems:

WGS84 is what I would call the “normal” latitude and longitude coordinate system. These days web developers passing around latitude and longitude values are normally using this. It’s what OpenStreetMap works with if you want to point to a location with a URL such as : http://www.openstreetmap.org/?mlat=51.52237&mlon=-0.10322&zoom=16 (the placr.co.uk office).

Ordnance Survey OSGB36 – Involves coordinates which are also called “latitude and longitude”. The numbers look very similar, and in fact if you get them confused and use one in place of the other, you’re generally only ~100 metres off. At this point I could mention “datums” and “ellipsoids”, but who cares? How do we fix it? With the above ruby code, use the function convertWGS84toOSGB36 (or convertWGS84toOSGB36 in the other direction)

Couple of formatting things to note: Both WGS84 and OSGB36 latitude & longitude values can be expressed in degrees and minutes rather than decimal, and with N S E W letters on the end. For example 51°31′20.53″N 000°06′11.60″W If longitude has a W on the end it would more “normally” be a negative number. The letters on the end do not make this an “Eastings/Northings” coordinate. That’s a different system…

Ordnance Survey National Grid Reference Eastings and Northings look totally different. For the placr office the easting is 531691. The northing is 182089. On these numbers the first digit is special. It’s identifying which “grid” square we are in, counting from zero. Everything in London is in grid square 5,1

National Grid References with letters are the older more human friendly version of that. So “TQ 3169 8208” is the equivalent. The 5 and 1 are removed, and instead we represent the grid square separately at the front. Everything in London is in grid square “TQ”. Just to munge things together a bit more, we might drop one digit of precision and drop the spaces to give “TQ316820”.

Don’t ask me how Landranger sheet numbers fit in. I’m going back to my WGS84 thankyou very much.