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
All links in this post on delicious: http://delicious.com/dougvdotcom/converting-latitude-and-longitude-coordinates-between-decimal-and-hours-minutes-seconds
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!
@Matthew: I get the gist of what you want. If you would like me to assist you with a specific implementation of my code, I do that for hire. See http://dougv.com/need-more-help for details.
Hello, thanks for this sample. I prepared a javascript version sample of this Degrees to Decimal Converter for Google Maps. And here: http://jsbin.com/abolil/2/
And Revision #3
http://jsbin.com/abolil/3/edit
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?
@Udo:
http://dougv.com/copyright-attribution/
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.
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!
@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.
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.
Thanx guys, it work’s for me (y)
Hi Doug.
I have a website where I show the co-ordinates of an airfield in lat / long and one of my users asked if I could also show the Degree / Minutes / Seconds so that he can program his garmin sat nav which takes that format. You can see an example of one of the cards here… http://www.airfieldcards.com/wordpress/airfield_card/rufforth/
I looked at your code, and it looks like it will do the job for me, but it is at a level above my understanding.
My site is powered by wordpress, and the lat / long information is held in advanced custom fields, and I do know how to “extract that data” into a $variable, I simply want to take that information in the variable and show it in the other format, maybe below what I already have (another box in the table maybe?)
Please help, I have built this site, over many years, for the benefit of other pilots, with no remuneration whatsoever.
Hi @Vincent,
The good news is you can do this via JavaScript, not just PHP. All you need is some way to know where to capture from the rendered page the latitude and and longitude you want to convert.
You have decimal coordinates on your page in a format something like this:
Then you can use JQuery’s document.ready to render the translated address.
First, add to your page some elements that will take on the translated coordinates:
Then build some JavaScript to do the translation. We will need a JavaScript interpretation of the decimal-to-degrees conversion from this blog post; and a little JQuery to capture the values we want to convert and update some HTML elements with the converted values. (JQuery runs in safe mode in WordPress.)
You can just add this code directly to the page template where you want to use it, but usually JavaScript is added to a WordPress site using the wp_enqueue_script function via a plugin or the functions.php file. I have a tutorial on creating WordPress plugins.