17

Im trying to write a function that takes my decimal degrees (lat or long) and converts them to DMS degrees minutes seconds. I know I am meant to times the decimal point number by 60 then it's decimal again. But am a noob. Would I split the number?

function ConvertDDToDMS(DD) {
    eg. DD =-42.4
    D= 42;
    M= 4*60;
    S= .M * 60;
    var DMS =

    return DMS //append Direction (N, S, E, W);
}

Am I on the right track?

Gabe
  • 84,912
  • 12
  • 139
  • 238
Christopher
  • 12,057
  • 9
  • 31
  • 37

12 Answers12

38
function ConvertDDToDMS(D, lng) {
  return {
    dir: D < 0 ? (lng ? "W" : "S") : lng ? "E" : "N",
    deg: 0 | (D < 0 ? (D = -D) : D),
    min: 0 | (((D += 1e-9) % 1) * 60),
    sec: (0 | (((D * 60) % 1) * 6000)) / 100,
  };
}

The above gives you an object {deg, min, sec, dir} with sec truncated to two digits (e.g. 3.14) and dir being one of N, E, S, W depending on whether you set the lng (longitude) parameter to true. e.g.:

ConvertDDToDMS(-18.213, true) == {
   deg : 18,
   min : 12,
   sec : 46.79,
   dir : 'W'
}

Or if you just want the basic string:

function ConvertDDToDMS(D){
  return [0|D, 'd ', 0|(D=(D<0?-D:D)+1e-4)%1*60, "' ", 0|D*60%1*60, '"'].join('');
}

ConvertDDToDMS(-18.213) == `-18d 12' 47"`

[edit June 2019] -- fixing an 8 year old bug that would sometimes cause the result to be 1 minute off due to floating point math when converting an exact minute, e.g. ConvertDDToDMS(4 + 20/60).

[edit Dec 2021] -- Whoops. Fix #2. Went back to the original code and added 1e-9 to the value which a) bumps any slightly low floating point errors to the next highest number and b) is less than .01 sec, so has no effect on the output. Added 1e-4 to the "string" version which is the same fix, but also rounds seconds (it's close to 1/2 sec).

  • You could possibly pass an array of ['N', 'S'] or ['E','W'] or ['','-'] to make it a bit easier/more generic instead of a true/false??? – Nigel Johnson Dec 26 '16 at 07:15
  • @NigelJohnson - You shouldn't need to pass those. This is pretty old code, but I'd just do: `ConvertLatToDMS = (d) => ConvertDDToDMS(d, false)` and `ConvertLngToDMS = (d) => ConvertDDToDMS(d, true)` –  Dec 26 '16 at 07:56
  • 1
    @MarkKahn, your edit from 9 June 2019 is incorrect. A negative input value will result in negative `min` and `sec` as well as an incorrect `sec` value. – Nick Oct 09 '19 at 14:06
  • @MarkKahn, Nick is correct in pointing out this error, happened for me too. – gromain Sep 17 '20 at 22:03
  • Should be fixed now –  Dec 14 '21 at 08:39
13

It's not clear how you need the output. Here's a version that returns all 3 values as a string:

function ConvertDDToDMS(dd)
{
    var deg = dd | 0; // truncate dd to get degrees
    var frac = Math.abs(dd - deg); // get fractional part
    var min = (frac * 60) | 0; // multiply fraction by 60 and truncate
    var sec = frac * 3600 - min * 60;
    return deg + "d " + min + "' " + sec + "\"";
}
Gabe
  • 84,912
  • 12
  • 139
  • 238
  • Minor note, this will return 60 seconds in edge cases such as 46.99992 (which should be 47 0' 0"). Adding a check similar to [Aleadam's answer](http://stackoverflow.com/a/5786627/877472) (accounting for negatives and lat/lon boundaries) solves this. – Paul Richter Oct 01 '15 at 19:49
7

Update: I remove the part that did not make any sense (thanks cwolves!).

Here you have yet another implementation. It won't be as short nor efficient as the previous ones, but hopefully much easier to understand.

To get it right, first you need to understand how the calculations are done and only then attempt to implement them. For that, pseudocode is a great option, since you write down the steps in plain English or a simplified syntax that is easy to understand, and then translate it onto the programming language of choice.

I hope it's useful!

/* This is the pseudocode you need to follow:
 * It's a modified version from 
 * http://en.wikipedia.org/wiki/Geographic_coordinate_conversion#Conversion_from_Decimal_Degree_to_DMS

function deg_to_dms ( degfloat )
   Compute degrees, minutes and seconds:
   deg ← integerpart ( degfloat )
   minfloat ← 60 * ( degfloat - deg )
   min ← integerpart ( minfloat )
   secfloat ← 60 * ( minfloat - min )
   Round seconds to desired accuracy:
   secfloat ← round( secfloat, digits )
   After rounding, the seconds might become 60. These two
   if-tests are not necessary if no rounding is done.
   if secfloat = 60
      min ← min + 1
      secfloat ← 0
   end if
   if min = 60
      deg ← deg + 1
      min ← 0
   end if
   Return output:
   return ( deg, min, secfloat )
end function
*/

function deg_to_dms (deg) {
   var d = Math.floor (deg);
   var minfloat = (deg-d)*60;
   var m = Math.floor(minfloat);
   var secfloat = (minfloat-m)*60;
   var s = Math.round(secfloat);
   // After rounding, the seconds might become 60. These two
   // if-tests are not necessary if no rounding is done.
   if (s==60) {
     m++;
     s=0;
   }
   if (m==60) {
     d++;
     m=0;
   }
   return ("" + d + ":" + m + ":" + s);
}
Aleadam
  • 40,203
  • 9
  • 86
  • 108
  • Negatives are most certainly allowed... and your direction is completely off. You can't ascertain longitude vs latitude based on the number. You seem to be confusing polar co-ordinates with DMS (they are completely different...). In other words, you can't tell the difference between `N` & `E` with just a number. –  Apr 26 '11 at 06:25
  • @cwolves Thanks for the comment. Reading a little more it didn't make any sense... I should read my own suggestions some time: "understand how the calculations are done and only then attempt to implement them" :/ – Aleadam Apr 26 '11 at 13:51
  • there is an error: what if due rounding and your d++ degrees exceed the 180 degrees longitude limit?, what if it exceed the 90 degrees latitude limit? – AlexWien May 08 '13 at 10:12
  • 3
    The Javascript implementation does not work for negative values of deg – Jaco Briers Jan 26 '15 at 11:53
  • 1
    It's not just that it does not work for negative values, it gives the wrong value. – psiphi75 Jun 16 '16 at 01:47
  • Easy to overcome a negative value. store positive or negative value in another variable (h, for hemisphere) and then run Math.abs(deg) to normalize it to positive. Then in the return value, include the h variable. This way your receiver can look at the h value and determine if it's N/S for lat or E/W for lon. This is a good function, that can be great with just a small tweak. – Michael Sep 19 '17 at 03:30
2

Try this working perfect!!!

function truncate(n) {
    return n > 0 ? Math.floor(n) : Math.ceil(n);
}

function getDMS(dd, longOrLat) {
    let hemisphere = /^[WE]|(?:lon)/i.test(longOrLat)
    ? dd < 0
      ? "W"
      : "E"
    : dd < 0
      ? "S"
      : "N";

    const absDD = Math.abs(dd);
    const degrees = truncate(absDD);
    const minutes = truncate((absDD - degrees) * 60);
    const seconds = ((absDD - degrees - minutes / 60) * Math.pow(60, 2)).toFixed(2);

    let dmsArray = [degrees, minutes, seconds, hemisphere];
    return `${dmsArray[0]}°${dmsArray[1]}'${dmsArray[2]}" ${dmsArray[3]}`;
}

var lat = 13.041107;
var lon = 80.233232;

var latDMS = getDMS(lat, 'lat'); 
var lonDMS = getDMS(lon, 'long');
console.log('latDMS: '+ latDMS);
console.log('lonDMS: '+ lonDMS);

Output:
latDMS: 13°2'27.99" N
lonDMS: 80°13'59.64" E 
siva.picky
  • 499
  • 5
  • 17
2

A solution with the option for specifying the decimal places in output seconds and correction of any edge cases due to rounding seconds and minutes.

// @ input {deg}     Numeric; degrees number to convert
// @ input {dplaces} Decimal places to use for output seconds
//                   Default 0 places
// @ return {DMS} string degrees (°) minutes (') seconds (")
//
function degToDMS (deg, dplaces=0) {
  var d = Math.floor (deg);          // make degrees
  var m = Math.floor((deg-d)*60);    // make minutes
  var s = Math.round(((deg-d)*60-m)*60*Math.pow(10,dplaces))/Math.pow(10,dplaces); // Make sec rounded
  s == 60 && (m++, s=0 );            // if seconds rounds to 60 then increment minutes, reset seconds
  m == 60 && (d++, m=0 );            // if minutes rounds to 60 then increment degress, reset minutes
  return (d + "° " + m + "' " + s+'"');   // create output DMS string
}

// ----- tests ------
console.log(degToDMS(55.23456));         // 55° 14' 4"
console.log(degToDMS(55.23456   ,3));    // 55° 14' 4.416"
console.log(degToDMS(4 + 20/60  ,2));    // 4° 20' 0"
console.log(degToDMS(89.64789   ,2));    // 89° 38' 52.4"
console.log(degToDMS(-23.1234567,3));    // -24° 52' 35.556"
Mohsen Alyafei
  • 4,765
  • 3
  • 30
  • 42
2

This one works %100 in TypeScript:

    ConvertDDToDMS(deg: number, lng: boolean): string {

    var d = parseInt(deg.toString());
    var minfloat = Math.abs((deg - d) * 60);
    var m = Math.floor(minfloat);
    var secfloat = (minfloat - m) * 60;
    var s = Math.round((secfloat + Number.EPSILON) * 100) / 100
    d = Math.abs(d);

    if (s == 60) {
      m++;
      s = 0;
    }
    if (m == 60) {
      d++;
      m = 0;
    }

    let dms = {
      dir: deg < 0 ? lng ? 'W' : 'S' : lng ? 'E' : 'N',
      deg: d,
      min: m,
      sec: s
    };
    return `${dms.deg}\u00B0 ${dms.min}' ${dms.sec}" ${dms.dir}`
  }
RezaNikfal
  • 927
  • 11
  • 22
1
private static DecimalFormat DecimalFormat = new DecimalFormat(".##");
public static void main(String[] args){
    double decimal_degrees = 22.4229541515;

    System.out.println(getDMS(decimal_degrees));
}
public static String getDMS(double decimal_degrees) {
    double degree =  Math.floor(decimal_degrees);
    double minutes = ((decimal_degrees - Math.floor(decimal_degrees)) * 60.0); 
    double seconds = (minutes - Math.floor(minutes)) * 60.0;
    return ((int)degree)+":"+((int)minutes)+":"+decimalFormat.format(seconds);

}

INPUT : 22.4229541515 OUTPUT: 22:25:22.63

0

Based on above answer, i've written them into javascript and php style.

JS-

function convertDDToDMS(deg, lng){
    var d = parseInt(deg);
    var minfloat  = Math.abs((deg-d) * 60); 
    var m = Math.floor(minfloat);
    var secfloat = (minfloat-m)*60;
    var s = Math.round(secfloat); 
    d = Math.abs(d);

    if (s==60) {
        m++;
        s=0;
    }
    if (m==60) {
        d++;
        m=0;
    }

    return {
        dir : deg<0?lng?'W':'S':lng?'E':'N',
        deg : d,
        min : m,
        sec : s
    };
}

PHP-

function convertDDtoDMS($deg, $lng){
    $dd = intval($deg);
    $minfloat = abs(($deg - $dd) * 60);
    $mm = floor($minfloat);
    $secfloat = ($minfloat - $mm) * 60;
    $ss = round($secfloat);
    $dd = abs($dd);

    if($ss == 60){
        $mm++;
        $ss = 0;
    }

    if($mm == 60){
        $dd++;
        $mm = 0;
    }

    $dd = array(
        'dir' => $deg < 0 ? ($lng ? 'W' : 'S') : ($lng ? 'E' : 'N'),
        'deg' => abs($dd),
        'min' => $mm,
        'sec' => $ss,
    );

    return $dd;
}
Ralph
  • 51
  • 2
0

couldnt get the script above working, after some time came up with this; just give the dms to the script

function ConvertDMSToDEG(dms) {   
    var dms_Array = dms.split(/[^\d\w\.]+/); 
    var degrees = dms_Array[0];
    var minutes = dms_Array[1];
    var seconds = dms_Array[2];
    var direction = dms_Array[3];

    var deg = (Number(degrees) + Number(minutes)/60 + Number(seconds)/3600).toFixed(6);

    if (direction == "S" || direction == "W") {
        deg = deg * -1;
    } // Don't do anything for N or E
    return deg;
}

and visa versa just give the degrees to the script, and true of false for lat (latitude)

function ConvertDEGToDMS(deg, lat) {
    var absolute = Math.abs(deg);

    var degrees = Math.floor(absolute);
    var minutesNotTruncated = (absolute - degrees) * 60;
    var minutes = Math.floor(minutesNotTruncated);
    var seconds = ((minutesNotTruncated - minutes) * 60).toFixed(2);

    if (lat) {
        var direction = deg >= 0 ? "N" : "S";
    } else {
        var direction = deg >= 0 ? "E" : "W";
    }

    return degrees + "°" + minutes + "'" + seconds + "\"" + direction;
}

hope this helps people..

0

I'm surprised all solutions are using some additional logic to handle the "rounds to 60" cases (if they're aware of it at all), but nobody thought of doing it the other way round, starting with (rounded) seconds and then using mod and int-div and not have to worry about all that:

function coordToStr(coord)
{   
    let seconds = Math.round(Math.abs(coord) * 3600)
    let sec = Math.floor(seconds % 60)
    let minutes = Math.floor(seconds / 60)
    let min = minutes % 60
    let deg = Math.floor(minutes / 60)
    return deg + "°" + ((min < 10) ? "0" : "") + min + "'" + ((sec < 10) ? "0" : "") + sec
}

Sorry, this is without the N/S, E/W part, would need some additional method calling it.

If you want second-fractions, you could use this:

function coordToStrWithDecimals(coord)
{   
    let centiSecs = Math.round(Math.abs(coord) * 360000)
    let frac = Math.floor(centiSecs % 100)
    let seconds = Math.floor(centiSecs / 100)
    let sec = Math.floor(seconds % 60)
    let minutes = Math.floor(seconds / 60)
    let min = minutes % 60
    let deg = Math.floor(minutes / 60)
    return deg + "°" + ((min < 10) ? "0" : "") + min + "'" + ((sec < 10) ? "0" : "") + sec + "." + ((frac < 10) ? "0" : "") + frac + '"'
}
slarti76
  • 1
  • 1
0

Good answers here but to get the DMS in the standard format.

function padZero(num, targetLength) {
  return String(num).padStart(targetLength, "0");
}

function ddToDms(deg, latOrlon) {
  var absolute = Math.abs(deg);
  var degrees = Math.floor(absolute);
  var minNt = (absolute - degrees) * 60;
  var minutes = Math.floor(minNt);
  var seconds = ((minNt - minutes) * 60).toFixed(2);
  var secs = Math.floor(seconds);

  // Get cardinal direction
  if (latOrlon == "lat") {
    var direction = deg >= 0 ? "N" : "S";
  } else if (latOrlon == "lon") {
    var direction = deg >= 0 ? "E" : "W";
  }

  // Ensure 60 minutes add 1 to degree and 60 seconds add 1 to minutes
  if (seconds == 60) {
    minutes++;
    seconds = 0;
  }
  if (minutes == 60) {
    degrees++;
    minutes = 0;
  }

  // Pad with zero
  if (
    (degrees < 10 && latOrlon == "lat") ||
    (degrees > 10 && degrees < 100 && latOrlon == "lon")
  ) {
    var degrees = padZero(degrees, String(degrees).length + 1);
  } else if (degrees < 10 && latOrlon == "lon") {
    var degrees = padZero(degrees, String(degrees).length + 2);
  }
  if (minutes < 10) {
    var minutes = padZero(minutes, String(minutes).length + 1);
  }
  if (secs < 10) {
    var seconds = padZero(seconds, String(seconds).length + 1);
  }

  // Validate lat and lon
  if (deg > 90 && latOrlon == "lat") {
    alert("LATITUDE CANNOT BE MORE THAN 90");
  } else if (deg > 180 && latOrlon == "lon") {
    alert("LONGITUDE CANNOT BE MORE THAN 180");
  } else {
    return degrees + "°" + minutes + "'" + seconds + '"' + direction;
  }
}

// Example
var lat = 9.129438;
var lon = -4.233587;

var latDMS = ddToDms(lat, 'lat'); 
var lonDMS = ddToDms(lon, 'lon');

console.log('latDMS: '+ latDMS);
console.log('lonDMS: '+ lonDMS);

/*
Output
latDMS: 09°07'45.98"N
lonDMS: 004°14'00.91"W
*/
-1

just for remark, the answer

function ConvertDDToDMS(D){
return [0|D, 'd ', 0|(D<0?D=-D:D)%1*60, "' ", 0|D*60%1*60, '"'].join('');

}

does not work for angles between -1° and 0°. Bad luck! hc

archi pelago
  • 49
  • 1
  • 4