坐标转换变得容易

定位服务-包括基于GPS的导航系统和地图站点,例如Google Maps和Yahoo! 地图-已在消费者中流行。 许多组织已经在使用位置感知服务,并且更多组织将利用这些服务,因为他们意识到这些服务所具有的好处和潜力。 Gartner在2006年指出,“位置感知应用程序将在未来两到五年内成为主流应用”,并且“已经有越来越多的组织部署了位置感知移动业务应用程序”。

当组织决定实施位置感知的应用程序时,编写此类应用程序最终是开发人员的任务。 构建位置感知服务涉及许多任务,无论大小,一个任务(相对较小)可能是将坐标从一个系统转换为另一个系统。 本文介绍执行此类转换的代码,这可以节省您许多时间。

两种不同的坐标系

在深入研究本文的代码之前,我们需要讨论该代码旨在处理的坐标系:熟悉的经度和纬度系统以及通用横轴墨卡托(UTM)系统。 我们还将介绍基于UTM的军事网格参考系统(MGRS)。

纬度和经度

纬度和经度系统可能是指定地理坐标的最著名方法。 它用两个数字表示一个位置。 纬度是从地球中心到地球表面上某些东西线的夹角。 经度是从地球中心到地球表面南北方向的夹角。 纬度和经度可以表示为十进制度(DD)或表示为度,分钟和秒(DMS); 后者以49°30'00“ S 12°30'00” E等格式给出数字。这是GPS设备中通常使用的格式。

地球被赤道(北纬0度)划分为北半球和南半球,而经度为0°(从北极到南极的假想线,穿过英国格林威治市)划分为东半球和西半球。 北半球的纬度在0到90度之间,南半球的纬度在0到-90度之间。 东半球在0到180度之间,西半​​球在0到-180度之间。

例如,坐标61.44、25.40(在DD中)或61°26'24''N,25°23'60''E(在DMS中)位于芬兰南部。 坐标-47.04,-73.48(在DD中)或47°02'24''S,73°28'48''W(在DMS中)位于智利南部。 图1显示了用纬度和经度线覆盖的地球:

图1.地球,显示纬度和经度线
地球,显示纬度和经度线

通用横轴墨卡托

UTM坐标系是用于指定坐标的基于网格的方法。 UTM系统将地球分为60个区域,每个区域均基于“横轴墨卡托”投影。 地图制图中的地图投影是一种在平面上呈现二维曲面的方法,例如法线地图。 图2显示了一个遍历墨卡托投影:

图2.横向墨卡托投影
遍历墨卡托

UTM经度区编号为1到60。 除了两个区域(稍后会有更多区域)之外,所有区域从东到西均为6°宽。 经度区域覆盖了80°S和84°N纬度之间的整个地球表面。

有20个UTM纬度区,每个纬度为8°。 区域从C到X字母(省略字母I和O)。 区域A,B,Y和Z位于该系统之外; 它们覆盖了南极和北极地区。 图3显示了欧洲的UTM区域。 在图3中可以看到两个非标准经度区域:区域32V扩展到覆盖整个挪威南部,而区域31V缩小了,因此仅覆盖了开阔水域。

图3.欧洲的UTM区域
欧洲UTM区

UTM坐标显示格式如下经度纬度区的区东向北移,其中东向从经度区的中央经线和北投影距离是从赤道的投影距离。 东和北的值均以米为单位。 例如,纬度/经度坐标61.44、25.40在UTM中显示为35 V 414668 6812844; 纬度/经度坐标-47.04,-73.48是UTM中的18 G 615471 4789269。

军事网格参考系统

MGRS是北约军队使用的标准。 MGRS基于UTM,并将每个区域进一步划分为100 km×100 km的正方形。 这些正方形由两个字母的图表示:第一个字母是经度区域内的东西方位置,第二个字母是南北位置。

例如,UTM点35 V 414668 6812844等效于MGRS点35VMJ1466812844。 此MGRS点在1米内准确,并使用15个字符表示,后10个字符是指定网格内的东和北值。 MGRS可以使用15个字符(如前面的示例)或13、11、9或7个字符来表示; 这样表示的值分别在1、10、100、1,000或10,000米内是准确的。

转换坐标

要至少为地球上的某个位置定义纬度和经度坐标,您必须能够看到恒星或太阳,并具有六分仪和时钟,以GMT显示时间。 您可以根据天体与地平线之间的角度确定纬度,也可以根据地球自转来计算经度。 本文不讨论这些细节。 相反,我们假设我们已经具有DD,DMS或UTM格式的坐标。

将十进制度数转换为度/分/秒,反之亦然

在DD和DMS之间转换坐标很容易。 这是从DD转换为DMS的公式:

DD: dd.ff
DMS: dd mm ss

dd=dd
mm.gg=60*ff
ss=60*gg

gg是计算的小数部分。 负纬度表示在南半球(S)中的位置,负经度表示在西半球(W)中的位置。 例如,假设您的坐标(DD格式)为61.44,25.40。 您将按以下方式转换它们:

lat dd=61
lat mm.gg=60*0.44=26.4
lat ss=60*0.4=24

和:

lon dd=25
lon mm.gg=60*0.40=24.0
lon ss=60*0.0=0

因此,在DMS格式中,坐标为61°26'24''N 25°24'00''E。

从DMS转换为DD的公式如下:

DD: dd.ff
DMS: dd mm ss

dd.ff=dd + mm/60 + ss/3600

请记住,南半球(S)的位置为负纬度,而西半球(W)的位置为负经度。

让我们将DMS坐标47°02'24''S,73°28'48''W转换为DD表示法:

lat dd.ff= - (47 + 2/60 + 24/3600 )=-47.04
lon dd.ff= - (73 + 28/60 + 48/3600)=-73.48

因此,DD中的坐标为-47.04,-73.48。

从纬度/经度转换为UTM,反之亦然

与可以使用六分仪和精密天文表确定的十进制坐标不同,如果没有计算,就无法确定UTM坐标。 尽管这些计算仅是基本的三角函数和代数,但公式非常复杂。 如果您查看“通用网格:通用横向墨卡托(UTM)和通用极地立体照相(UPS)”,您将会明白我的意思。

这里没有提供UTM转换公式,但是下一节中的源代码提供了一些启示。

用Java代码转换坐标

本节介绍在十进制度和UTM之间执行坐标转换的库类的源代码。 该Java类名为com.ibm.util.CoordinateConversion ; 我的想法是制作一个具有转换方法的类。 该类包括实际进行转换的内部类; 如有必要,可以从CoordinateConversion类中重构内部类,以创建库包或将类添加到现有包中。 班级执行的转换精确到1米以内。

CoordinateConversion的源代码约有750行,因此本文全文中未介绍。 以下各节介绍了相关方法,完整的源代码包含在“ 下载”一节中。

坐标转换

CoordinateConversion是主要类,在需要时会实例化以执行坐标转换。 清单1给出了相关的公共方法,以及CoordinateConversion类中包含的私有内部类:

清单1. CoordinateConversion
public class CoordinateConversion
{

  public CoordinateConversion()
  {

  }
  
  public double[] utm2LatLon(String UTM)
  {
    UTM2LatLon c = new UTM2LatLon();
    return c.convertUTMToLatLong(UTM);
  }

  public String latLon2UTM(double latitude, double longitude)
  {
    LatLon2UTM c = new LatLon2UTM();
    return c.convertLatLonToUTM(latitude, longitude);

  }

  //..implementation omitted

  private class LatLon2UTM
  {
    public String convertLatLonToUTM(double latitude, double longitude)
    {
      //..implementation omitted
    }
    //..implementation omitted
  }
  
  private class LatLon2MGRUTM extends LatLon2UTM
  {
    public String convertLatLonToMGRUTM(double latitude, double longitude)
    {
      //..implementation omitted
    }
    //..implementation omitted
  }


  private class MGRUTM2LatLon extends UTM2LatLon
  {
    public double[] convertMGRUTMToLatLong(String mgrutm)
    {
      //..implementation omitted
    }
    //..implementation omitted
  }
 
 
  private class UTM2LatLon
  {
    public double[] convertUTMToLatLong(String UTM)
    {
      //..implementation omitted
    }
    //..implementation omitted
  }

  private class Digraphs
  {
    //used to get digraphs when doing conversion between
    //lat/long and MGRS
    //..implementation omitted
  }

  private class LatZones
  {
    //include methods to determine latitude zones
    //..implementation omitted
  }

下一节将详细介绍纬度/经度和UTM转换。

从经度/纬度转换为UTM

使用String latLon2UTM(double latitude, double longitude)方法将坐标从纬度/经度转换为UTM。 该方法实现创建内部类LatLon2UTM c = new LatLon2UTM();的新实例LatLon2UTM c = new LatLon2UTM(); 并以15个字符的字符串(即1米精度)返回UTM坐标。 清单2显示了LatLon2UTM方法的实现:

清单2. public String convertLatLonToUTM(双纬度,双经度)
public String convertLatLonToUTM(double latitude, double longitude)
{
  validate(latitude, longitude);
  String UTM = "";

  setVariables(latitude, longitude);

  String longZone = getLongZone(longitude);
  LatZones latZones = new LatZones();
  String latZone = latZones.getLatZone(latitude);

  double _easting = getEasting();
  double _northing = getNorthing(latitude);

  UTM = longZone + " " + latZone + " " + ((int) _easting) + " "+ ((int) _northing);

  return UTM;
}

该方法通过调用各种方法来获取其经度和纬度,并计算东经和北经等来执行其转换。 使用validate()方法validate()输入; 如果子句(latitude < -90.0 || latitude > 90.0 || longitude < -180.0 || longitude >= 180.0)为true,则抛出IllegalArgumentException

清单3中的setVariables()方法设置了计算转换所需的各种变量(有关更多信息,请查看“通用网格”):

清单3.受保护的void setVariables(双纬度,双经度)
protected void setVariables(double latitude, double longitude)
{
  latitude = degreeToRadian(latitude);
  rho = equatorialRadius * (1 - e * e) / POW(1 - POW(e * SIN(latitude), 2), 3 / 2.0);

  nu = equatorialRadius / POW(1 - POW(e * SIN(latitude), 2), (1 / 2.0));

  double var1;
  if (longitude < 0.0)
  {
    var1 = ((int) ((180 + longitude) / 6.0)) + 1;
  }
  else
  {
    var1 = ((int) (longitude / 6)) + 31;
  }
  double var2 = (6 * var1) - 183;
  double var3 = longitude - var2;
  p = var3 * 3600 / 10000;

  S = A0 * latitude - B0 * SIN(2 * latitude) + C0 * SIN(4 * latitude) - D0
      * SIN(6 * latitude) + E0 * SIN(8 * latitude);

  K1 = S * k0;
  K2 = nu * SIN(latitude) * COS(latitude) * POW(sin1, 2) * k0 * (100000000) / 2;
  K3 = ((POW(sin1, 4) * nu * SIN(latitude) * Math.pow(COS(latitude), 3)) / 24)
      * (5 - POW(TAN(latitude), 2) + 9 * e1sq * POW(COS(latitude), 2) + 4
      * POW(e1sq, 2) * POW(COS(latitude), 4))
      * k0
      * (10000000000000000L);

  K4 = nu * COS(latitude) * sin1 * k0 * 10000;

  K5 = POW(sin1 * COS(latitude), 3) * (nu / 6)
      * (1 - POW(TAN(latitude), 2) + e1sq * POW(COS(latitude), 2)) * k0
      * 1000000000000L;

  A6 = (POW(p * sin1, 6) * nu * SIN(latitude) * POW(COS(latitude), 5) / 720)
      * (61 - 58 * POW(TAN(latitude), 2) + POW(TAN(latitude), 4) + 270
      * e1sq * POW(COS(latitude), 2) - 330 * e1sq
      * POW(SIN(latitude), 2)) * k0 * (1E+24);

}

清单4中的getLongZone()方法和LatZones类(在源代码中提供 )用于获取经度和纬度区域。 经度区域是根据经度参数计算得出的,而纬度区域基本上是使用LatZones类中的数组进行硬编码的。

清单4.受保护的字符串getLongZone(双经度)
protected String getLongZone(double longitude)
{
  double longZone = 0;
  if (longitude < 0.0)
  {
    longZone = ((180.0 + longitude) / 6) + 1;
  }
  else
  {
    longZone = (longitude / 6) + 31;
  }
  String val = String.valueOf((int) longZone);
  if (val.length() == 1)
  {
    val = "0" + val;
  }
  return val;
}

getNorthing() (在清单5中)和getEasting() (在清单6中)方法计算北向和东向值。 这两种方法都使用清单3setVariables()方法中设置的变量。

清单5.受保护的double getNorthing(双纬)
protected double getNorthing(double latitude)
{
  double northing = K1 + K2 * p * p + K3 * POW(p, 4);
  if (latitude < 0.0)
  {
    northing = 10000000 + northing;
  }
  return northing;
}
清单6.受保护的double getEasting()
protected double getEasting()
{
  return 500000 + (K4 * p + K5 * POW(p, 3));
}

清单7包含一些示例输出,包括一些纬度/经度坐标和相应的UTM坐标:

清单7.到UTM的纬度/经度测试值
( 0.0000    0.0000  )     "31 N 166021 0"
( 0.1300   -0.2324  )     "30 N 808084 14385"
(-45.6456   23.3545 )     "34 G 683473 4942631"
(-12.7650  -33.8765 )     "25 L 404859 8588690"
(-80.5434  -170.6540)     "02 C 506346 1057742"
( 90.0000   177.0000)     "60 Z 500000 9997964"
(-90.0000  -177.0000)     "01 A 500000 2035"
( 90.0000    3.0000 )     "31 Z 500000 9997964"
( 23.4578  -135.4545)     "08 Q 453580 2594272"
( 77.3450   156.9876)     "57 X 450793 8586116"
(-89.3454  -48.9306 )     "22 A 502639 75072"

从UTM转换为纬度/经度

从UTM坐标转换为纬度和经度比反向过程要容易一些。 同样,“通用网格”包含转换的公式。 清单8显示了convertUTMToLatLong()方法的代码。 这将返回一个double数组,其中第一个元素(数组索引[0] )是纬度,第二个元素(数组索引[1] )是经度。 由于UTM字符串参数的精度为1米,因此纬度/经度坐标具有相同的精度。

清单8. public double [] convertUTMToLatLong(String UTM)
public double[] convertUTMToLatLong(String UTM)
{
  double[] latlon = { 0.0, 0.0 };
  String[] utm = UTM.split(" ");
  zone = Integer.parseInt(utm[0]);
  String latZone = utm[1];
  easting = Double.parseDouble(utm[2]);
  northing = Double.parseDouble(utm[3]);
  String hemisphere = getHemisphere(latZone);
  double latitude = 0.0;
  double longitude = 0.0;

  if (hemisphere.equals("S"))
  {
    northing = 10000000 - northing;
  }
  setVariables();
  latitude = 180 * (phi1 - fact1 * (fact2 + fact3 + fact4)) / Math.PI;

  if (zone > 0)
  {
    zoneCM = 6 * zone - 183.0;
  }
  else
  {
    zoneCM = 3.0;
  }

  longitude = zoneCM - _a3;
  if (hemisphere.equals("S"))
  {
    latitude = -latitude;
  }

  latlon[0] = latitude;
  latlon[1] = longitude;
  return latlon;

}

convertUTMToLatLong()方法拆分输入的UTM字符串(格式为34 G 683473 4942631 ),并使用getHemisphere()方法确定该字符串表示的位置所在的半球。确定半球很容易:纬度区域ACDEFGHJKLM在南半球,其余在北半球。

清单9中所示的setVariables()方法设置计算所需的变量,然后立即计算纬度。 经度是使用经度区域计算的。

清单9.受保护的void setVariables()
protected void setVariables()
{
  arc = northing / k0;
  mu = arc
      / (a * (1 - POW(e, 2) / 4.0 - 3 * POW(e, 4) / 64.0 - 5 * POW(e, 6) / 256.0));

  ei = (1 - POW((1 - e * e), (1 / 2.0)))
      / (1 + POW((1 - e * e), (1 / 2.0)));

  ca = 3 * ei / 2 - 27 * POW(ei, 3) / 32.0;

  cb = 21 * POW(ei, 2) / 16 - 55 * POW(ei, 4) / 32;
  cc = 151 * POW(ei, 3) / 96;
  cd = 1097 * POW(ei, 4) / 512;
  phi1 = mu + ca * SIN(2 * mu) + cb * SIN(4 * mu) + cc * SIN(6 * mu) + cd
      * SIN(8 * mu);

  n0 = a / POW((1 - POW((e * SIN(phi1)), 2)), (1 / 2.0));

  r0 = a * (1 - e * e) / POW((1 - POW((e * SIN(phi1)), 2)), (3 / 2.0));
  fact1 = n0 * TAN(phi1) / r0;

  _a1 = 500000 - easting;
  dd0 = _a1 / (n0 * k0);
  fact2 = dd0 * dd0 / 2;

  t0 = POW(TAN(phi1), 2);
  Q0 = e1sq * POW(COS(phi1), 2);
  fact3 = (5 + 3 * t0 + 10 * Q0 - 4 * Q0 * Q0 - 9 * e1sq) * POW(dd0, 4) / 24;

  fact4 = (61 + 90 * t0 + 298 * Q0 + 45 * t0 * t0 - 252 * e1sq - 3 * Q0
          * Q0)
          * POW(dd0, 6) / 720;

  lof1 = _a1 / (n0 * k0);
  lof2 = (1 + 2 * t0 + Q0) * POW(dd0, 3) / 6.0;
  lof3 = (5 - 2 * Q0 + 28 * t0 - 3 * POW(Q0, 2) + 8 * e1sq + 24 * POW(t0, 2))
          * POW(dd0, 5) / 120;
  _a2 = (lof1 - lof2 + lof3) / COS(phi1);
  _a3 = _a2 * 180 / Math.PI;

}

setVariables()使用东和北值来设置所需的变量。 这些都是类变量,并且在convertUTMToLatLong(String UTM)方法中设置(请参见清单8 )。

其他方法

源代码还包括其他公共和私有方法和类。 例如,用于在纬度/经度之间转换坐标的方法和类,以及用于执行度到弧度转换(反之亦然)以及用于各种数学运算(例如POW,SIN,COS和TAN)的一些辅助方法。

摘要

本文介绍了一些世界坐标系理论,以及用于执行坐标转换的Java类。 通常,在日常开发工作中不需要此理论-除非在极少数情况下没有其他好方法可以这样做,正如我最近在遇到坐标变换任务时发现的那样。

我需要在纬度和经度,UTM和MGRS之间进行转换,因此我进行了基础研究并在Java类中实现了转换。 对我来说,开发工作要花几个小时。 我希望您可以将这些时间节省下来用于其他任务,并希望CoordinateConversion在您自己的工作中很有用。


翻译自: https://www.ibm.com/developerworks/java/library/j-coordconvert/index.html

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值