Convert latitudes and longitudes into distances on the globe in C#

This example calculates distances between points on the globe. The formulas for performing those calculations is interesting but the program is also interesting for the ways it uses LINQ.

It stores information about several important cities around the globe in CityData objects. The CityData class has Name, Latitude, and Longitude fields. The following code shows the class's most interesting method, a constructor that initializes an object from a string of the form "Delhi 28°40'N 77°14'E."

// Initialize with data of the form: Delhi    28°40'N 77°14'E
// This format is available at:
// en.wikipedia.org/wiki/Latitude_and_longitude_of_cities,_I-P
public CityData(string data)
{
    // Find the first digit.
    Regex reg_exp = new Regex(@"\d");
    Match match = reg_exp.Match(data);
    int pos = match.Index;

    // Assign the name.
    Name = data.Substring(0, pos - 1).Trim();

    // Parse the latitude.
    data = data.Substring(pos);
    pos = data.IndexOf('°');
    double lat_degrees = double.Parse(data.Substring(0, pos));
    data = data.Substring(pos + 1);

    pos = data.IndexOf(''');
    double lat_minutes = double.Parse(data.Substring(0, pos));
    data = data.Substring(pos + 1);
    Latitude = lat_degrees + lat_minutes / 60;

    if (data.Substring(0, 1).ToUpper() == "S") Latitude = -Latitude;
    data = data.Substring(1).Trim();

    // Parse the latitude.
    pos = data.IndexOf('°');
    double long_degrees = double.Parse(data.Substring(0, pos));
    data = data.Substring(pos + 1);

    pos = data.IndexOf(''');
    double long_minutes = double.Parse(data.Substring(0, pos));
    data = data.Substring(pos + 1);
    Longitude = long_degrees + long_minutes / 60;
    if (data.Substring(0, 1).ToUpper() == "E") Longitude = -Longitude;
}

This code simply parses the string to find the city's name, latitude, and longitude.

The main program uses the following code to initialize a series of cities.

// Locations of known cities.
private CityData[] Cities =
{
    new CityData("Rome 	41°48?N 12°36?E"),
    new CityData("Tokyo 	35°40?N 139°45?E"),
    new CityData("Mexico City 	19°24?N 99°09?W"),
    ...
    new CityData("San Francisco 	37°47?N 122°26?W"),
};

The form's Load event handler uses the following code to make the form's two ComboBoxes display the city names.

// Load the list of cities.
private void Form1_Load(object sender, EventArgs e)
{
    var city_query =
        from CityData city_data in Cities
        orderby city_data.Name
        select city_data.Name;
    cboCityFrom.DataSource = city_query.ToArray();
    cboCityTo.DataSource = city_query.ToArray();
    cboCityTo.SelectedIndex = 1;
}

This code uses LINQ to list the cities' names in sorted order. It then sets the DataSource properties for both ComboBoxes to the result of the query. It finishes by selecting the cboCityTo control's second item (with index 1) so the two start with different cities selected.

When the user selects a city, the program copies the city's latitude and longitude into the corresponding TextBoxes. For example, the following code shows how the program gets information about the selected "To" city.

private void cboCityTo_SelectedIndexChanged(object sender, EventArgs e)
{
    // Find the selected city.
    var city_query =
        from CityData city_data in Cities
        where (city_data.Name == cboCityTo.Text)
        select city_data;

    // Display the latitude and longitude.
    CityData city = city_query.First();
    txtLatitudeTo.Text = city.Latitude.ToString();
    txtLongitudeTo.Text = city.Longitude.ToString();
}

This code makes a LINQ query to select the CityData object with Name matching the selected city's name. It gets the first matching object and displays its latitude and longitude. The code that executes when the user selects a "From" city is similar.

After all that setup code, the program uses the following code to calculate the distance between the selected cities.

// Calculate the distances.
private void btnCalculate_Click(object sender, EventArgs e)
{
    // Get the entered latitudes and longitudes.
    double lat_from = double.Parse(txtLatitudeFrom.Text);
    if (lat_from < 0) lat_from += 360;

    double lon_from = double.Parse(txtLongitudeFrom.Text);
    if (lon_from < 0) lon_from += 360;

    double lat_to = double.Parse(txtLatitudeTo.Text);
    if (lat_to < 0) lat_to += 360;

    double lon_to = double.Parse(txtLongitudeTo.Text);
    if (lon_to < 0) lon_to += 360;

    // Calculate the differences in latitude and longitude.
    double dlat = Math.Abs(lat_from - lat_to);
    if (dlat > 180) dlat = 360 - dlat;

    double dlon = Math.Abs(lon_from - lon_to);
    if (dlon > 180) dlon = 360 - dlon;

    // Flat Earth.
    txtMethod1.Text = FlatEarth(lat_from, lon_from, lat_to, lon_to).ToString();

    // Haversine.
    txtMethod2.Text = Haversine(lat_from, lon_from, lat_to, lon_to).ToString();
}

This code parses the latitudes and longitudes entered in the TextBoxes. It does a little processing to ensure that the latitudes and longitudes are between 0 and 360 degrees. It calculates the differences in latitude and longitude and makes sure those are no greater than 180 degrees. (For example, a difference of 330 degrees is the same as a difference of 30 degrees in the other direction.)

Next the code invokes the FlatEarth and Haversine methods to calculate the actual distances. The following code shows the FlatEarth method.

// Methods for calculating distances.
private const double EarthRadius = 3958.756;
private double FlatEarth(double lat1, double lon1, double lat2, double lon2)
{
    // Calculate the differences in latitude and longitude.
    double dlat = Math.Abs(lat1 - lat2);
    if (dlat > 180) dlat = 360 - dlat;

    double dlon = Math.Abs(lon1 - lon2);
    if (dlon > 180) dlon = 360 - dlon;

    double x = 69.1 * dlat;
    double y = 53.0 * dlon;
    return Math.Sqrt(x * x + y * y);
}

The FlatEarth method is basically the Pythagorean theorem with the X and Y values adjusted by reasonable scale factors. This method is only accurate for some parts of the globe.

The following code shows the Haversine method, which is more complicated but also more precise.

private double Haversine(double lat1, double lon1, double lat2, double lon2)
{
    double dlat = DegreesToRadians(lat2 - lat1);
    double dlon = DegreesToRadians(lon2 - lon1);
    double a = Math.Sin(dlat / 2) * Math.Sin(dlat / 2) +
        Math.Cos(DegreesToRadians(lat1)) *
        Math.Cos(DegreesToRadians(lat2)) *
        Math.Sin(dlon / 2) * Math.Sin(dlon / 2);
    return 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a)) * EarthRadius;
}

// Convert degrees into radians.
private double DegreesToRadians(double degrees)
{
    return degrees / 180 * Math.PI;
}

The code converts the latitude and longitudes from degrees into radians and then uses the Haversine formula to calculate the distances. (For more information on the Haversine formula, see Wikipedia and Wolfram MathWorld.

Generally the Haversine method is closer to the correct distance than the FlatEarth method. Here are some sample values (in miles).

FromToFlat EarthHaversineCorrect DistanceFlat Earth % ErrHaversine % Err
San DiegoLos Angeles1101121121.790.00
Los AngelesSan Francisco3403483472.020.29
San DiegoSan Francisco4494604581.970.44
BeijingBerlin55294571466818.442.08
MumbaiCanberra54456146623860.801.47

   

 

What did you think of this article?




Trackbacks
  • No trackbacks exist for this post.
Comments
  • No comments exist for this post.
Leave a comment

Submitted comments are subject to moderation before being displayed.

 Name

 Email (will not be published)

 Website

Your comment is 0 characters limited to 3000 characters.