位置转换器






4.55/5 (14投票s)
如何转换流行格式之间的经纬度坐标,以及配套的应用程序
引言
虽然这肯定不是我的职业,甚至也不是我的业余爱好,但我确实经常查看地图坐标。无论是简单地想在 Google 地图上找到我的位置、查找某个地标、定位数字图片的来源,还是和朋友们一起进行地理寻宝,那些经纬度坐标总是在那里。问题是我从来不知道它们会是什么格式,而我经常需要将它们转换成另一种格式才能完成我当前的任务。
这篇文章和应用程序不适合谁?如果你是飞行员、制图师,日常使用指南针,或者仅仅是擅长心算数字,那么这里可能没有什么新东西。请继续浏览。
我想也值得一提的是,这是我使用 Android Studio 完成的第一个“完整”应用程序。我已经用它创建了数十个其他应用程序,但没有一个是真正的“完整”应用程序,也就是说,除了我自己之外,没有人值得使用。准备好上传一个 Eclipse 项目,只需删除 bin 文件夹即可。下次打开项目时,该文件夹会自动重新创建。使用 Android Studio,我仍在学习哪些可以删除,哪些必须保留。如果你发现我遗漏了什么,请给我留言告诉我。
背景
纬度被定义为指定地球表面一点的南北位置的地理坐标。它被测量为从赤道 0° 到两极 90°(北或南)的角度。经度被定义为指定地球表面一点的东西位置的地理坐标。它被测量为从本初子午线开始向东或向西的角度,从本初子午线 0° 到向东 +180° 和向西 −180°。无论你查看哪个坐标,每度都被细分为 60 分钟,每分钟又被细分为 60 秒。
虽然可能存在几十种其他格式,但我最常遇到的三种格式是
decimal
D:M:S
D° M' S"
最后两种格式是表示 六十进制记数法 的不同方式。
要将十进制转换为 D:M:S 格式,我们使用以下公式
度 = ⌊十进制度数⌋
分 = ⌊60.0 * (十进制度数 - 度数)⌋
秒 = 3600.0 * (十进制度数 - 度数) - (60.0 * 分钟)
例如,自由女神像的十进制坐标是纬度 40.689167,经度 -74.044444。将这些值代入上述公式将如下所示:
纬度
度 = floor(40.689167) = 40
分 = floor(60.0 * (40.689167 - 40)) = 41
秒 = 3600.0 * (40.689167 - 40) - (60.0 * 41) = 21.0012
经度
度 = floor(74.044444) = 74
分 = floor(60.0 * (74.044444 - 74)) = 2
秒 = 3600.0 * (74.044444 - 74) - (60.0 * 2) = 39.9984
插入适当的分隔符,并考虑负度数以确定方向,我们的 D:M:S 格式如下所示:
40:41:21.0012 N
74:2:39.9984 W
要将 D:M:S 或 D° M' S" 转换为十进制格式,我们使用以下公式:
十进制度数 = 度数 + (分钟 / 60.0) + (秒 / 3600.0)
将自由女神像的 D:M:S 坐标代入上述公式将如下所示:
纬度
十进制度数 = 40 + (41 / 60.0) + (21.0012 / 3600.0) = 40.689167
经度
十进制度数 = 74 + (2 / 60.0) + (39.9984 / 3600.0) = 74.044444
考虑负度数以确定方向,我们的十进制格式如下所示:
40.689167 N
74.044444 W
Application
在了解了坐标的定义以及它们是如何在一个格式与其他格式之间转换之后,我们现在可以开始研究这些如何转化为代码。这段代码或由此产生的应用程序没有什么花哨或特别之处。没有炫酷的图形。屏幕上只有“技术性”数字!
虽然该应用程序包含多个活动(和一个片段),但只有其中一个具有重要意义。主活动 UI 如下所示:
无论你输入自己的坐标,还是单击“我的位置”按钮使用当前位置(后者需要你的权限),转换都是相同的,并且由附加到“上”和“下”按钮上的“点击”监听器处理。
btnDown = (ImageButton) findViewById(R.id.btnDown); btnUp = (ImageButton) findViewById(R.id.btnUp); btnDown.setOnClickListener(this); btnUp.setOnClickListener(this);
在 onClick()
函数中,我们确定哪个按钮被点击,然后调用相应的转换函数。
@Override public void onClick(View v) { if (v.getId() == R.id.btnUp) { if (convertLatitudeToDecimal()) { convertLongitudeToDecimal(); } } else if (v.getId() == R.id.btnDown) { if (convertLatitudeToDMS()) { convertLongitudeToDMS(); } } }
这四个转换函数有一个相似的主题:从相应的“源”字段获取值,验证它,进行转换,将结果设置在对应的“目标”字段中。因此,将纬度从 D:M:S 格式转换为十进制格式如下所示:
EditText etLatitude2 = (EditText) findViewById(R.id.etLatitude2); m_strLatitudeDMS = etLatitude2.getText().toString(); if (Util.isValidLatitude(m_strLatitudeDMS)) { String[] strDMS = TextUtils.split(m_strLatitudeDMS, ":"); int nDegrees = Integer.valueOf(strDMS[0]); int nMinutes = Integer.valueOf(strDMS[1]); double dSeconds = Double.valueOf(strDMS[2]); m_dLatitude = nDegrees + (nMinutes / 60.0) + (dSeconds / 3600.0); EditText etLatitude1 = (EditText) findViewById(R.id.etLatitude1); etLatitude1.setText(String.format(Locale.getDefault(), "%.6f", m_dLatitude)); Spinner spinLatitudeDir1 = (Spinner) findViewById(R.id.spinLatitudeDir1); Spinner spinLatitudeDir2 = (Spinner) findViewById(R.id.spinLatitudeDir2); spinLatitudeDir1.setSelection(spinLatitudeDir2.getSelectedItemPosition()); }
你可能会想,为什么在设置 etLatitude1
字段的值时,只使用了 6 位精度。简单地说,显示超过这个精度是没有意义的。要获得更好的解释,请参阅 whuber 在 此处 的回复。
有两种验证例程,每种格式一种。要验证纬度的十进制格式,我们只需要检查坐标的范围,如下所示:
public static boolean isValidLatitude( double dLatitude ) { return dLatitude >= 0.0 && dLatitude <= 90.0; }
验证纬度的D:M:S 格式同样简单,只需稍多几行代码即可。
public static boolean isValidLatitude( String strLatitude ) { // this handles the format but doesn't check the ranges (without extra symbols) // return strLatitude.matches("[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}(\\.[0-9]+)?"); String[] lat = TextUtils.split(strLatitude, ":"); if (lat.length == 3) // all three parts must exist { double dDegrees = Double.parseDouble(lat[0]); double dMinutes = Double.parseDouble(lat[1]); double dSeconds = Double.parseDouble(lat[2]); // and each must be within range return (dDegrees >= 0.0 && dDegrees <= 90.0) && (dMinutes >= 0.0 && dMinutes < 60.0) && (dSeconds >= 0.0 && dSeconds < 60.0); } return false; }
对于经度也存在相同的两个例程,只是度数的上限是 180。
将纬度从十进制转换为 D:M:S 格式如下所示:
EditText etLatitude1 = (EditText) findViewById(R.id.etLatitude1); String strLatitude = etLatitude1.getText().toString(); if (! strLatitude.isEmpty()) { m_dLatitude = Double.parseDouble(strLatitude); if (Util.isValidLatitude(m_dLatitude)) { EditText etLatitude2 = (EditText) findViewById(R.id.etLatitude2); m_strLatitudeDMS = Location.convert(m_dLatitude, Location.FORMAT_SECONDS); etLatitude2.setText(m_strLatitudeDMS); Spinner spinLatitudeDir1 = (Spinner) findViewById(R.id.spinLatitudeDir1); Spinner spinLatitudeDir2 = (Spinner) findViewById(R.id.spinLatitudeDir2); spinLatitudeDir2.setSelection(spinLatitudeDir1.getSelectedItemPosition()); } }
你无疑会注意到缺少任何转换代码,就像本文开头解释的那样。这是因为我选择使用内置的 Location.convert()
函数。它能达到相同的结果,但方式不同,这很像我用铅笔和纸做的。例如,将自由女神像的十进制坐标转换为 D:M:S 格式的公式将如下所示:
度 = ⌊十进制⌋
十进制 = (十进制 - 度数) * 60.0 // 去掉完整的度数
分 = ⌊十进制⌋
十进制 = (十进制 - 分钟) * 60.0 // 去掉完整的分钟
秒 = 十进制
将自由女神像的 40.689167 纬度和 -74.044444 经度坐标代入上述公式将如下所示:
纬度
度 = floor(十进制) = 40
十进制 = (十进制 - 度数) * 60.0 = 41.35002
分 = floor(十进制) = 41
十进制 = (十进制 - 分钟) * 60.0 = 21.0012
秒 = 十进制 = 21.0012
经度
度 = floor(十进制) = 74
十进制 = (十进制 - 度数) * 60.0 = 2.66664
分 = floor(十进制) = 2
十进制 = (十进制 - 分钟) * 60.0 = 39.9984
秒 = 十进制 = 39.9984
插入适当的分隔符,并考虑负度数以确定方向,我们的 D:M:S 格式如下所示:
40:41:21.0012 N
74:2:39.9984 W
附加功能
选项菜单包含一个 地图 选项和一个 帮助 选项。前者允许你使用 Google 地图查看转换后的坐标。在那里,你可以点击图钉并验证它是否显示了正确的坐标。后者提供了如何使用该应用程序以及在各个字段中应输入什么内容的帮助。
在其当前状态下,该应用程序的目标是 Android 5 (Lollipop),它使用 API 21。
结语
正如我之前提到的,这是一篇没有附加条件的文章和应用程序。你可以在网上找到更多关于这方面的信息,而且细节也更多。我只是根据我的需求提取了必要的部分,并将其整合到一个方便的小型移动应用程序中。我看过一些网站展示了数十种格式以及它们之间的转换方法,但在我看来,其中很多似乎是多余的。
尽情享用!