创建启用 GPS 的 Web 应用程序

使用 PHP、XML、jQuery 以及内置在浏览器中的 GPS 构建启用定位功能的新闻提要

本文将引导您使用 PHP 构建一个启用 GPS 的 web 应用程序的后端和前端。

Jack D Herrington, 高级软件工程师, Fortify Software, Inc.

Jack Herrington 的照片Jack Herrington 是一位生活和工作在海湾地区的工程师、作家和主持人。您可以通过 http://jackherrington.com 来关注他的工作和作品。



2011 年 9 月 19 日

启用 GPS 的 web 应用程序

常用缩略词

  • Ajax:异步 JavaScript + XML
  • API:应用编程接口
  • DOM:文档对象模型
  • GPS:全球定位系统
  • HTML:超文本标记语言
  • HTTP:超文本传输协议
  • PDO:PHP 数据对象
  • SQL:结构化查询语言
  • URL:统一资源定位符
  • W3C:万维网联盟
  • XML:可扩展标记语言

根据您的地理位置提供服务的 Web 站点在互联网上颇为流行。Foursquare、Yelp 以及 Google Maps 等所有这些站点都利用您的位置来为您提供与您所在位置相关的信息。您可以很容易地获得某个人的位置,并根据其位置为其提供信息。

在本文中,您将构建一个使用 GPS 定位为用户提供新闻内容的应用程序的后端和前端。后端是使用 PHP 编写的,使用 MySQL 保存文章列表以及与其相关的位置和坐标。前端 Web 页面使用 webkit 浏览器支持的定位服务来获取用户的 GPS 坐标。然后在 Ajax 请求中将这些坐标发送到后端。PHP 后端系统用文章的 XML 清单进行响应,前端会动态地呈现该清单。

您可以使用本文中的两种不同方法的定位。第一种方法是当您将新文章加入到数据库中时,您将为 PHP 后端提供一个位置(例如,Fremont, CA 或 Washington, DC),页面使用 Yahoo!提供的地理定位服务将该位置转变为 GPS 坐标。使用定位的第二种方法是当 Web 页面向浏览器发送请求以获得用户的位置时,使用该信息用 Ajax 查询数据库。


构建后端

后端代码起始于数据库,本例中数据库为 MySQL。清单 1 显示了单表数据库的模式。

清单 1. db.sql
DROP TABLE IF EXISTS articles;
CREATE TABLE articles(
     lon FLOAT,
     lat FLOAT,
     address TEXT,
     title TEXT,
     url TEXT,
     summary TEXT );

该表有六列,起始于纬度值和经度值以及为文章提供的原始地址(例如,Fremont, CA)。然后是一些有关物品的传记信息,包括标题、URL 和摘要。

要构建数据库,首先使用 mysqladmin 创建数据库,然后使用 mysql 命令来运行 db.sql 脚本:

% mysqladmin --user=root --password=foo create articles
% mysql --user=root --password=foo articles < db.sql

创建数据库后,您可以构建 PHP 页面,您将使用该 PHP 页面将记录添加到数据库。清单 2 显示了 insert.php 页面的代码。(注意:第 5 行和第 6 行上 $url 的值应当作为单个字符串显示,仅由于格式的需要将它显示为两个较短的字符串。)

清单 2. insert.php
<?php
$dd = new PDO('mysql:host=localhost;dbname=articles', 'root', '');
if ( isset( $_POST['url'] ) ) {
// You need a Yahoo! PlaceFinder application key
// Go to: http://developer.yahoo.com/geo/placefinder/
  $url = "http://where.yahooapis.com/geocode?q=".urlencode($_POST['address']).
         "&appid=[yourappid] ";

  $ch = curl_init(); 
  curl_setopt($ch, CURLOPT_URL, $url); 
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); 
  $body = curl_exec($ch); 

  preg_match( "/\<latitude\>(.*?)\<\/latitude\>/", $body, $lat );
  preg_match( "/\<longitude\>(.*?)\<\/longitude\>/", $body, $lon );

  $sql = 'INSERT INTO articles VALUES ( ?, ?, ?, ?, ?, ? )';
  $sth = $dd->prepare($sql);
  $sth->execute( array( 
    $lon[1],
    $lat[1],
    $_POST['address'],
    $_POST['title'],
    $_POST['url'],
    $_POST['summary']
    ) );
}
?>
<html>
<body>
<form method="post">
<table>
<tr><th>Title</td><th><input type="text" name="title" /></td></tr>
<tr><th>Summary</th><td><input type="text" name="summary" /></td></tr>
<tr><th>Address</th><td><input type="text" name="address" /></td></tr>
<tr><th>URL</th><td><input type="text" name="url" /></td></tr>
</table>
<input type="submit" value="Add Article" />
</form>
</body>
</html>

该页面是一个将记录插入数据库的非常标准的 PHP 页面。在顶部,您要检查用户是否已经将页面提交给您;如果提交,将使用 PDO 库将记录添加到数据库中。在脚本的底部是一个用于输入字段的 HTML 表单。

此处有趣的是在到 Yahoo!定位 web 服务的请求中使用了 Curl 库。要获取该代码以便运行,您需要一个由 Yahoo!为其 PlaceFinder 服务提供的应用程序 ID。对于该服务的 URL,参见 参考资料清单 2 中的代码。

PlaceFinder 服务将为给定的位置返回一个 XML 响应。该响应包括纬度和经度(加了大量其他有趣信息)。使用简单的正则表达式从 XML 提取纬度和经度。

如果某个地址总是被解析为某个坐标时,需要编写该代码。如果您选择在自己的系统中使用该代码,一定要检查是否会返回位置并且如果未对某个位置进行解析时为用户呈现一条告知性的错误消息。

要测试该代码,请在 PHP 服务器上安装 insert.php 页面,然后在浏览器中将其打开。您应当会看到类似于 图 1 中的表单。

图 1. 文章输入表单
带有标题、摘要、地址和 URL 字段,以及 'Add Article' 按钮的文章输入表单的屏幕截图

图 1 显示了一个简单的带有标题、摘要、地址和 URL 四个字段的 web 表单以及一个将文章加入到数据库中的按钮。图 2 显示了使用相关值进行填充后的字段。

图 2. 已填充的文章输入表单
用值填充后的文章输入表单的屏幕截图

图 2 中,使用本地文章的标题、摘要和 URL 填充这些字段。在地址字段中将 “fremont, ca” 指定为位置。您可以指定某个街道地址、邮编或可解析为纬度/经度对的其他任何信息。

此时,您应当使用 insert.php 页面对数据库填充一些有关您本地区的新闻文章。或者使用来自任何其他地方的一些文章来填充,但是要填入您的地址,这样由您的浏览器提供的 GPS 定位将会返回一些结果。

随着数据库的建立且不断得到填充,您可以构建前端会使用的搜索服务。


对于搜索服务,您需要的是一个 PHP 页面,该页面使用纬度和经度对以及半径,并且返回与该圆具有一定位置关系的任何文章。应当将返回的结果编码为 XML,这样 web 页面(或任何其他的应用程序)便可以很容易地读取它。清单 3 显示了这样一个 PHP 页面。

清单 3. find.php
<?php
define( 'LATMILES', 1 / 69 );
define( 'LONMILES', 1 / 53 );

// Change these default coordinates to your current location
$lat = 37.3328;
$lon = -122.036;
$radius = 1.0;

if ( isset( $_GET['lat'] ) ) { $lat = (float)$_GET['lat']; }
if ( isset( $_GET['lon'] ) ) { $lon = (float)$_GET['lon']; }
if ( isset( $_GET['radius'] ) ) { $radius = (float)$_GET['radius']; }

$minlat = $lat - ( $radius * LONMILES );
$minlon = $lon - ( $radius * LATMILES );
$maxlat = $lat + ( $radius * LONMILES );
$maxlon = $lon + ( $radius * LATMILES );

$dbh = new PDO('mysql:host=localhost;dbname=articles', 'root', '');

$sql = 'SELECT * FROM articles WHERE lat >= ? AND lat <= ? AND lon >= ? AND lon <= ?';

$params = array( $minlat, $maxlat, $minlon, $maxlon );

if ( isset( $_GET['q'] ) ) {
  $sql .= " AND name LIKE ?";
  $params []= '%'.$_GET['q'].'%';
}

$q = $dbh->prepare( $sql );
$q->execute( $params );

$doc = new DOMDocument();
$r = $doc->createElement( "locations" );
$doc->appendChild( $r );

foreach ( $q->fetchAll() as $row) {
  $dlat = ( (float)$row['lat'] - $lat ) / LATMILES;
  $dlon = ( (float)$row['lon'] - $lon ) / LONMILES;
  $d = sqrt( ( $dlat * $dlat ) + ( $dlon * $dlon ) );
  if ( $d <= $radius ) {
    $e = $doc->createElement( "article" );
    $e->setAttribute( 'lat', $row['lat'] );
    $e->setAttribute( 'lon', $row['lon'] );
    $te = $doc->createElement('title');
    $te->appendChild( $doc->createTextNode( utf8_encode( $row['title'] ) ) );
    $e->appendChild( $te );
    $se = $doc->createElement('summary');
    $se->appendChild( $doc->createTextNode( utf8_encode( $row['summary'] ) ) );
    $e->appendChild( $se );
    $ue = $doc->createElement('url');
    $ue->appendChild( $doc->createTextNode( utf8_encode( $row['url'] ) ) );
    $e->appendChild( $ue );
    $ae = $doc->createElement('address');
    $ae->appendChild( $doc->createTextNode( utf8_encode( $row['address'] ) ) );
    $e->appendChild( $ae );
    $e->setAttribute( 'd', $d );
    $r->appendChild( $e );
  }
}

print $doc->saveXML();
?>

代码看起来长且复杂,但实际上并非如此。您可以将其分割为两个部分。在顶部,脚本在 SELECT 语句中执行一个查找某个限制范围内所有文章的查询,该限制范围使用经度和纬度以及最小/最大限制值。

第二部分代码通过遍历每篇文章并进行检查以查看它是否落在圆内来对结果进行进一步限制。在 foreach 循环的开始有一些三角函数的代码。如果结果落在圆内,将会被添加到动态创建的 XML DOM 文档中。最后一些代码只是打印生成的 XML 文档。

由于所创建的所有节点,XML 的创建确实有点冗长。除了经度和纬度之外,所有文章特性都将表示为子节点,所以它们的大小不受限制。该方法对于特别冗长的摘要字段尤其重要,因为该字段应当包含有几个段落长的文章概要。

要测试查找服务,首先将其安装在服务器中,然后在浏览器中导航至该页。您应当会看到类似于 图 3 中的结果。

图 3. 呈现在浏览器中的查找结果 XML
呈现在浏览器中的查找结果 XML 的屏幕截图

图 3 显示了呈现 XML 结果的浏览器,这些 XML 结果作为各种各样的 Web 页面返回,不过看起来不美观,挤压在一起并且没有任何格式。要查看其 XML 样式,请在浏览器中选择 View Source 显示类似于 图 4 的代码。

图 4. 查看 find.php 结果的源代码
find.php 结果的 XML 源代码的屏幕截图

图 4 显示了原始 XML 格式的查询结果。

如果您没有看到结果,可能有几个问题。可能是脚本未能连接到数据库。位置可能有问题。您应当将 find.php 脚本中的默认位置更改为接近您的文章所指定的位置,或者您应当将经纬度参数添加到 Web 页面的 URL 中。另一种可能是您的文章具有无效的位置。要检查位置是否有效,请登录到 MySQL 服务器中,查询文章表来看看纬度和经度字段填写得是否合适。

假设 find.php 脚本返回一些结果,现在就可以在该系统上部署前端了。


构建用户界面的第一个版本

用户界面的第一个版本是非常简单的。从浏览器获取 GPS 坐标,将它们发送至服务器并指定搜索半径为 20 英里,然后将返回的数据进行格式化以便显示。您可以在 清单 4 中查看第一个版本的代码。

清单 4. index.php
<html>
<head>
<script src="jquery-1.6.1.min.js"></script>
<script>
$(document).ready(function() {
  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(successCallback, errorCallback);
  } else {
    alert('GPS not supported');
  }
});

function successCallback(position) {
  loadArticles( position.coords.latitude, position.coords.longitude );
}

function loadArticles( lat, lon ) {
  $.ajax({
    url: "find.php",
    dataType: 'xml',
    data: {
    lat:lat,
    lon:lon,
    radius:20
    },
    success: buildArticlePage
  });
}

function buildArticlePage( data ) {
  $(data).find("article").each(function() {
  var title = $(this).find("title").text();
  var summary = $(this).find("summary").text();
  var url = $(this).find("url").text();
  var address = $(this).find("address").text();
    $("#output").append( "<a target=\"_blank\" href=\""+url+"\">"+title+"</a><br/>");
    $("#output").append( "<i>"+address+"</i><br />");
    $("#output").append( summary+"<br /><br />");
  });
}

function errorCallback(error) {
  alert('Error getting GPS:'+error);
}
</script>
</head>
<body>
<div id="output">
</div>
</body> 
</html>

清单 4 中,代码最有趣的部分刚好在文件的顶部。加载代码之后,代码执行的最早的事情之一就是查找 navigator.geolocation,如果找到的话,在其上运行 getCurrentPosition 方法。该方法接受两个参数,这两个参数均为函数。如果找到位置将调用第一个函数。如果找不到位置将调用第二个函数。

如果找到某个位置,将调用 successCallback,该函数使用指定的坐标转而调用 loadArticles。如果浏览器提供坐标有问题的话,可以将这两个函数调用分开以便于调试。您也可以使用您所选择的任意坐标直接调用 loadArticles。

loadArticles 函数使用 jQuery Ajax 包向服务器发送 Ajax 请求。如果 web 服务器成功响应,便会调用 buildArticlePage。如果 find.php 位于其他的目录而非该页,您可能需要更改 loadArticles 中的 URL 以指向 find.php 所在位置。

buildArticlePage 使用一系列 jQuery 调用来解析 XML 并且将新 HTML 标签添加至位于 HTML 主体部分的 “output” 分区。

这是真的。请相信我,在 iOS、Android 或其他平台上将 GPS 支持写入某个 web 页面比使用专有 API 来获取位置要容易得多。如果您希望位置迅速改变的话,浏览器事件中的定位服务支持跟踪位置。

要测试该页,请在 web 服务器上安装 index.html 页面,然后在浏览器中将其打开,如 图 5 所示。

图 5. 使用 GPS 坐标获取文章结果后的 index 页面
基于 GPS 坐标列出文章结果的 index 页面的屏幕截图

图 5 显示数据库中文章的 web 页面,这些文章位于 Web 浏览器所提供的位置周围。在看到数据之前,您可能会看到类似于 图 6 中的警告。

图 6. Safari 位置警告提示
Safari 位置警告提示屏幕截图

图 6 显示来自服务器的关于将您当前位置提供给站点的警告。您可以根据您的喜好选择接受或拒绝,但是出于自己的测试目的,您需要接受,这样 JavaScript 可以获取坐标,进行 Ajax 调用并呈现结果。

如果您看到 “GPS not supported” 错误,那么您正在使用非 webkit 的浏览器,可能是 Microsoft® Internet Explorer®。在撰写本文时 Internet Explorer 不支持定位服务。

如果一切工作正常,但是您没有得到结果,那么可能是 20 英里的搜索半径太小。只需将半径更改为 2000,您应当便会看到一些结果,当然,要假定您在数据库中相同的地区放置了一些数据作为您的位置。

本文的最后一步是将其升级为具有半径控制的功能。


增强用户界面

要为页面添加半径控制,为页面添加一个 <select> 标签,该标签对您想要支持的每个半径值都提供选项。然后添加一些 jQuery 代码来监视半径控制的值并如 清单 5 所示更新页面。

清单 5. index2.php
<html>
<head>
<script src="jquery-1.6.1.min.js"></script>
<script>
var startLan = null;
var startLon = null;
$(document).ready(function() {
  $('#radius').change( function() {
    loadArticles( startLan, startLon );
  } );
  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(successCallback, errorCallback);
  } else {
    alert('GPS not supported');
  }
});

function successCallback(position) {
  loadArticles( position.coords.latitude, position.coords.longitude );
}

function loadArticles( lat, lon ) {
  startLan = lat;
  startLon = lon;
  $.ajax({
    url: "find.php",
    dataType: 'xml',
    data: {
    lat:lat,
    lon:lon,
    radius:$('#radius').val()
    },
    success: buildArticlePage
  });
}

function buildArticlePage( data ) {
  $('#output').empty();
  $(data).find("article").each(function() {
  var title = $(this).find("title").text();
  var summary = $(this).find("summary").text();
  var url = $(this).find("url").text();
  var address = $(this).find("address").text();
    $("#output").append( "<a target=\"_blank\" href=\""+url+"\">"+title+"</a><br/>");
    $("#output").append( "<i>"+address+"</i><br />");
    $("#output").append( summary+"<br /><br />");
  });
}

function errorCallback(error) {
  alert('Error getting GPS:'+error);
}
</script>
</head>
<body>
<select id="radius">
  <option value="5" selected>5</option>
  <option value="15">15</option>
  <option value="50">50</option>
  <option value="150">150</option>
</select>
<div id="output">
</div>
</body> 
</html>

清单 5 中,更改刚好起始于页面的顶部,这里为半径控制添加一个更改侦听器。当控制改变时,该代码将调用 loadArticles 函数。为了让代码更简单,loadArticles 方法存储最后一个位置,以便在半径发生更改时可以重新使用。页面的 HTML 部分也得到更新以添加 <select> 标签和半径选项。

要测试增强功能,首先要在服务器上安装更新的 web 页面作为 index2.html,然后在 web 浏览器中导航至该页面。您应当会看到类似于 图 7 的屏幕。

图 7. 目前具有半径控制的搜索页面
具有下拉字段以控制搜索半径的文章列表的屏幕截图

图 7 在页面的顶部显示了文章清单和新的半径下拉控制。动态更改该半径控制将通过重新运行具有已更新的半径值的 Ajax 请求来重新构建该页面。然后清空 output <div> 标签并添加新的已找到文章清单。至于 图 8,我将该值更改为 150。

图 8. 150 英里的搜索结果
在 150 英里半径内的搜索结果的屏幕截图

您可以在 图 8 中看到一些添加在页面底部的其他结果。

结束语

FourSquare、Yelp 和 Google Maps 等基于位置的服务是很流行的,因为人们想要知道在本地区有什么好东西,而且人们想要与其周围的人们取得联系。在 web 浏览器中使用这个新的位置 API、一点 PHP、一些 jQuery JavaScript、一点 XML 以及一些定位支持,您就将拥有您自己的启用定位功能的 Web 应用程序。以该代码作为起点,您可以使用自己的数据模型构建自己的基于位置的服务。如果您的应用程序需要客户端源源不断地提供位置数据,地理定位服务也支持该功能,这进一步开辟了可能性。

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML, Open source, Web development
ArticleID=758216
ArticleTitle=创建启用 GPS 的 Web 应用程序
publish-date=09192011