Last edited on 15 January 2009
From blog post "A Geographic Distance Class in C#".
/*
Distance.cs - By Forrest Croce, January 2009.
This code is released as open source; please attribute the author and source.
*/
using System;
namespace ForrestCroce.Geography {
///
/// Distinguishes between different systems of units of measure.
///
public enum UnitSystem {
///
/// The Imperial units ( foot, mile, etc ) are more or less only used in the United States anymore.
///
Imperial,
///
/// The metric system units ( meter, kilometer ) have taken over most in of the world.
///
Metric
}
///
/// Distinguishes between the units making up the imperial system of measuring distance.
///
public enum ImperialUnits {
///
/// An inch.
///
Inch,
///
/// A foot; 12 inches.
///
Foot,
///
/// A yard; 3 feet.
///
Yard,
///
/// A mile; 5,280 feet.
///
Mile,
///
/// A nautical ( or Admirality ) mile; 1,852 meters by convention.
///
NauticalMile
}
///
/// Distinguishes between the units making up the metric system of measuring distance.
///
public enum MetricUnits {
///
/// A millimeter ( mm ).
///
Millimeter,
///
/// A centimeter ( cm ); 10 mms.
///
Centimeter,
///
/// A decimeter; 10 cms.
///
Decimeter,
///
/// A meter; 10 decimeters or 100 cms.
///
Meter,
///
/// A kilometer; 1,000 meters.
///
Kilometer
}
///
/// Represents the distance between two points ( on a 2D or 3D surface ) in much the same way a TimeSpan represents the
/// distance
///
public class Distance {
#region Conversion constants
private const decimal InchesPerFoot = 12;
private const decimal FeetPerYard = 3;
private const decimal YardsPerMile = 1760;
private const decimal FeetPerMile = 5280;
private const decimal MetersPerNauticalMile = 1852;
private const decimal CentimetersPerMillimeter = 10;
private const decimal CentimetersPerDecimeter = 0.1M;
private const decimal CentimetersPerMeter = 0.01M;
private const decimal CentimetersPerKilometer = 0.00001M;
//private const decimal MilesPerKilometer = 0.621371192M; // From Google
//private const decimal MilesPerKilometer = 0.621371192237M; // From http://www.conversion-metric.org/
private const decimal MilesPerKilometer = 0.621371192237334M; // From http://www.unitsconversion.com.ar/
#endregion
private UnitSystem nativeType;
private ImperialUnits nativeImperialType;
private MetricUnits nativeMetricType;
private decimal nativeValue;
#region New / class constructor
///
/// Creates a new, empty instance of the Distance class.
///
public Distance() {
}
///
/// Creates a new instance of the Distance class, and populates it with a value and unit to describe that value.
///
/// The raw number representing the amount of distance.
/// What scale the 'value' number exists on, eg feet, yards, miles, etc.
public Distance(decimal value, ImperialUnits unit) {
SetValue(value, unit);
}
///
/// Creates a new instance of the Distance class, and populates it with a value and unit to describe that value.
///
/// The raw number representing the amount of distance.
/// What scale the 'value' number exists on, eg meters, kilometers, etc.
public Distance(decimal value, MetricUnits unit) {
SetValue(value, unit);
}
public Distance(decimal value, Enum unit) {
SetValue(value, unit);
}
#endregion
///
/// Provides a string representation of this amount of distance, as a number plus the unit label.
///
/// A string with the number of units and unit type making up this instance.
public override string ToString() {
return InternalValue.ToString("#,##0.00") + " " + (nativeType == UnitSystem.Metric ? nativeMetricType.ToString() : nativeImperialType.ToString());
}
#region Conversion functions
///
/// Converts between imperial units of distance.
///
/// A value to be converted.
/// The unit the value should be converted from ( eg feet ).
/// The unit hte value should be converted to ( eg miles ).
/// A decimal containing the equivelant value in the new format.
public static decimal Convert(decimal value, ImperialUnits fromUnit, ImperialUnits toUnit) {
if (fromUnit == toUnit)
return value;
#region Inches
if (fromUnit == ImperialUnits.Inch) {
if (toUnit == ImperialUnits.Foot)
return value / InchesPerFoot;
if (toUnit == ImperialUnits.Yard)
return value / InchesPerFoot / FeetPerYard;
if (toUnit == ImperialUnits.Mile)
return value / InchesPerFoot / FeetPerMile;
if (toUnit == ImperialUnits.NauticalMile)
return Convert(value / InchesPerFoot / FeetPerMile, ImperialUnits.Mile, MetricUnits.Meter) / MetersPerNauticalMile;
}
#endregion
#region Feet
if (fromUnit == ImperialUnits.Foot) {
if (toUnit == ImperialUnits.Inch)
return value * InchesPerFoot;
if (toUnit == ImperialUnits.Yard)
return value / FeetPerYard;
if (toUnit == ImperialUnits.Mile)
return value / FeetPerMile;
if (toUnit == ImperialUnits.NauticalMile)
return Convert(value / FeetPerMile, ImperialUnits.Mile, MetricUnits.Meter) / MetersPerNauticalMile;
}
#endregion
#region Yards
if (fromUnit == ImperialUnits.Yard) {
if (toUnit == ImperialUnits.Inch)
return value * FeetPerYard * InchesPerFoot;
if (toUnit == ImperialUnits.Foot)
return value * FeetPerYard;
if (toUnit == ImperialUnits.Mile)
return value / YardsPerMile;
if (toUnit == ImperialUnits.NauticalMile)
return Convert(value / YardsPerMile, ImperialUnits.Mile, MetricUnits.Meter) / MetersPerNauticalMile;
}
#endregion
#region Miles
if (fromUnit == ImperialUnits.Mile) {
if (toUnit == ImperialUnits.Inch)
return value * FeetPerMile * InchesPerFoot;
if (toUnit == ImperialUnits.Foot)
return value * FeetPerMile;
if (toUnit == ImperialUnits.Yard)
return value * FeetPerMile / FeetPerYard;
if (toUnit == ImperialUnits.NauticalMile)
return Convert(value, ImperialUnits.Mile, MetricUnits.Meter) / MetersPerNauticalMile;
}
#endregion
#region Nautical miles
if (fromUnit == ImperialUnits.NauticalMile) {
if (toUnit == ImperialUnits.Inch)
return Convert(value * MetersPerNauticalMile, MetricUnits.Meter, ImperialUnits.Inch);
if (toUnit == ImperialUnits.Foot)
return Convert(value * MetersPerNauticalMile, MetricUnits.Meter, ImperialUnits.Inch) / InchesPerFoot;
if (toUnit == ImperialUnits.Yard)
return Convert(value * MetersPerNauticalMile, MetricUnits.Meter, ImperialUnits.Inch) / InchesPerFoot / FeetPerYard;
if (toUnit == ImperialUnits.Mile)
return Convert(value * MetersPerNauticalMile, MetricUnits.Meter, ImperialUnits.Inch) / InchesPerFoot / FeetPerYard / YardsPerMile;
}
#endregion
return decimal.MinValue;
}
///
/// Converts between metric units of distance.
///
/// A value to be converted.
/// The unit the value should be converted from ( eg meters ).
/// The unit hte value should be converted to ( eg kilometers ).
/// A decimal containing the equivelant value in the new format.
public static decimal Convert(decimal value, MetricUnits fromUnit, MetricUnits toUnit) {
if (fromUnit == toUnit)
return value;
if (fromUnit == MetricUnits.Millimeter) {
if (toUnit == MetricUnits.Centimeter)
return value / CentimetersPerMillimeter;
if (toUnit == MetricUnits.Decimeter)
return (value / CentimetersPerMillimeter) * CentimetersPerDecimeter;
if (toUnit == MetricUnits.Meter)
return (value / CentimetersPerMillimeter) * CentimetersPerMeter;
if (toUnit == MetricUnits.Kilometer)
return (value / CentimetersPerMillimeter) * CentimetersPerKilometer;
}
if (fromUnit == MetricUnits.Centimeter) {
if (toUnit == MetricUnits.Millimeter)
return value * CentimetersPerMillimeter;
if (toUnit == MetricUnits.Decimeter)
return value * CentimetersPerDecimeter;
if (toUnit == MetricUnits.Meter)
return value * CentimetersPerMeter;
if (toUnit == MetricUnits.Kilometer)
return value * CentimetersPerKilometer;
}
if (fromUnit == MetricUnits.Decimeter) {
if (toUnit == MetricUnits.Millimeter)
return value / CentimetersPerDecimeter * CentimetersPerMillimeter;
if (toUnit == MetricUnits.Centimeter)
return value / CentimetersPerDecimeter;
if (toUnit == MetricUnits.Meter)
return value / CentimetersPerDecimeter * CentimetersPerMeter;
if (toUnit == MetricUnits.Kilometer)
return value / CentimetersPerDecimeter * CentimetersPerKilometer;
}
if (fromUnit == MetricUnits.Meter) {
if (toUnit == MetricUnits.Millimeter)
return (value / CentimetersPerMeter) * CentimetersPerMillimeter;
if (toUnit == MetricUnits.Centimeter)
return value / CentimetersPerMeter;
if (toUnit == MetricUnits.Decimeter)
return (value / CentimetersPerMeter) * CentimetersPerDecimeter;
if (toUnit == MetricUnits.Kilometer)
return (value / CentimetersPerMeter) * CentimetersPerKilometer;
}
if (fromUnit == MetricUnits.Kilometer) {
if (toUnit == MetricUnits.Millimeter)
return (value * CentimetersPerMillimeter) / CentimetersPerKilometer;
if (toUnit == MetricUnits.Centimeter)
return value / CentimetersPerKilometer;
if (toUnit == MetricUnits.Decimeter)
return (value * CentimetersPerDecimeter) / CentimetersPerKilometer;
if (toUnit == MetricUnits.Meter)
return (value * CentimetersPerMeter) / CentimetersPerKilometer;
}
return decimal.MinValue;
}
///
/// Converts from a imperial to a metric unit of distance.
///
/// A value to be converted.
/// The unit the value should be converted from ( eg miles ).
/// The unit hte value should be converted to ( eg kilometers ).
/// A decimal containing the equivelant value in the new format.
public static decimal Convert(decimal value, ImperialUnits fromUnit, MetricUnits toUnit) {
decimal miles = fromUnit == ImperialUnits.Mile ? value : Convert(value, fromUnit, ImperialUnits.Mile);
decimal kms = miles / MilesPerKilometer;
return Convert(kms, MetricUnits.Kilometer, toUnit);
}
///
/// Converts from a metric to a imperial unit of distance.
///
/// A value to be converted.
/// The unit the value should be converted from ( eg kilometers ).
/// The unit hte value should be converted to ( eg miles ).
/// A decimal containing the equivelant value in the new format.
public static decimal Convert(decimal value, MetricUnits fromUnit, ImperialUnits toUnit) {
decimal kms = fromUnit == MetricUnits.Kilometer ? value : Convert(value, fromUnit, MetricUnits.Kilometer);
decimal miles = kms * MilesPerKilometer;
return Convert(miles, ImperialUnits.Mile, toUnit);
}
///
/// Converts between units of distance, without needing to be told explicitly which measurement type each
/// unit falls into. For example, this method can convert feet to yards, or feet to meters.
///
/// A value to be converted.
/// The unit the value should be converted from ( eg meters ).
/// The unit hte value should be converted to ( eg kilometers ).
/// A decimal containing the equivelant value in the new format.
public static decimal Convert(decimal value, Enum fromUnit, Enum toUnit) {
if (fromUnit == toUnit)
return value;
if (fromUnit is ImperialUnits && toUnit is ImperialUnits)
return Convert(value, (ImperialUnits)fromUnit, (ImperialUnits)toUnit);
if (fromUnit is MetricUnits && toUnit is MetricUnits)
return Convert(value, (MetricUnits)fromUnit, (MetricUnits)toUnit);
if (fromUnit is ImperialUnits && toUnit is MetricUnits)
return Convert(value, (ImperialUnits)fromUnit, (MetricUnits)toUnit);
if (fromUnit is MetricUnits && toUnit is ImperialUnits)
return Convert(value, (MetricUnits)fromUnit, (ImperialUnits)toUnit);
if (!(fromUnit is ImperialUnits) && !(fromUnit is MetricUnits))
throw new ArgumentException("All unit type enumerations must be either ImperialUnits or MetricUnits.", "fromUnit");
if (!(toUnit is ImperialUnits) && !(toUnit is MetricUnits))
throw new ArgumentException("All unit type enumerations must be either ImperialUnits or MetricUnits.", "toUnit");
throw new ArgumentException("All unit type enumerations must be either ImperialUnits or MetricUnits.");
}
///
/// Converts the InternalValue of one Distance object (d2) to match another Distance object's (d1's) unit-type, and returns the corresponding value.
///
/// The Distance object to match.
/// The Distance object to convert from.
/// The distance represented by d2, in terms of d1's internal unit of measure.
public static decimal Normalize(Distance d1, Distance d2) {
if (d1.InternalUnitType == UnitSystem.Metric && d2.InternalUnitType == UnitSystem.Metric)
return Distance.Convert(d2.InternalValue, d2.InternalMetricUnit, d1.InternalMetricUnit);
else if (d1.InternalUnitType == UnitSystem.Imperial && d2.InternalUnitType == UnitSystem.Imperial)
return Distance.Convert(d2.InternalValue, d2.InternalImperialUnit, d1.InternalImperialUnit);
else if (d1.InternalUnitType == UnitSystem.Metric && d2.InternalUnitType == UnitSystem.Imperial)
return Distance.Convert(d2.InternalValue, d2.InternalImperialUnit, d1.InternalMetricUnit);
else if (d1.InternalUnitType == UnitSystem.Imperial && d2.InternalUnitType == UnitSystem.Metric)
return Distance.Convert(d2.InternalValue, d2.InternalMetricUnit, d1.InternalImperialUnit);
return 0; // This code will never get to execute, but is needed without turning the last else if into just an else.
}
#endregion
#region Set value without having to call the property or create a new instance
///
/// Allows the user to set a distance value for a imperial unit of measure, without having to call the specific property representing that unit.
///
/// The distance as a raw number.
/// The unit the distance should be represented in.
public void SetValue(decimal value, ImperialUnits unit) {
nativeType = UnitSystem.Imperial;
nativeImperialType = unit;
nativeValue = value;
}
///
/// Allows the user to set a distance value for a metric unit of measure, without having to call the specific property representing that unit.
///
/// The distance as a raw number.
/// The unit the distance should be represented in.
public void SetValue(decimal value, MetricUnits unit) {
nativeType = UnitSystem.Metric;
nativeMetricType = unit;
nativeValue = value;
}
///
/// Allows the user to set a distance value for a imperial or metric unit of measure, without having to call the specific property representing that unit.
///
/// The distance as a raw number.
/// The unit the distance should be represented in.
public void SetValue(decimal value, Enum unit) {
if (unit is ImperialUnits)
SetValue(value, (ImperialUnits)unit);
else if (unit is MetricUnits)
SetValue(value, (MetricUnits)unit);
else
throw new ArgumentException("The unit must be a member of the ImperialUnits or MetricUnits enumeration.");
}
#endregion
#region Properties
#region Imperial
///
/// Gets or sets the distance being described in terms of inches ( in ).
///
public decimal Inches {
get {
if (nativeType == UnitSystem.Imperial && nativeImperialType == ImperialUnits.Inch)
return nativeValue;
if (nativeType == UnitSystem.Imperial)
return Distance.Convert(nativeValue, nativeImperialType, ImperialUnits.Inch);
return Distance.Convert(nativeValue, nativeMetricType, ImperialUnits.Inch);
}
set {
SetValue(value, ImperialUnits.Inch);
}
}
///
/// Gets or sets the distance being described in terms of feet ( ft ).
///
public decimal Feet {
get {
if (nativeType == UnitSystem.Imperial && nativeImperialType == ImperialUnits.Foot)
return nativeValue;
if (nativeType == UnitSystem.Imperial)
return Distance.Convert(nativeValue, nativeImperialType, ImperialUnits.Foot);
return Distance.Convert(nativeValue, nativeMetricType, ImperialUnits.Foot);
}
set {
SetValue(value, ImperialUnits.Inch);
}
}
///
/// Gets or sets the distance being described in terms of yards.
///
public decimal Yards {
get {
if (nativeType == UnitSystem.Imperial && nativeImperialType == ImperialUnits.Yard)
return nativeValue;
if (nativeType == UnitSystem.Imperial)
return Distance.Convert(nativeValue, nativeImperialType, ImperialUnits.Yard);
return Distance.Convert(nativeValue, nativeMetricType, ImperialUnits.Yard);
}
set {
SetValue(value, ImperialUnits.Yard);
}
}
///
/// Gets or sets the distance being described in terms of miles ( mi ).
///
public decimal Miles {
get {
if (nativeType == UnitSystem.Imperial && nativeImperialType == ImperialUnits.Mile)
return nativeValue;
if (nativeType == UnitSystem.Imperial)
return Distance.Convert(nativeValue, nativeImperialType, ImperialUnits.Mile);
return Distance.Convert(nativeValue, nativeMetricType, ImperialUnits.Mile);
}
set {
SetValue(value, ImperialUnits.Mile);
}
}
///
/// Gets or sets the distance being described in terms of nautical miles ( nm ).
///
public decimal NauticalMiles {
get {
if (nativeType == UnitSystem.Imperial && nativeImperialType == ImperialUnits.NauticalMile)
return nativeValue;
if (nativeType == UnitSystem.Imperial)
return Distance.Convert(nativeValue, nativeImperialType, ImperialUnits.NauticalMile);
return Distance.Convert(nativeValue, nativeMetricType, ImperialUnits.NauticalMile);
}
set {
SetValue(value, ImperialUnits.NauticalMile);
}
}
#endregion
#region Metric
///
/// Gets or sets the distance being described in terms of millimeters ( mm ).
///
public decimal Millimeters {
get {
if (nativeType == UnitSystem.Metric && nativeMetricType == MetricUnits.Millimeter)
return nativeValue;
if (nativeType == UnitSystem.Metric)
return Distance.Convert(nativeValue, nativeMetricType, MetricUnits.Millimeter);
return Distance.Convert(nativeValue, nativeImperialType, MetricUnits.Millimeter);
}
set {
SetValue(value, MetricUnits.Millimeter);
}
}
///
/// Gets or sets the distance being described in terms of cenimeters ( cm ).
///
public decimal Cenimeters {
get {
if (nativeType == UnitSystem.Metric && nativeMetricType == MetricUnits.Centimeter)
return nativeValue;
if (nativeType == UnitSystem.Metric)
return Distance.Convert(nativeValue, nativeMetricType, MetricUnits.Centimeter);
return Distance.Convert(nativeValue, nativeImperialType, MetricUnits.Centimeter);
}
set {
SetValue(value, MetricUnits.Centimeter);
}
}
///
/// Gets or sets the distance being described in terms of decimeters.
///
public decimal Decimeters {
get {
if (nativeType == UnitSystem.Metric && nativeMetricType == MetricUnits.Decimeter)
return nativeValue;
if (nativeType == UnitSystem.Metric)
return Distance.Convert(nativeValue, nativeMetricType, MetricUnits.Decimeter);
return Distance.Convert(nativeValue, nativeImperialType, MetricUnits.Decimeter);
}
set {
SetValue(value, MetricUnits.Decimeter);
}
}
///
/// Gets or sets the distance being described in terms of meters ( m ).
///
public decimal Meters {
get {
if (nativeType == UnitSystem.Metric && nativeMetricType == MetricUnits.Meter)
return nativeValue;
if (nativeType == UnitSystem.Metric)
return Distance.Convert(nativeValue, nativeMetricType, MetricUnits.Meter);
return Distance.Convert(nativeValue, nativeImperialType, MetricUnits.Meter);
}
set {
SetValue(value, MetricUnits.Meter);
}
}
///
/// Gets or sets the distance being described in terms of kilometers ( km ).
///
public decimal Kilometers {
get {
if (nativeType == UnitSystem.Metric && nativeMetricType == MetricUnits.Kilometer)
return nativeValue;
if (nativeType == UnitSystem.Metric)
return Distance.Convert(nativeValue, nativeMetricType, MetricUnits.Kilometer);
return Distance.Convert(nativeValue, nativeImperialType, MetricUnits.Kilometer);
}
set {
SetValue(value, MetricUnits.Kilometer);
}
}
#endregion
#region Internal values
///
/// Gets the internal value being used by this instance of the Distance object. This is an advanced property, and is being exposed
/// to allow large sums to be computed without introducing rounding errors which would be forced by the need to convert each item to
/// a common unit.
///
public decimal InternalValue {
get {
return nativeValue;
}
}
///
/// Gets the type of units ( imperial or metric ) being used internally by this Distance object.
///
public UnitSystem InternalUnitType {
get {
return nativeType;
}
}
///
/// Returns the unit of measure being used natively as an Enum, so that client code will not have to use if statements while creating or comparing instances.
///
public Enum InternalUnit {
get {
if (nativeType == UnitSystem.Metric)
return nativeMetricType;
return nativeImperialType;
}
}
///
/// Gets the metric unit being used internally by this Distance object, if metric is being used.
///
public MetricUnits InternalMetricUnit {
get {
return nativeMetricType;
}
}
///
/// Gets the imperial unit being used internally by this Distance object, if imperial is being used.
///
public ImperialUnits InternalImperialUnit {
get {
return nativeImperialType;
}
}
#endregion
#endregion
#region Operator overloading
///
/// Adds two Distance objects together, and returns the result, after correcting for different units of measure.
///
/// The first Distance object to add.
/// The second Distance object to add.
/// A new Distance object, with the combined total of value of the two parts.
public static Distance operator +(Distance d1, Distance d2) {
return new Distance(d1.InternalValue + Distance.Normalize(d1, d2), d1.InternalUnit);
}
///
/// Subtracts two Distance objects together, and returns the result, after correcting for different units of measure.
///
/// The first Distance object to subtract.
/// The second Distance object to subtract.
/// A new Distance object, with the combined total difference in value of the two points.
public static Distance operator -(Distance d1, Distance d2) {
return new Distance(d1.InternalValue - Distance.Normalize(d1, d2), d1.InternalUnit);
}
///
/// Determines whether a Distance object is less than another Distance object.
///
/// The first object to use in the comparison.
/// The second object to use in the comparison.
/// True if the first object (d1) is less than the second object (d2); false otherwise.
public static bool operator <(Distance d1, Distance d2) {
return d1.InternalValue < Distance.Normalize(d1, d2);
}
///
/// Determines whether a Distance object is greater than another Distance object.
///
/// The first object to use in the comparison.
/// The second object to use in the comparison.
/// True if the first object (d1) is greater than the second object (d2); false otherwise.
public static bool operator >(Distance d1, Distance d2) {
return d1.InternalValue > Distance.Normalize(d1, d2);
}
#endregion
}
}