Work the Shell - 使用 Shell 脚本探索经纬度

作者:Dave Taylor

随着移动设备上地理定位系统的兴起(想想苹果 iPhone 上的“我周围”),一种衡量地球上点的统一方法变得非常重要。使用的标准是纬度和经度,它们分别测量地球上一点位于赤道以北或以南的距离,以及本初子午线(穿过英国格林威治)以东或以西的距离。您的 GPS 设备都理解这种表示法,Google 地图、Yahoo 地图、MapQuest 等也是如此。

从 shell 脚本的角度来看,我们既有兴趣识别地球上某点的经纬度,然后,掌握这些信息,看看我们是否可以计算地球上两点之间的距离。

第一个问题似乎几乎难以克服,直到您了解到 Yahoo 地图有一个非常简单的 API,它允许您指定一个包含街道地址的 URL,并返回一个包含其经纬度值的 XML 对象。

这个地方在哪里?

例如,您可能熟悉华盛顿特区宾夕法尼亚大道 1600 号。我知道您看过这个地方的照片。它的经纬度是多少?

$ u='http://api.maps.yahoo.com/ajax/geocode'
$ a='?appid=onestep&qt=1&id=m&qs=1600+pennsylvania+ave+washington+dc'
$ curl "$u$a"
YGeoCode.getMap({"GeoID"      : "m",
                 "GeoAddress" : "1600 pennsylvania ave washington dc",
                 "GeoPoint"   : {"Lat" : 38.89859,
                                 "Lon" : -77.035971},
                 "GeoMID"     : false,
                 "success"    : 1} ,1);
<!-- xm6.maps.re3.yahoo.com uncompressed/chunked
     Tue Aug  4 12:16:51 PDT 2009 -->

请注意,输出实际上是两行返回的;上面的数据以及其他示例中的数据,已经过重新格式化以使其更具可读性。

浏览一下返回的对象,您会看到纬度 = 38.89859,经度 = -77.035971。将这两个值作为“38.89859,-77.035971”输入到 Google 地图进行检查,您将找到图 1 中显示的图像。

Work the Shell - Exploring Lat/Lon with Shell Scripts

图 1. 白宫

您猜对了,这是白宫的街道地址。

让我们从创建一个简单的脚本开始,您可以在其中指定街道地址,它将输出经纬度值。

编写我们的解决方案脚本

第一部分很容易:获取命令行中指定的任何内容,并“重新编码”使其 URL 友好。然后,将其附加到 Yahoo API URL,并输出 curl 调用的结果

#!/bin/sh

url='http://api.maps.yahoo.com/ajax/geocode'
args='?appid=onestep&qt=1&id=m&qs='
converter="$url$args"

addr="$(echo $* | sed 's/ /+/g')"
curl -s "$converter$addr"
exit 0

让我们这次用不同的地址测试一下

$ sh whereis.sh 2001 Blake Street, Denver, CO
YGeoCode.getMap({"GeoID"      : "m",
                 "GeoAddress" : "2001 Blake Street, Denver, CO",
                 "GeoPoint"   : {"Lat" : 39.754386,
                                 "Lon" : -104.994261},
                 "GeoMID"     : false,
                 "success"    : 1}, 1);
<!-- x1.maps.sp1.yahoo.com uncompressed/chunked
     Tue Aug  4 12:37:44 PDT 2009 -->

如果您愿意,可以弄清楚这个地址在哪里。更重要的是,您可以看到这个简单的四行脚本完成了这项工作——某种程度上。

清理输出

然而,我们真正想要的是仅提取经度和纬度值,并丢弃其他所有内容。这可以使用许多不同的工具来完成,当然,包括 Perl 和 awk,但我是一个叛逆者,所以我改用 cut。

为此,我们需要计算输出块中的双引号 (")。第 12 个双引号紧邻纬度值之前,第 15 个双引号紧邻经度值之后。如果我们只使用这个,我们将得到

$ sh whereis.sh 2001 Blake Street, Denver, CO | cut -d\" -f13-15
:39.754386,"Lon":-104.994261},

好的,这完成了大部分工作。但是,更好的是指定两个不同的特定字段(13,15 而不是 13-15)

$ sh whereis.sh 2001 Blake Street, Denver, CO | cut -d\" -f13,15
:39.754386,":-104.994261},

这 99% 是我们想要的。现在我们只需要清理噪音。为此,我将跳回到脚本本身,而不是在命令行上进行实验

curl -s "$converter$addr" | \
    cut -d\" -f13,15 | \
    sed 's/[^0-9\.\,\-]//g'

并进行测试

$ sh whereis.sh 2001 Blake Street, Denver, CO
39.754386,104.994261,

几乎。真的,非常接近了。但是,最后一个逗号是不需要的。嗯....

好的!要删除最后一个逗号,我们只需要在 sed 语句中添加第二个替换,这样完整的 sed 表达式现在是

sed 's/[^0-9\.\,\-]//g;s/,$//'

(调用是 substitute/old-pattern/new-pattern/。)

现在我们得到了我们最初想要创建的东西。让我们用另一个地址试一下

$ sh whereis.sh 1313 S. Disneyland Drive, Anaheim CA
33.814413,-117.924424

是的,那是加利福尼亚州迪士尼乐园的停车场。

两点之间的距离

现在是这件事的难点,实际上。我们可以获得任何所需地址的经纬度,但计算两点之间的距离有点棘手,因为涉及的数学相当复杂,因为我们基本上要做的是相对于地球周长进行测量。

我在网上找到了一个 JavaScript 公式作为起点

var R    = 6371;        // kilometers
var dLat = (lat2-lat1);
var dLon = (lon2-lon1);
var a    = Math.sin(dLat/2) * Math.sin(dLat/2) +
           Math.cos(lat1.toRad()) * Math.cos(lat2.toRad()) *
           Math.sin(dLon/2) * Math.sin(dLon/2);
var c    = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var d    = R * c;

在这种情况下,周长是 R,为 6,371 公里。由于地球是一个扁球体,而不是一个完美的球体,我预计这会存在一些小的误差,但让我们继续看看我们能得到什么。

要在 Linux shell 中完成任何复杂的数学运算,我们几乎只能使用 bc,但即使它有点笨拙,也足以完成这项任务。

例如,这是您如何在 bc 脚本中设置 pi 值的方法

pi=$(echo "scale=10; 4*a(1)" | bc -l)

我们遇到的第一个障碍是 bc 想要使用弧度而不是度,但我们获得的经纬度值是以度为单位的,因此我们需要转换它们。

但在我们这样做之前,这是我们寻求的中间输出,因为我们现在需要处理两个地址,而不仅仅是一个

$ sh farapart.sh \
  "1600 pennsylvania ave, washington dc" \
  "1313 s. disneyland drive, anaheim, ca"

Lat/long for 1600 pennsylvania ave, washington dc

= 38.89859, -77.035971

Lat/long for 1313 s. disneyland drive, anaheim, ca

= 33.814413, -117.924424

下个月,我们将打开脚本,看看我是如何同时处理两个地址并将它们拆分为我们稍后需要的四个变量的。然后,我们将看看如何使用 bc 进行数学运算。

Dave Taylor 自 1980 年首次登录在线网络以来就一直参与 UNIX。这意味着,是的,他即将迎来 30 周年。您几乎可以在网上任何地方找到他,但从这里开始:www.DaveTaylorOnline.com。除了他的所有其他项目外,Dave 现在还是一名影评人。您可以在 www.DaveOnFilm.com 阅读他的评论。

加载 Disqus 评论