Converting Latitude And Longitude Coordinates Between Decimal And Degrees, Minutes, Seconds

I received an email yesterday from a reader who needed help with implementing my blog post, “Calculating The Bearing And Compass Rose Direction Between Two Latitude / Longitude Coordinates In PHP.”

His problem: He has latitude and longitude coordinates in his database tables that are in degrees-minutes-seconds (DMS) format, rather than decimal format. In other words, his coordinates look more like 55° 45′ 06″ N, 37° 37′ 04″ E than 55.751667, 37.617778.

So, my reader needed a way to convert back and forth between the two.

Fortunately, that’s easily done with a couple PHP functions. And since there’s not a lot that comes up in a Google search about how to make those conversions in PHP (lots of standalone converters, some JavaScript code, little PHP), I’ll fill the void here.

I should also note this function can be used for my other PHP latitude / longitude post, “Getting All ZIP Codes In A Given Radius From A Known Point / ZIP Code Via PHP And MySQL.”

Converting From DMS To Decimal

The easier of the two conversions is going from degrees-minutes-seconds to decimal. That’s because just as in an hour of time, each degree has 60 minutes, and each minute has 60 seconds.

So, to convert from DMS to decimal, we simply start with the degrees; then add the minutes value divided by 60, and the seconds value divided by 3600 (60 * 60). If the coordinates are South (latitude) or West (longitude), we need to make the decimal coordinate negative; North and East coordinates are positive.

Thus, a North latitude or East longitude is converted as:

$decimal = $degrees + ($minutes / 60) + ($seconds / 3600);

A South latitude or West longitude is calculated as:

$decimal = ($degrees + ($minutes / 60) + ($seconds / 3600)) * -1;

Here’s a simple function that makes these conversions. It takes, as arguments, the degrees, minutes and seconds of either a latitude or longitude coordinate, as well as its direction (N, S, E or W), and returns a decimal response.

function DMS2Decimal($degrees = 0, $minutes = 0, $seconds = 0, $direction = 'n') {
	//converts DMS coordinates to decimal
	//returns false on bad inputs, decimal on success
	
	//direction must be n, s, e or w, case-insensitive
	$d = strtolower($direction);
	$ok = array('n', 's', 'e', 'w');
	
	//degrees must be integer between 0 and 180
	if(!is_numeric($degrees) || $degrees < 0 || $degrees > 180) {
		$decimal = false;
	}
	//minutes must be integer or float between 0 and 59
	elseif(!is_numeric($minutes) || $minutes < 0 || $minutes > 59) {
		$decimal = false;
	}
	//seconds must be integer or float between 0 and 59
	elseif(!is_numeric($seconds) || $seconds < 0 || $seconds > 59) {
		$decimal = false;
	}
	elseif(!in_array($d, $ok)) {
		$decimal = false;
	}
	else {
		//inputs clean, calculate
		$decimal = $degrees + ($minutes / 60) + ($seconds / 3600);
		
		//reverse for south or west coordinates; north is assumed
		if($d == 's' || $d == 'w') {
			$decimal *= -1;
		}
	}
	
	return $decimal;
}

You can test this function here: http://demo.dougv.com/php_lat_lon_conversion/dms2decimal.php

You call this function as you would any built-in PHP function. For example, assuming you have DMS coordinate values stored in the PHP variables $deg, $min, $sec and $dir, you could assign a value to a decimal equivalent variable, $dec, thus:

$dec = DMS2Decimal($deg, $min, $sec, $dir);

Converting Decimal To DMS

Going from decimal coordinates to degrees-minutes-seconds is a bit more complicated.

  • Set the DMS direction to South (negative latitude), West (negative longitude), North (positive latitude) or East (positive longitude);
  • set the decimal to its absolute (i.e., unsigned) value;
  • set degrees to be the floor of the decimal;
  • subtract degrees from decimal, to get its fractional portion;
  • multiply that by 3600, to get the total number of seconds;
  • divide the seconds by 60, to get the total number of minutes;
  • subtract the total number of minutes, times 60, from seconds; then
  • set seconds value to its floor.

To accomplish this task via a PHP function, we will pass to it six arguments: the decimal coordinate; variables for degrees, minutes, seconds and direction; and a Boolean indicating if this is a latitude or longitude coordinate.

Four of our six arguments — degrees, minutes, seconds and direction — will be passed by reference. What that means to a layman is, the values of those arguments will be set by the function, without the need to specifically set them via an evaluation.

In other words, in PHP, you usually set a variable value via a function like this:

$foo = myfunction($bar);

But we’ll do it this way:

myfunction($bar, $foo);

An example of that in practice follows the function code.

function DecimalToDMS($decimal, &$degrees, &$minutes, &$seconds, &$direction, $type = true) {
	//set default values for variables passed by reference
	$degrees = 0;
	$minutes = 0;
	$seconds = 0;
	$direction = 'X';

	//decimal must be integer or float no larger than 180;
	//type must be Boolean
	if(!is_numeric($decimal) || abs($decimal) > 180 || !is_bool($type)) {
		return false;
	}
	
	//inputs OK, proceed
	//type is latitude when true, longitude when false
	
	//set direction; north assumed
	if($type && $decimal < 0) { 
		$direction = 'S';
	}
	elseif(!$type && $decimal < 0) {
		$direction = 'W';
	}
	elseif(!$type) {
		$direction = 'E';
	}
	else {
		$direction = 'N';
	}
	
	//get absolute value of decimal
	$d = abs($decimal);
	
	//get degrees
	$degrees = floor($d);
	
	//get seconds
	$seconds = ($d - $degrees) * 3600;
	
	//get minutes
	$minutes = floor($seconds / 60);
	
	//reset seconds
	$seconds = floor($seconds - ($minutes * 60));	
}

You can see this in action here: http://demo.dougv.com/php_lat_lon_conversion/index.php

Let’s suppose you have the variables $deg, $min, $sec, $dir, for degrees, minutes, seconds and direction, respectively. You also have a decimal coordinate stored in $dec.

This is how you would invoke the function above to assign values to your DMS variables:

//if your decimal coordinate is a latitude:
DecimalToDMS($dec, $deg, $min, $sec, $dir, true);

//if your decimal coordinate is a longitude:
DecimalToDMS($dec, $deg, $min, $sec, $dir, false);

This code on github: https://github.com/dougvdotcom/php_convert_lat_lon

I distribute code under the GNU GPL. See Copyright & Attribution for details.

All links in this post on delicious: http://delicious.com/dougvdotcom/converting-latitude-and-longitude-coordinates-between-decimal-and-hours-minutes-seconds

Comments

  1. Matthew says

    I am trying to use this, but not in a form, just as function in my wordpress. I can put the code in the functions.php, but without having a form to submit it with I am just not getting it. I think I want two functions, one for lat and one for lng then I want to say

    Right? This is just really not sinking in, any help is apreciated! Just want this decimals to dms using an on page variable not a form…thanks man!

  2. Udo Braavi says

    Hello,
    Thanks for your code. May I use a part of your code? I need to use the core logic on a project. Going to make it return a string though instead of using references in the function parameters.
    Alternatively I think I might return an array and recover the variables with list()

    If I may, though it seems to be that the seconds can be represented as decimal values and do not need to be rounded up. But maybe there are issues with precision of php floats that might force this?

  3. says

    @Udo:

    May I use a part of your code?

    https://www.dougv.com/copyright-attribution/

    If I may, though it seems to be that the seconds can be represented as decimal values and do not need to be rounded up.

    The conversion of a coordinates between DMS and decimals, when the seconds of the DMS coordinate is not expressed as a high-precision decimal, naturally introduces precision in favor of the DMS measurement.

    That’s not a factor of PHP, which uses the same 32-bit and 64-bit integers that other scripting languages use. It’s a factor of the limitations of a DMS coordinate itself.

    In other words, a latitude coordinate of 46.5232912482 is considerably more precise than a DMS of 46° 31′ 23″ N, which you will see if you convert between the two without rounding.

    Therefore, it makes sense to round results in both directions, as the inaccuracy of DMS coordinates without fractional seconds will introduce error in both directions during conversion if you don’t round.

    In other words, converting 46° 31′ 23″ N to a decimal will produce 46.5230555… {ad infinitum}, not 46.5232912482. If we then reconvert that result to DMS, we get 46° 31′ 22″ N. Repeat that process enough, and you won’t be anywhere near the initial coordinate, whether it was decimal or DMS.

    So this conversion doesn’t need high precision. If anything, high precision gives us false confidence in our results, because this conversion is, by nature, imprecise.

  4. Jose Ricardo Borba says

    Dear Doug,
    Your contribution is a way good. I like the way you write. Simple and clear. But your code have a mistake: if the seconds from a coordinate is more than 59 (yes, this happens), your code return false for lat or lng. Please, correct this for other people that learn with your code (my coordinate is “30 0m 14.478s S, 51 6m 59.108s W”). Best regards and keep your good work!

  5. says

    @Jose: Thanks for your note. You are correct; one should not pass in fractional seconds coordinates.

    You can fix this one of two ways. The preferred way would be to floor() your seconds argument before passing it in, since the accuracy of fractional seconds coordinates is an illusion. (That is, it seems that a fractional seconds coordinate is more precise, and technically it is, but in truth it’s probably not any more accurate than had you simply passed in the next integer.)

    Alternatively, you can change the sanitation routine to see if the minutes and seconds are less than 60.

    //seconds must be integer or float between 0 and 59
    elseif(!is_numeric($seconds) || $seconds < 0 || $seconds >= 60) {
         $decimal = false;
    }
    

    Again, I would floor() my seconds argument before passing it in, because while it might seem like a decimal seconds argument is more precise, and technically it is, the actual precision you are getting from a fractional seconds coordinate is pretty much useless.

Leave a Reply

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