вторник, 26 марта 2013 г.

Интеграция геопозиционирования в веб-приложения

Создание приложений HTML5

Интеграция геопозиционирования в веб-приложения

Брэндон Сэтром
Продукты и технологии:
HTML5, Geolocation API, Internet Explorer 9, WebMatrix, Bing Maps AJAX Control, Modernizr
В статье рассматриваются:
• Geolocation API;
• как работает геопозиционирование (geolocation);
• распознавание и полизаполнение поддержки геопозиционирования.
Исходный код можно скачать по ссылке code.msdn.microsoft.com/mag201112HTML5.
Если у вас есть смартфон, то, вероятно, вы используете какое-то приложение или встроенную функциональность для получения инструкций о том, как попасть из точки, где вы находитесь, в нужное место или для поиска ближайшего ресторанчика, отеля или бензоколонки в незнакомом вам городе. С технической точки зрения, этот механизм называется геопозиционированием (geolocation), и он описывает, каким образом устройство может использовать внешние данные о местоположении (данные от системы глобального позиционирования [global positioning system, GPS], триангуляционной системы вышек сотовой связи [cell tower triangulation] и Wi-Fi), чтобы найти, где вы находитесь, и предоставить эту информацию приложению или сервису, запрашивающему эти данные. Ваше местонахождение обычно выражается с использованием таких данных, как широта и долгота, скорость движения, направление и т. п. Благодаря этой информации устройство может поддерживать функциональность вроде навигации с указанием поворотов (turn-by-turn navigation) или ближайшие места размещения фургончиков, из которых торгуют закусками и которые открыты в два часа ночи.
Геопозиционирование воспринимается нами на смартфонах как нечто само собой разумеющееся, но его применение быстро выходит за рамки мобильных приложений. Сейчас все больше лэптопов и мобильных ПК оснащаются чипами GPS, и взрывной рост использования Web в качестве конкурентной платформы для разработки мобильных приложений вызвал спрос на сервисы геопозиционирования, которые были бы доступны прямо из браузера.
К счастью для нас, W3C довольно рано уловила этот тренд и выработала спецификацию, которая описывает стандартный способ использования сервисов геопозиционирования из веб-приложений. Эта спецификация (bit.ly/3r737c) «официально» не является частью HTML5, но, поскольку я употребляю этот термин в данной серии статей для описания набора открытых веб-технологий (как это делает W3C), я не мог упустить представившуюся возможность совершить с вами краткий экскурс по этой интересной технологии.
Сегодня я дам обзор Geolocation API и покажу, как приступить к работе с ним, а также как обрабатывать ошибки наиболее распространенных типов, которые могут возникать в ходе запроса геопозиционирования. Мы обсудим, как геопозиционирование реализуется настольными и мобильными браузерами, и бросим взгляд на то, как можно использовать библиотеку полизаполнений (polyfilling library), чтобы обеспечить базовую поддержку геопозиционирования для пользователей более старых браузеров.

Приступаем к работе с геопозиционированием

Согласно спецификации W3C, геопозиционирование — это «…API, который обеспечивает скриптовый доступ к информации о географическом местоположении, сопоставленной с хост-устройством». Поскольку геопозиционирование является встроенным API, для начала работы с ним вам нужен лишь браузер с его поддержкой. На момент написания этой статьи Internet Explorer 9 (в том числе Internet Explorer 9 в Windows Phone 7.5 «Mango»), а также текущие версии Chrome, Firefox, Safari и Opera поддерживают геопозиционирование, так что эта спецификация не только широко поддерживается, но и, вероятно, доступна большому сегменту вашей базы пользователей.
Geolocation API находится в глобальном объекте navigator в JavaScript (window.navigator.geolocation). Он поддерживает два способа работы с сервисами поиска местоположения: однократные запросы определения позиции и повторяющиеся обновления позиции. В первом случае используется метод getCurrentPosition, например так:
  1. navigator.geolocation.getCurrentPosition(function(position) {
  2.   var coordinates = position.coords;
  3.   console.log("Your latitude: " + coordinates.latitude +
  4.     " and longitude: " + coordinates.longitude +
  5.     " (Accuracy of: " + coordinates.accuracy + " meters)");
  6. }, errorHandler, { maximumAge: 100, timeout: 6000,
  7.    enableHighAccuracy: true});
Метод getCurrentPosition принимает три параметра. Первый — это функция обратного вызова, срабатывающая, когда браузер успешно определяет текущее местоположение устройства. Второй — функция обратного вызова, срабатывающая, если возникает ошибка (подробнее об этом чуть позже). Третий параметр является литеральным объектом для указания параметров геопозиционирования. Доступные параметры и их значения по умолчанию перечислены в табл. 1.
Табл. 1. Параметры геопозиционирования
ПараметрОписаниеЗначение
maximumAgeДлительность времени (в миллисекундах) кеширования полученного местоположения От 0 (по умолчанию) до бесконечности
TimeoutДлительность времени (в миллисекундах) ожидания ответа от сервиса геопозиционированияОт 0 до бесконечности (по умолчанию)
enableHighAccuracyОпределяет, должен ли браузер пытаться использовать сервисы высокоточного геопозиционирования (например, GPS), если они поддерживаются браузером и устройством.true, false (по умолчанию)
Обработчик успешного завершения предоставляет объект position, в котором содержатся важные свойства, относящиеся к текущему местоположению пользователя. В спецификации Geolocation определяется несколько свойств, но лишь три из них единообразно реализованы во всех браузерах: latitude, longitude и accuracy. Прочие свойства, такие как heading, direction и altitude, доступны для реализации в браузерах и могут оказаться весьма полезными в мобильных браузерах в будущем.
В случае запросов на повторяющееся обновление позиции объект geolocation предлагает два открытых метода: watchPosition и clearWatch; watchPosition можно использовать в таком стиле:
  1. var watchId = navigator.geolocation.watchPosition(
  2.   function(position) {
  3.   var coordinates = position.coords;
  4.   console.log("Your latitude: " + coordinates.latitude +
  5.     " and longitude: " + coordinates.longitude +
  6.     " (Accuracy of: " + coordinates.accuracy + " meters)");
  7. }, errorHandler, {maximumAge: 100, timeout: 6000,
  8.    enableHighAccuracy: true});
Метод watchPostion принимает те же параметры, что и getCurrentPosition (обработчик успешного завершения, обработчик ошибок и параметры геопозиционирования). Главное различие в том, что watchPosition немедленно возвращает идентификатор, по которому можно определять интересующую вас функцию. Так как watchPosition является многократно вызываемой функцией, браузер будет вызывать ее всякий раз, когда обнаружит изменение в позиции пользователя, и так будет происходить до тех пор, пока либо не закроется окно, либо скрипт явно не отменит слежение. Чтобы отменить watchPosition, вы просто вызываете clearWatch с идентификатором, возвращенным watchPosition, например:
  1. clearWatch(watchId);
Давайте посмотрим на применение getCurrentPosition в приложении-примере. Здесь я продолжу работать с WebMatrix-примером Bakery (http://aka.ms/webm), который я использовал в своей последней статье. Данный сайт является тем местом, где посетители могут заказывать выпечку с доставкой, но я хочу расширить эту функциональность, чтобы пользователи могли указывать ближайший офис обслуживания, где можно самому забрать заказ.
Модифицированное приложение-пример Bakery я начал использовать еще в позапрошлой статье, демонстрируя возможности HTML5 Forms (msdn.microsoft.com/magazine/hh547102), а затем добавил функцию, позволяющую выбирать ближайшую пекарню щелчком ссылки на форме. Я использую AJAX-элемент управления Bing Maps (который можно скачать по ссылке msdn.microsoft.com/library/gg427610); после включения необходимых ссылок и создания HTML-элемента, содержащего мою карту, я добавил в страницу несколько тегов, представляющих разделы Pickup и Shipping формы. Каждый div содержит ссылку, с помощью которой пользователь может переключаться между двумя вариантами. Когда вы щелкаете ссылку «Interested in picking your order up at one of our stores?», я запускаю соответствующую функцию из JavaScript-файла locateBakery.js. Основные ее методы показаны на рис. 1.
Рис. 1. Файл locateBakery.js
  1. var mapDiv = document.getElementById("map");
  2. var _map = new Microsoft.Maps.Map(mapDiv, {
  3.   credentials: myCredentials });
  4.  
  5. function hideMap() {
  6.   $('#pickup').hide();
  7.   $('#map').hide();
  8.   $('#ship').show();
  9. }
  10.  
  11. function displayError(msg) {
  12.   $('#error').val(msg);
  13. }
  14.  
  15. function placeLocationOnMap(latitude, longitude) {
  16.   var location = new Microsoft.Maps.Location(
  17.     latitude, longitude);
  18.   var bakeries = lookupNearbyBakeries(latitude, longitude);
  19.   _map.setView({ zoom: 12, center: location });
  20.  
  21.   // Добавляем на карту кнопку-заколку,
  22.   // представляющую текущую позицию
  23.   var pin = new Microsoft.Maps.Pushpin(location);
  24.   _map.entities.push(pin);
  25.  
  26.   for (var i=0;i<bakeries.length;i++) {
  27.     _map.entities.push(bakeries[i]);
  28.   }
  29. }
  30.  
  31. function errorHandler(e) {
  32.   if (e === 1{ // PERMISSION_DENIED
  33.      hideMap();
  34.   } else if (e === 2{ // POSITION_UNAVAILABLE
  35.      displayError('Cannot find your location. Make sure
  36.        your network connection is active and click the link
  37.        to try again.');
  38.      hideMap();
  39.   } else if (e === 3{ // TIMEOUT
  40.      displayError('Cannot find your location. Click the link
  41.        to try again.');
  42.      hideMap();
  43.   }
  44. }
  45.  
  46. function locate() {
  47.   navigator.geolocation.getCurrentPosition(
  48.     function (position) {
  49.     var coordinates = position.coords;
  50.     placeLocationOnMap(coordinates.latitude,
  51.     coordinates.longitude);
  52.   }, errorHandler);
  53. }
Функция locate вызывает navigator.geolocation.getCurrentPosition и, если вызов успешен, передает координаты в функцию place¬LocationOnMap, которая помечает позицию пользователя на карте и выводит список ближайших пекарен (логика поиска пекарен опущена для экономии места). Это позволяет найти ближайшую пекарню, откуда посетитель сможет забрать свой заказ. Результат представлен на рис. 2.
Рис. 2. Использование Geolocation API совместно с приложением Bakery
Using Geolocation with the Bakery Application
Конечно, геопозиционирование зависит от сетевого сервиса, и отсюда возникает вопрос: а что делать, когда такой сервис недоступен? Возможно, вы заметили, что в вызовах функций getCurrentPosition и watchPosition я указываю второй параметр с именем errorHandler. Согласно спецификации Geolocation, в вызовах getCurrentPosition и watchPosition следует предусматривать вызовы как обработчика успешного завершения, так и обработчика ошибок. При вызове сервиса геопозиционирования возможны три ошибки:
  • пользователь отклонил запрос браузера на отслеживание информации о позиции;
  • время ожидания ответа на запрос определения позиции истекло;
  • запрос определения позиции закончился неудачей из-за окончания периода ожидания.
Добавляя поддержку геопозиционирования в свое приложение, вы обязаны обрабатывать все три случая. Соответствующий пример показан в исходном коде на рис. 1, где я отображаю сообщение об ошибке и заново вывожу раздел с информацией о доставке, когда вызов геопозиционирования заканчивается неудачей по любой из причин.

Как работает геопозиционирование

Хотя в спецификации Geolocation весьма детально определяется открытый API, который должен быть доступен разработчикам в каждом браузере, в ней ничего не говорится о том, как именно должна быть реализована поддержка геопозиционирования в браузере. Общее руководство заключается в том, что каждый браузер должен пытаться балансировать точность с экономией энергопотребления и обеспечивать приложения достаточно точным набором координат.
В настольном варианте или на лэптопах большинство браузеров использует одинаковый механизм для предоставления данных геопозиционирования, хотя фоновые сервисы, на которые они опираются, различны. В случае Internet Explorer 9 и выше браузер получает ваш IP-адрес или список ближайших точек доступа Wi-Fi, а затем передает эту информацию в Microsoft Location Service (на устройствах Windows Phone используются встроенные средства определения местоположения того же сервиса), чтобы получить набор координат. Google Chrome и Mozilla Firefox также используют данные об IP-адресе или точках доступа Wi-Fi, но запрос выдают Google Location Service. Метод сервиса, определяющий местоположение пользователя, жертвует точностью ради скорости (и меньшего расхода энергии аккумуляторов), и это довольно полезный резервный механизм, особенно в отсутствие встроенного чипа GPS.
В настольном варианте или на лэптопах большинство браузеров использует одинаковый механизм для предоставления данных геопозиционирования, хотя фоновые сервисы, на которые они опираются, различны.
В случае мобильных браузеров ситуация посложнее и вновь оставляется на усмотрение разработчиков конкретного браузера. Поскольку большинство современных смартфонов оснащено чипами GPS, браузер может выбрать использование GPS-сервисов определения местоположения, предоставляемых мобильной ОС в попытке получить более точные координаты. Браузеры Internet Explorer 9 в Windows Phone 7.5 и Safari в iOS так и делают. В результате вы получаете более точные координаты за счет дополнительного расходования энергии аккумуляторов, что обычно вполне приемлемый компромисс во многих случаях определения координат с мобильных устройств, в частности при навигации с указанием поворотов. Однако важно отметить, что браузер мобильного устройства не обязательно использует геопозиционирование на основе GPS только потому, что в устройстве есть нужный чип. Поскольку спецификация оставляет реализацию на усмотрение разработчиков браузеров, ваш браузер или нижележащий сервис мобильной ОС может выбрать переключение на триангуляцию по сотовым вышкам или поиск по IP-адресу либо задействовать комбинацию сотовой триангуляции и GPS. (Многие мобильные ОС уже делают так для встроенных приложений.)
Это важные новости для веб-разработчика, поскольку это означает, что с помощью геопозиционирования можно создавать кроссплатформенные мобильные приложения, способные использовать встроенные в устройство механизмы определения местоположения (как это делают разработчики встроенных приложений).

Распознавание и полизаполнение поддержки геопозиционирования

Добавляя поддержку геопозиционирования в свое приложение, подумайте, какой подход вы будете использовать для посетителей вашего сайта, браузеры которых не поддерживаются. Как я уже неоднократно говорил в прошлых статьях, чтобы выяснить поддерживается ли геопозиционирование данным браузером, нужно либо самостоятельно распознавать возможности браузера ("if (!!navigator.geolocation)"), либо использовать библиотеку наподобие Modernizr (modernizr.com). Для браузеров без поддержки геопозиционирования возможны два варианта дальнейших действий: корректное сокращение функциональности приложения или полизаполнение через внешнюю библиотеку. В первом случае вы можете корректно сократить функциональность своего приложения, показывая форму, где пользователи могут указывать свое местоположение; после этого вы определяете (через вызов подходящего сервиса) широту и долготу позиции пользователя. Этот вариант я рассматривал в сентябрьской статье, поэтому повторяться не стану (необходимый исходный код и описание сценария см. по ссылке msdn.microsoft.com/magazine/hh394148).
Полизаполнение — это библиотека, которая предоставляет реализацию некоей функциональности HTML5 на основе скриптов.
На этот раз я подробнее расскажу о втором варианте. Как я упоминал в предыдущих статьях, когда вам нужно найти на рынке кроссбраузерное полизаполнение для какой-либо технологии HTML5, первым делом отправляйтесь на основную страницу Modernizr на GitHub, где опубликован весьма внушительный список имеющихся полизаполнений (bit.ly/eBMoLW). На момент написания этой статьи было доступно три полизаполнения для геопозиционирования. Я выбрал предлагаемое Полом Айришем (Paul Irish) (bit.ly/q20TOl) просто в качестве примера для этой статьи.
Вероятно, вы помните из моей сентябрьской статьи, что полизаполнение (polyfill) — это библиотека, которая предоставляет реализацию некоей функциональности HTML5 на основе скриптов, и обычно соответствует документированному API для конкретной спецификации. Последний момент очень важен: добавив такую библиотеку к своему приложению, можно продолжать использовать возможности этой функциональности так, будто она полностью поддерживается браузером; при этом нет нужды создавать ветвление логики в программе для поддержки более старых браузеров.
Как я уже делал в статьях из этой серии, я определяю наличие поддержки геопозиционирования с помощью Modernizr и, если она есть, асинхронно загружаю файл скрипта geolocationShim.js:
  1. Modernizr.load({
  2.   test: Modernizr.geolocation,
  3.   nope: '../js/geolocationShim.js',
  4.   complete: function() {
  5.   locate();
  6.   }
  7. });
Если геопозиционирование поддерживается управление перейдет к функции locate. В ином случае будет загружено полизаполнение Geolocation, а по окончании этой операции управление вновь перейдет к той же функции. Когда я запускал пример с этой логикой в Internet Explorer 9, все работало нормально и вызывалась встроенная функциональность геопозиционирования.
Чтобы проверить, как это работает в старых браузерах, можно открыть окно Developer Tools в Internet Explorer 9, нажав клавишу F12, и переключить Browser Mode в Internet Explorer 8. Сделав это и заново запустив страницу, я увидел, что Modernizr распознал отсутствие поддержки геопозиционирования и загрузил мой скрипт-прослойку (shim). После вызова метода getCurrentPosition в этом скрипте, выполняется код, показанный на рис. 3.
Рис. 3. Метод getCurrentPosition, реализованный через скрипт-прослойку
  1. geolocation.getCurrentPosition = function(callback){
  2.   if (cache) callback(cache);
  3.  
  4.   $.getScript('//www.google.com/jsapi',function(){
  5.     cache = {
  6.       coords : {
  7.         "latitude": google.loader.ClientLocation.latitude,
  8.         "longitude": google.loader.ClientLocation.longitude
  9.       }
  10.     };
  11.     callback(cache);
  12.   });
  13. };
Поскольку эта библиотека полизаполнения соответствует спецификации Geolocation API и предоставляет методы getCurrentPosition и watch¬Position, мне не нужно реализовывать дополнительные проверки или условную логику в своей основной функции getLocation.
Что бы вы ни выбрали — корректное сокращение функциональности своего приложения или реализацию скрипта-прослойки, при внедрении геопозиционирования вы сможете гарантированно обеспечить удобство использования вашего сайта тем посетителям, у которых нет современных браузеров.
Многое из того, что называют HTML5, или Open Web Technologies, является набором технологий, нацеленных на то, чтобы сделать возможной разработку полноценных веб-приложений. Разрыв между веб- и настольными приложениями сужается, и геопозиционирование — отличный пример того, что теперь стало возможным в веб-приложениях.
В этой статье я изложил базовую информацию о спецификации Geolocation. Вы узнали, как приступить к работе с соответствующим API, как геопозиционирование реализуется в настольных и мобильных браузерах и как использовать полизаполнение поддержки геопозиционирования в более старых браузерах. Теперь настала пора добавить эту потрясающую функциональность в ваши веб-приложения!
Если вы ищете более подробную информацию о поддержке геопозиционирования в Internet Explorer 9, посмотрите Internet Explorer 9 Guide for Developers по ссылке msdn.microsoft.com/ie/ff468705. Чтобы найти другие кроссбраузерные полизаполнения для поддержки геопозиционирования, проверьте полный список по ссылке bit.ly/qNE92g.
И в заключение замечу, что все демонстрации для этой статьи (они доступны и в онлайновом режиме) созданы с применением WebMatrix — бесплатного, облегченного инструмента веб-разработки от Microsoft. Вы можете сами опробовать WebMatrix на сайте aka.ms/webm.

Брэндон Сэтром (Brandon Satrom) — разработчик-идеолог в Microsoft. Ведет блог UserInexperience.com и пишет заметки в Twitter.com/BrandonSatrom.
Выражаю благодарность за рецензирование статьи экспертам Джону Боксу (Jon Box), Джону Хрватину (John Hrvatin), Кларку Селлу (Clark Sell) и Энди Зиглеру (Andy Zeigler).