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

6 Comments

  1. 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

  2. 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

  3. Trev

    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

  4. 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.

  5. Andrew and Trev are legends hands down.

  6. Mike

    Thanks dude saved me precious time and brain cells

Leave Your Comment

Your email will not be published or shared. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>