13,663,987 members
Tip/Trick
alternative version

#### Stats

10.5K views
6 bookmarked
Posted 15 Jan 2016
Licenced CPOL

# Converting Geographic Coordinates

, 15 Jan 2016
Geographic coordinates from decimal degree to degrees, minutes & seconds. And vice versa.

## Introduction

Geodetic conventions and conversions are a prime example of: "why do it simple, if you can do it difficult?". Nonetheless, we have to live with conventions and therefore this tip & trick on the representation of geographic coordinates (a.k.a. geographicals or latitude/longitude or latlons). In short, latlons are the spherical coordinates of a location on earth. If you zoom in to the location, a latitude is a sort of Y coordinate and longitude a sort of X coordinate. Latlons are commonly given in degrees of an arc with a circle divided in 360 degrees. But because we do not like it simple, the earth's coordinate system ranges from -180o to 180o west to east and 90o to -90o north to south. Representations commonly are decimal degrees (D) e.g. 56.36782361 or degrees minutes and (decimal) seconds (DMS) e.g. 56o 56' 34.2561". The minus sign can be replaced by hemisphere indications N(orth, S(outh), W(est) or E(ast). So: `-127.5 = -127<sup>o</sup> 30' 00" = 127<sup>o</sup> 30' 00" W`. Converting between the representations is not difficult but has some quirks.

## Background

In this tip/trick, I'll discuss a VB.NET class I wrote to easily convert between the different notations. The full class is attached and can be downloaded. The core of this class is not from me but written by Mario Vernari (2011) in C# (I think) and can be found here. I ported this class to VB.NET, added more 'quirk' handling and extended the functionality. I ported this class to VB.NET, added more 'quirk' handling and extended the functionality. In pseudo code, the conversion is simple:

```DMS(127o 30' 00") = 127 + (30/60) + (0/3600) = DEC(127.5)
and
DEC(127.5) = D(floor(127.5))o M(floor(fraction of deg*60))' S(fraction of min*60)"```

The quirks are in two things:

1. handling of negative coordinates. This is done pretty good by Vernari's code.
2. handling of precision. As a rule of thumb, I use 8 decimals for a degree, 6 decimals for a minute and 4 decimals for a second. This is precise enough for my work but may be not for yours; you can adjust this in the class. If I use a decimal degree of `127.9999999999` (ten decimals) and a precision of 8 decimals I convert it with the pseudo code to `127<sup>o</sup> 59' 60.0000"` in DMS notation. But this is wrong because it should be: `128<sup>o</sup> 00' 00.0000"`. This, precision dependent quirk is handled in my class.

## Discussion of the Code

The VB.NET class `LatLong` consists of a proper class and added Shared functions (as if part of a Module). This is quite a nifty construction I took over from Vernari as you will see later in the use of the class. The class starts off with:

```Imports System.ComponentModel

Public Class LatLong

Private _round4 As String = "0.0000"
Private _round6 As String = "0.000000"
Private _round8 As String = "0.00000000"

Public IsNegative As Boolean = False
Public DecimalDegrees As Double = 0
Public DecimalMinutes As Double = 0
Public DecimalSeconds As Double = 0
Public Degrees As Integer = 0
Public Minutes As Integer = 0
Public IsOk As Boolean = True
Public EpsilonDeg As Double = 0.0000000001

Public Enum CoordinateType
Longitude
Latitude
Undefined
End Enum

Public Enum CoordinateFormat
<Description("Deg.dec")> D
<Description("Deg Min.dec")> DM
<Description("Deg Min Sec.dec")> DMS
End Enum

Public Sub New()
End Sub

'rest of the code

End Class```

The `private` globals at the top define the three precision (discussed above) `string`s used to format the numbers. Then followed by the `public` properties of the class. Note that an instance of the class has all the number parts of a latlong that it can possibly consist of. So for degrees and minutes, there is an `Integer` and `Double` type. Seconds are always `Double`. The sign of the `latlong` is handled by the boolean `IsNegative`. And the threshold number to define if a double is zero or not is defined by `EpsilonDeg`, all coming back to the precision quirk. The `Enum`'s can control the type of `latlong` (e.g. `Longitude` ranges from `-90` to `90` and `Latitude` from `-180` to `180`) and the format they are represented in `D` for decimal degree, `DM` for decimal minute and `DMS` for decimal seconds (believe me, all kinds of variations occur in especially legal documents). The class is constructed without parameters.

The next section is the `Shared` part (i.e. functions which can be called without an instance of the class). I'll discuss here the functions `FromDegreeMinutesDecimalSeconds` and `Empty`. There are more functions in this section (i.e., `FromDegreeDecimalMinutes`, but they are analogous). In short, the function takes in the parameters degree, minute and second and does a minimal check if it fails in the earth's range (I do not make a distinction here between `Latitude` and `Longitude`; you could by passing the `CoordinateType` `enum`).

```#Region " Shared "

Public Shared Function FromDegreeMinutesDecimalSeconds(degree As Integer, _
minute As Integer, second As Double) As LatLong

Try
'checks, make sure they are in the range -180 to 180
If degree < -180 OrElse degree > 180 Then _
Throw New Exception("Latlong out of range -180 to 180")

Dim ll As New LatLong

If degree < 0 Then ll.IsNegative = True
degree = Math.Abs(degree)
minute = Math.Min(Math.Max(Math.Abs(minute),0),59)
second = Math.Min(Math.Max(Math.Abs(second),0),60-ll.EpsilonDeg)

ll.Degrees = CInt(Math.Floor(degree))
ll.Minutes = CInt(Math.Floor(minute))
ll.DecimalDegrees = degree + (minute/60) + (second/3600)
ll.DecimalMinutes = minute + (second/60)
ll.DecimalSeconds = second

Return ll
Catch
Return LatLong.Empty
End Try
End Function

Public Shared Function Empty() As LatLong
Dim em As New LatLong
em.IsOk = False
Return em
End Function

#End Region```

As can be seen, instance `ll` of the `LatLong` class is created and the parameters are first trimmed to their proper form: if the degree is negative, the class `IsNegative` boolean is set. Then, the absolute value of all parameters is taken and trimmed between the ranges they should have (e.g., `minute` between integers `0` and `59` and `second` between doubles `0` and `60-EpsilonDeg = 0` and `60-0.0000000001`). Next, the decimal degree and minute parts are calculated as described above. The instantiated class `ll` is fully defined and returned. If something fails, an empty `LatLong` class is return by function `Empty`. The reverse shared function `FromDecimalDegrees` looks like this:

```Public Shared Function FromDecimalDegree(decdeg As Double) As LatLong
Try
'checks, make sure they are in the range -180 to 180
If decdeg < -180 OrElse decdeg > 180 Then Throw _
New Exception("Latlong out of range -180 to 180")

Dim ll As New LatLong
Dim delta As Double = 0

If decdeg < 0 Then ll.IsNegative = True
decdeg = Math.Abs(decdeg)

'get the degree
ll.DecimalDegrees = decdeg
ll.Degrees = CInt(Math.Floor(ll.DecimalDegrees))
delta = ll.DecimalDegrees - ll.Degrees

'get the minutes
ll.DecimalMinutes = delta * 60
ll.Minutes = CInt(Math.Floor(ll.DecimalMinutes))
delta = ll.DecimalMinutes - ll.Minutes

'get the seconds
ll.DecimalSeconds = delta * 60

Return ll
Catch
Return LatLong.Empty
End Try
End Function```

Here again, a fully defined class is returned, only the calculation is different in the sense that it restores the DMS part of the decimal degree. Note that for finding the fraction, I use `Double delta`.

The following part of the class is the code belonging to a class instance. It mainly involves outputting the `LatLong` in the desired notation. As an example, I will discuss here the overridden function `ToString` and the normal function `ToStringDMS`. As can be seen, by simple `string` formatting, the desired notation is output. I have chosen the output of the `ToString` override to be the decimal degree because it is the least complex notation which can be easily converted to a `Double`.

```Public Overrides Function ToString() As String
If IsNegative Then
Return (-1 * DecimalDegrees).ToString
Else
Return DecimalDegrees.ToString
End If
End Function

Public Function ToStringDMS(decorate As Boolean) As String
'decimal seconds will be rounded to 4 decimals, more is only apparent accuracy
Dim s As String = ""
Dim sign As String = ""
If IsNegative Then sign = "-"

CorrectMinuteOrSecondIs60(CoordinateFormat.DMS)

If decorate Then
s = String.Format("{0}{1}° {2}' {3}""",sign, _
Else
s = String.Format("{0}{1} {2} {3}",sign, _
End If
Return s
End Function

Public Function ToStringDMS() As String
End Function```

Two points worth noting: (1) the sign of the `LatLong` is only returned in the `string` representation; it doesn't play a role in any of the calculations of the class. This is the quirk 1 handling described above. (2) The quirk 2 handling is done by the `CorrectMinuteOrSecondIs60` method. This method changes the values in the class as follows:

```Public Function CorrectMinuteOrSecondIs60(latlongFormat As CoordinateFormat) As Boolean
If latlongFormat = CoordinateFormat.D Then
'nothing to do
Return True
ElseIf latlongFormat = CoordinateFormat.DM
'check if min is 60
If minuteOrSecondIs60(DecimalMinutes,_round6) = 1 Then
Degrees += 1
Minutes = 0
End If
Return True
ElseIf latlongFormat = CoordinateFormat.DMS
'check if sec is 60
If minuteOrSecondIs60(DecimalSeconds,_round4) = 1 Then
Minutes += 1
DecimalSeconds = 0
If Minutes = 60 Then
Degrees += 1
Minutes = 0
End If
End If
Return True
End If
Return False
End Function

Private Function minuteOrSecondIs60(minuteOrSec As Double, roundStr As String) As Integer
Try
Dim minorsecstr As String = minuteOrSec.ToString(roundStr)
If CDbl(minorsecstr) >= 60 - EpsilonDeg AndAlso CDbl(minorsecstr) <= 60 + EpsilonDeg Then
Return 1
Else
Return 0
End If
Catch ex As Exception
Debug.Print("MinuteOrSecondIs60 " & ex.ToString)
Return -1
End Try
End Function```

The `CorrectMinuteOrSecondIs60` is a `Public` wrapper for the function `minuteOrSecondIs60` which does the real work. This latter method takes value together with a rounding `string`. It simply converts the number to a rounded `string` and checks if this `string` yields a minute or second as `60`. If so, it simply adds `1` to the `Minutes` and/or `Degrees`. Remember that we do not want a notation like: `52<sup>o</sup> 59' 60.0000"` but `53<sup>o</sup> 00' 00.0000"`. If the `Minute` is changed in the DMS format, this could cascade upward to the `Degree` (e.g., if `Minutes` would become `60` by adding `1`, it should increment `Degrees` by `1` and become `0` itself). In this way, a wrong notation can be prevented but it is a real quirk; something you only come across with geographicals. Please also refer to comments below this Tip from Philippe Mori and myself on another way of dealing with quirk 2. I may implement the VB.NET version of the C# method from Philippe into my class.

### Using the Code

You can simply add the class to your project and use it like this:

```'Church tower Amersfoort, The Netherlands in decimal degrees
Dim lat As Double = 52.15800833
Dim lon As Double = 5.38995833

Debug.Print("input: {0}, {1}",lat,lon)
Debug.Print("toDMS: {0}, {1}",    LatLong.FromDecimalDegree(lat).ToStringDMS, _
LatLong.FromDecimalDegree(lon).ToStringDMS)

'should be:
'input:  52.15800833, 5.38995833
'toDMS:  52  9 28.8300,   5 23 23.8500

'Church tower Amersfoort, The Netherlands in DMS
Dim latdeg As Integer = 52
Dim latmin As Integer = 9
Dim latsec As Double = 28.83
Dim londeg As Integer = 5
Dim lonmin As Integer = 23
Dim lonsec As Double = 23.85

Debug.Print("input: {0} {1} {2}, {3} {4} {5}",latdeg,latmin,latsec,londeg,lonmin,lonsec)
Debug.Print("to D : {0}, {1}",LatLong.FromDegreeMinutesDecimalSeconds(latdeg,latmin,latsec).ToStringD, _
LatLong.FromDegreeMinutesDecimalSeconds(londeg,lonmin,lonsec).ToStringD)

'should be:
'input:  52 9 28.83, 5 23 23.85
'to D :  52.15800833,   5.38995833```

You can check on quirk 2 by coding the following:

```Dim lat = 59.9999999999
Dim lon = 5.999999999999

Dim latff As LatLong = LatLong.FromDecimalDegree(lat)
Dim lonff As LatLong = LatLong.FromDecimalDegree(lon)

Debug.Print("input D      : {0} and {1}",lat,lon)
Debug.Print("corrected DMS: {0} and {1}", latff.ToStringDMS(False), lonff.ToStringDMS(False))

'should be
'input D      :  59.9999999999 and 5.999999999999
'corrected DMS:  60  0  0.0000 and   6  0  0.0000```

I hope the class will be helpful to anyone...

## History

• January 15, 2016: First draft

## Share

 Engineer VeeTools Netherlands
A (usually exploring) geologist who sometimes develops software for fun and colleagues... Check out my new website at www.veetools.xyz for online mapping and coordinate conversion.

## You may also be interested in...

 First Prev Next
 Code too complex Philippe Mori15-Jan-16 6:51 Philippe Mori 15-Jan-16 6:51
 Re: Code too complex veen_rp15-Jan-16 6:57 veen_rp 15-Jan-16 6:57
 Re: Code too complex (with simplified C# code that seems to works). Philippe Mori15-Jan-16 16:55 Philippe Mori 15-Jan-16 16:55
 Re: Code too complex (with simplified C# code that seems to works). veen_rp15-Jan-16 19:26 veen_rp 15-Jan-16 19:26
 Re: Code too complex (with simplified C# code that seems to works). Philippe Mori16-Jan-16 3:34 Philippe Mori 16-Jan-16 3:34
 Re: should use decimal and make an overload for double Philippe Mori16-Jan-16 3:56 Philippe Mori 16-Jan-16 3:56
 Re: Another alternative is TimeSpan Philippe Mori16-Jan-16 3:48 Philippe Mori 16-Jan-16 3:48
 Last Visit: 31-Dec-99 18:00     Last Update: 16-Aug-18 23:44 Refresh 1