定位服务-包括基于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坐标显示格式如下经度纬度区的区东向北移,其中东向从经度区的中央经线和北投影距离是从赤道的投影距离。 东和北的值均以米为单位。 例如,纬度/经度坐标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中)方法计算北向和东向值。 这两种方法都使用清单3的setVariables()
方法中设置的变量。
清单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()
方法确定该字符串表示的位置所在的半球。确定半球很容易:纬度区域A
, C
, D
, E
, F
, G
, H
, J
, K
, L
和M
在南半球,其余在北半球。
清单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