Визуализация маршрутов Bing в Windows Phone 7
Посетителей: 1255 | Просмотров: 1671 (сегодня 0)
Microsoft Windows Phone 7 поставляется с простым в использовании API геопозиционирования, который позволяет определять текущие координаты и перемещение пользователя, выражаемые в долготе и широте (а иногда и в высоте над уровнем моря). Располагая доступом к этим данным, вы можете добавить в приложение Windows Phone 7 средства распознавания местонахождения.
Если вы создаете приложение для кафе быстрого питания, было бы неплохо — помимо вывода меню и других предложений — находить ближайшее к пользователю кафе. Еще одна отличная возможность — поиск людей поблизости от вас. Достаточно вообразить, что вы торговый представитель, которому нужно посетить своих клиентов между деловыми встречами.
В этой статье основное внимание будет уделено тому, как получить доступ к таким данным из приложения Windows Phone 7 и какими способами можно визуализировать маршруты и местонахождения. Эти данные на самом деле поступают от Bing Maps API. Поэтому, прежде чем разбираться в некоторых «продвинутых» концепциях, важно рассмотреть основы Bing Maps API.
Введение в Bing Maps API
Первое, что вам понадобится, — рабочая учетная запись в Bing. Чтобы создать учетную запись для Bing Maps, зайдите на сайт Bing Maps Account Center (bingmapsportal.com) и используйте Windows Live ID. После этого у вас будет доступ к разделу дополнительной информации, связанной с учетной записью, где вы сможете создать ключ. Так как вы создаете приложение для Windows Phone 7, в качестве Application URL можно написать нечто вроде http://localhost.
Помимо создания учетной записи, на той же странице можно отслеживать использование Bing Maps API. Если вы решите задействовать Bing Maps в производственном приложении, то вам также потребуется вернуться на эту страницу, чтобы обеспечить корректное лицензирование.
Bing Maps API на самом деле предоставляет несколько сервисов, но приложения Windows Phone 7 будут использовать SOAP-сервисы. Я дам краткие обзоры следующих из таких сервисов:
- Geocode (dev.virtualearth.net/webservices/v1/geocodeservice/geocodeservice.svc);
- Imagery (dev.virtualearth.net/webservices/v1/imageryservice/imageryservice.svc);
- Route (dev.virtualearth.net/webservices/v1/routeservice/routeservice.svc);
- Search (dev.virtualearth.net/webservices/v1/searchservice/searchservice.svc).
Сервис Geocode позволяет работать с координатами и адресами, сервис Imagery — с реальными изображениями (полученными с помощью аэрофотосъемки, с высоты птичьего полета и виды с дорог), сервис Route помогает вычислять маршрут между двумя и более точками, а сервис Search дает возможность искать конкретные места на основе ввода от пользователей (например, в виде «рестораны в Брюсселе»).
Чтобы задействовать эти сервисы, достаточно добавить ссылки на эти сервисы по указанным выше URL. Заметьте, что в результате этого создается или обновляется файл ServiceReferences.ClientConfig. На рис. 1 показан пример того, как можно вызвать метод Geocode одноименного сервиса.
Рис. 1. Запуск метода Geocode сервиса Geocode
Каждый метод сервиса работает по принципу «запрос-ответ». Вы создаете объект запроса, в котором формулируется вопрос к серверу, и настраиваете ключ API. В данном случае я создал GeocodeRequest и прошу сервер вернуть мне GeocodeLocation для «Brussels, Belgium». После создания запроса я просто запускаю клиент для асинхронной работы. Наконец, вы всегда будете получать некий ответ либо с нужной информацией, либо с ошибкой в случае любых проблем. Запустите приложение-пример из пакета исходного кода, который можно скачать для данной статьи, и посмотрите на GeocodeServiceClient в действии. Вы увидите, как на экране (через элементы управления, связанные с данными) отображается какое-либо место (или ошибка).
Это приложение будет использовать сервисы Geocode и Route для вычисления маршрута между двумя адресами и отображения результата пользователю.
Вычисление маршрута
Используя сервис Bing Route, можно вычислить маршрут от точки A до точки B. Как и в предыдущем примере, этот сервис работает по принципу «запрос-ответ». Сначала вам понадобится найти реальные географические координаты каждого адреса (с помощью GeocodeRequest), а затем вы сможете создать RouteRequest. Приложение-пример в пакете исходного кода для этой статьи содержит весь необходимый код, а на рис. 2 приведен короткий пример того, как это делается.
Существует много областей применения, в которых приложения приносят больше пользы, если им известно местонахождение пользователя. |
Рис. 2. Создание RouteRequest
Заметьте, что свойство Waypoints запроса является набором, а это позволяет добавлять несколько точек маршрута (waypoints). Это может быть полезно, когда нужно знать маршрут для всего пути следования, а не просто от точки A до точки B.
Когда вы запускаете метод CalculateRouteAsync, сервис приступает к самой трудной части работы: вычислению маршрута, перечислению всех элементов на пути следования (таких действий, как повороты, съезды с магистрали и т. д.), расчету времени следования и расстояния, перечислению всех точек (геопозиционирование) и др. На рис. 3 показана общая схема некоторых важных данных, присутствующих в RouteResponse.
Используя сервис Bing Route, можно вычислить маршрут от точки A до точки B. |
Рис. 3. Содержимое RouteResponse
Отображение маршрута на карте
В первом примере я воспользуюсь RoutePath Points для отображения маршрута на карте. Поскольку вWindows Phone 7 Toolkit уже включен элемент управления Bing Maps, вам нужно лишь добавить ссылку на сборку Microsoft.Phone.Controls.Maps. После этого отображение карты на дисплее устройства не составит никакого труда. Вот пример того, как показать карту Брюсселя (CredentialsProvider необходим для задания ключа API):
Если вы собираетесь использовать какие-либо элементы управления из сборки Maps, я советую добавлять ссылку на эту сборку до добавления любой ссылки на сервисы Bing. Тогда ссылка на сервис Bing позволит повторно использовать такие типы, как Microsoft.Phone.Controls.Maps.Platform.Location, вместо создания новых типов, и вам не придется писать методы преобразования или конвертеры значений для работы с некоторыми данными, возвращаемыми сервисом.
Теперь вы знаете, как вычислить маршрут между двумя точками и как показать карту на смартфоне. Давайте соединим эти знания и попробуем визуализировать маршрут на карте. Для рисования на карте будут использоваться Points вRoutePath. Элемент управления Map позволяет добавлять фигуры наподобие Pushpin (например, чтобы помечать начало и конец маршрута) и MapPolyline (чтобы рисовать маршрут на основе GeoCoordinates).
Поскольку тип точек, возвращаемых сервисом, не совпадает с типом точек, применяемых в элементе управления Maps, я создал два небольших метода расширения для приведения точек к корректному типу (рис. 4).
Рис. 4. Методы расширения для приведения точек к корректному типу
Вы можете использовать эти методы расширения в RouteResponse по завершении метода CalculateRoute. После преобразования эти методы расширения будут возвращать типы, которые можно использовать, например, для связывания с элементом управления Bing Maps. Так как это приложение Silverlight, мы должны применять IValueConverter для выполнения реальной операции преобразования. На рис. 5 показан пример конвертера значений, который будет преобразовывать Locations в GeoCoordinates.
Рис. 5. Применение IValueConverter
Теперь пора заняться конфигурированием связывания с данными. Связывание с данными использует конвертеры, поэтому важно объявлять их первыми. Это можно делать в ресурсах страницы или в ресурсах приложения (если вы планируете повторно использовать конвертеры), как показано ниже:
После объявления конвертеров можно добавить элемент управления Maps и другие, оверлейные элементы управления (вроде MapPolyline и Pushpin), а потом связать их с необходимыми свойствами:
Поскольку в Windows Phone 7 Toolkit уже включен элемент управления Bing Maps, вам нужно лишь добавить ссылку на сборку Microsoft.Phone.Controls.Maps. |
Как видите, эти привязки используют уже объявленные конвертеры для преобразования данных в формат, понятный элементу управления Maps. Наконец, вам нужно задать следующие свойства по завершении CalculateMethod:
На рис. 6 показано, как выглядит дисплей после запуска приложения и вычисления маршрута.
Рис. 6. Визуальное представление маршрута
Вывод инструкций
Как видите, отображение маршрута на карте — задача довольно стандартная. В следующем примере я покажу, как создать собственный элемент управления, который выводит инструкции по маршруту от начала до конца, используя текст и сводку из каждого ItineraryItem. Конечный результат приведен на рис. 7.
Рис. 7. Отображение инструкций по всему маршруту
Вы уже видели, что Legs является одним из свойств RouteResult. Свойство Legs содержит один или более объектов Leg (отрезков маршрута), каждый из которых включает набор ItineraryItems. Используя ItineraryItems, можно заполнить элемент управления, как показано на рис. 7. Для каждого отрезка на рис. 7 выводится ItineraryItem с полной длиной и временем в пути (в секундах), а также номер текущего отрезка. ItineraryItem не отслеживает номер текущего отрезка пути, поэтому я создал небольшой класс ItineraryItemDisplay:
Пример в пакете исходного кода для этой статьи также содержит метод расширения со следующей сигнатурой:
Код в этом методе перебирает все элементы, записывает важные значения в новый объект ItineraryItemDisplay и отслеживает текущий номер отрезка маршрута в свойстве Index. Наконец, ItineraryItemDisplayConverter выполняет преобразование при связывании с данными. Как вы, вероятно, заметили на рис. 7, каждый промежуточный отрезок (этап) форматируется (названия городов и улиц выделяются полужирным) с помощью собственного элемента управления ItineraryItemBlock. Его единственное предназначение — форматирование текста ItineraryItem для улучшения восприятия информации. На рис. 7 также видно, что кое-какая дополнительная информация помещается в синие блоки, но это осуществляется через обычное связывание с данными:
Атрибут TemplatePart определяет элемент, который должен присутствовать в элементе управления Template, и его тип. В данном случае это должен быть TextBlock с именем ItemTextBlock:
Причина выбора TextBlock очевидна. С помощью TextBlock-свойства Inlines можно программно добавить контент в TextBlock. В собственном элементе управления можно переопределить OnApplyMethod, и именно там потребуется найти ItemTextBlock (рис. 8).
Рис. 8. Нахождение TextBlock
Далее анализируется ItineraryItem-свойство Text, и его содержимое используется для заполнения этого TextBlock с дополнительным форматированием. На практике это делается довольно легко, так как свойство Text содержит практически обычный текст и лишь некоторые части заключаются в XML-теги:
Поскольку мне нужно выделить лишь названия городов и улиц, я написал небольшой метод, который разбирает такие теги, как VirtualEarth:Action или VirtualEarth:TurnDir. После получения TextBlock вызывается метод SetItinerary, и именно в нем Inlines добавляется в TextBlock (рис. 9).
Рис. 9. Добавление Inlines в TextBlock методом SetItinerary
Как видно в предыдущем примере XML-текста, не в каждой части текста содержится какой-либо XML-элемент. Чтобы этот текст можно было использовать в XmlReader, первым делом его нужно обернуть в бутафорский XML-элемент. Это позволяет создать новый XmlReader с этим текстом. Используя XmlReader-метод Read, вы можете перебрать каждую часть XML-строки.
На основе NodeType вы можете определить текущую позицию в XML-строке. Возьмем, к примеру, следующий элемент: Guido Gezellestraat . При использовании XmlReader-метода Read у вас будет три итерации. Первая — , и этоXmlNodeType.Element. Поскольку ставится цель отформатировать названия городов и улиц полужирным, именно здесь к TextBlock Inlines добавляется объект Run с FontWeight, установленным в «bold». Добавление объекта Run к Inlines просто добавляет кое-какой текст в конец строки TextBlock.
В любом другом случае форматирование не требуется, и тогда вы добавляете обычный объект Run, содержащий только текст, и не настраиваете никакие свойства форматирования.
ItineraryItem не отслеживает номер текущего отрезка пути. |
Вот и все, что нужно для собственного элемента управления. Вся запись ItineraryItemDisplay отображается с помощью собственного DataTemplate для ListBox. Этот DataTemplate также содержит ссылку на собственный элемент управления (рис. 10).
Рис. 10. Вся запись ItineraryItemDisplay в собственном DataTemplate для Listbox
Теперь, когда собственный элемент управления и оформление готовы, остается лишь одна задача — реализовать это в элементе управления Pivot и в коде. Как я уже упоминал, собственные элемент управления и DataTemplate будут использоваться в ListBox:
Свойство ItemSource этого ListBox связано со свойством Itinerary, и именно так заполняется это свойство; остальное делает ItineraryItemDisplayConverter. Как видите, применив собственный элемент управления и добавив немного элементов оформления, вы можете получать промежуточные данные от сервиса Route и наглядно представлять их пользователю:
Определение текущего местонахождения
Из предыдущих примеров вы узнали, как использовать сервисы Geocode и Route, чтобы получить инструкции по маршруту из точки A в точку B, и как визуализировать эти инструкции. Теперь пора рассмотреть API геопозиционирования.
GeoCoordinateWatcher — это класс, которым вы будете пользоваться для нахождения текущих GPS-координат:
Если вы создаете приложение, которое показывает инструкции, то могли бы использовать изменения в текущем местонахождении пользователя для автоматической прокрутки каждого этапа и даже проигрывать звуковой сигнал. |
GeoCoordinateWatcher будет проходить через различные стадии после выполнения метода Start, но, когда состояние выставлено в Ready, вы получаете доступ к текущей позиции. По окончании работы с GeoCoordinateWatcher рекомендуется вызывать метод Stop:
Теперь приложение-пример предоставляет и средства определения местонахождения. Кроме того, в классе GeoCoordinateWatcher есть событие PositionChanged, которое позволяет отслеживать изменения позиции. Если вы создаете приложение, которое показывает инструкции, то могли бы использовать изменения в текущем местонахождении пользователя для автоматической прокрутки каждого этапа и даже проигрывать звуковой сигнал на основе VirtualEarth:Action в ItineraryItem Text. В итоге вы получили бы приложение с настоящей GPS-навигацией.
Вы отлаживаете приложение, используя эмулятор Windows Phone 7? Если вы при этом тестируете функциональность геопозиционирования, то можете столкнуться с небольшой проблемой с GeoCoordinateWatcher Status: оно всегда будет содержать NoData и никогда не будет меняться на Ready. Вот почему важно писать код с применением интерфейса (IGeoPositionWatcher ), а не самой реализации (GeoCoordinateWatcher). В статье Тима Хойера (Tim Heuer) в блоге bit.ly/cW4fM1 предлагается скачать исходный код класса EventListGeoLocationMock, который имитирует реальное GPS-устройство.
Класс EventListGeoLocationMock принимает набор GeoCoordinateEventMocks, который должен имитировать изменение координат пользователя с течением времени. Это позволяет вам протестировать определение местонахождения пользователя и его передвижения:
По имени устройства вы могли бы определять, где выполняется ваше приложение — на реальном устройстве или в эмуляторе, и определять, какой IGeoPositionWatcher следует использовать. Проверяйте значение расширенного свойства DeviceName, которое всегда устанавливается в XDeviceEmulator при выполнении приложения в эмуляторе:
В качестве альтернативы в блоге Драгоса Манолеску (Dragos Manolescu) (bit.ly/h72vXj) вы найдете другой способ имитировать потоки событийWindows Phone 7 с использованием Reactive Extensions (Rx).
Не забывайте, что ваше приложение выполняется на мобильном устройстве, и регулярное Wi-Fi-соединение доступно не всегда. |
Реальные приложения и производительность
Когда вы создаете приложение и хотите продать его, то очевидно, что оно должно быть привлекательным для пользователей. Пользователям нужно быстрое приложение с интересными им возможностями. Как демонстрируют предыдущие примеры, вам потребуется вызывать методы некоторых веб-сервисов и асинхронно обрабатывать несколько событий до того, как вы сможете показать пользователю некие результаты. Не забывайте, что ваше приложение выполняется на мобильном устройстве, и регулярное Wi-Fi-соединение доступно не всегда.
Уменьшение числа вызовов веб-сервисов и количества обменов данными по сети могло бы ускорить работу вашего приложения. Во введении я упоминал о приложении для кафе быстрого питания, которое предоставляет меню и поддерживает средства определения местонахождения. Если вы создаете такое приложение, то в облаке можно было разместить сервис, передающий меню и различные предложения на смартфоны. И почему бы не воспользоваться им для сложных расчетов вместо того, чтобы выполнять их на мобильном устройстве? Вот пример:
Вы могли бы создать сервис, принимающий текущее местонахождение пользователя. Этот сервис будет запускать несколько потоков (в примере применяется Parallel.ForEach) и параллельно вычислять расстояние между этой позицией и координатами других кафе (рис. 11).
Рис. 11. Вычисление расстояния между пользователем и тремя ближайшими кафе
Параллельно перебирая все кафе из списка, вы преобразуете местонахождение каждого из них в географические координаты с помощью GeocodeServiceClient. На основе этого местонахождения и позиции пользователя вычисляется маршрут между ними; для этого применяется RouteServiceClient. Наконец, на основе свойства TotalSeconds в сводке маршрута отыскиваются три ближайших кафе, координаты которых передаются на устройство.
Здесь преимущество в том, что вычисления выполняются параллельно (используется Parallel.ForEach и то количество потоков, которое допускают машинные ресурсы), а как только они завершаются, в Windows Phone передаются лишь релевантные данные. С точки зрения производительности вы сразу же почувствуете разницу; мобильное приложение будет вызывать лишь один метод веб-сервиса и получать по сети только небольшой блок данных.
Кроме того, радикально уменьшится объем кода и число асинхронных вызовов в Windows Phone 7, как показано ниже:
На рис. 12 демонстрируется отображение на дисплее смартфона ближайших кафе.
Рис. 12. Отображение на дисплее смартфона трех ближайших кафе
Уменьшение числа вызовов веб-сервисов и количества обменов данными по сети могло бы ускорить работу вашего приложения. |
Процесс передачи в Windows Phone 7 Marketplace
Последнее, о чем хотел бы упомянуть, — процесс передачи приложения в Windows Phone 7 Marketplace. Ваше приложение должно удовлетворять набору требований, чтобы оно было размещено на торговой площадке Marketplace. Одно из требований — определение возможностей вашего приложения в файле манифеста. Если вы решили использовать GeoCoordinateWatcher, вы также должны указать в этом файле возможность ID_CAP_LOCATION.
На странице MSDN Library «How to: Use the Capability Detection Tool for Windows Phone» (bit.ly/hp7fjG) поясняется, как с помощью утилиты Capability Detection Tool определить все возможности, используемые вашим приложением. Обязательно прочитайте эту статью, прежде чем передавать свое приложение!
Приложение-пример
В пакете исходного кода для этой статьи содержится решение с двумя проектами. Один из них — библиотека классов, которая включает элемент управления, методы расширения и стили; ее очень легко интегрировать в собственные проекты. Второй — пример Pivot-приложения Windows Phone 7, которое сочетает в себе все примеры в виде одной небольшой программы.