65.9K
CodeProject 正在变化。 阅读更多。
Home

地理坐标转换

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2016 年 1 月 15 日

CPOL

6分钟阅读

viewsIcon

34302

downloadIcon

680

将十进度的地理坐标转换为度、分和秒。反之亦然。

引言

大地测量学的惯例和转换是“为什么不简单做,如果你能做复杂?”的一个典型例子。 尽管如此,我们必须遵守惯例,因此这个技巧是关于地理坐标(又称地理信息、经纬度或 latlons)的表示。简而言之,latlons 是地球上一个位置的球坐标。如果你放大到那个位置,纬度就像 Y 坐标,经度就像 X 坐标。Latlons 通常用圆周的度数表示,一个圆分为 360 度。但因为我们不喜欢简单,所以地球的坐标系统从西到东是 -180o 到 180o,从北到南是 90o 到 -90o。常见的表示方法是十进制角度(D),例如 56.36782361,或者度、分和(十进制)秒(DMS),例如 56o 56' 34.2561"。减号可以被半球指示 N(北)、S(南)、W(西)或 E(东)替换。所以:-127.5 = -127<sup>o</sup> 30' 00" = 127<sup>o</sup> 30' 00" W。不同表示方法之间的转换并不难,但有一些细微差别。

背景

在本技巧中,我将介绍一个我编写的 VB.NET 类,用于轻松地在不同表示法之间进行转换。完整的类已附带,可以下载。这个类的核心不是我写的,而是 Mario Vernari(2011 年)用 C#(我想)写的,可以在 这里找到。我将这个类移植到了 VB.NET,增加了更多的“细微差别”处理,并扩展了功能。我将这个类移植到了 VB.NET,增加了更多的“细微差别”处理,并扩展了功能。用伪代码表示,转换很简单。

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)"

细微差别在于两点:

  1. 处理负坐标。Vernari 的代码在这方面做得很好。
  2. 处理精度。作为经验法则,我为度使用 8 位小数,为分使用 6 位小数,为秒使用 4 位小数。这对于我的工作来说足够精确,但可能对您来说不够;您可以在类中进行调整。如果我使用一个十进制角度 127.9999999999(十位小数)和 8 位小数的精度,我用伪代码将其转换为 DMS 表示法的 127<sup>o</sup> 59' 60.0000"。但这错了,因为它应该是:128<sup>o</sup> 00' 00.0000"。这个取决于精度的细微差别在我的类中得到了处理。

代码讨论

VB.NET 类 LatLong 由一个标准的类和添加的共享函数(就像模块的一部分)组成。正如您稍后在类用法中看到的,这是我从 Vernari 那里继承过来的一个非常巧妙的结构。类以以下内容开始:

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

顶部的 private 全局变量定义了三个精度(如上所述)用于格式化数字的 string。然后是类的 public 属性。请注意,类的实例拥有 latlong 可能包含的所有数字部分。因此,对于度和分,有 IntegerDouble 类型。秒总是 Doublelatlong 的符号由布尔值 IsNegative 处理。定义一个双精度数是否为零的阈值由 EpsilonDeg 定义,所有这些都回到了精度细微差别。Enums 可以控制 latlong 的类型(例如,Longitude 的范围是 -9090Latitude 的范围是 -180180)以及它们表示的格式 D 表示十进制角度,DM 表示十进制分,DMS 表示十进制秒(相信我,各种变体尤其出现在法律文件中)。该类在没有参数的情况下构造。

下一节是 Shared 部分(即,可以在没有类实例的情况下调用的函数)。我将在这里讨论 FromDegreeMinutesDecimalSecondsEmpty 函数。此部分还有更多函数(例如 FromDegreeDecimalMinutes),但它们是类似的。简而言之,该函数接收度、分和秒作为参数,并进行最少的检查,以确定其是否在地球的范围内(我在这里不区分 LatitudeLongitude;您可以通过传递 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

如您所见,创建了 LatLong 类的实例 ll,并首先将参数修剪到其正确形式:如果度数是负数,则设置类 IsNegative 布尔值。然后,取所有参数的绝对值,并将其限制在应有的范围内(例如,minute 在整数 059 之间,second 在双精度数 060-EpsilonDeg = 060-0.0000000001 之间)。接下来,计算十进制角度和分部分,如上所述。实例化类 ll 后完全定义并返回。如果出现任何问题,将通过函数 Empty 返回一个空的 LatLong 类。反向共享函数 FromDecimalDegrees 如下所示:

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

此处再次返回一个完全定义的类,只是计算方式不同,因为它恢复了十进制角度的 DMS 部分。请注意,为了找到分数,我使用了 Double delta

类的以下部分是属于类实例的代码。它主要涉及以所需的表示法输出 LatLong。作为示例,我将在此讨论重写的 ToString 函数和普通函数 ToStringDMS。如您所见,通过简单的 string 格式化,即可输出所需的表示法。我选择 ToString 重写的输出为十进制角度,因为它是最不复杂的表示法,可以轻松转换为 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, _
            Degrees.ToString.PadLeft(3), _
            Minutes.ToString.PadLeft(2), _
            DecimalSeconds.ToString(_round4).PadLeft(7))
    Else
        s = String.Format("{0}{1} {2} {3}",sign, _
            Degrees.ToString.PadLeft(3), _
            Minutes.ToString.PadLeft(2), _
            DecimalSeconds.ToString(_round4).PadLeft(7))
    End If
    Return s
End Function

Public Function ToStringDMS() As String
    Return ToStringDMS(False)
End Function

有两点值得注意:(1)LatLong 的符号仅在 string 表示法中返回;它在类的任何计算中都不起作用。这是上面描述的细微差别 1 的处理。(2)细微差别 2 的处理由 CorrectMinuteOrSecondIs60 方法完成。此方法按如下方式更改类中的值:

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
            'cascades?
            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

CorrectMinuteOrSecondIs60 是函数 minuteOrSecondIs60 的一个 Public 包装器,该函数执行实际工作。后者方法接受值以及一个舍入 string。它只需将数字转换为舍入的 string,然后检查该 string 是否产生 60 分钟或秒。如果是,它只需将 1 添加到 Minutes 和/或 Degrees。请记住,我们不想要像 52<sup>o</sup> 59' 60.0000" 这样的表示法,而是 53<sup>o</sup> 00' 00.0000"。如果 DMS 格式中的 Minute 被更改,这可能会向上级联到 Degree(例如,如果 Minutes 通过加 1 变成 60,它应该将 Degrees 增加 1 并自身变为 0)。这样,就可以避免错误的表示法,但这是一个真正的细微差别;只有在处理地理信息时才会遇到。另请参阅 Philippe Mori 和我在此提示下方的评论,了解处理细微差别 2 的另一种方法。我可能会将 Philippe 的 C# 方法的 VB.NET 版本实现到我的类中。

Using the Code

您可以直接将该类添加到您的项目中,并按如下方式使用它:

'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

您可以通过编写以下代码来检查细微差别 2:

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

希望这个类对某人有所帮助……

历史

  • 2016 年 1 月 15 日:初稿
© . All rights reserved.