Formatting double to latitude/longitude human readable format
Asked Answered
A

5

8

If the formula for converting latitude or longitude to double is

((Degree) + (Minute) / 60 + (Second) / 3600) * ((South || West) ? -1 : 1)

then what's the formula for parsing degrees, minutes, seconds from a double?

It'd make sense to have two separate methods for parsing latitude and longitude, but I'm not sure how to parse the degrees, minutes, seconds from the double.

ParseLatitude(double value)
{
    //value is South if negative, else is North.
}

ParseLongitude(double value)
{
    //value is West if negative, else is East.
}

Example coordinates:

latitude: 43.81234123

longitude: -119.8374747

The final code to convert back and forth, thanks again to Peter and James for the answer. I had to convert value to Decimal because this is being used in Silverlight and Math.Truncate(double) is not available):

public class Coordinate
{
    public double Degrees { get; set; }
    public double Minutes { get; set; }
    public double Seconds { get; set; }
    public CoordinatesPosition Position { get; set; }

    public Coordinate() { }
    public Coordinate(double value, CoordinatesPosition position)
    {
        //sanity
        if (value < 0 && position == CoordinatesPosition.N)
            position = CoordinatesPosition.S;
        //sanity
        if (value < 0 && position == CoordinatesPosition.E)
            position = CoordinatesPosition.W;
        //sanity
        if (value > 0 && position == CoordinatesPosition.S)
            position = CoordinatesPosition.N;
        //sanity
        if (value > 0 && position == CoordinatesPosition.W)
            position = CoordinatesPosition.E;

        var decimalValue = Convert.ToDecimal(value);

        decimalValue = Math.Abs(decimalValue);

        var degrees = Decimal.Truncate(decimalValue);
        decimalValue = (decimalValue - degrees) * 60;

        var minutes = Decimal.Truncate(decimalValue);
        var seconds = (decimalValue - minutes) * 60;

        Degrees = Convert.ToDouble(degrees);
        Minutes = Convert.ToDouble(minutes);
        Seconds = Convert.ToDouble(seconds);
        Position = position;
    }
    public Coordinate(double degrees, double minutes, double seconds, CoordinatesPosition position)
    {
        Degrees = degrees;
        Minutes = minutes;
        Seconds = seconds;
        Position = position;
    }
    public double ToDouble()
    {
        var result = (Degrees) + (Minutes) / 60 + (Seconds) / 3600;
        return Position == CoordinatesPosition.W || Position == CoordinatesPosition.S ? -result : result;
    }
    public override string ToString()
    {
        return Degrees + "º " + Minutes + "' " + Seconds + "'' " + Position;
    }
}

public enum CoordinatesPosition
{
    N, E, S, W
}

Unit Test (nUnit)

[TestFixture]
public class CoordinateTests
{
    [Test]
    public void ShouldConvertDoubleToCoordinateAndBackToDouble()
    {
        const double baseLatitude = 43.81234123;
        const double baseLongitude = -119.8374747;

        var latCoordN = new Coordinate(baseLatitude, CoordinatesPosition.N);
        var latCoordS = new Coordinate(baseLatitude, CoordinatesPosition.S);
        var lonCoordE = new Coordinate(baseLongitude, CoordinatesPosition.E);
        var lonCoordW = new Coordinate(baseLongitude, CoordinatesPosition.W);

        var convertedLatitudeS = latCoordS.ToDouble();
        var convertedLatitudeN = latCoordN.ToDouble();
        var convertedLongitudeW = lonCoordW.ToDouble();
        var convertedLongitudeE = lonCoordE.ToDouble();

        Assert.AreEqual(convertedLatitudeS, convertedLatitudeN);
        Assert.AreEqual(baseLatitude, convertedLatitudeN);
        Assert.AreEqual(convertedLongitudeE, convertedLongitudeW);
        Assert.AreEqual(baseLongitude, convertedLongitudeE);
    }
}
Afreet answered 21/12, 2010 at 23:36 Comment(0)
B
10
ParseLatitude(double Value)
{
    var direction = Value < 0 ? Direction.South : Direction.North;

    Value = Math.Abs(Value);

    var degrees = Math.Truncate(Value);

    Value = (Value - degrees) * 60;       //not Value = (Value - degrees) / 60;

    var minutes = Math.Truncate(Value);
    var seconds = (Value - minutes) * 60; //not Value = (Value - degrees) / 60;
    //...
}

ParseLongitude(double Value)
{
    var direction = Value < 0 ? Direction.West : Direction.East;

    Value = Math.Abs(Value);

    var degrees = Math.Truncate(Value);

    Value = (Value - degrees) * 60;       //not Value = (Value - degrees) / 60;

    var minutes = Math.Truncate(Value);
    var seconds = (Value - minutes) * 60; //not Value = (Value - degrees) / 60;
    //...
}

EDIT

I came back to this because of a recent upvote. Here's a DRY-er version, with the Value parameter renamed to reflect the most common coding convention, in which parameters start with lower-case letters:

ParseLatitude(double value)
{
    var direction = value < 0 ? Direction.South : Direction.North;
    return ParseLatituteOrLongitude(value, direction);
}

ParseLongitude(double value)
{
    var direction = value < 0 ? Direction.West : Direction.East;
    return ParseLatituteOrLongitude(value, direction);
}

//This must be a private method because it requires the caller to ensure
//that the direction parameter is correct.
ParseLatitudeOrLongitude(double value, Direction direction)
{
    value = Math.Abs(value);

    var degrees = Math.Truncate(value);

    value = (value - degrees) * 60;       //not Value = (Value - degrees) / 60;

    var minutes = Math.Truncate(value);
    var seconds = (value - minutes) * 60; //not Value = (Value - degrees) / 60;
    //...
}
Buie answered 21/12, 2010 at 23:46 Comment(3)
Sorry phoog, value needs to be multiplied, not divided.Afreet
Voted yours as an answer as well because Truncate is the right method to use, rather than checking for +- and using Floor/Ceiling.Afreet
@c_bit: I edited the answer. Thanks for correcting my error. And for the vote :-)Buie
H
2
#include <math.h>

void ParseLatitude(double Value, bool &north, double &deg, double &min, double &sec)
{
  if ( Value < 0 )
  {
    ParseLatitude( -Value, north, deg, min, sec );
    north = false;
  }
  else
  {
    north = true;
    deg = floor(Value);
    Value = 60*(Value - deg);
    min = floor(Value);
    Value = 60*(Value - min);
    sec = Value;
  }
}

// ParseLongitude is similar
Heaviness answered 21/12, 2010 at 23:46 Comment(2)
Wont the round function round up anything from 0.5 of a degree to a whole degree?Tao
Thanks Peter (and James)! I needed it for C#, but your formula within the else statement is accurate. I almost selected phoog's answer as it had 1 vote, but he's dividing and that caused my unit tests to fail. :)Afreet
I
1

I've written a class in C# which does a lot of this. Perhaps it is useful, otherwise you can check out the implementation:

http://code.google.com/p/exif-utils/source/browse/trunk/ExifUtils/ExifUtils/GpsCoordinate.cs

Immerge answered 21/12, 2010 at 23:40 Comment(1)
That looks pretty thorough, but didn't suit my needs. Thanks anyway.Afreet
S
1

In addition to parsing out the degree, minutes, seconds (which is just radix 60 arithmetic), you may also want to deal with sign of the doubles being converted to "North/South" for latitude and "East/West" for longitude.

It's pretty standard to identify positive degrees latitude with the Northern Hemisphere and negative degrees latitude with the Southern Hemisphere. It's also common here in the Western Hemisphere to take positive degrees longitude to mean degrees West of the Greenwich Meridian and conversely negative degrees longitude to mean degrees East of that Meridian. However the preferred convention for this is the opposite, to take degrees East of the Greenwich Meridian as negative. You may want to consult with your client/analyze the application design to determine which choice applies to this conversion.

Note also that the discontinuity of longitude at ±180 is a cause for care in converting coordinates that may result from calculations. If the conversion is not intended to handle wrap-around at the 180° meridian, then it's likely an exception should be thrown for such inputs. Of course the design decision should be documented either way.

Certainly latitudes outside the ±90° range are errors on input.

Added: Given the above differences in parsing latitude and longitude, issues that would best be handled in the distinct ParseLatitude & ParseLongitude routines, we could use a common utility to do the conversion from double to degrees/minutes/seconds.

I'm not sure what the target language should be here, so I wrote something in plain vanilla C:

#include <math.h>

void double2DegMinSec(double angle, int *Sign, int *Deg, int *Min, double *Sec)
{ /* extract radix 60 Degrees/Minutes/Seconds from "angle" */

    Sign = 1;  

    if (angle < 0.0)  /* reduce to case of nonnegative angle */  
    {  
         Sign = -Sign;  
         angle = -angle;  
    }  

    *Deg = floor(angle);  
    angle -= *Deg;  
    angle *= 60.0;  
    *Min = floor(angle);  
    angle -= *Min;  
    angle *= 60.0;  
    *Sec = angle;  

    return;  
}  

Likely ParseLatitude and ParseLongitude should manage the conversion of angle's sign to the appropriate geographic designation, but I've included an argument Sign that will allow that sign checking to be done after conversion (though it would be fine if the conversion were only ever called with nonnegative angles).

I made the function double2DegMinSec have a return type of void. Results are thus to be returned through its formal arguments of type pointer to int and pointer to double (in the case of seconds Sec, which might have fractional part).

Calling the conversion in C might be done like this:

double longitude = -119.8374747;
int Sign, Degrees, Minutes;
double Seconds;

double2DegMinSec(longitude, &Sign, &Degrees, &Minutes, &Seconds);  

In C++ we would make the calling syntax a bit glibber by using call-by-reference instead of pointers.

Sagesagebrush answered 22/12, 2010 at 0:3 Comment(0)
Y
0

With just multiplying you'll get conversion errors without noticing it, I noticed it when mapping the points on a map. You'll need to take into account the slope and other variables, like this:

public static void GeoToMercator(double xIn, double yIn, out double xOut, out double yOut)
    {
        double xArg = xIn / 100000, yArg = yIn / 100000;
        xArg = 6371000.0 * Math.PI / 180 * xArg;
        yArg = 6371000.0 * Math.Log(Math.Tan(Math.PI / 4 + Math.PI / 180 * yArg * 0.5));
        xOut = xArg / 10000;
        yOut = yArg / 10000;
    }

I'm guessing you're using Mercator values as double representation. To convert the Mercator value back into the correct longitude/latitude values, just use the reverse:

public static void MercatorToGeo(double xIn, double yIn, out double xOut, out double yOut)
    {
        double xArg = xIn, yArg = yIn;
        xArg = 180 / Math.PI * xArg / 6371000.0;
        yArg = 180 / Math.PI * (Math.Atan(Math.Exp(yArg / 6371000.0)) - Math.PI / 4) / 0.5;
        xOut = xArg * 10;
        yOut = yArg * 10;
    }

This did the trick for me.

Year answered 7/4, 2011 at 11:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.