当我第一次计划从安娜堡的学校开车回新泽西州的家时,我记得我在谷歌地图上查了一下方向,注意到,就像这里的结果一样,它真的不需要很多步骤-或者不需要驾驶动作-就可以到达似乎相当长的一段穿越美国的路程。我记得我在谷歌地图上查到了方向,就像这里的结果一样,真的不需要很多步骤-或者开车-就可以到达似乎相当远的地方。事实上,使用谷歌提供的路线只需要17个转弯,甚至这个数字也被夸大了(那些“保持左边”和“保持右边”的步骤可能是有帮助的,但不是必要的)。
只是为了好玩,我试着将那500多英里的旅程与它的一小部分进行了比较,接近10英里的东西。你可以在这里看到这样一条路线。
值得注意的是,从新泽西州的一个小镇到另一个小镇的短途跳跃距离只有2%,只需要15步,或者只比去密歇根州的公路旅行少两步。
我开始想:公路旅行的长度和路线的复杂性之间有什么关系?大多数旅行,无论是长途旅行还是短途旅行,都需要大致相同的步骤吗?这个国家最复杂的路线有多少个台阶?在毗邻的美国,每条可能的路线的步数分布是什么?
事实证明,这些问题很容易解决,这在很大程度上要归功于Google的Directions API,它让像我这样的低级开发人员能够访问他们的全套地图、地理位置和路径查找算法,海量数据存储,以及可以在几分钟内将数万个查询结果发送到一台客户端计算机的快速服务器。
不过,在我们深入研究方法和结果之前,让我们先确切地列出我们要寻找的内容:
我们想要美国境内一些有代表性的路线样本的步数直方图。这会给我们一个很好的感觉,让我们知道一次典型的公路旅行可能有多复杂。
我们想要找到这个国家最复杂的路线,也就是说,两个点之间的行驶方向,由谷歌给出,包含的步数比毗邻的美国的任何其他两个点都要多。我们极不可能找到这条怪兽路线,但至少我们希望大致估计出它的步数-是35,500,90,180步?
我们想要一张路线距离的曲线图,而不是路线的复杂性。剧情会是什么样子?复杂性是距离的线性函数吗?有没有直接的或相反的关系?会不会有什么模式呢?
找出美国地区的“摩擦系数”将是相当酷的,也就是说,根据一条经过该地区的路线平均有多少步,对驾车通过该地区有多难进行数字估计。我们可以使用这些信息来创建整个美国的“热图”,每个县或邮政编码都受到摩擦的影响。这样的地图将帮助我们找出哪些州的道路最难走,或者哪里的路线最直截了当,或者哪些城市最难走出去。
然后,为了开始,我去寻找一个随机的点样本。要做到这一点,一种方法是绘制一个刻在美国大陆内的方框,并在该方框内简单地生成随机的经度。我认为,这种方法的麻烦在于,它很容易把你送到荒芜或荒谬的地方,比如沙漠或湖泊;我想把重点放在从一个人口中心到另一个人口中心的看似合理的现实生活旅行上。
所以我去找一个数据集,没过多久,我就找到了一个:“MaxMind World Cities with Population”文件,一个33MB的免费下载,有足够多的数据来推动工作:剔除了美国以外的城市(一个简单的grep就能做到这一点,因为文本结构很好),我只剩下141,989分,几乎覆盖了这个国家的每一个角落。
我拼凑了一个很小的Rails项目(RoR是我现在让一切看起来像钉子的锤子),目的是(A)将城市和经度加载成某种结构化的形式,(B)将数据放入连接到JavaScript Google Directions API的HTML页面,以及(C)将结果写回数据库。所有相关代码,以及带有结构化城市数据和结果的SQLite3数据库,都可以在这个GitHub项目页面上找到。
也许最重要的代码片段(我将在下面摘录)是实际对点进行采样并与Google对话的代码:
var map;var directionDisplay;var directionsService;var stepDisplay;var markerArray=[];var step_count=[];var step_Summary aries=[];function initialize(){//实例化方向服务。directionsService=新谷歌。地图。DirectionsService();}函数calcRoute(Start,End){//检索起始和结束位置并使用行驶方向创建//DirectionsRequest。VAR CITY_START=[START[2],START[3]]。JOIN(";,";);var CITY_END=[END[2],END[3]]。JOIN(";,";);var LatLong_start=[start[0],start[1]]。JOIN(";,";);var LatLong_End=[end[0],end[1]]。Join(";,";);var request={Origin:LatLong_Start,Destination:LatLong_End,Travel Mode:Google。地图。DirectionsTravelMode。DRIVING};//选择方向路线并将响应传递给//函数以统计返回的步数。方向服务。route(请求,功能(响应,状态){if(状态==google。地图。方向状态。OK){var step_ct=countSteps(响应);step_counts。PUSH(Step_Ct);step_Summary aries。PUSH([step_ct,City_start,City_end])}否则{控制台。警告(";无法计算此路由的步骤。";);}});}函数countSteps(DirectionResult){var myRoute=directionResult。路由[0]。LEGES[0];返回myRoute。台阶。Length;}函数getAndExecutePair(N){$.。get(";/directions/get_paols";,{n:n},function(Ret){pains=ret;console。LOG(n+";经度/经度对下载成功。";);EXECUTE(PARAES);}}函数EXECUTE(PARAES){FOR(i=0;I<;Pair)。长度;i++){Pair=Pair[i];Start=Pair[0];End=Pair[1];calcRoute(Start,End);}}。
这一切都很简单明了。该过程由getAndExecutePair()函数开始,该函数只访问n对城市的Rails服务器。这是它所称的代码:
def get_pains n=params[:n]。To_i Cities=City。查找(:All,:Limit=>;n*2,:Order=>;&34;Random()&34;);lat_long s=Cities。收集{|c|[c.。纬度,c。经度,c。城市,c。状态]}对=哈希[*LAT_LONG]。呈现:JSON=>;对结束(_A)。
就是这样。只需要大约100行代码,我就能够很好地掌握上面提出的四个问题中的前三个问题。特别是:
1.在毗邻的美国,每条可能的路线的步数分布是什么?
基于大约2000个点的随机抽样(后来又有8000多个试验证实),答案是你有一种右倾分布,以20-30步为中心,在60步附近逐渐减少。
这不是很确定,但我得到的答案是69步,从新墨西哥州的Ponderose Pine到明尼苏达州的Wildwood。一位朋友建议使用下面这样的方法来寻找更复杂的路线:
假设你在A点和B点之间有一条“好的”(也就是逐步的)路线,在A点和B点周围画一个方框,在这个方框内“摆动”你的起点。如果在一个方向上摆动可以移除步骤,那么尝试在另一个方向上摆动;或者,如果方向不重要,而是一些棘手的事情,比如“在开发项目内或在河流后面”,也许您可以随机选择框中的点,并将分数分配给不同的区域(有点像战舰游戏)。这样,你就可以慢慢地优化有希望的路线,直到你最终得到真正高的数字。
此方法的一个潜在缺陷是可能存在不连续性-A到B可能需要35步,但(A+ε)到B可能需要70步-在这种情况下,您可能一开始就没有选择正确的起点。但这可能只有在正常社区旁边有像迷宫一样的东西的情况下才会发生。
上图根据步数绘制路线距离(测量为两个经度对之间的曲面距离)。除了几个异常值之外,你会注意到非常宽的步数范围被相对较窄的距离范围所覆盖:也就是说,步数的大部分变化都是在不到几百英里的行程中造成的;在边际上,就路线复杂性而言,多走一英里几乎不会给你带来什么。
也许最有趣的地方是那些步数很多的短路。例如,参见上面提到的69步路线(略高于1000英里),或从NH的南林德博罗到佐治亚州哈特利的67步路线(875英里)。
(您会注意到一些超过70步的路线-这些路线可以安全地忽略,因为它们要么始发于阿拉斯加,要么以阿拉斯加为终点(哎呀!))。
4.你能不能生成一个“热图”,根据区域的“摩擦系数”对区域进行着色,也就是根据一条经过该区域的路线平均有多少步,用数值估算通过该区域的难度?“热图”可以用“摩擦系数”来对区域进行着色,也就是说,根据一条经过该区域的路线平均有多少步来估算通过该区域的难度。
这是留给读者的练习,还有寻找更长路线的任务(这里是75步),以及更普遍的潜在问题,即理解哪些特征-城市、道路和地理-与路线复杂性有关。