Converting GIS spatial coordinates

Converting GIS spatial coordinates

Different formats and standards exist for describing geographical coordinates in GIS systems and applications. This article explains how to convert between the most used formats, presenting a working library written in C#.

 

Many coordinate systems

I have been playing with GIS systems recently, and a thing that fascinated me, in both good and bad way, is the existence of several types of spatial coordinate systems to express a position of a place on the globe. For those familiar with OGC-compliant GIS systems, you may know that the spatial_ref_sys metadata table holds conversion data to allow conversions from one coordinate system to another.

Each entry in this table contains specific information such as units of measurement, where the origin is located, and even the starting offset of a measurement. Most of us are familiar with seeing a coordinate pair such as this:

54.852726, -1.832299

If you have a GPS built into your mobile phone, fire it up and watch the display. You’ll see something similar to this coordinate pair. Note that on some devices and apps, the coordinates may be swapped.

This coordinate pair is known as latitude and longitude. The first number, latitude, is the degrees north or south from the equator with north being positive and south being negative.

The second number, longitude, is the degrees east or west of the Prime Meridian with west being negative and east being positive. The correct geospatial name for this coordinate system is WGS84, generally known as World Geodetic System.

Most commercial GPS applications and devices, however, use a different system for expressing geo coordinates, called National Marine Electronics Association, more specifically NMEA 0183. This system expresses latitude and longitude as a combination of degrees, minutes and seconds:

5321.5802 N, 00630.3372 W

The format of the string is DDMM.mmmm for the latitude (vertical) direction and DDDMM.mmmm for the longitude (horizontal) direction.

Starting with the latitude measurement in the string, the first two digits are the number of degrees, and the remaining numbers are the minutes. The numbers after the decimal point are fractions of a minute. This gives us:

Latitude: 53 degrees, 21.5802 minutes north

For the longitude measurement, the first three digits are the number of degrees, and the remaining digits are the minutes. All the numbers after the decimal are fractions of a minute.

This gives us:

Longitude: 6 Degrees, 30.3372 minutes west

Because this data is string data, it’s essentially an exercise in cutting the string at specific points to derive the values you want. Once you have them, the math to convert them to the more familiar latitude and longitude format (if you remember that was WGS84) is very simple.

 

GPS applications use the NMEA 0183 coordinate system

 

Converting coordinates

I have implemented latitude and longitude as a class, GeoCoordinates, and have exposed methods for system conversion, which include parsing and formatting strings in the different formats.

First, we need to separate the first two digits from the latitude string and the first three from the longitude. This gives us the following:

53 and 21.5802 for the north direction

006 and 30.3372 for west

Because there are 60 minutes in a degree, we must divide the minutes digits by sixty to find what fraction of a degree they are, and then combine them with our whole degrees. So, for our latitude:

53 + (21.5812 / 60) will give us 53.359686 degrees.

And for our longitude:

6 + (30.3372 / 60) will give us 6.505620 degrees.

To finish the conversion, we need to apply the north and west directions as positive or negative numbers. The easiest way to manage which directions are positive or negative is to change any west or south measurements to negative. So with our numbers, the final coordinates in WGS84 latitude and longitude are:

53.359686, -6.505620

Let’s see how this translates into C# code.

Latitude and Longitude are decimal properties of the GeoCoordinates class. I decided to use decimal as data type as it offers higher accuracy and is less prone to loss of precision in mathematical calculations, as compared to the floating-point data types float and double.

Latitude extends from the equator (0 degrees) to the North Pole (90 degrees) or the South Pole (-90 degrees). Longitude extends from the Greenwich meridian to the right-hand side (East) for 180 degrees, and to the left-hand side (West) for 180 degrees. Therefore, I have enforced range validation on setting the properties.

private decimal _latitude;

public decimal Latitude

{

   get

   {

      return _latitude;

   }

   set

   {

      if (value < -90.0M || value > 90.0M)

      {

          throw new ArgumentOutOfRangeException("Latitude must be between -90.0 and 90.0.");

      }

  

       _latitude = value;

    }

}

 

private decimal _longitude;

public decimal Longitude

{

   get

   {

       return _longitude;

   }

   set

   {

       if (value < -180.0M || value > 180.0M)

       {

           throw new ArgumentOutOfRangeException("Longitude must be between -180.0 and 180.0.");

       }

  

       _longitude = value;

   }

}

Latitude and Longitude as C# properties

 

The GeoCoordinates class expresses coordinates in WGS84 format (that is, decimal values). We want to convert coordinates from the format in use in GPS applications (NMEA 0183), which is expressed in degrees and minutes. For example, our hypothetical application would have a simple instruction like the following one:

string degrees = "5321.5802 N, 00630.3372 W";

GeoCoordinates coords = GeoCoordinates.FromNMEA0183(degrees);

 

The static function FromNMEA0183 converts the string representation of coordinates into its decimal elements.

public static GeoCoordinates FromNMEA0183(string degrees)

{

   if (string.IsNullOrEmpty(degrees))

   {

      throw new ArgumentNullException(nameof(degrees));

   }

  

   string[] coords = degrees.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

  

   if (coords.Length < 2)

   {

      throw new ArgumentException("Invalid degree coordinates.", nameof(degrees));

   }

 

   return new GeoCoordinates

   {

      Latitude = ParseLatitude(coords[0].Trim()),

      Longitude = ParseLongitude(coords[1].Trim())

   };

}

The method to convert from textual to decimal representation of coordinates

 

After some obvious validation, the coordinates’ string is split into its two elements of latitude and longitude, separated by comma, and each element is then parsed separately. ParseLatitude and ParseLongitude are very similar methods, but I decided to implement them individually to avoid confusing “if” conditions to handle either element of the geo coordinates. I’ll describe the code for ParseLatitude in this article, the full source code is available on CodePlex.

private static decimal ParseLatitude(string coords)

{

    if (!coords.EndsWith("N", StringComparison.OrdinalIgnoreCase) && !coords.EndsWith("S", StringComparison.OrdinalIgnoreCase))

    {

        throw new ArgumentException("Latitude coordinate not found.");

    }

 

    if (coords.Length < 4)

    {

        throw new ArgumentException("Invalid latitude format.");

    }

   

    int dd = 0;

    try

    {

        dd = int.Parse(coords.Substring(0, 2));

        if (dd > 90)

        {

            throw new ArgumentOutOfRangeException();

        }

    }

    catch when (dd > 90)

    {

        throw new ArgumentOutOfRangeException("Degrees in latitude cannot exceed 90.");

    }

    catch

    {

        throw new ArgumentException("Invalid degrees format in latitude.");

    }

   

    double mm = 0.0D;

    try

    {

        string minutes = Regex.Match(coords.Substring(2), @"(\d+).(\d+)").Value;

        mm = double.Parse(minutes);

        if ((dd == 90 && mm > 0.0D) || mm >= 60.0D)

        {

            throw new ArgumentOutOfRangeException();

        }

    }

    catch when (dd == 90 && mm > 0.0D)

    {

        throw new ArgumentOutOfRangeException("Degrees in latitude cannot exceed 90.");

    }

    catch when (mm >= 60.0D)

    {

        throw new ArgumentOutOfRangeException("Minutes in latitude cannot exceed 60.");

    }

    catch

    {

        throw new ArgumentException("Invalid minutes format in latitude.");

    }

    

    decimal latitude = Convert.ToDecimal(dd + mm / 60);

    if (coords.EndsWith("S", StringComparison.OrdinalIgnoreCase))

    {

        latitude = decimal.Negate(latitude);

    }

 

    return latitude;

}

The method to parse the latitude coordinate

 

There is a lot of validation in place, as you would expect, to make sure the input string is in the expected format. The process of conversion of coordinates, basically, takes places into three steps:

1.     Read the degrees portion and convert into an integer number; if the number is higher than 90, throw an out-of-range exception.

dd = int.Parse(coords.Substring(0, 2));

if (dd > 90)

{

    throw new ArgumentOutOfRangeException();

}

 

2.     Read the minutes portion, including decimals, and convert into a double number; minutes are read after extracting them from the string representation, by taking out the initial two digits for degrees, and any trailing non-numeric signs (for N or S). Range validation is also applied.

string minutes = Regex.Match(coords.Substring(2), @"(\d+).(\d+)").Value;

mm = double.Parse(minutes);

if ((dd == 90 && mm > 0.0D) || mm >= 60.0D)

{

    throw new ArgumentOutOfRangeException();

}

 

3.     Put all together by adding the minutes to the degrees and converting into decimal; sign is also applied depending on the direction of the latitude (North is positive, South is negative).

decimal latitude = Convert.ToDecimal(dd + mm / 60);

if (coords.EndsWith("S", StringComparison.OrdinalIgnoreCase))

{

    latitude = decimal.Negate(latitude);

}

 

Exactly the same logic applies to parsing longitude, with the obvious difference on the input format.

 

Printing coordinates

After parsing input values, we now want to produce something in output, specifically represent coordinates in either format NMEA0183 or WGS84. Code-wise, we need something to print out coordinates as a string, for example:

Console.WriteLine("NMEA0183: {0}", coords.ToString(GeoCoordinates.NMEA0183));

Console.WriteLine("WGS84: {0}", coords.ToString(GeoCoordinates.WGS84));

 

Let’s start from defining two simple constant values that will help us identifying which format to apply. I defined these two constants as string, but an enum would suffice as well.

public const string NMEA0183 = "NMEA0183";

public const string WGS84 = "WGS84";

 

Then we need to implement a ToString method with an input parameter to specify which format to apply:

public string ToString(string format)

{

    switch (format)

    {

        case NMEA0183:

            return FormatNMEA0183();

 

        case WGS84:

            return FormatWGS84();

 

        default:

            throw new ArgumentOutOfRangeException("Invalid format specified.", nameof(format));

    }

}

 

The FormatNMEA0183 is pretty straightforward, with the latitude and longitude components formatted individually. As an output, we want to obtain a string with the latitude and longitude coordinates separated by comma, as in 5321.5802 N, 00630.3372 W

private string FormatNMEA0183()

{

    string latitude = FormatLatitudeDegrees(this.Latitude);

    string longitude = FormatLongitudeDegrees(this.Longitude);

 

    return $"{latitude}, {longitude}";

}

 

To format latitude, we need to convert from a decimal number into DDMM.mmmm, e.g. 53.359686 à 5321.5802 N

private string FormatLatitudeDegrees(decimal latitude)

{

    string sign = latitude > 0 ? "N" : "S";

 

    latitude = Math.Abs(latitude);

    string dd = decimal.Truncate(latitude).ToString("0#");

    string mm = (decimal.Subtract(latitude, decimal.Truncate(latitude)) * 60).ToString("0#.0000");

 

    return $"{dd}{mm} {sign}";

}

 

To format longitude, we need to convert from a decimal number into DDDMM.mmmm, e.g. -6.505620 à 00630.3372 W

private string FormatLongitudeDegrees(decimal longitude)

{

    string sign = longitude > 0 ? "E" : "W";

 

    longitude = Math.Abs(longitude);

    string dd = decimal.Truncate(longitude).ToString("00#");

    string mm = (decimal.Subtract(longitude, decimal.Truncate(longitude)) * 60).ToString("0#.0000");

 

    return $"{dd}{mm} {sign}";

}

 

Formatting coordinates into WGS84 is even easier, as implemented in the FormatWGS84 method; all we need to do is to combine latitude and longitude in a comma-separated string, e.g. 53.359686, 6.505620.

private string FormatWGS84()

{

    return $"{this.Latitude}, {this.Longitude}";

}

 

 


  Comments

 

 Source Code

Project Name: GisSpatial


 Related Content
A flexible Default Value for your DateTime properties
When creating an MVC application with Entity Framework, it is possible to set default values for most properties in a model using the DefaultValue attribute. However, no much flexibility is offered for a DateTime property. This article presents a custom validation attribute for DateTime types that accepts different formats for defining the default value of the property.
Adding a Secured Geo-located Audit Trail
How I built a social sharing component for my own web site and added a secured geo-located audit trail. Step by step, let’s analyse technologies and source code for developing this component.
Adding Social Sharing to a Web Site
How I built a social sharing component for my own web site and added a secured geo-located audit trail. Step by step, let’s analyse technologies and source code for developing this component.
Best practices for mobile form design in SharePoint
Build effective SharePoint forms with Nintex that are accessible anywhere, at any time, and on any device. You built the workflows, you built the forms, now make them mobile.
Bring your “A” game to ESPC16
With just over 3 weeks to go to Europe's largest gathering of SharePoint & Office 365 professionals, take a look at these tips that will help you get the most out of ESPC16…
Building an MVC application for SharePoint
Learn how to write code to perform basic operations with the SharePoint 2013 .NET Framework client-side object model (CSOM), and build an ASP.NET MVC application that retrieves information from a SharePoint server.
CIO vs CTO
What are the synergies and differences of the roles of a Chief Information Officer and a Chief Technology Officer? An open conversation about two roles with one mission…
Coded UI test automation of MVC applications with Visual Studio
Whether you are a software developer, tester, administrator or analyst, this article can help you master different types of UI testing of an MVC application, by using Visual Studio for creating coded UI test suites that can be automated for continuous execution.
Creating mobile accessible forms in SharePoint
With the release of the Nintex Mobile apps, SharePoint users can now optimise their experience across popular mobile devices and platforms.
Define your Performance Testing strategy with Visual Studio
Performance Testing is an essential part of software testing, with the specific goal of determining how a system performs in terms of responsiveness and stability under a particular workload. In this series of posts we’ll define and execute a good strategy for testing performance of an application using Visual Studio.
Disserting about colliding GUIDs and the Big Bang Theory
Can you generate two identical GUIDs? Would the world end if two GUIDs collide? How long does it take to generate a duplicate GUID and would we be still here when the result is found?
GIS Location Services in Dynamics CRM
A design paper about implementing GIS-based services for a local Council in Dynamics CRM, structuring address data, and delivering location services in the form of WebAPI endpoints via an Enterprise Service Bus.
Group or Team?
All teams are groups but not all groups are teams. What defines a group and what a team? When do we need one over the other one?
How to Give Constructive Feedback
Learning to give and receive constructive feedback is an essential part of learning, growing, improving and achieving our goals.
Mirroring an iPad on your laptop
Have you ever wanted to see your iPhone or iPad on a larger screen? Play games, watch movies, demo apps or present to your computer from your iPhone or iPad. Reflector mirrors iOS devices on big screens wirelessly using iOS built-in AirPlay mirroring.
Mobilize your SharePoint workflows
Build workflow applications in SharePoint that can be accessed on mobile devices using the Nintex solution for business process mobilization.
Natural String Sorting
Have you ever desired to have in your code a way to order a sequence of strings in the same way as Windows does for files whose name contains a mix of letters and numbers? Natural string sorting is not natively supported in .NET but can be easily implemented by specialising a string comparer and adding a few extensions to the enumerable string collection.
Sales Effectiveness in Dynamics CRM with Azure IoT and Machine Learning - Part 1
How can an organisation optimise its sales channels and product targeting by building a 365-degree view of its customers in Dynamics CRM? The answer, and topic of this article, is with the help of Azure IoT and Machine Learning services!
Scaling Applications with Azure Redis and Machine Learning - Part 1
This article presents design best practices and code examples for implementing the Azure Redis Cache and tuning the performance of ASP.NET MVC applications, optimising cache hit ratio and reducing “miss rate” with smart algorithms processed by Machine Learning.
Software Development Management in 11 steps
What it takes to be a great Software Development Manager? I have been building software for the last 15 years and have collected a few stories and experiences to share. Some I have used as questions when interviewing candidates. In 11 points, this is my story to date.
SOLID SharePoint apps with MVC
Practical code examples of ASP.NET MVC applications that connect to a SharePoint Server and comply with the SOLID principles.
The art of outsourcing projects
Outsourcing may look financially attractive, but working with companies in far-off lands introduces challenges that, if not considered properly, can drive any project to failure. Let’s explore some common pitfalls when working with offshore partners and a common-sense approach to work around them.
The value of hashtags
Customers expect a modern approach to advertising. Digital advertising can leverage evolving technology to provide just-in-time, just-at-the-right-place promotions.
We don't need no Yoda's syntax
There is an urban myth in the programmers’ community that the so called “Yoda’s syntax” performs better when checking an object for nullity. Let's demystify it...