Neutral Scent

App developments & Gadgets

WP8でBing Maps REST Servicesを使って地名・駅名から位置情報を取得する


かなりピンポイントな話ですが、これから利用する人が増えるのを期待して。
地名や駅名から緯度経度を取得したい場合、これまでであればGoogle Geocoding APIやgeocooding.jpを利用したりしていたわけですが、せっかくマイクロソフトのプラットフォームですから、Bing Maps REST Servicesを使ってみましょう、という話です。
サンプルプロジェクトのダウンロード: BingMapsLocationQuerySample.zip 直
APIリファレンスはこちら:

Bing Maps REST Services API Reference
http://msdn.microsoft.com/en-us/library/ff701722.aspx

今回は、Locations APIのFind a Location by Queryを呼んでみます。
RESTリクエストのURLはこういう感じで、Bing Maps Keyの取得に関してはとりあえずこのあたりを見てみていただくこととして今回は割愛。

http://dev.virtualearth.net/REST/v1/Locations/品川?c=ja&o=xml&key=(Bing Maps Key)

すると、こんな感じのレスポンスが来るはずです、

<?xml version="1.0" encoding="UTF-8"?>
<Response xmlns="http://schemas.microsoft.com/search/local/ws/rest/v1" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Copyright>Copyright &copy; 2013 Microsoft and its suppliers. All rights reserved. This API cannot be accessed and the content and any results may not be used, reproduced or transmitted in any manner without express written permission from Microsoft Corporation.</Copyright><BrandLogoUri>http://dev.virtualearth.net/Branding/logo_powered_by.png</BrandLogoUri><StatusCode>200</StatusCode><StatusDescription>OK</StatusDescription><AuthenticationResultCode>ValidCredentials</AuthenticationResultCode><TraceId>...</TraceId><ResourceSets>
    <ResourceSet>
      <EstimatedTotal>2</EstimatedTotal><Resources>
        <Location>
          <Name>品川駅</Name>
          <Point>
            <Latitude>35.6301507288277</Latitude>
            <Longitude>139.740439825337</Longitude>
          </Point>
          <BoundingBox>
            <SouthLatitude>35.6202375948277</SouthLatitude>
            <WestLongitude>139.728202787337</WestLongitude>
            <NorthLatitude>35.6400638628277</NorthLatitude>
            <EastLongitude>139.752676863337</EastLongitude>
          </BoundingBox>
          <EntityType>RailwayStation</EntityType>
          <Address>
            <AddressLine>高輪3丁目</AddressLine>
            <AdminDistrict>東京都</AdminDistrict>
            <CountryRegion>日本</CountryRegion>
            <FormattedAddress>品川駅</FormattedAddress>
            <Locality>港区</Locality>
            <PostalCode>108-0074</PostalCode>
            <Landmark>品川駅</Landmark>
          </Address>
          <Confidence>Medium</Confidence>
          <MatchCode>Ambiguous</MatchCode>
          <MatchCode>Good</MatchCode>
            <GeocodePoint>
            <Latitude>35.6301507288277</Latitude>
            <Longitude>139.740439825337</Longitude>
            <CalculationMethod>Rooftop</CalculationMethod>
            <UsageType>None</UsageType>
          </GeocodePoint>
        </Location>
        <Location>
          <Name>東京都品川区</Name>
          <Point>
            <Latitude>35.608905711111</Latitude>
            <Longitude>139.73029975</Longitude>
          </Point>
          <BoundingBox>
            <SouthLatitude>35.568351981111</SouthLatitude>
            <WestLongitude>139.68974602</WestLongitude>
            <NorthLatitude>35.649459441111</NorthLatitude>
            <EastLongitude>139.77085348</EastLongitude>
          </BoundingBox>
          <EntityType>PopulatedPlace</EntityType>
          <Address>
            <AdminDistrict>東京都</AdminDistrict>
            <CountryRegion>日本</CountryRegion>
            <FormattedAddress>東京都品川区</FormattedAddress>
            <Locality>品川区</Locality>
            <Landmark>東京都品川区</Landmark>
          </Address>
          <Confidence>Medium</Confidence>
          <MatchCode>Ambiguous</MatchCode>
          <MatchCode>Good</MatchCode>
          <GeocodePoint>
            <Latitude>35.608905711111</Latitude>
            <Longitude>139.73029975</Longitude>
            <CalculationMethod>Rooftop</CalculationMethod>
            <UsageType>None</UsageType>
          </GeocodePoint>
        </Location>
      </Resources>
    </ResourceSet>
  </ResourceSets>
</Response>

というわけで、こいつを呼び出してパースするコードを書いてみましょう。
今回はせっかくWP8ですしasync/awaitを使ってさくっと作りたい、ということで、nugetからWP8 Async WebClientを引っ張ってきて参照しています。

using System;
using System.Net;
using System.Linq;
using System.Xml.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Device.Location;
using Microsoft.Phone.Maps.Controls;

namespace BingMapsLocationQuerySample
{
    public class Location
    {
        public string Name { get; set; }
        public GeoCoordinate Center { get; set; }
        public LocationRectangle Bounds { get; set; }
        public string Type { get; set; }
        public string Address { get; set; }
    }

    public class BingLocations
    {
        private static readonly string BINGMAP_API_KEY = "(Bing Maps Key)";

        public async Task<IEnumerable<Location>> RequestPoints(string query)
        {
            if (string.IsNullOrWhiteSpace(query))
                return Enumerable.Empty<Location>();
            var client = new WebClient();
            var url = string.Format("http://dev.virtualearth.net/REST/v1/Locations/{0}?c=ja&o=xml&key={1}", Uri.EscapeDataString(query), BINGMAP_API_KEY);

            // WP8 Async WebClient の DownloadStringTaskAsync を使用 (nugetで参照追加)
            var result = await client.DownloadStringTaskAsync(new Uri(url, UriKind.Absolute));

            var x = XElement.Parse(result);
            var ns = x.Name.Namespace;
            var points = x.Descendants(ns + "Location");
            var list = points.Where(p =>
            {
                try
                {
                    var country = p.Element(ns + "Address").Element(ns + "CountryRegion").Value.ToLower();
                    return country == "日本" || country == "japan";
                }
                catch (Exception)
                {
                    return false;
                }
            }).Select(p =>
            {
                var point = p.Element(ns + "Point");
                var bounds = p.Element(ns + "BoundingBox");
                var address = p.Element(ns + "Address");
                var item = new Location()
                {
                    Name = p.Element(ns + "Name").Value,
                    Center = new GeoCoordinate(
                        double.Parse((string)point.Element(ns + "Latitude") ?? "0"), 
                        double.Parse((string)point.Element(ns + "Longitude") ?? "0")),
                    Bounds = new LocationRectangle(
                        double.Parse((string)bounds.Element(ns + "NorthLatitude") ?? "0"),
                        double.Parse((string)bounds.Element(ns + "WestLongitude") ?? "0"),
                        double.Parse((string)bounds.Element(ns + "SouthLatitude") ?? "0"),
                        double.Parse((string)bounds.Element(ns + "EastLongitude") ?? "0")),
                    Type = (string)p.Element(ns + "EntityType"),
                    Address = (string)address.Element(ns + "AdminDistrict")
                        + (string)address.Element(ns + "Locality") 
                        + (string)address.Element(ns + "AddressLine")
                };
                return item;
            });
            return list;
        }
    }
}

例によって例外処理/初期値などはてきとうなのでお好みで。
あとは、呼び出して帰ってきたIEnumirableをこんな感じで突っ込んでListBoxあたりにBindするなどしてやればOkですね。

    Items = new ObservableCollection<Location>(await bing.RequestPoints(textQuery.Text));

なんやようわからん、という人もasync/awaitとWebリクエストからXMLのパース、Linq、Bindingのちょっとした使い方まで色々盛り沢山なサンプルだと思うのでじっくり読んでみてください。
プロジェクトのビルドの前にWP8 Async WebClientの参照追加とBing Maps Keyの書き換えを忘れずに。
サンプルプロジェクトのダウンロード: BingMapsLocationQuerySample.zip 直