views:

170

answers:

5

I'm looking for the best C or C++ code to encode and decode decimal latitude and longitude values from/to double/char. I'd prefer the code convert from double to char[] and vice-versa rather than c++ strings.

If you have a code snippet that would be great too.

To clarify: I need to convert from a string Degrees/Minutes/Seconds to double and back to string. I have 300 million records, so speed is a big concern.

See: http://en.wikipedia.org/wiki/Geographic_coordinate_conversion

A: 
Jonathan Grynspan
I don't think I made my question clear. Longitude and Latitude can be stored in a Degrees/Minutes/Seconds string format. I need to convert from that to decimal degrees stored in a double. Very sorry.
amanda
as the poster said her primary concern is speed, snprintf and sscanf are HORRIBLE choices. While they're incredibly flexible, they are amazingly slow, as they handle all manner of input and output.
abelenky
The OP did *not* mention speed when I originally posted this response. As originally posted, @amanda asked only for a solution that did not use `std::string`. Raw speed was not part of the question.
Jonathan Grynspan
The title "What's the fastest..." hasn't been edited, unless it was a ninja edit.
Ben Voigt
Yes, but just about every question ever posted on Stack Overflow wants the fastest possible answer without regard for readability or portability, because everyone wants premature optimization. In the absence of the question body explaining why speed is important, and in the presence of a question that is otherwise fairly basic to answer, would you assume the user is an optimizing guru and really knows for certain that this function will be called "300 million times", given that he or she has not stated it would be at the time you answer?
Jonathan Grynspan
+2  A: 

Here's the code I developed:

double Str2LatLong(char* coord)
{
    // char* testInput = "47,26'14\"";

    int i = 0;
    int parts[3] = {0};  // parts[0] is degrees, parts[1] is minutes, parts[2] is seconds
    int* pCurr = parts;

    do
    {
        if (coord[i] == '\0')
        {
            break;
        }
        if (!isdigit(coord[i]))
        {
            *pCurr++; // skip from parts[0] ==> [1], or [1] ==> [2]
            continue;
        }
        *pCurr = 10* (*pCurr) + coord[i] - '0';
        ++i;
    } while (1);

    return parts[0] + ((double)parts[1])/60.0 + ((double)parts[2])/3600.0;
}

Because it is written for speed, there is NO input validation.
You must supply proper input, or it will mess up badly.

I kept everything to integer math, and sequential memory as best I could.
It doesn't check for "proper" delimiters. Rather, anytime something is not a digit, it assumes that is the transition from degrees to minutes, or minutes to seconds.

It is only at the very last line that it converts to double with some very simple floating point operations.

I suspect you'll want to modify it to handle positive/negative values and North/South, East/West indicators, and decimal places after the seconds. But I think this code is a good foundation for a really fast conversion routine.

I hope this will test out very fast. Please let me know how it goes.

abelenky
A: 

Here's another variation.

The logic is the same, but it uses a switch-case statement for better organization, fewer break/continue statements, and a faster return.

You should also be able to enhance this one with

case 'N':
case 'S':
case 'E':
case 'W': 

as needed.

double Str2LatLong(char* coord)
{
    // char* testInput = "47,26'14\"";

    int i = 0;
    int parts[3] = {0};
    int* pCurr = parts;

    while(1)
    {
        switch (coord[i])
        {
            /* Any digit contributes to either degrees,minutes, or seconds (as *pCurr) */
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                *pCurr = 10* (*pCurr) + coord[i] - '0';
                break;

            /* A Null terminator triggers return (no break necessary) */
            case '\0':
                return parts[0] + ((double)parts[1])/60.0 + ((double)parts[2])/3600.0;

            /* Any other symbol moves pCurr from degrees to minutes, or minutes to seconds */
            default:
                *pCurr++;
        }
        i++;
    }

    return -1.0;  /* Should never reach this point! */
}
abelenky
This one is very promising! On a development server it seems to be about 17.6 times faster than anything we've tried. However is does not handle decimal strings such as: 50-50-50.2433N so I'm unsure if it's faster or just fast because it's not working for all cases.
amanda
I think making it handle decimals would be pretty trivial.Making it handle *all* cases may be a bit challenging, but not insurmountable.
abelenky
Out of 300mil, those are the only ones that came out incorrect. Since the code is using integers, I'm not sure how you could graft on the decimals without changing to floats or am I missing something? Not looking for code, just direction!
amanda
Its not as trivial as I thought, but I am working on it. :)
abelenky
A: 

The hard part is going to be representing all the format variations with a single grammar. Once you do, you can use a lexer generator tool to spit out a highly optimized DFA that will be competitive with the best hand-tuned code.

Ben Voigt