January
16th 2010
A Geographic Distance Class in C#

Posted under C# & GIS

The .NET Framework is pretty feature rich, helping developers put applications together quickly. A glaring omission, however, is support for anything to do with geography. I recently had a need to deal with distances between points in GPS files, calculated using the haversine formula, and came up with an elegant solution.

I modeled my class, Distance, on the TimeSpan struct, with a few important changes. This should make the programming model as familiar as possible. Just as a TimeSpan converts the same value ( say, 36 hours ) between different formats ( 1.5 days; 2,160 minutes, etc ), a Distance object converts between formats like feet, meters, and miles.

If this isn’t clear, a short code sample should help. The full code can be found at Distance.cs.

Distance d = new Distance();

d.Miles = 1;
Debug.Assert(d.Yards == 1760);
Debug.Assert(d.Feet == 5280);
Debug.Assert(d.Inches == 63360);

Because I’ve used the TimeSpan structure as a model, in explaining the Distance class as much as in developing it, there are a few important differences I should point out.

Enums (UnitSystem, ImperialUnits, MetricUnits)

A raw number has no meaning when divorced from its context; three what? While the elevation data in a GPS tracklog comes in meters, many US residents would prefer to see this in feet. There are three structures that provide the context that makes this data meaningful. These should be fairly self explanatory ( ImperialUnits contains inches, feet, etc while MetricUnits contains centimeters, kilometers, and more ), but it’s worth mentioning that a third struct, UnitSystem, distinguishes between the two. I felt this was a better model than simply listing every available type of 2D measurement in one enumeration ( feet, meters, and furlongs - oh my! ).

This leads to a curious paradigm for calling code - what you’ll write if you use this class. Several methods have three specific overloads, taking a decimal value and one of the following types: ImperialUnits, MetricUnits, or Enum. The third overload, taking a base Enum, lets you abstract away the specific type. The code sample below shows three ways to instantiate a Distance object with a value.

Distance d1 = new Distance(1, ImperialUnits.Mile);
Distance d2 = new Distance(2, MetricUnits.Meter);

Enum unit = userText == "mile" ? ImperialUnits.Mile : MetricUnits.Meter;
Distance d3 = new Distance(2, unit);

Normalize

While the method name may give away my background as a SQL Server developer, it has a different, but equally important meaning here. The flexibility of the Distance class would make comparisons a little bit cumbersome; one instance could describe inches while the other might be in miles or meters. Therefore, the ( static ) normalize method takes two Distance arguments, and returns the value ( as a System.Decimal ) of the second in terms of the first. In other words, if you ask for 1 yard to be normalized against 1 foot, the result will be the value 3.

Operator Overloads

I’ve overloaded + and - as well as < and > in the Distance class, which cascade so that +=, -=, <=, and >= are also redefined.

Part of the reason I chose to implement distance as a class and not a struct is to retain the object semantics most developers are used to, which includes identity comparisons by default, and the ability to change the value in an instance without creating a second instance ( eg values in a list ). This means that == is left to determine whether two instances are the same object in memory, not whether they have the same value.

Direct Access to Internal Values

Again, owing to the flexibility of representation, I felt it necessary ( although a bit dangerous ) to provide access to the underlying data.

You can set an object’s value by calling its specialized properties ( d.Miles = 2; ), however, if you have a variable representing the unit to use and would prefer to abstract the call, the SetValue method allows this ( d.SetValue(2, ImperialUnits.Mile); ).

A read-only property, InternalValue, gives developers a choice for how to perform value comparisons. This is paired with a second property, InternalUnit, of type Enum, and two others provided only for convenience, to avoid casting in client code: InternalMetricUnit, and InternalImperialUnit, plus, finally, an InternalUnitType property telling you which to use. While this may sound complicated, it is very easy to code against. You can compare the same property ( Meters, for example ) from two objects to determine whether they have the same value, avoid the mess by calling Normalize as described above, or compare the internal values and types.

System.Decimal

The decimal struct is much slower than the double, but it allows much greater precision after the decimal point. Because of the way IEEE defines floating point representations, there are many fractional values that cannot be represented exactly as a single or double precision float. Decimals, apart from using more bytes for storage, use a base-ten representation of both parts of the number, although this is done in .net code, and is truly a software solution. I feel that this is an acceptable trade-off, however; I don’t expect a great deal of math to be done against Distance objects, but I do expect people will be startled if metric calculations don’t work as expected.

Unit Tests

This class has been heavily tested, with at least one non-trivial conversion from every unit to every other unit. All code paths through Normalize have also been tested.

I’m not publishing the tests, however, as I don’t believe they have much or any use other than in preserving my sanity while I wrote this code. If I’m wrong and you could benefit from the tests, leave a comment and I’ll be happy to provide them, but, for brevity’s sake, I’ll point you at the actual code.

Trackback URI | Comments RSS

Leave a Reply