Ordnance Survey Easting/Northing to Lat/Long
Recently I had the need to convert Ordnance Survey East/North coordinates to Latitude and Longitude. By using the resources available on the OS site I wrote the following piece of PHP code:
<?php
// Converts OS Easting/Northing to Lat/Long
// by bramp
function Marc($bf0, $n, $PHI0, $PHI) {
/*
Compute meridional arc.
Input: -
ellipsoid semi major axis multiplied by central meridian scale factor (bf0) in meters;
n (computed from a, b and f0);
lat of false origin (PHI0)
initial or final latitude of point (PHI) IN RADIANS.
*/
$n2 = pow($n, 2);
$n3 = pow($n, 3);
$ans = ((1 + $n + ((5 / 4) * ($n2)) + ((5 / 4) * $n3)) * ($PHI - $PHI0));
$ans -= (((3 * $n) + (3 * $n2) + ((21 / 8 ) * $n3)) * (sin($PHI - $PHI0)) * (cos($PHI + $PHI0)));
$ans += ((((15 / 8 ) * $n2) + ((15 / 8 ) * $n3)) * (sin(2 * ($PHI - $PHI0))) * (cos(2 * ($PHI + $PHI0))));
$ans -= (((35 / 24) * $n3) * (sin(3 * ($PHI - $PHI0))) * (cos(3 * ($PHI + $PHI0))));
return $bf0 * $ans;
}
function initialLat($North, $n0, $afo, $PHI0, $n, $bfo) {
/*
Compute initial value for Latitude (PHI) IN RADIANS.
Input: - _
northing of point (North) and northing of false origin (n0) in meters;
semi major axis multiplied by central meridian scale factor (af0) in meters;
latitude of false origin (PHI0) IN RADIANS;
n (computed from a, b and f0)
ellipsoid semi major axis multiplied by central meridian scale factor (bf0) in meters.
*/
//First PHI value (PHI1)
$PHI1 = (($North - $n0) / $afo) + $PHI0;
//Calculate M
$M = Marc($bfo, $n, $PHI0, $PHI1);
//Calculate new PHI value (PHI2)
$PHI2 = (($North - $n0 - $M) / $afo) + $PHI1;
//Iterate to get final value for InitialLat
while ( abs($North - $n0 - $M) > 0.00001 ) {
$PHI2 = (($North - $n0 - $M) / $afo) + $PHI1;
$M = Marc($bfo, $n, $PHI0, $PHI2);
$PHI1 = $PHI2;
}
return $PHI2;
}
function E_N_to_Lat_Long($East, $North) {
$a = 6377563.396; // Semi-major axis, a
$b = 6356256.910; //Semi-minor axis, b
$e0 = 400000.000; //True origin Easting, E0
$n0 = -100000.000; //True origin Northing, N0
$f0 = 0.999601271700; //Central Meridan Scale, F0
$PHI0 = 49.0; // True origin latitude, j0
$LAM0 = -2.0; // True origin longitude, l0
//Convert angle measures to radians
$RadPHI0 = $PHI0 * (M_PI / 180);
$RadLAM0 = $LAM0 * (M_PI / 180);
//Compute af0, bf0, e squared (e2), n and Et
$af0 = $a * $f0;
$bf0 = $b * $f0;
$e2 = ($af0*$af0 - $bf0*$bf0 ) / ($af0*$af0);
$n = ($af0 - $bf0) / ($af0 + $bf0);
$Et = $East - $e0;
//Compute initial value for latitude (PHI) in radians
$PHId = InitialLat($North, $n0, $af0, $RadPHI0, $n, $bf0);
$sinPHId2 = pow(sin($PHId), 2);
$cosPHId = pow(cos($PHId), -1);
$tanPHId = tan($PHId);
$tanPHId2 = pow($tanPHId, 2);
$tanPHId4 = pow($tanPHId, 4);
$tanPHId6 = pow($tanPHId, 6);
//Compute nu, rho and eta2 using value for PHId
$nu = $af0 / (sqrt(1 - ($e2 * $sinPHId2)));
$rho = ($nu * (1 - $e2)) / (1 - $e2 * $sinPHId2);
$eta2 = ($nu / $rho) - 1;
//Compute Longitude
$X = $cosPHId / $nu;
$XI = $cosPHId / ( 6 * pow($nu, 3)) * (($nu / $rho) + 2 * $tanPHId2);
$XII = $cosPHId / ( 120 * pow($nu, 5)) * (5 + 28 * $tanPHId2 + 24 * $tanPHId4);
$XIIA = $cosPHId / (5040 * pow($nu, 7)) * (61 + 662 * $tanPHId2 + 1320 * $tanPHId4 + 720 * $tanPHId6);
$VII = $tanPHId / ( 2 * $rho * $nu);
$VIII = $tanPHId / ( 24 * $rho * pow($nu, 3)) * ( 5 + 3 * $tanPHId2 + $eta2 - 9 * $eta2 * $tanPHId2 );
$IX = $tanPHId / (720 * $rho * pow($nu, 5)) * (61 + 90 * $tanPHId2 + 45 * $tanPHId4 );
$long = (180 / M_PI) * ($RadLAM0 + ($Et * $X) - pow($Et,3) * $XI + pow($Et,5) * $XII - pow($Et,7) * $XIIA);
$lat = (180 / M_PI) * ($PHId - (pow($Et,2) * $VII) + (pow($Et, 4) * $VIII) - (pow($Et, 6) * $IX));
return array($lat, $long);
}
?>
it is used in the following way:
$e = 349000; $n = 461000; print_r( E_N_to_Lat_Long( $e, $n) );
If you find a need to use this please place a link back to my site. thanks
Hi Andrew
Thanks for this, I added a function at the front of it to read from the command line and then called it from a Perl batch program. This sounds perverse but effective:
$en = parseArgs($argv) ;
$latlon = E_N_to_Lat_Long( $en['e'], $en['n']);
echo "$latlon[0] $latlon[1]" ;
function parseArgs($argv){
array_shift($argv);
$out = array();
foreach ($argv as $arg){
if (substr($arg,0,2) == ‘–’){
$eqPos = strpos($arg,’=');
if ($eqPos === false){
$key = substr($arg,2);
$out[$key] = isset($out[$key]) ? $out[$key] : true;
} else {
$key = substr($arg,2,$eqPos-2);
$out[$key] = substr($arg,$eqPos+1);
}
} else if (substr($arg,0,1) == ‘-’){
if (substr($arg,2,1) == ‘=’){
$key = substr($arg,1,1);
$out[$key] = substr($arg,3);
} else {
$chars = str_split(substr($arg,1));
foreach ($chars as $char){
$key = $char;
$out[$key] = isset($out[$key]) ? $out[$key] : true;
}
}
} else {
$out[] = $arg;
}
}
return $out;
}
and in the Perl script:
my ($lon,$lat) = split(/,/,`php entolatlon.php –e=$array[10] –n=$array[11]`) ;
the array entries are the easting and the northing from OS Open Data postcode list…
Hope that’s useful as well..
Hugh
Hi Andrew,
I’ve just found your post linking through to this entry. Thank you for sharing this bit of code. Very useful, and saves me making my head hurt trying to convert formula’s in to workable code!
Mat
If you want to use Google Maps to plot the data, then you might need to add a little extra code to make it plot absolutely spot on.
This is because the OS use a co-ordinate system called Airy1830/OSGB36 and Google Maps use one called WGS84 (150 years newer) – all to do with the way the earth isn’t totally round apparently but baffles me.
The easiest way I found was after you take the result of the above, get yourself a script called PHPcoord (link at end), include() that and then pass the co-ordinates through it thus:
# Original code
$easting = 352383;
$northing = 529509;
$output = E_N_to_Lat_Long($easting, $northing);
# New code based on PHPcoord
include(‘PHPcoord.php’)
$googleoutput = new LatLng($output[0], $output[1]);
echo "Latitude: " . $googleoutput->lat;
echo "
";
echo "Longitude: " . $googleoutput->lng;
It’s not amazingly out anyway, but it depends where you are in the UK as East Anglia can be a lot more inaccurate than say Wales.
PHPcoord is available from:
http://www.jstott.me.uk/phpcoord/
A big thank you to whoever wrote the code here as it saved soooo much hassle trying to work out what to do with the OS postcodes to co-ordinates data from:
https://www.ordnancesurvey.co.uk/opendatadownload/products.html
Trev
Correction in above code – missed a line:
include(‘PHPcoord.php’)
$googleoutput = new LatLng($output[0], $output[1]);
$googleoutput->OSGB36ToWGS84();
…as that’s the critical bit which makes it convert.
Andrew and Trev are legends hands down.
Thanks dude saved me precious time and brain cells