CodeSOD: Projection Your Failures

CodeSOD: Projection Your Failures

Geoinformatics ends up needing to deal with a pretty thorny problem: projecting a sphere onto a 2D coordinate system. There is no single correct way to do this, and depending on many factors, you may need to choose different projections- for maps covering small regions, the curvature of the Earth might have only a minor effect; the curvature is different depending on your latitude; depending on your needs, you might need different projections that prioritize retaining (or discarding) certain relationships between map coordinates.

Given how hard this problem is, people have put a lot of time and effort into solving it with some standards. In this case, the standard is EPSG codes, a registry of reference systems, different values for the shape of the Earth, and different coordinate systems, all granted a unique identifier in the range 1024–32767.

Each possible coordinate system has a different range of possible coordinates, and the coordinates mean wildly different things, so it's important to make sure that any coordinate you're passing around has its EPSG code attached to it. If you don't, the coordinate value is basically meaningless.

Well, it's basically meaningless unless you're Samantha's co-worker, who came up with a "clever" solution for figuring out the ESPG code for a coordinate:

static int GetProjectionCodeFromCoordinate(double num)
{
    var digits = 0;
    while (num >= 1)
    {
        digits++;
        num /= 10;
    }

    switch (digits)
    {
        case 6:
            return 25832; // 32N
        case 8:
            return 4647; // N32  
        case 7:
            return 31467; // GK3 
        default:
            log.Error("Data/Projection not supported!");
            return -1;
    }
}

We start by counting the number of digits. Already, I hate it, because this is a very CS101 method of counting digits by someone who never heard of logarithms. It's not wrong, it's just bad.

Given the number of digits, we then convert the coordinate to an ESPG code. And, to quote Samantha, this solution is "incredibly cursed". A 6 digit coordinate means we must be using the 32N projection, an 8 digit coordinate means we must be using N32, a seven digit means GK3. And yes, I am annoyed that the switch doesn't go in order, but that's not the WTF.

Think about it: is there any reason to assume a 6 digit coordinate always means a 32N projection? Is it realistic that someone would make a coordinate system that ranges between 100,000 and 999,999 and never leaves that range? Of course not.

The original developer derived this function by looking at the coordinates in the database. They saw a pattern emerge, and assumed they understood the coordinate systems. However, the coordinates they used just happened to be in Germany. Which, in Germany, this function holds true- all 32N coordinates in Germany will be 6 digits. All GK3 will be 7. But stray outside of Germany, even by a few miles, and these assumptions break down.

I'll let Samantha explain:

I'm dreading the day when a user tries to import points outside Germany of a river flowing into the country. It's an entirely plausible scenario for the domain that would throw an exception — which would sometimes get swallowed further up the call stack, of course — in the transforming code meaning that sometimes the application would falsely report successful processing of data, or fail with a cryptic error message that users then would have to track down the reason for in the incredibly verbose log file.

There's also a bonus WTF here: the application supports four different coordinate systems. This function, as you can see, only supports 3. The other projection, within Germany, would have 2-3 digits in its coordinates. Presumably (2 or 3) wasn't something the developer couldn't figure out how to fit neatly into the switch statement, so they just… didn't. Note how the default branch doesn't throw an exception either. The invalid -1 result value gets propagated up the call stack until a function chokes on it, leading to an actual exception that has no relation to the code which caused it, which brings us back to the "cryptic error message" and reading through the "incredibly verbose log file".

[Advertisement] Continuously monitor your servers for configuration changes, and report when there's configuration drift. Get started with Otter today!

This post originally appeared on The Daily WTF.

Geoinformatics ends up needing to deal with a pretty thorny problem: projecting a sphere onto a 2D coordinate system. There is no single correct way to do this, and depending on many factors, you may need to choose different projections- for maps covering small regions, the curvature of the Earth might have only a minor effect; the curvature is different depending on your latitude; depending on your needs, you might need different projections that prioritize retaining (or discarding) certain relationships between map coordinates.

Given how hard this problem is, people have put a lot of time and effort into solving it with some standards. In this case, the standard is EPSG codes, a registry of reference systems, different values for the shape of the Earth, and different coordinate systems, all granted a unique identifier in the range 1024–32767.

Each possible coordinate system has a different range of possible coordinates, and the coordinates mean wildly different things, so it's important to make sure that any coordinate you're passing around has its EPSG code attached to it. If you don't, the coordinate value is basically meaningless.

Well, it's basically meaningless unless you're Samantha's co-worker, who came up with a "clever" solution for figuring out the ESPG code for a coordinate:

static int GetProjectionCodeFromCoordinate(double num)
{
    var digits = 0;
    while (num >= 1)
    {
        digits++;
        num /= 10;
    }

    switch (digits)
    {
        case 6:
            return 25832; // 32N
        case 8:
            return 4647; // N32  
        case 7:
            return 31467; // GK3 
        default:
            log.Error("Data/Projection not supported!");
            return -1;
    }
}

We start by counting the number of digits. Already, I hate it, because this is a very CS101 method of counting digits by someone who never heard of logarithms. It's not wrong, it's just bad.

Given the number of digits, we then convert the coordinate to an ESPG code. And, to quote Samantha, this solution is "incredibly cursed". A 6 digit coordinate means we must be using the 32N projection, an 8 digit coordinate means we must be using N32, a seven digit means GK3. And yes, I am annoyed that the switch doesn't go in order, but that's not the WTF.

Think about it: is there any reason to assume a 6 digit coordinate always means a 32N projection? Is it realistic that someone would make a coordinate system that ranges between 100,000 and 999,999 and never leaves that range? Of course not.

The original developer derived this function by looking at the coordinates in the database. They saw a pattern emerge, and assumed they understood the coordinate systems. However, the coordinates they used just happened to be in Germany. Which, in Germany, this function holds true- all 32N coordinates in Germany will be 6 digits. All GK3 will be 7. But stray outside of Germany, even by a few miles, and these assumptions break down.

I'll let Samantha explain:

I'm dreading the day when a user tries to import points outside Germany of a river flowing into the country. It's an entirely plausible scenario for the domain that would throw an exception — which would sometimes get swallowed further up the call stack, of course — in the transforming code meaning that sometimes the application would falsely report successful processing of data, or fail with a cryptic error message that users then would have to track down the reason for in the incredibly verbose log file.

There's also a bonus WTF here: the application supports four different coordinate systems. This function, as you can see, only supports 3. The other projection, within Germany, would have 2-3 digits in its coordinates. Presumably (2 or 3) wasn't something the developer couldn't figure out how to fit neatly into the switch statement, so they just… didn't. Note how the default branch doesn't throw an exception either. The invalid -1 result value gets propagated up the call stack until a function chokes on it, leading to an actual exception that has no relation to the code which caused it, which brings us back to the "cryptic error message" and reading through the "incredibly verbose log file".

[Advertisement] Continuously monitor your servers for configuration changes, and report when there's configuration drift. Get started with Otter today!

This post originally appeared on The Daily WTF.

Leave a Reply

Your email address will not be published. Required fields are marked *