Создание Web-приложений, поддерживающих GPS

Создаем новостной фид, учитывающий местонахождение, при помощи PHP, XML, jQuery и встроенной в браузер поддержки GPS

В статье описывается создание серверной и клиентской частей поддерживающего GPS Web-приложения с использованием PHP.

Джек Д Херрингтон, главный инженер-программист, Leverage Software Inc.

Джек Д. Херрингтон (Jack D. Herrington) - главный инженер-программист с более чем двадцатилетним опытом работы. Он автор трех книг: "Генерирование кода в действии", "Podcasting Hacks" и "PHP Hacks". Написал более 30 статей. Вы можете связаться с Джеком по адресу jherr@pobox.com.



11.04.2012

Web-приложения, поддерживающие GPS

Часто используемые сокращения

  • Ajax: Asynchronous JavaScript + XML (асинхронный JavaScript и XML)
  • API: Application Programming Interface (прикладной интерфейс программирования)
  • DOM: Document Object Model (объектная модель документа)
  • GPS: Global Positioning System (система глобального позиционирования)
  • HTML: Hypertext Markup Language (язык разметки гипертекста)
  • HTTP: Hypertext Transfer Protocol (протокол передачи гипертекста)
  • PDO: PHP Data Object (расширение для PHP, предоставляющее интерфейс для доступа к различным базам данных)
  • SQL: Structured Query Language (язык структурированных запросов)
  • URL: Uniform Resource Locator (унифицированный указатель ресурсов)
  • W3C: World Wide Web Consortium (консорциум WWW)
  • XML: Extensible Markup Language (расширяемый язык разметки)

Существует много Web-сайтов, предоставляющих сервисы на основе географического местонахождения пользователя. Такие сайты, как Foursquare, Yelp и Google Maps, используют данные о вашем местонахождении для предоставления важной для вас информации в соответствии с тем, где вы находитесь. Они легко могут определить местонахождение пользователя и отправить ему информацию на основании этого местоположения.

В этой статье мы создадим серверную и клиентскую части приложения, учитывающего данные GPS при предоставлении пользователям новостей. Серверная часть написана на PHP и хранит в базе данных MySQL список статей и связанных с ними данных о географическом местоположении и координатах. Интерфейсная Web-страница использует сервисы определения местоположения, поддерживаемые webkit-браузерами, для получения GPS-координат пользователя. Затем она отправляет эти координаты на сервер в Ajax-запросе. Серверная PHP-система возвращает XML-список статей, который динамически визуализируется клиентом.

В этой статье данные о местоположении используются два раза. Первый раз – при добавлении новых статей в базу данных. Мы указываем PHP-серверу местоположение (например, Fremont, CA или Washington, DC), а страница c помощью геолокационного сервиса, предоставляемого 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-страницу, которая будет служить для добавления записей в базу данных. В листинге 2 приведен исходный код страницы insert.php. (Примечание: значения $url в строках 5 и 6 должны находиться в одной строке символов. Здесь эта строка разделена на две только в целях форматирования).

Листинг 2. insert.php
<?php
$dd = new PDO('mysql:host=localhost;dbname=articles', 'root', '');
if ( isset( $_POST['url'] ) ) {
// Нам необходим предоставляемый Yahoo! ключ приложения PlaceFinder.
// Перейдите на страницу 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-форма ввода.

Интересным здесь является запрос к Web-сервису определения местоположения Yahoo! Location, использующий библиотеку Curl. Для работы данного кода необходим идентификатор приложения, предоставляемый Yahoo! для своего сервиса PlaceFinder. Адрес URL этого сервиса приведен в разделе Ресурсы и в листинге 2.

Сервис PlaceFinder возвращает XML-ответ для данного местоположения. Ответ содержит широту и долготу (а также много дополнительной интересной информации). Для извлечения из XML нужных значений широты и долготы используется простое регулярное выражение.

Данный код написан в предположении, что адрес всегда преобразуется в координаты. При использовании этого кода в своей собственной системе обязательно проверяйте присутствие в возвращаемой информации нужных данных и предоставляйте пользователю информативное сообщение об ошибке, если местоположение не найдено.

Для тестирования этого кода разместите страницу insert.php на PHP-сервере и откройте ее в браузере. Должна отобразиться форма, показанная на рисунке 1.

Рисунок 1. Форма ввода статьи
Рисунок 1. Форма ввода статьи

На рисунке 1 показана простая Web-форма из четырех полей (title, summary, address и URL) и кнопки для добавления статьи в базу данных. На рисунке 2 показана форма с заполненными полями.

Рисунок 2. Заполненная форма ввода статьи
Рисунок 2. Заполненная форма ввода статьи

На рисунке 2 в поля введены заголовок, краткое изложение и URL-адрес местной статьи о каком-то судебном решении. Местоположение указывается в поле Address как "fremont, ca". Можно было бы указать улицу и номер дома, почтовый индекс или что-нибудь еще, что преобразуется в пару широта/долгота.

На этом этапе необходимо, используя страницу insert.php, заполнить базу данных несколькими новыми статьями для вашей местности. Можно также использовать любые статьи, но указать для них ваш адрес, чтобы GPS-сервис, предоставляемый браузером, возвращал какие-либо результаты.

После создания и заполнения базы данных можно приступать к созданию сервиса поиска, который будет использовать клиент.


Сервис поиска – это PHP-страница, принимающая пару значений широта/долгота и радиус и возвращающая все статьи, местоположение которых вписывается в соответствующий круг. Данные должны быть возвращены в виде XML, чтобы Web-страница (или любое другое приложение) могла с легкостью прочитать их. В листинге 3 приведена именно такая PHP-страница.

Листинг 3. find.php
<?php
define( 'LATMILES', 1 / 69 );
define( 'LONMILES', 1 / 53 );

// Измените координаты по умолчанию на ваше текущее местоположение
$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();
?>

Исходный код выглядит длинным и сложным, но на самом деле это не так. Его можно разделить на две части. В самом начале сценарий выполняет поиск всех статей, местоположение которых вписывается в квадрат, определяемый широтой, долготой и ограничениями min/max в выражении SELECT.

Вторая часть кода еще больше ограничивает возвращаемые результаты, проверяя каждую статью на попадание в круг. В начале цикла foreach присутствует небольшой тригонометрический код. Если местоположение попадает в круг, результат добавляется в XML DOM-документ, создаваемый "на лету". Последняя часть кода просто выводит полученный XML-документ.

Код создания XML получается немного длинным, поскольку создает все узлы документа. Все параметры статьи, кроме широты и долготы, являются подузлами (subnode), поэтому могут иметь неограниченный размер. Это особенно важно для поля summary, которое может быть довольно длинным, так как может содержать конспект статьи длиной в несколько параграфов.

Для тестирования сервиса поиска сначала установите его на сервере, а затем вызовите в браузере. Должна отобразиться информация, показанная на рисунке 3.

Рисунок 3. Результаты поиска в браузере
Рисунок 3. Результаты поиска в браузере

На рисунке 3 показан браузер, отображающий XML-результаты в виде Web-страницы, которая выглядит уродливой, бессмысленной и абсолютно не отформатированной. Чтобы просмотреть ее в виде XML, выберите View Source в браузере, после чего отобразится что-то похожее на рисунок 4.

Рисунок 4. Просмотр исходного кода результата работы find.php
Рисунок 4. Просмотр исходного кода результата работы find.php

На рисунке 4 показан результат запроса в исходном XML-формате.

Отсутствию результатов может быть несколько причин. Возможно, сценарий не смог подключиться к базе данных. Возможна проблема с местоположением. Следует изменить в сценарии find.php местоположение по умолчанию на что-то более близкое к тому, которое указано для ваших статей, либо добавить параметры lat и lon к URL-адресу Web-страницы. Еще одной причиной может быть некорректное местоположение, указанное для статей. Для проверки зайдите на сервер MySQL и выполните запрос к таблице articles самостоятельно, чтобы убедиться в правильности заполнения полей lat и lon.

Предполагая, что сценарий 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 отправляет Ajax-запрос серверу, используя оболочку jQuery Ajax. При успешном ответе Web-сервера вызывается buildArticlePage. Возможно, понадобится изменить URL в loadArticles, чтобы он указывал на местонахождение файла find.php, если этот файл находится не в каталоге данной страницы.

buildArticlePage выполняет ряд jQuery-вызовов для анализа XML и для добавления новых HTML-тегов в выходной тег <div>, расположенный в секции body HTML-страницы.

Пока это все. Поверьте, что реализовать поддержку GPS на Web-странице намного проще, чем использовать фирменный интерфейс получения местоположения на iOS, Android или других платформах. Сервис geolocation поддерживает отслеживание местоположения, если ожидается его быстрое изменение.

Для тестирования страницы установите index.html на Web-сервере и откройте ее в браузере, как показано на рисунке 5.

Рисунок 5. Страница index после получения результатов с использованием GPS-координат
Рисунок 5. Страница index после получения результатов с использованием GPS-координат

На рисунке 5 показана Web-страница со статьями из базы данных, относящимися к области, указанной Web-браузером. Перед отображением этих данных возможно появление предупреждения, показанного на рисунке 6.

Рисунок 6. Предупреждение в Safari
Рисунок 6. Предупреждение в Safari

На рисунке 6 показано предупреждение сервера о передаче вашего текущего местоположения на Web-сайт. Вы можете разрешить или запретить такую передачу, но для тестирования нужно разрешить, чтобы код JavaScript смог получить координаты, выполнить Ajax-вызов и визуализировать результаты.

Появление ошибки "GPS not supported" (GPS не поддерживается) говорит о том, что используется несовместимый с webkit браузер, например Microsoft® Internet Explorer®. На момент написания данной статьи Internet Explorer не поддерживал сервисы определения местоположения.

Если все работает, но результатов нет, возможно, радиус поиска в двадцать миль слишком мал. Просто изменив радиус на 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 изменения начинаются в верхней части страницы, где добавляется код прослушивания (listener) изменений для управления радиусом. Этот код активизирует функцию loadArticles при изменении радиуса. Чтобы упростить код, метод loadArticles сохраняет последнее местоположение для повторного использования при изменении радиуса. В HTML-секцию страницы тоже добавляются тег <select> и варианты радиуса.

Для тестирования изменений сначала разместите обновленную Web-страницу на сервере как index2.html, а затем откройте ее в Web-браузере. Должен отобразиться экран, показанный на рисунке 7.

Рисунок 7. Страница поиска с управлением радиусом
Рисунок 7. Страница поиска с управлением радиусом

На рисунке 7 показан список статей вместе с новым элементом управления (раскрывающимся списком радиусов) в верхней части страницы. Изменение этого элемента управления динамически перестраивает страницу путем выполнения Ajax-запроса с обновленным значением радиуса. Затем очищается выходной тег <div> и добавляется новый набор найденных статей. Для рисунка 8 я изменил значение на 150.

Рисунок 8. Результаты поиска для радиуса 150 миль
Рисунок 8. Результаты поиска для радиуса 150 миль

На рисунке 8 можно увидеть дополнительные результаты, добавленные в нижнюю часть страницы.

Заключение

Сервисы определения местоположения, такие как FourSquare, Yelp и Google Maps, популярны, потому что люди интересуются событиями в своей местности и хотят общаться со своими соседями. Используя этот новый API определения местоположения (немного PHP, jQuery JavaScript, XML и поддержка определения местоположения в Web-браузере), вы можете создавать Web-приложения, работающие с GPS-координатами. Используя исходный код, рассмотренный в данной статье, в качестве отправной точки, вы можете создать свой собственный сервис со своей моделью данных. А если ваше приложение требует постоянного получения данных о местоположении от клиента, геолокационные сервисы позволят решить и эту задачу, что открывает еще больше возможностей.

Ресурсы

  • Оригинал статьи: Create GPS-enabling web applications (EN).
  • Сервис Yahoo! PlaceFinder: преобразование адресов в пары координат широта/долгота.
  • Geocoding (Википедия): отличная страница по геокодированию (geocoding) - процессу преобразования адресов в пары значений широта/долгота.
  • Спецификация W3C Geolocation API Specification (редакторский проект, февраль 2010 года): спецификация для сервиса определения местоположения в браузере.
  • Библиотека jQuery: отличный Web-сайт, на котором размещена вся техническая документация и доступные расширения.
  • Сайт PHP: лучшая справочная информация по PHP.
  • W3C: отличный сайт по стандартам, в частности, по стандартам XML, имеющим отношение к данной статье.
  • Другие статьи данного автора (Джек Херрингтон (Jack Herrington), developerWorks, с марта 2005 года по настоящее время): статьи об Ajax, JSON, PHP, XML и других технологиях.
  • Сертификация IBM по XML: информация о получении сертификата IBM-Certified Developer по XML и смежным технологиям.
  • developerWorks в Твиттере. Следите за твитами developerWorks.
  • Демонстрации по запросу на developerWorks. Смотрите на developerWorks предоставляемые по запросу демонстрации – от установки и настройки продуктов для начинающих до продвинутых функциональных возможностей для опытных разработчиков.

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=XML, Open source
ArticleID=809730
ArticleTitle=Создание Web-приложений, поддерживающих GPS
publish-date=04112012