Главная » Разработка для Windows Phone 7 » Использование картографического сервиса Windows Phone 7

0

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

В реальном приложении для телефона, скорее всего, использовался бы Bing Maps, в частности, элемент управления Silverlight Bing Maps, специально предназначенный для телефона. К сожалению, применение Bing Maps в приложении требует открытия учетной записи разработчика и получения ключа доступа и учетных данных. Все это делается бесплатно и просто, но данный вариант не очень удобен для программы, используемой в качестве примера в книге.

Поэтому я воспользуюсь альтернативным вариантом, для которого не нужны ключи или учетные данные. Такой альтернативой являются Microsoft Research Maps, которые можно

найти на сайте msrmaps.com. Материалы аэрофотосъемки предоставлены Службой геологии, геодезии и картографии США (United States Geological Survey, USGS). Microsoft Research Maps предоставляет эти снимки через Веб-сервис MSR Maps Service, который по старой памяти до сих пор называют TerraService.

Недостаток в том, что эти снимки несколько устаревшие, и сервис не вполне надежен.

MSR Maps Service – это сервис SOAP (Simple Object Access Protocol[5]), операции которого описаны в файле WSDL (Web Services Description Language[6]). На нижнем уровне все транзакции между приложением и Веб-сервисом осуществляются в виде XML-файлов. Но чтобы облегчить жизнь разработчика, обычно на базе WSDL-файла создается прокси. Он представляет собой коллекцию классов и структур, благодаря которым приложение может взаимодействовать с Веб-сервисом посредством вызовов методов и событий.

Этот прокси можно сформировать прямо в Visual Studio. Вот как я сделал это. Сначала я создал в Visual Studio проект Windows Phone 7 под названием SilverlightLocationMapper. В Solution Explorer я щелкнул правой кнопкой мыши имя проекта и выбрал Add Service Reference (Добавить ссылку на сервис). В поле Address (Адрес) я ввел URL WSDL-файла MSR Maps Service: http://MSRMaps.com/TerraService2.asmx.

(Можно было бы предположить, что URL должен быть

http://msrmaps.com/TerraService2.asmx?WSDL, поскольку именно так обычно описываются ссылки на WSDL-файлы. Такой адрес работал бы только поначалу, поскольку является устаревшим.)

После того, как ввели URL в поле Address, нажмите Go. Visual Studio выполнит доступ к сайту и возвратит то, что найдет там. Это будет один сервис со старым именем TerraService.

После этого можно заменить универсальное имя ServiceReferencel в поле Namespace (Пространство имен). Я ввел имя MsrMapsService и нажал OK.

MsrMapsService появится под проектом в Solution Explorer. Если щелкнуть маленький значок Show All Files (Показать все файлы) вверху Solution Explorer, можно увидеть все созданные файлы. В частности, под MsrMapsService и Reference.svcmap вы увидите вложенный Reference.cs – большой файл (более 4000 строк) с пространством имен XnaLocationMapper.MsrMapsService, являющимся сочетанием исходного имени проекта и имени, выбранного нами для Веб-сервиса.

Этот файл Reference.cs включает все классы и структуры, необходимые для работы с Веб- сервисом и задокументированные на сайте msrmaps.com. Чтобы получить возможность работать с этими классами в приложении, добавим директиву using:

using SilverlightLocationMapper.MsrMapsService;

Также понадобиться ссылка на сборку System.Device и директивы using для пространств имен System.Device.Location, System.IO и System.Windows.Media.Imaging.

В файле MainPage.xaml я сохранил для свойства SupportedOrientations настройку по умолчанию, Portrait, удалил заголовок страницы, чтобы высвободить больше места, и переместил панель заголовка под сетку для содержимого, просто на случай, если вдруг что- то выйдет за ее рамки и заслонит заголовок. Перенос панели заголовка под сетку для содержимого в файле XAML гарантирует, что визуально она будет расположена сверху.

Рассмотрим сетку для содержимого:

Проект Silverlight: SilverlightLocationMapper Файл: MainPage.xaml (фрагмент)

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <TextBlock Name="statusText"

HorizontalAlignment="Center" VerticalAlignment="Center" TextWrapping="Wrap" />

<Image Source="Images/usgslogoFooter.png" Stretch="None" HorizontalAlignment="Right" VerticalAlignment="Bottom" />

</Grid>

TextBlock используется для отображения состояния и (возможных) ошибок; Image выводит логотип United States Geological Survey.

Растровые изображения карт будут вставляться между TextBlock и Image, таким образом, они будут заслонять TextBlock, но Image останется сверху.

В файле выделенного кода всего два поля: одно для GeoCoordinateWatcher, который обеспечивает данные о местоположении; и другое для прокси-класса, созданного при добавлении Веб-сервиса:

Проект Silverlight: SilverlightLocationMapper Файл: MainPage.xaml.cs (фрагмент)

public partial class MainPage : PhoneApplicationPage {

GeoCoordinateWatcher geoWatcher = new GeoCoordinateWatcher(); TerraServiceSoapClient proxy = new TerraServiceSoapClient();

}

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

Событие завершение выполнения имеет несколько аргументов. Ими являются свойство Cancelled (Отменен) типа bool; свойство Error (Ошибка), которое равно null, если ошибки нет; и свойство Result (Результат), которое зависит от того, что запрашивалось.

Я захотел, чтобы процесс начинался после загрузки приложения и вывода его на экран, поэтому задал обработчик события Loaded. Это обработчик Loaded определяет обработчики для двух событий завершения, которые я запрошу от прокси, и также запускает GeoCoordinateWatcher:

Проект Silverlight: SilverlightLocationMapper Файл: MainPage.xaml.cs (фрагмент)

public MainPage() {

InitializeComponent(); Loaded += OnMainPageLoaded;

}

void OnMainPageLoaded(object sender, RoutedEventArgs args) {

// Задаем обработчики событий для прокси TerraServiceSoapClient proxy.GetAreaFromPtCompleted += OnProxyGetAreaFromPtCompleted;

proxy.GetTileCompleted += OnProxyGetTileCompleted;

// Запускаем выполнение GeoCoordinateWatcher statusText.Text = "Obtaining geographic location…"; geoWatcher.PositionChanged += OnGeoWatcherPositionChanged; geoWatcher.Start();

}

Когда координаты получены, вызывается следующий метод OnGeoWatcherPositionChanged (При изменении местоположения). Этот метод начинает выполнение с отключения GeoCoordinateWatcher. Программа не может постоянно обновлять экран, поэтому никакие дополнительные сведения о местоположении ей уже не нужны. Долгота и широта выводятся в TextBlock под названием ApplicationTitle (Заголовок приложения), отображаемом вверху экрана.

Проект Silverlight: SilverlightLocationMapper Файл: MainPage.xaml.cs (фрагмент)

void OnGeoWatcherPositionChanged(object sender,

GeoPositionChangedEventArgs<GeoCoordinate> args)

{

// Отключаем GeoWatcher

geoWatcher.PositionChanged -= OnGeoWatcherPositionChanged; geoWatcher.Stop();

// Координаты задаются как текст заголовка GeoCoordinate coord = args.Position.Location;

ApplicationTitle.Text += ": " + String.Format("{0:F2}°{1} {2:F2}°{3}",

Math.Abs(coord.Latitude), coord.Latitude > 0 ? ‘N’ : ‘S’, Math.Abs(coord.Longitude), coord.Longitude > 0 ? ‘E’ : ‘W’);

// Запрашиваем прокси для AreaBoundingBox LonLatPt center = new LonLatPt(); center.Lon = args.Position.Location.Longitude; center.Lat = args.Position.Location.Latitude;

statusText.Text = "Accessing Microsoft Research Maps Service…"; proxy.GetAreaFromPtAsync(center, 1, Scale.Scale16m, (int)ContentPanel.ActualWidth,

(int)ContentPanel.ActualHeight); }

Метод завершается после первого обращения к прокси. При вызове GetAreaFromPtAsync широта и долгота используются как точка центрирования. Также передаются некоторые другие данные. Второй аргумент – 1, чтобы получить вид со спутника, и 2, чтобы получить карту (как будет показано в конце данной главы). Третий аргумент – это желаемый масштаб изображения, элемент перечисления Scale. Выбранный в данном случае элемент означает, что каждый пиксел возвращенного растрового изображения эквивалентен 16 метрам.

Будьте внимательны, некоторые коэффициенты масштабирования (в частности, Scale2m, Scale8m и Scale32m) обусловливают возвращение файлов в формате GIF. Никогда не забывайте, что Silverlight не поддерживает GIF! Для остальных масштабных коэффициентов возвращаются файлы JPEGS.

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

Все возвращаемые MSR Maps Service растровые изображения квадратные со стороной 200 пикселов. Практически всегда для заполнения всей выделенной области потребуется несколько таких изображений. Например, если последние два аргумента GetAreaFromPtAsync – 400 и 600, понадобится 6 растровых изображений.

Ну, на самом деле, нет. Для заполнения области высотой 400 и шириной 600 пикселов потребуется 12 растровых изображений, 3 по горизонтали и 4 по вертикали.

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

В результате вызова GetAreaFromPtAsync (в следующем методе OnProxyGetAreaFromPtCompleted) возвращается объект типа AreaBoundingBox (Ограничивающее область окно). Это довольно сложная структура, которая, тем не менее, содержит все данные, необходимые для запроса всех требуемых элементов карты по отдельности и последующей их компоновки в сетке.

Проект Silverlight: SilverlightLocationMapper Файл: MainPage.xaml.cs (фрагмент)

void OnProxyGetAreaFromPtCompleted(object sender, GetAreaFromPtCompletedEventArgs

args) {

if (args.Error != null) {

statusText.Text = args.Error.Message; return;

}

statusText.Text = "Getting map tiles…";

AreaBoundingBox box = args.Result; int xBeg = box.NorthWest.TileMeta.Id.X; int yBeg = box.NorthWest.TileMeta.Id.Y; int xEnd = box.NorthEast.TileMeta.Id.X; int yEnd = box.SouthWest.TileMeta.Id.Y;

// Выбор всех необходимых фрагментов карты for (int x = xBeg; x <= xEnd; x++)

for (int y = yBeg; y >= yEnd; y–) {

// Создание объекта Image для отображения фрагмента карты Image img = new Image(); img.Stretch = Stretch.None;

img.HorizontalAlignment = HorizontalAlignment.Left; img.VerticalAlignment = VerticalAlignment.Top; img.Margin = new Thickness((x – xBeg) * 200 – box.NorthWest.Offset.XOffset,

(yBeg – y) * 200 -

box.NorthWest.Offset.YOffset,

0, 0);

// Вставка после TextBlock, но перед Image с логотипом ContentPanel.Children.Insert(1, img);

// Определение ID фрагмента карты TileId tileId = box.NorthWest.TileMeta.Id; tileId.X = x; tileId.Y = y;

// Вызов прокси для получения фрагмента карты (Обратите внимание, что

Image является пользовательским объектом)

proxy.GetTileAsync(tileId, img);

}

}

Не буду останавливаться на AreaBoundingBox подробно, потому что он достаточно хорошо описан на сайте msrmaps.com. Я нашел там очень полезные фрагменты кода, реализующие аналогичную логику, которые были написаны для Windows Forms (которые, как мне кажется, уже несколько устарели).

Обратите внимание, что в цикле для каждого фрагмента карты создается объект Image. Все эти объекты имеют одинаковые значения свойств Stretch, HorizontalAlignment и VerticalAlignment, но разные Margin. Margin определяет, как каждый фрагмент размещается в сетке для содержимого. Свойства XOffset (Смещение по Х) и YOffset (Смещение по Y) показывают, на сколько фрагменты карты будут выступать сверху и слева. Сетка для содержимого не обрезает содержимого, поэтому эти фрагменты могут выступать за границу страницы приложения.

Заметьте также, что каждый объект Image передается как второй аргумент в метод GetTileAsync прокси-класса. Это аргумент UserState (Состояние пользователя). Прокси ничего не делает с этим аргументом, просто возвращает его как свойство UserState аргументов события завершения, что показано ниже:

Проект Silverlight: SilverlightLocationManager Файл: MainPage.xaml.cs (фрагмент)

void OnProxyGetTileCompleted(object sender, GetTileCompletedEventArgs args) {

if (args.Error != null) {

return;

}

Image img = args.UserState as Image; BitmapImage bmp = new BitmapImage(); bmp.SetSource(new MemoryStream(args.Result)); img.Source = bmp;

}

Вот так метод связывает отдельный фрагмент растрового изображения с определенным элементом Image, уже находящимся в сетке для содержимого.

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

Если изменить второй аргумент в вызове proxy.GetAreaFromPtAsync и задать 2 вместо 1, приложению будут возвращены изображения карты, а не снимки со спутника:

В этом есть некоторое очарование старины (и я поклонник живописи), но, боюсь, современные пользователи привыкли уже к чему-то более «продвинутому».

Источник: Чарльз Петзольд, Программируем Windows Phone 7, Microsoft Press, © 2011.

По теме:

  • Комментарии