地理定位
房地产行业有句老话,房产最重要的三件事是位置、位置还是位置。我们可以认为这在房地产领域仍然适用,但对于 Web 应用程序而言也越来越真实。我最近的一些咨询项目都以某种方式包含了处理各种地址和位置的需求。
鉴于 Web 正在成为我们沟通、存储信息和工作的方式,这不应该太令人惊讶。当我访问的网站祝我“早上好”时,这给我一种温暖(如果有点令人毛骨悚然)的感觉,因为它知道现在是我所在地区的早晨。当地图程序默认显示我当前的位置时,这非常有用。作为运行各种应用程序的人,我喜欢我可以了解有关用户的基本地理信息这一事实——这样我既可以提供额外的服务,同时又可以接收有用的数据。
处理街道地址、位置坐标等都属于“地理定位”的范畴。因此,在本文中,我将回顾一些使用地理定位的技术和选项,并就如何在您自己的 Web 应用程序中包含此类功能提供一些建议。
选择哪个服务器?在地理定位方面,首先要意识到的是,您几乎肯定无法单独完成。当然,如果有大量的时间和金钱,您或许可以找出世界上大多数人的位置和街道地址,但您不太可能这样做。这意味着您将不得不连接到一个或多个拥有并通过 API 分发地图信息的公司,例如 Google、Bing(微软)或类似公司。
商业地图提供商有免费和开源的替代方案,例如 http://freegeoip.net 和 http://www.openstreetmap.org。然而,商业产品的 API 更丰富,而且似乎得到了更好的支持。即使是一些免费服务也需要或期望您拥有 API 密钥,为此您需要注册。这使他们能够跟踪您发出的请求数量,并在您不购买商业层级的情况下限制您的使用。虽然使用开源工具很有用且令人愉快,但本专栏的其余部分假设您正在使用商业提供商。
请注意,某些 API 库为街道地址和 IP 地址提供与多个服务器的单一接口。例如,Ruby 的 Geocoder gem(由 Alex Reisner 编写和维护)允许您从许多不同的地图提供商以及许多 IP 地址提供商中进行选择,默认分别为 Google 和 freegeoip.net。然后,您可以根据您的用例决定是使用免费服务还是商业服务,或者将两者混合使用。
同样重要的是要记住,准确率远非 100%。例如,我决定查找我在伊利诺伊州斯科基居住时的一个旧地址。我编写了一个小的 Ruby 程序来执行此操作
require 'geocoder'
Geocoder.search('9120b niles center road skokie il')
作为默认解码系统的 Google 几乎立即返回了格式更好的地址版本,以及大量其他信息。我能够从系统中获取地址
Geocoder.search('9120b niles center road skokie
↪il')[0].formatted_address
=> "9120C Niles Center Road, Skokie, IL 60076, USA"
现在,事实是,那栋联排别墅中的 B 和 C 单元彼此相邻。而且很可能,如果我在地图上查看,或者甚至向其中一个地址发送邮件,差异将是显而易见的。但正如您所看到的,从 Google 返回的地址不一定是正确的地址。
Google API 的优点之一是它包含全球各地的大量位置。例如,我可以查找我当前的地址
Geocoder.search('14 migdal oz street modiin israel')
但在这种情况下,我没有得到与我的地址匹配的地址,而是我的城市莫迪因的总体条目。实际上,我甚至没有得到单个条目,而是三个,每个条目都以不同的方式代表莫迪因,拼写略有不同。如果我从返回的三个结果对象中的每一个请求坐标,则条目之间的差异最为明显
Geocoder.search('14 migdal oz street modiin
↪israel').map {|a| a.geometry['location'] }
=> [
[0] { "lat" => 31.90912, "lng" => 35.002462 },
[1] { "lat" => 31.890267, "lng" => 35.010397 },
[2] { "lat" => 31.893661, "lng" => 34.96079 }
]
对于许多目的而言,这些坐标都足够接近。但是,如果您正在创建依赖于精确精度的应用程序,例如 GIS 导航应用程序,您可能需要比较不同的服务,甚至执行多次查询,以获得与感兴趣位置最匹配的结果。
地址和坐标现在您已经看到了几个示例,说明如何使用 Geocoder Ruby gem 轻松执行地理编码。给定一个地址,您可以调用 Geocoder 对象上的“search”类方法,获取一个 Geocoder 结果对象数组,其中包含有关结果地址的各种信息。即使只有一个结果,您也会收到一个数组。而且,Google API 会尽力匹配某些内容。它返回了“1 Main Street, Fredonia”的结果,但当我输入“1 zzz street, yyy qqq”时返回了一个空数组。
结果对象包含大量信息。如果我对地址的标准化版本感兴趣,我可以调用结果对象上的“address_components”方法,这将返回一个哈希数组,其中包含街道号码、街道名称、村庄名称等等。结果的这一部分包含的信息比您在美国地址信封所需的信息更多——例如,它包括县和城市名称,以及州和邮政编码。您可以单独获取这些信息,也可以调用将它们组合在一起的方法。我可以使用“formatted_address”方法(如上所示)来获取完整地址,或者使用“street_address”方法来仅获取最重要的部分。
在过去几年中,我为客户编写的几个应用程序都使用了地理编码 API 来标准化地址,确保它们具有符合美国规范的“官方”地址。这也有助于避免拼写错误和其他可能在未来造成麻烦的错误。因此,即使当用户输入他们自己的地址时,我们也会通过地理编码工具运行它,并存储此搜索的结果。(最好也存储最初输入的地址。)
除了地址(或作为地址的补充)之外,您通常还希望获取坐标,包括经度和纬度。由于坐标给出了地球上的确切位置,因此您可以在各种与个人地址无关的地方使用它们,例如地图软件或 GIS 数据库(例如 PostGIS,PostgreSQL 的 GIS 扩展)。如果我有一个特定地点的坐标,那么我可以非常精确地将其绘制在地图上。近年来,我的两位客户要求我在地图上显示用户地址时出于隐私原因将其隐藏。编造地址(例如,将“123 Main Street”更改为“456 Main Street”)几乎肯定会引起麻烦和失败,但将坐标更改一个小的随机因子效果很好。
地理编码 IP 地址虽然我的一些地理编码工作涉及从用户输入中获取地址,但其中大部分恰恰相反——试图找出用户的位置,然后利用该信息做一些事情。换句话说,我想获取用户的 IP 地址并用它来精确定位用户的位置。
首先要意识到的是,至少在某种程度上,HTML5 地理定位 API 已经减少了对此类事物的需求。该 API 在客户端和 JavaScript 中实现,允许应用程序要求浏览器报告其当前位置。(标准要求浏览器在发送位置信息之前询问用户。)然后,您可以使用 JavaScript 在网页中使用该信息,或者调用 Ajax 调用将该信息发送到服务器,在那里可以对其进行解析和使用。
在最近的一个项目中,我并不想用地理定位信息打扰用户,也不想在 Web 应用程序中使用该信息。相反,我想查看应用程序的日志并总结访问者来自哪些国家/地区。为此,我需要查看每个日志文件条目,然后查找每个 IP 地址,确定其国家/地区。
现在,请注意,此类信息可能非常不准确。例如,我目前正在以色列莫迪因的当地公共图书馆撰写本文,我的 IP 地址被报告为 81.218.200.112 给外界。我可以让 Geocoder 告诉我它认为我在哪里
result = Geocoder.search('81.218.200.112')
不幸的是,除了知道我在以色列之外,它一无所知
result[0].country
=> "Israel"
result[0].city
=> ""
根据 http://www.iplocation.net(它为个人访问者提供 IP 位置信息),它认为我在佩塔提克瓦——一个不错的城市,但距离我坐的地方有 40 分钟车程。那是因为地理定位正在寻找电信设施或提供商,而不是我所在的具体位置。
因此,您应该始终以健康的怀疑态度对待 IP 地理定位。此外,许多 IP 地址不在地理定位数据库中。其他 IP 地址与公司或服务(例如,Google 的 searchbot)相关联,这些公司或服务将访问您的网站并发出请求,但没有位置信息。还有一些访问者通过手机和服务访问您的网站,这些手机和服务通常在全国范围内,因此无法提供准确的读取。
也就是说,如果您有兴趣了解有关用户的概括信息——他们的原籍国和时区——那么 IP 位置可以很好地工作。正如您所看到的,Geocoder gem 允许您使用相同的类方法“search”来请求有关 IP 地址的信息。它可以判断您输入的是 IP 地址、坐标还是街道地址,并据此进行处理。对于最近的一个项目,我能够通过 IP 地理定位库运行 IP 地址,从而提供有关人员来自哪些国家/地区的有趣信息和分析。
作为一般规则,您永远不应该在用户访问您的网站时实时执行此类操作。您最好运行后台任务或每小时的 cron 作业。当您收集和存储 IP 位置信息时,您几乎肯定应该将其存储在数据库中,或者至少缓存它,以避免向地理定位服务发出过多请求。
如果您最终将 Geocoder 与 Rails 一起使用,您将获得一个“location”方法,您可以在“request”对象上调用该方法,从而允许您通过 IP 地址自动获取用户信息。我还没有测试过调用“location”方法是否会显着增加响应时间,或者它是否以某种方式在单独的线程中处理,或者通过转向本地服务器上数据的缓存副本,但在投入生产之前检查性能影响是明智的。
配置到目前为止,我几乎没有提到配置,因为我发现 Geocoder 开箱即用效果很好。也就是说,有时我希望或需要重新配置它。幸运的是,配置非常简单明了,通过调用 Geocoder.configure 类方法即可完成。
例如,在图书馆撰写本文时,我发现 Wi-Fi 连接非常慢——甚至简单的 API 调用也超时了。我惊喜地发现 Geocoder gem 非常智能,可以意识到问题是超时,并建议我可以通过调用 Geocoder.configure 来避免超时。现在,这正是我希望更常见的那种错误消息!所以,我调用了
Geocoder.configure(:timeout => 1000)
果然,我未来的调用工作正常,即使它们需要一段时间才能执行。
您始终可以通过调用 Geocoder.location 而不带任何选项来获取当前配置设置。这将返回一个哈希,其中包含与配置系统关联的所有名称-值对。
首先,如果您想使用与默认 Google 不同的地理编码 API,您可以通过更改配置系统中的“lookup”参数来实现
Geocoder.configure(lookup: :nominatim)
现在,搜索结果将不再是 Geocoder::Result::Google 的数组(这是您之前收到的),而是 Geocoder::Result::Nominatim。每个结果对象都有一组不同的方法和属性,这意味着您不能简单地将一个 API 换成另一个 API。可用的方法和数据尽可能地反映了从地理编码 API 收到的信息。
总结地理定位远非完美精确。但是,这种缺乏精度并不意味着您应该避免在应用程序中使用它。无论您是想向用户发送本地化问候语、标准化地址,还是创建有关谁在访问您的应用程序的摘要和报告,地理编码都是您可能会发现对您的许多应用程序有用的技术。商业和免费 API 可能易于使用,但像 Geocoder 这样的开源库的存在使其更加容易。
资源Ruby Geocoder gem 的主页位于 https://github.com/alexreisner/geocoder。该 gem 仍在积极开发中,GitHub 页面包含大量文档和示例。
开源和免费的地理编码站点 http://freegeoip.net 和 http://OpenStreetMap.org 应用程序(它正在构建任何人都可以使用的世界地图)都值得访问,甚至可以将其合并到您的应用程序中。
如果您是 Python 用户,您应该查看 pygeocoder 包,该包在 PyPI (https://pypi.python.org/pypi) 上可用,它执行与本专栏中讨论的 Geocoder Ruby gem 类似的操作。
最后,如果您有兴趣将地理定位结果存储在数据库中,您应该研究 PostGIS (http://postgis.org),它是 PostgreSQL 数据库的扩展,包含 GIS。我仍在 PostGIS 中迈出第一步,但 Regina Obe 和 Leo Hsu 编写并由 Manning 出版的 PostGIS in Action 一书提供了有用的介绍和教程。