Статьи

Простое обратное геокодирование с Windows Phone 8 и MVVMLight

Проработав в географических информационных системах более 20 лет, я могу с полным основанием сказать, что новых  картографических возможностей и возможностей определения местоположения в  Windows Phone 8 более чем достаточно, чтобы сделать карту маньяка, похожего на меня, с блестящими глазами. У него есть возможности, которые не слышали даже пару лет назад — и мне не нужна большая рабочая станция, мне даже не нужен ПК — он работает на моем телефоне. Мир в моем кармане — в самом буквальном смысле этого слова.

Два популярных приложения ГИС —  геокодирование  и  обратное геокодирование . Геокодирование позволяет вам найти положение Земли для описательного текста — например, адреса, города, названия здания или любой другой фразы, указывающей место на Земле. Обычно это довольно просто. Обратное  геокодирование с точностью до наоборот — это «что здесь?» вопрос — учитывая местоположение,  что я  здесь нахожу? Кстати, отвечая на такие вопросы, я зарабатываю на жизнь в Викрее .

Windows Phone 8 делает обратное геокодирование практически невероятно простым. Даже при использовании  MVVMLight . Поэтому я сделал простое приложение, которое показывает адреса, найденные в том месте, где вы нажимаете на карте.

Мы начнем с простой модели с двумя свойствами:

using System.Collections.ObjectModel;
using System.Device.Location;
using System.Linq;
using GalaSoft.MvvmLight;
using Microsoft.Phone.Maps.Services;

namespace TapReverseGeocode.Logic.ViewModels
{
  public class MapViewModel : ViewModelBase
  {
    public MapViewModel()
    {
      Addresses = new ObservableCollection<string>();
    }

    private GeoCoordinate tapCoordinate;
    public GeoCoordinate TapCoordinate
    {
      get { return tapCoordinate; }
      set
      {
        tapCoordinate = value;
        RaisePropertyChanged(() => TapCoordinate);
        StartReverseGeoCoding();
      }
    }

    public ObservableCollection<string> Addresses { get; set; }
  }
}

ObservableCollection «Адреса» будет содержать результаты, и, как обычно, при связывании с ObservableCollection вы должны убедиться, что он инициализируется раньше всего — конструктор является хорошим местом для этого. Дизайнер может связать это с каким-то элементом графического интерфейса, который отображает результат.

Свойство TapCoordinate является GeoCoordinate, которое запускает реальное обратное геокодирование — и я специально пропустил обычную проверку « if (viewModelPropertyName! = Value) ». Даже когда пользователь дважды нажимает на одно и то же местоположение, я хочу, чтобы код обратного геокодирования срабатывал каждый раз.

Код, который запускает обратное геокодирование, сам по себе не является ракетостроением:

private void StartReverseGeoCoding()
{
  var reverseGeocode = new ReverseGeocodeQuery();
  reverseGeocode.GeoCoordinate = 
    new GeoCoordinate(TapCoordinate.Latitude, TapCoordinate.Longitude);
  reverseGeocode.QueryCompleted += ReverseGeocodeQueryCompleted;
  reverseGeocode.QueryAsync();
}

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

Последний кусок — это простой обратный вызов, который обрабатывает результат обратного геокодирования.

private void ReverseGeocodeQueryCompleted(object sender, 
  QueryCompletedEventArgs<System.Collections.Generic.IList<MapLocation>> e)
{
  var reverseGeocode = sender as ReverseGeocodeQuery;
  if (reverseGeocode != null)
  {
    reverseGeocode.QueryCompleted -= ReverseGeocodeQueryCompleted;
  }
  Addresses.Clear();
  if (!e.Cancelled)
  {
    foreach (var adress in e.Result.Select(adrInfo => adrInfo.Information.Address))
    {
      Addresses.Add(string.Format("{0} {1} {2} {3} {4}", 
        adress.Street, adress.HouseNumber, adress.PostalCode,
        adress.City,adress.Country).Trim());
    }
  }
}

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

Это полная модель обратного геокодирования, которая, в принципе, не заботится о том, откуда берется GeoCoordinate или куда идет результат. Так что это очень универсально. Там нет никакого графического интерфейса, и все же у нас уже есть работающее приложение

Исходный XAML для связывания этого материала — после установки текста данных для этой модели представления — выглядит довольно просто:

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
  <maps:Map/>
  <Grid Height="58" VerticalAlignment="Top" Background="#7F000000">
    <phone:LongListSelector ItemsSource="{Binding Addresses}" 
      HorizontalContentAlignment="Left" Margin="12,0"/>
  </Grid>
</Grid>

… и затем мы сталкиваемся с проблемой. Два на самом деле. Последнее нажатое местоположение  не является свойством, которое мы можем привязать , и что это местоположение —  Точка  — местоположение на  экране , а не GeoCoordinate в координатах реального мира.

Это может быть решено с помощью свойства Attached Dependency Property (я думаю), с помощью некоторого кода позади или моего товарного знака — путем создания простого поведения. В конце концов, я не хочу беспокоить дизайнеров кодом, и мне нравится простота повторного использования поведения:

using System.Device.Location;
using System.Windows;
using Microsoft.Phone.Maps.Controls;
using Wp7nl.Behaviors;

namespace Wp8nl.Behaviors
{
  public class TapToCoordinateBehavior : SafeBehavior<Map>
  {
    protected override void OnSetup()
    {
      AssociatedObject.Tap += AssociatedObjectTap;
    }

    void AssociatedObjectTap(object sender, 
      System.Windows.Input.GestureEventArgs e)
    {
      var tapPosition = e.GetPosition((UIElement)sender);
      TappedCoordinate = 
        AssociatedObject.ConvertViewportPointToGeoCoordinate(tapPosition);
    }

    protected override void OnCleanup()
    {
      AssociatedObject.Tap -= AssociatedObjectTap;
    }

 // GeoCoordinate TappedCoordinate dependency property omitted

   }
}

Это поведение реализовано как   дочерний класс SafeBehavior , чтобы предотвратить утечки памяти. На самом деле все довольно просто — он перехватывает событие «Tap», определяет местоположение, преобразует его в GeoCoordinate и помещает его в свойство зависимостей TappedCoordinate. Который, в свою очередь, может быть привязан к представлению модели. Дизайнер может просто перетащить это поведение поверх карты и настроить привязку данных. Разве ты не любишь смесь? XAML взять 2:

<Grid x:Name="ContentPanel" Grid.Row="1" 
   Margin="12,0,12,0">
  <maps:Map>
    <i:Interaction.Behaviors>
      <Behaviors:TapToCoordinateBehavior 
          TappedCoordinate="{Binding TapCoordinate, Mode=TwoWay}"/>
    </i:Interaction.Behaviors>
  </maps:Map>
  <Grid Height="58" VerticalAlignment="Top" Background="#7F000000">
    <phone:LongListSelector ItemsSource="{Binding Addresses}" 
           HorizontalContentAlignment="Left" Margin="12,0"/>
  </Grid>
</Grid>

И это все, что нужно сделать. Обратное геокодирование в Windows Phone 8 безумно просто.

Полный исходный код, как обычно,  можно скачать здесь .