現在位置の近くにある宿を検索するサンプルプログラム

2013年6月28日(金)
薬師寺 国安

場所が変更された時の処理

文字列型の新しいリストである、HotelUrlオブジェクトを作成します。

myCoreDispacher.RunAsyncメソッドで、イベントディスパッチャを実行し、ディスパッチされたイベントの結果を非同期に返します。ディスパッチャの優先順位は標準のNormalを指定しています。

緯度と経度のデータや都市の住所データを含めることができるGeoPositionクラス型の変数psoを宣言し、PositionChangedイベントに関連付けられた場所データを取得します。

Clearメソッドで、地図内を一度クリアしておきます。

新しいPushpinクラスのインスタンスmyPinオブジェクトを作成します。ピンの背景色をCrimsonに指定します。

地理的位置に関連付けされた緯度と経度で初期化された、新しいLocationクラスのインスタンスmyLocationオブジェクトを作成します。Locationクラスは、地図上の場所の標高と座標値を含むクラスです。

MapLayerクラスのSetPositionメソッドで、マップレイヤー内にピンの位置を設定します。myMapにAddメソッドでピンを追加します。
SetViewメソッドで、指定された中心部に位置、ズーム レベル、方位、およびピッチにマップビューを設定します。この場合myLocationの位置に16レベルでズームインします。

現在位置の住所を取得するWeb APIのURLを設定します。latに現在の緯度、lonには現在の経度、appidにはYahooのキーを指定し、変数myAddressUriに格納しておきます。

新しいHttpClientクラスのインスタンスmyAddressHttpClientオブジェクトを作成します。HttpClientクラスは、URIで識別されるリソースにHTTP要求を送信し、そのリソースからHTTP応答を受信するための基本クラスが含まれています。

GetStringAsyncメソッドでmyAddressUriの結果を文字列として返し、変数myCurrentAddressに格納します。GetStringAsyncメソッドは、指定URIにGET要求を送信し、非同期操作で応答本体を文字列として返すメソッドです。

返される文字列はXML形式になっています。このXMLのルート要素には、名前空間や不要な属性が定義されていて、内容を取得する際の邪魔(-_-;)になりますので、Replace関数で、ルート要素()だけに置換しています。

XElement.Parseメソッドで置換された結果のXMLが格納されている、myCurrentAddressを文字列として読み込みます。

読み込んだXMLから

要素の内容を取得して、変数myAddressに格納しておきます。

新しいStackPanelのインスタンスmyAddressStackPanelオブジェクトを作成します。
Marginプロパティに5を指定して余白を設けます。
背景色をNavyに指定します。このStackPanelオブジェクトのインスタンスを、非表示としておきます。

新しいTextBlockのインスタンスmyAddressTextBlockオブジェクトを作成します。文字色をRedに指定します。
文字サイズに24を指定し、パディングに5を指定します。
Textプロパティに住所を格納している変数myAddressと文字列「辺り」を連結して指定します。

myStackPanelオブジェクトにAddメソッドで住所の設定された、myAddressTextBlockを追加します。

SetValueメソッドでmyAddressStackPanelのZIndexPropertyに20を指定します。
myAddressStackPaneが前面に表示されます。

MapLayerクラスのSetPositionメソッドで、マップレイヤー内にmyAddressStackPanelオブジェクトの位置を設定します。
MapLayerクラスは、地図上の要素の位置を保持しているマップレイヤーを表すクラスです。

myMapにAddメソッドでmyAddressStackPanelオブジェクトを追加します。
myAddressStackPanelは最初の状態では非表示になっているのでわかりませんが、ピンの位置にStackPanelオブジェクトが表示されることになります。

AddHandlerステートメントでピンをタップした時のイベントハンドラを追加します。イベントハンドラ内では、myAddressStackPanelオブジェクトを表示状態にします。

AddHandlerステートメントでmyAddressStackPanelをタップした時のイベントハンドラを追加します。
イベントハンドラ内では、myAddressStackPanelオブジェクトを非表示状態にします。

宿の情報を取得する「じゃらん」のWeb APIのURLを設定します。keyにはAPIキーをxとyには経度と緯度を指定します。pos.Coordinate.Longitude とpos.Coordinate.Latitudeで取得する経度と緯度はdegree形式です。
じゃらんのWeb APIに指定する経度と緯度はミリ秒である必要があるため、3600000を乗算して変換しています。Rangeには1を指定して、1Km範囲内としています。countには20を指定して、最大で20件までの情報を取得します。
これらの式を変数myUriに格納しておきます。

新しいHttpClientのインスタンスmyHttpClientを作成します。
GetStringAsyncメソッドで「じゃらん」の Web APIの式を格納しているmyUriの内容を読み取り、返ってくるXMLを変数resultAddressに格納します。
返ってきたXMLには、不要な名前空間が付いていますので、Replace関数でというルート要素だけに置換します。

XElement.ParseメソッドでresultAddressが格納しているXMLの内容を、文字列として読み込みます。

Descendantsメソッドで、すべての要素の内容を、変数resultに格納しながら、以下の処理を行います。

変数noの値を1ずつ増加させます。この値は、これから作成する「Webで表示」ボタンの、どのボタンがクリックされたかの判定に使用します。

文字列型のリストであるHotelUri変数にAddメソッドで、要素の内容を追加していきます。

新しいPushPinクラスのインスタンスhotelPinオブジェクトを作成します。背景色にNavyを指定し、Textプロパティに1ずつ増加する変数noの値を指定します。ピンの表面に連番が表示されます。

要素の値を変数myLatitude(緯度)に、要素の値を変数myLongitude(経度)に格納します。
地理的位置に関連付けされた緯度と経度で初期化された、新しいLocationクラスのインスタンスmyLocationオブジェクトを作成します。
取得されるの値はミリ秒であるため、これをdegree形式に変換させるため、今度は3600000で除算しています。

MapLayerクラスのSetPositionメソッドで、マップレイヤー内に宿のピンの位置を設定します。myMapにAddメソッドで宿の位置にピンを追加します。

新しいStackPanelのインスタンスmyStackPanelオブジェクトを作成します。Marginに5、背景色にPinkを指定し、非表示としておきます。

新しいTextBlockのインスタンスmyTextBlockを作成します。
Widthに400、文字の回り込みは可、文字色はRed、文字サイズは24、パディングは5と指定します。
Textプロパティには、文字列「宿名」と要素の内容を連結して指定します。宿の名前が表示されます。

新しいTextBlockのインスタンスmyAddressTextBlockを作成します。
Widthに400、文字の回り込みは可、文字色はNavy、文字サイズは20、パディングは5と指定します。
Textプロパティには、文字列「住所」と要素の値と、の値を連結して表示します。
郵便番号付き住所が表示されます。

新しいTextBlockのインスタンスdescriptionTextBlockを作成します。
Widthに400、文字の回り込みは可、文字色はNavy、文字サイズは20、パディングは5と指定します。
Textプロパティには、文字列「概要」と要素の内容を連結して指定します。
宿の概要が表示されます。

新しいTextBlockのインスタンスstationTextBlockを作成します。
Widthに400、文字の回り込みは可、文字色はNavy、文字サイズは20、パディングは5と指定します。
Textプロパティには、文字列「最寄り駅」と要素の内容を連結して指定します。
宿のへの最寄り駅名が表示されます。

新しいButtonのインスタンスmyButtonを作成します。
Contentプロパティに「Webで表示」と指定します。枠線の色と文字色をBlackに指定します。
Tagプロパティに変数noの値を指定します。どのButtonがクリックされたかの判定に使用します。

SetValueメソッドで、myStackPanelのZIndexPropertyに変数noの値を指定します。myStackPanelがPushPinよりも前面に表示されるようになります。

myStackPanelオブジェクトにAddメソッドで、myTextBlock、addressTextBlock、descriptionTextBlock、stationTextBlock、myButtonを追加します。

MapLayerクラスのSetPositionメソッドで、マップレイヤー内にmyStackPanelの位置を設定します。
myMapにAddメソッドでmyStackPanelを追加します。

AddHandlerステートメントで、宿の位置を表すピンがタップされた時のイベントハンドラを追加します。myStackPanelオブジェクトが表示状態になり、宿の情報が表示されます。

AddHandlerステートメントで、宿の情報を表示した、myStackPanelがタップされた時のイベントハンドラを追加します。myStackPanelオブジェクトが非表示になります。

AddHandlerステートメントで、Buttonがクリックされた時のイベントハンドラを追加します。
myFrameを表示状態にし、クリックされたButtonのTagの値を変数myIndexに格納します。
宿のリスト情報を持っているHotelUrl内で変数myIndex-1に位置するホテルのURLを変数sendUriに格納します。
myIndexを-1しているのは、インデックスが0から始まるためです。sendUriを引数にWeBrowserPageに遷移します。

非同期処理で行われるためメソッドの先頭にAsyncを追加します。

  Private Async Sub myGeolocator_PositionChanged(sender As Geolocator, args As PositionChangedEventArgs)
    Dim HotelUrl As New List(Of String)
 
    Dim myLatitude As String = String.Empty
    Dim myLongitude As String = String.Empty
    Dim no As Integer = 0
    Await myCoreDispacher.RunAsync(CoreDispatcherPriority.Normal, Async Sub()
                    Dim pos As Geoposition = args.Position
                    myMap.Children.Clear()
                    Dim myPin As New Pushpin
                    myPin.Background = New SolidColorBrush(Colors.Crimson)
                    Dim myLocation = New Location(CDbl(pos.Coordinate.Latitude), CDbl(pos.Coordinate.Longitude))
  
                    MapLayer.SetPosition(myPin, myLocation)
                    myMap.Children.Add(myPin)
                    myMap.SetView(myLocation, 16)
 
                    Dim myAddressUri As String = String.Format("http://reverse.search.olp.yahooapis.jp/OpenLocalPlatform/V1/reverseGeoCoder?lat={0}&lon={1}&appid={2}", pos.Coordinate.Latitude, pos.Coordinate.Longitude, AppID)
 
                    Dim myAddressHttpClient As New HttpClient
 
                    Dim myCurrentAddress = Await myAddressHttpClient.GetStringAsync(myAddressUri)
                    myCurrentAddress = myCurrentAddress.Replace("<YDF firstResultPosition=" & ChrW(34) & "1" & ChrW(34) & " totalResultsAvailable=" & ChrW(34) & "1" & ChrW(34) & " totalResultsReturned=" & ChrW(34) & "1" & ChrW(34) & " xmlns=" & ChrW(34) & "http://olp.yahooapis.jp/ydf/1.0" & ChrW(34) & ">", "<YDF>")
 
                    Dim xmldoc As XElement = XElement.Parse(myCurrentAddress)
                    Dim myAddress = xmldoc.Descendants("Address").Value
                    Dim myAddressStackPanel As New StackPanel
                    myAddressStackPanel.Margin = New Thickness(5)
                    myAddressStackPanel.Background = New SolidColorBrush(Colors.Navy)
                    myAddressStackPanel.Visibility = Xaml.Visibility.Collapsed
                    Dim myAddressTextBlock As New TextBlock
                    myAddressTextBlock.Foreground = New SolidColorBrush(Colors.Red)
                    myAddressTextBlock.FontSize = 24
                    myAddressTextBlock.Padding = New Thickness(5)
                    myAddressTextBlock.Text = myAddress & " 辺り"
                    myAddressStackPanel.Children.Add(myAddressTextBlock)
                    myAddressStackPanel.SetValue(Canvas.ZIndexProperty, 20)
 
                    MapLayer.SetPosition(myAddressStackPanel, New Location(CDbl(pos.Coordinate.Latitude), CDbl(pos.Coordinate.Longitude)))
            myMap.Children.Add(myAddressStackPanel)
              AddHandler myPin.Tapped, Sub()
                myAddressStackPanel.Visibility = Xaml.Visibility.Visible
              End Sub
              AddHandler myAddressStackPanel.Tapped, Sub()
                myAddressStackPanel.Visibility = Xaml.Visibility.Collapsed
              End Sub
              
              Dim myUri As String = String.Format("http://jws.jalan.net/APIAdvance/HotelSearch/V1/?key={0}&x={1}&y={2}&range=1.0&count=20", HotelAppID, CInt(CDbl(pos.Coordinate.Longitude * 3600000)), CInt(CDbl(pos.Coordinate.Latitude * 3600000)))
              Dim myHttpClient As New HttpClient
              Dim resultAddress = Await myHttpClient.GetStringAsync(myUri)
  resultAddress = resultAddress.Replace("<Results xmlns=" & ChrW(34) & "jws" & ChrW(34) & ">", "<Results>")
              Dim httpDoc As XElement = XElement.Parse(resultAddress)
              For Each result In From c In httpDoc.Descendants("Hotel") Select c
                  no += 1
                  HotelUrl.Add(result.Element("HotelDetailURL").Value)
                  Dim hotelPin As New Pushpin
                  hotelPin.Background = New SolidColorBrush(Colors.Navy)
                  hotelPin.Text = no.ToString
                  myLatitude = result.Element("Y").Value
                  myLongitude = result.Element("X").Value
                  Dim hotelLocation = New Location(CDbl(CDbl(myLatitude) / 3600000), CDbl(CDbl(myLongitude) / 3600000))
                  MapLayer.SetPosition(hotelPin, hotelLocation)
                  myMap.Children.Add(hotelPin)
                  Dim myStackPanel As New StackPanel
                  myStackPanel.Margin = New Thickness(5)
                  myStackPanel.Background = New SolidColorBrush(Colors.Pink)
                  myStackPanel.Visibility = Xaml.Visibility.Collapsed
                  Dim myTextBlock As New TextBlock
                  myTextBlock.Width = 400
                  myTextBlock.TextWrapping = TextWrapping.Wrap
                  myTextBlock.Foreground = New SolidColorBrush(Colors.Red)
                  myTextBlock.FontSize = 24
                  myTextBlock.Padding = New Thickness(5)
                    myTextBlock.Text = "【宿名】=" & result.Element("HotelName").Value
                    Dim addressTextBlock As New TextBlock
                    addressTextBlock.Width = 400
                    addressTextBlock.TextWrapping = TextWrapping.Wrap
                    addressTextBlock.Foreground = New SolidColorBrush(Colors.Navy)
                    addressTextBlock.FontSize = 20
                    addressTextBlock.Padding = New Thickness(5)
                    addressTextBlock.Text = "【住所】=" & result.Element("PostCode").Value & " " & result.Element("HotelAddress").Value
          
                    Dim descriptionTextBlock As New TextBlock
                    descriptionTextBlock.Width = 400
                    descriptionTextBlock.TextWrapping = TextWrapping.Wrap
                    descriptionTextBlock.Foreground = New SolidColorBrush(Colors.Navy)
                    descriptionTextBlock.FontSize = 20
                    descriptionTextBlock.Padding = New Thickness(5)
                    descriptionTextBlock.Text = "【概要】=" & result.Element("HotelCaption").Value
 
                    Dim stationTextBlock As New TextBlock
                    stationTextBlock.Width = 400
                    stationTextBlock.TextWrapping = TextWrapping.Wrap
                    stationTextBlock.Foreground = New SolidColorBrush(Colors.Navy)
                    stationTextBlock.FontSize = 20
                    stationTextBlock.Padding = New Thickness(5)
                    stationTextBlock.Text = "【最寄り駅】=" & result.Element("AccessInformation").Value
                    Dim myButton As New Button
                    myButton.Content = "Webで表示"
                    myButton.BorderBrush = New SolidColorBrush(Colors.Black)
                    myButton.Foreground = New SolidColorBrush(Colors.Black)
                    myButton.Tag = no.ToString
                    myStackPanel.SetValue(Canvas.ZIndexProperty, no)
                    myStackPanel.Children.Add(myTextBlock)
                    myStackPanel.Children.Add(addressTextBlock)
                    myStackPanel.Children.Add(descriptionTextBlock)
                    myStackPanel.Children.Add(stationTextBlock)
                    myStackPanel.Children.Add(myButton)
                    MapLayer.SetPosition(myStackPanel, New Location(CDbl(CDbl(myLatitude) / 3600000), CDbl(CDbl(myLongitude) / 3600000)))
                      myMap.Children.Add(myStackPanel)
                        AddHandler hotelPin.Tapped, Sub()
                          myStackPanel.Visibility = Xaml.Visibility.Visible
                        End Sub
                        AddHandler myStackPanel.Tapped, Sub()
                          myStackPanel.Visibility = Xaml.Visibility.Collapsed
                        End Sub
                        AddHandler myButton.Click, Sub(mySender As Object, myArgs As RoutedEventArgs)
                        myFrame.Visibility = Xaml.Visibility.Visible
                        Dim myIndex As Integer = CInt(DirectCast(mySender, Button).Tag.ToString)
                        Dim sendUri = HotelUrl(myIndex - 1)
                        myFrame.Navigate(GetType(WebBrowserPage), sendUri)
                                       End Sub
                      Next
          End Sub)
  End Sub

Geolocatorの機能が変更された時に発生する処理

Geolocatorオブジェクトの更新後の状態を、変数positionStatusで参照します。

Select Case文で条件分岐を行います。場所データを提供するGeolocatorオブジェクトがDisabledであった場合、つまり、場所プロバイダーが無効であった場合は、RunAsyncメソッドでディスパッチされたイベントの結果を非同期に返します。ディスパッチャの優先順位は標準のNormalを指定しています。

この場合は、「位置情報を取得できません。GPSおよび位置情報取得可能なPCでお試しください。」というメッセージボックスを表示させ、処理を抜けます。

非同期で行われるためメソッドの先頭にAsyncを追加します。

  Private Async Sub myGeolocator_StatusChanged(sender As Geolocator, args As StatusChangedEventArgs)
    Dim positionStatus = args.Status
    Select Case positionStatus
      Case Windows.Devices.Geolocation.PositionStatus.Disabled
        Await myCoreDispacher.RunAsync(CoreDispatcherPriority.Normal, Async Sub()
          TitleTextBlock.Text = "位置情報を取得できません。GPSおよび位置情報取得可能なPCでお試しください。"
          Exit Sub 
          End Sub)
          End Select
  End Sub

戻る(←)アイコンがクリックされた時の処理

myFrameを非表示にします。

  Private Sub backButton_Click(sender As Object, e As RoutedEventArgs) Handles backButton.Click
    myFrame.Visibility = Xaml.Visibility.Collapsed
  End Sub
End Class

次に、ソリューションエクスプローラー内のWebBrowserPage.xamlを展開して表示される、WebBrowserPage.xaml.vbをダブルクリックしてリスト4のコードを記述します。

ロジックコードを記述する

リスト4 (WebBrowserPage.xaml.vb)

Option Strict On
Public NotInheritable Class WebBrowserPage
  Inherits Page

ページがアクティブになった時の処理

MainPage.xamlから送られた値を、e.Parameterで受け取ります。これはObject型であるため、DirectCastで文字列にキャストして、変数myUriに格納しておきます。WebBrowserのSourceプロパティに、myUriで初期化された、新しいUriを指定します。これで、ブラウザ内に、宿の情報が表示されます。

  Protected Overrides Sub OnNavigatedTo(e As Navigation.NavigationEventArgs)
    Dim myUri = DirectCast(e.Parameter, String)
    WebBrowser1.Source = New Uri(myUri, UriKind.Absolute)
  End Sub
End Class

解説では触れておりませんが、このプロジェクトにはAboutControl.xamlとAboutControl.xaml.vbが存在しています。これは「このアプリについて」をチャームから表示させるものです。このテンプレートはユーザーコントロールで作成しています。
このコードはMicrosoft様よりMVP向けに提供されたもので、私が書いたコードではありませんので、このなかでは触れていません。確認したい場合は、実行してチャームを表示させてみてください。「このアプリについて」の項目があります。これをタップするとAboutControl.xamlが表示されます。
また「プライバシーポリシー」を表示する項目もあります。このコードはApp.xaml.vb内に記述しています。これもMicrosoft様より提供されたもので、ここでは触れておりません。このサンプル以降もAboutControlやAppに関しては解説していませんので、興味のある方はソースコードを見て勉強してください。

今回の連載のサンプルは、実際にWindows ストアに申請して認定されたアプリのサンプルですので、上記のコードが含まれています。「プライバシーポリシー」に関しては、「これでリジェクトされない!Windowsストアアプリ申請のポイント」で触れています。

アイコンの作成

ソリューションエクスプローラーのAssetsフォルダ内には、4つのpngファイルが入っています(表1)。

表1:Assetsフォルダ内に入っているpngファイルの種類

ファイル名 サイズ
Logo.png 150×150
SmallLogo.png 30×30
SplashScreen.png 620×300
StoreLogo.png 50×50

表1の画像はデフォルトでは、□に×の画像になっています。ストアの審査では、このままの画像では審査に受かりませんので、4種類のアイコンを作る必要があります。このサンプルは実際にストアで審査の通ったアプリですので、Assetsフォルダ内には筆者の作成したアイコンが収められています。見てみてください。
SplashScreen.pngはアプリを起動した際に、一瞬最初に表示される画像です。スタート画面にピン止めされる画像はデフォルトでは150×150のLogo.pngが使用されます。これを長方形の画像にしたい場合は、310×150サイズのpng画像を作成し、WideLogo.png(任意の名前でOK)としてAssetsフォルダに追加しておきます。

詳細については、前回の記事を参照してください。

今回はここまでです。ありがとうございました。

筆者からのお知らせ

筆者はWindowsストアでアプリを公開しています。チャームの検索からWindowsストアを選択して、検索欄に、kuniyasuまたはYakushijiKuniyasuと入力すると、公開されているアプリの一覧が表示されます。上記はどちらも私のアカウントですので、興味のある方は是非ダウンロードして使ってみてください。

  • 現在位置の近くにある宿を検索するプログラム

薬師寺国安事務所

薬師寺国安事務所代表。Visual Basic プログラミングと、マイクロソフト系の技術をテーマとした、書籍や記事の執筆を行う。
1950年生まれ。事務系のサラリーマンだった40歳から趣味でプログラミングを始め、1996年より独学でActiveXに取り組む。1997年に薬師寺聖とコラボレーション・ユニット PROJECT KySS を結成。2003年よりフリーになり、PROJECT KySS の活動に本格的に参加、.NETやRIAに関する書籍や記事を多数執筆する傍ら、受託案件のプログラミングも手掛ける。Windows Phoneアプリ開発を経て、現在はWindows ストア アプリを多数公開中

Microsoft MVP for Development Platforms - Client App Dev (Oct 2003-Sep 2012)。Microsoft MVP for Development Platforms - Windows Phone Development(Oct 2012-Sep 2013)。Microsoft MVP for Development Platforms - Client Development(Oct 2013-Sep 2014)。Microsoft MVP for Development Platforms-Windows Platform Development (Oct 2014-Sep 2015)。

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています