Convert Latitude/Longitude Between Decimal and Degrees/Minutes/Seconds in C#

Latitude and longitude are usually represented in one of two formats: signed decimal and degrees/minutes/seconds (DMS). Signed decimal values range from -90 to 90 for latitude and -180 to 180 for longitude. DMS values, on the other hand, are broken into four parts: degrees, minutes, and seconds–obviously–plus a compass direction.

Converting between the two formats is not terribly difficult if you keep the following formula in mind:

Decimal Degrees = Degrees + minutes/60 + seconds/3600

Computers are good at math and converting things, so let’s write a converter to do the work for us! First, we need to define some types to work with. We have two different formats, so it makes sense for us to have a DecimalLocation and a DmsLocation. We can also get some re-use out of a DmsPoint type, and we’ll need an enumeration for PointType to help with displaying N/S & E/W directional indicators. I threw in some overloads for ToString to help with displaying results, too. Also note that N/S/E/W are determined based on the sign of Degrees for DMS points.

class DecimalLocation
{
    public decimal Latitude { get; set; }
    public decimal Longitude { get; set; }

    public override string ToString()
    {
        return string.Format("{0:f5}, {1:f5}",
            Latitude, Longitude);
    }
}

class DmsLocation
{
    public DmsPoint Latitude { get; set; }
    public DmsPoint Longitude { get; set; }

    public override string ToString()
    {
        return string.Format("{0}, {1}",
            Latitude, Longitude);
    }
}

class DmsPoint
{
    public int Degrees { get; set; }
    public int Minutes { get; set; }
    public int Seconds { get; set; }
    public PointType Type { get; set; }

    public override string ToString()
    {
        return string.Format("{0} {1} {2} {3}",
            Math.Abs(Degrees),
            Minutes,
            Seconds,
            Type == PointType.Lat
                ? Degrees < 0 ? "S" : "N"
                : Degrees < 0 ? "W" : "E");
    }
}

enum PointType
{
    Lat,
    Lon
}

Now that we’ve got our data types out of the way, we can work on the conversion. Our conversion formula shows how to calculate decimal degrees from DMS, so let’s do that first. It’s really straightforward–we just plug the formula into a function and call it for both latitude and longitude.

DecimalLocation Convert(DmsLocation dmsLocation)
{
    if (dmsLocation == null)
    {
        return null;
    }

    return new DecimalLocation
        {
            Latitude = CalculateDecimal(dmsLocation.Latitude),
            Longitude = CalculateDecimal(dmsLocation.Longitude)
        };
}

decimal CalculateDecimal(DmsPoint point)
{
    if (point == null)
    {
        return default(decimal);
    }
    return point.Degrees + (decimal)point.Minutes/60 + (decimal)point.Seconds/3600;
}

Going the other direction is a little more complicated. Ultimately, we’re just working backwards. Peel off the degrees, then calculate minutes, and seconds are left.

DmsLocation Convert(DecimalLocation decimalLocation)
{
    if (decimalLocation == null)
    {
        return null;
    }

    return new DmsLocation
        {
            Latitude = new DmsPoint
                {
                    Degrees = ExtractDegrees(decimalLocation.Latitude),
                    Minutes = ExtractMinutes(decimalLocation.Latitude),
                    Seconds = ExtractSeconds(decimalLocation.Latitude),
                    Type = PointType.Lat
                },
            Longitude = new DmsPoint
                {
                    Degrees = ExtractDegrees(decimalLocation.Longitude),
                    Minutes = ExtractMinutes(decimalLocation.Longitude),
                    Seconds = ExtractSeconds(decimalLocation.Longitude),
                    Type = PointType.Lon
                }
        };
}

int ExtractDegrees(decimal value)
{
    return (int)value;
}

int ExtractMinutes(decimal value)
{
    value = Math.Abs(value);
    return (int)((value - ExtractDegrees(value)) * 60);
}

int ExtractSeconds(decimal value)
{
    value = Math.Abs(value);
    decimal minutes = (value - ExtractDegrees(value)) * 60;
    return (int)Math.Round((minutes - ExtractMinutes(value)) * 60);
}

That’s it–we’ve got some lightweight methods to go between the two formats. Let’s whip up a quick test to verify the conversions:

void Main()
{
    DecimalLocation decimalLocation;
    DmsLocation dmsLocation;

    decimalLocation = new DecimalLocation
        {
            Latitude = 38.898611m,
            Longitude = -77.037778m
        };
    dmsLocation = Convert(decimalLocation);
    Console.WriteLine("{0} -> {1}", decimalLocation, dmsLocation);

    dmsLocation = new DmsLocation
        {
            Latitude = new DmsPoint
                {
                    Degrees = 38,
                    Minutes = 53,
                    Seconds = 55,
                    Type = PointType.Lat
                },
            Longitude = new DmsPoint
                {
                    Degrees = -77,
                    Minutes = 2,
                    Seconds = 16,
                    Type = PointType.Lon
                }
        };
    decimalLocation = Convert(dmsLocation);
    Console.WriteLine("{0} -> {1}", dmsLocation, decimalLocation);
}

// output:
//    38.89861, -77.03778 -> 38 53 55 N, 77 2 16 W
//    38 53 55 N, 77 2 16 W -> 38.89861, -76.96222

The converted values are off slightly, presumably due to some loss of precision, but the values produced are in the same general vicinity. (See here, here, and here.)

Full example:

void Main()
{
    DecimalLocation decimalLocation;
    DmsLocation dmsLocation;

    decimalLocation = new DecimalLocation
        {
            Latitude = 38.898611m,
            Longitude = -77.037778m
        };
    dmsLocation = Convert(decimalLocation);
    Console.WriteLine("{0} -> {1}", decimalLocation, dmsLocation);

    dmsLocation = new DmsLocation
        {
            Latitude = new DmsPoint
                {
                    Degrees = 38,
                    Minutes = 53,
                    Seconds = 55,
                    Type = PointType.Lat
                },
            Longitude = new DmsPoint
                {
                    Degrees = -77,
                    Minutes = 2,
                    Seconds = 16,
                    Type = PointType.Lon
                }
        };
    decimalLocation = Convert(dmsLocation);
    Console.WriteLine("{0} -> {1}", dmsLocation, decimalLocation);
}

class DecimalLocation
{
    public decimal Latitude { get; set; }
    public decimal Longitude { get; set; }

    public override string ToString()
    {
        return string.Format("{0:f5}, {1:f5}",
            Latitude, Longitude);
    }
}

class DmsLocation
{
    public DmsPoint Latitude { get; set; }
    public DmsPoint Longitude { get; set; }

    public override string ToString()
    {
        return string.Format("{0}, {1}",
            Latitude, Longitude);
    }
}

class DmsPoint
{
    public int Degrees { get; set; }
    public int Minutes { get; set; }
    public int Seconds { get; set; }
    public PointType Type { get; set; }

    public override string ToString()
    {
        return string.Format("{0} {1} {2} {3}",
            Math.Abs(Degrees),
            Minutes,
            Seconds,
            Type == PointType.Lat
                ? Degrees < 0 ? "S" : "N"
                : Degrees < 0 ? "W" : "E");
    }
}

enum PointType
{
    Lat,
    Lon
}

DecimalLocation Convert(DmsLocation dmsLocation)
{
    if (dmsLocation == null)
    {
        return null;
    }

    return new DecimalLocation
        {
            Latitude = CalculateDecimal(dmsLocation.Latitude),
            Longitude = CalculateDecimal(dmsLocation.Longitude)
        };
}

DmsLocation Convert(DecimalLocation decimalLocation)
{
    if (decimalLocation == null)
    {
        return null;
    }

    return new DmsLocation
        {
            Latitude = new DmsPoint
                {
                    Degrees = ExtractDegrees(decimalLocation.Latitude),
                    Minutes = ExtractMinutes(decimalLocation.Latitude),
                    Seconds = ExtractSeconds(decimalLocation.Latitude),
                    Type = PointType.Lat
                },
            Longitude = new DmsPoint
                {
                    Degrees = ExtractDegrees(decimalLocation.Longitude),
                    Minutes = ExtractMinutes(decimalLocation.Longitude),
                    Seconds = ExtractSeconds(decimalLocation.Longitude),
                    Type = PointType.Lon
                }
        };
}

decimal CalculateDecimal(DmsPoint point)
{
    if (point == null)
    {
        return default(decimal);
    }
    return point.Degrees + (decimal)point.Minutes/60 + (decimal)point.Seconds/3600;
}

int ExtractDegrees(decimal value)
{
    return (int)value;
}

int ExtractMinutes(decimal value)
{
    value = Math.Abs(value);
    return (int)((value - ExtractDegrees(value)) * 60);
}

int ExtractSeconds(decimal value)
{
    value = Math.Abs(value);
    decimal minutes = (value - ExtractDegrees(value)) * 60;
    return (int)Math.Round((minutes - ExtractMinutes(value)) * 60);
}
Advertisements

3 thoughts on “Convert Latitude/Longitude Between Decimal and Degrees/Minutes/Seconds in C#”

  1. Brilliant stuff. Thanks for putting all of this code together so completely. I changed the Seconds in your DmsPoint Class to a decimal and changed the ExtractSeconds function to get rid of the rounding to bump up the accuracy for when I use the results with M$’s BING Maps SDK.

Leave a comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s