指定した目的地までの距離をキャラクターが教えてくれるアプリを作ろう

2014年1月17日(金)
薬師寺 国安

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

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

リスト7 (MainWindow.xaml.vb)

最新の HTTP アプリケーションのプログラミング インターフェイスを提供するクラスの含まれる、System.Net.Http 名前空間をインポートします。

Imports System.Net.Http

Bing Mapsの使用を可能にするクラスの含まれる、Bing Maps名前空間をインポートします。

Imports Bing.Maps

Imports Windows.UI
Imports Windows.UI.Popups

装飾的なレンダリング、またはコントロールの非対話形式部分の合成での使用を目的とした、基本的な図形を定義するクラスの含まれるWindows.UI.Xaml.Shapes名前空間をインポートします。

Imports Windows.UI.Xaml.Shapes
Public NotInheritable Class MainPage
  Inherits Page

定数メンバー変数YahooIDをヤフーから取得したIDで初期化します。取得方法は後述します。

  Const YahooAppID As String = "ヤフーより取得したID"
  
  Private myStartLatitude As String
  Private myStartLongitude As String
  Private myMiddleStartLatitude As String
  Private myMiddleStartLongitude As String
  Private myArriveLatitude As String
  Private myArriveLongitude As String

地図上にPoylineを描画するMapPolylineクラス型のメンバー変数myMapPolyline、myMapPolyline2、を宣言します。

  Private myMapPolyline As MapPolyline
  Private myMapPolyline2 As MapPolyline

マップ上の位置の高度と座標値が含まれるLocationクラスのメンバー変数、firstLocation、middleLocation、endLocationを宣言します。

  Private firstLocation As Location
  Private middleLocation As Location
  Private endLocation As Location

キャラクタに喋らせる内容を格納するメンバー変数readingTextを宣言します。

  Private readingText As String

「出発点」の[決定]ボタンがタップされた時の処理

「出発点」にデータが入力されていない場合は、メッセージを表示して処理を抜けます。データが入力されていた場合は、以下の処理を行います。
GeocodingのWeb APIを使って、startTextBoxに入力された住所からレスポンスデータを受け取り、変数startMyUriに格納します。GeocodingのWeb APIについては、下記のURLを参照してください。
> Geocoding Web API

新しいHttpClientのインスタンスmyHttpClientオブジェクトを作成します。GetStringAsyncメソッドでstartMyUriから返される応答本体を文字列として受け取り、myResponse変数に格納します。
XElement.ParseメソッドでmyResponseデータを文字列として読み取り、xmldocで参照します。
メンバー変数myStartLatitudeに要素の子要素の値を格納します。
メンバー変数myStartLongitudeに要素の子要素の値を格納します。
PushPinクラスの新しいインスタンスmyPinオブジェクトを作成します。背景色をCrimsonとします。
新しいStackPanelのインスタンスmyStackPanelオブジェクトを作成します。Marginに「5」を指定して余白を設けます。背景色には「Pink」を指定します。

新しいTextBlockのインスタンス、startTextBlockオブジェクトを作成します。
文字サイズは「20」、パディングは「5」、TextプロパティにはstartTextBlock.Textに入力されたデータと文字列【出発点】を連結して表示します。文字色には「Red」を指定します。
myStackPanelオブジェクトにstartTextBlockを追加します。
新しいLocationオブジェクトを、メンバー変数myStartLatitudeとmyStartLongitudeで初期化し、メンバー変数firestLocationで参照します。
myStatStackPanelオブジェクトにAddメソッドでstartTextBlockの値を追加します。
MapLayer.SetPosition(myStartStackPanel, firstLocation)と指定すると、出発点の位置にピンが立ち、住所名が表示されます。
myMapにmyStackPanelオブジェクトを追加します。
MapLayer.SetPosition(myPin, firstLocation)と指定して、myMapにmyPinオブジェクトを指定します。
SetViewメソッドでfirstLocationの位置にズームインします。
「中継点」の[決定]ボタンの使用を可能にします。代わりに[出発点]のボタンの使用を不可とします。
非同期処理で行われるため、メソッドの先頭にAsyncを追加します。

  Private Async Sub OkButton_Click(sender As Object, e As RoutedEventArgs) Handles OkButton.Click
    If startTextBox.Text = String.Empty Then
      Dim message As New MessageDialog("出発点を入力してください。")
      Await message.ShowAsync
      Exit Sub
    End If
    myStartLatitude = String.Empty
    myStartLongitude = String.Empty
    Dim startMyUri As String = String.Format("http://www.geocoding.jp/api/?v=1.1&q={0}", Uri.EscapeDataString(startTextBox.Text))
 
    Dim myHttpClient As New HttpClient
    Dim myResponse = Await myHttpClient.GetStringAsync(New Uri(startMyUri, UriKind.Absolute))
    
    Dim xmldoc As XElement = XElement.Parse(myResponse)
    myStartLatitude = xmldoc.Descendants("coordinate").Elements("lat").Value
    myStartLongitude = xmldoc.Descendants("coordinate").Elements("lng").Value
    Dim myPin As New Pushpin
 
    myPin.Background = New SolidColorBrush(Colors.Crimson)
 
    Dim myStartStackPanel As New StackPanel
    With myStartStackPanel
      .Margin = New Thickness(5)
      .Background = New SolidColorBrush(Colors.Pink)
    End With
    Dim startTextBlock As New TextBlock
    With startTextBlock
      .FontSize = 20
      .Padding = New Thickness(5)
      .Text = "【出発点】=" & startTextBox.Text
      .Foreground = New SolidColorBrush(Colors.Red)
    End With
 
    firstLocation = New Location(CDbl(myStartLatitude), CDbl(myStartLongitude))
    myStartStackPanel.Children.Add(startTextBlock)
    MapLayer.SetPosition(myStartStackPanel, firstLocation)
    myMap.Children.Add(myStartStackPanel)
    
    MapLayer.SetPosition(myPin, firstLocation)
    myMap.Children.Add(myPin)
    
    myMap.SetView(firstLocation, 6)
    OkButton2.IsEnabled = True
    OkButton.IsEnabled = False
  End Sub

「中継点」の[決定]ボタンがタップされた時の処理

中継点の位置にズームインするまでの処理は、「■「出発点」の[決定]ボタンがタップされた時の処理」と同じですので、そちらを参照してください。
「出発点」から「中継点」に直線を引く処理について解説します。
新しいMapPolylineのインスタンスmyMapPolylineオブジェクトを作成します。MapPolygon や MapPolyline などの図形を含むレイヤーを表すクラスであるMapShapeLayerクラスのインスタンス、myShapeLayerオブジェクトを作成します。
色はRedとします。myMapPlylineオブジェクトの図形を定義する場所を表すLocationsプロパティにAddメソッドで、firestLocationの値を指定します。同じくmiddleLocationの値を指定します。線の幅は4とします。
レイヤー内のシェイプのコレクションを取得する、MapShapeLayerのShapesプロパティにAddメソッドでmyMapPolylineオブジェクトを追加します。最後にmyMapの地図の、シェイプレイヤーのコレクションを取得するShapeLayersプロパティにAddメソッドでmyShapeLayerオブジェクトを追加します。これで、出発点から中継点に直線が引かれます。
「中継点」の[決定]ボタンの使用を不可とし、「到達点」の[決定]ボタンの使用を可能とします。
非同期処理で行われるため、メソッドの先頭にAsyncを追加します。

  Private Async Sub OkButton2_Click(sender As Object, e As RoutedEventArgs) Handles OkButton2.Click
    Try
      If middleTextBox.Text = String.Empty Then
        Dim message As New MessageDialog("中継点を入力してください。")
        Await message.ShowAsync
      Exit Sub
    End If
    myMiddleStartLatitude = String.Empty
    myMiddleStartLongitude = String.Empty
    Dim middleMyUri As String = String.Format("http://www.geocoding.jp/api/?v=1.1&q={0}", Uri.EscapeDataString(middleTextBox.Text))
 
    Dim myMiddleHttpClient As New HttpClient
    Dim myMiddleResponse = myMiddleHttpClient.GetStringAsync(New Uri(middleMyUri, UriKind.Absolute))
    
    Dim myMiddleContent = myMiddleResponse.Result
    
    Dim middleXmldoc As XElement = XElement.Parse(myMiddleContent)
    myMiddleStartLatitude = middleXmldoc.Descendants("coordinate").Elements("lat").Value
    myMiddleStartLongitude = middleXmldoc.Descendants("coordinate").Elements("lng").Value

          Dim myMiddlePin As New Pushpin
 
    myMiddlePin.Background = New SolidColorBrush(Colors.Green)
    
    Dim myMiddleStartStackPanel As New StackPanel
    With myMiddleStartStackPanel
      .Margin = New Thickness(5)
      .Background = New SolidColorBrush(Colors.Pink)
    End With
    Dim middleTextBlock As New TextBlock
    With middleTextBlock
      .FontSize = 20
      .Padding = New Thickness(5)
      .Text = "【中継点】=" & middleTextBox.Text
      .Foreground = New SolidColorBrush(Colors.Green)
    End With
 
    middleLocation = New Location(CDbl(myMiddleStartLatitude), CDbl(myMiddleStartLongitude))
    myMiddleStartStackPanel.Children.Add(middleTextBlock)
    MapLayer.SetPosition(myMiddleStartStackPanel, middleLocation)
    myMap.Children.Add(myMiddleStartStackPanel)
 
    MapLayer.SetPosition(myMiddlePin, middleLocation)
    myMap.Children.Add(myMiddlePin)
    myMap.SetView(middleLocation, 6)
    
    myMapPolyline = New MapPolyline
 
    Dim myShapeLayer As New MapShapeLayer
    myMapPolyline.Color = Colors.Red
 
    myMapPolyline.Locations.Add(firstLocation)
  
    myMapPolyline.Locations.Add(middleLocation)
    myMapPolyline.Width = 4
    myShapeLayer.Shapes.Add(myMapPolyline)
    myMap.ShapeLayers.Add(myShapeLayer)

    OkButton2.IsEnabled = False
    OkButton3.IsEnabled = True
  Catch
    Exit Sub
  End Try
 End Sub

「到達点」の[決定]ボタンがタップされた時の処理

「到達点」の位置にズームインして、「中継点」から直線を引くまでの処理は、「■「中継点」の[決定]ボタンがタップされた時の処理」と同じですので、そちらを参照してください。
ただし、ここでは、TotalCoordinate変数に、「出発点」、「中継点」、「到達点」の緯度と経度をカンマと空白で区切って格納しておきます。

「出発点」から「到達点」までの距離を求める処理について解説します。

Dim distanceUri = String.Format("http://distance.search.olp.yahooapis.jp/OpenLocalPlatform/V1/distance?coordinates={0}&appid={1}", TotalCoordinate, YahooAppID)

と指定して、Yahooの「2点間距離API」を使用しています。このAPIについては下記のURLを参照してください。
> 2点間距離API

引数のcoordinatesに変数TotalCoordinateの値を、appidにヤフーより取得したIDを指定しています。ヤフーのIDは下記のURLより取得してください。
> アプリケーションIDを登録する

新しいHttpClientのインスタンスmyHttpClient3オブジェクトを作成します。GetStringAsyncメソッドでdistanceUriから返される応答本体を文字列として受け取り、resultResponse変数に格納します。
返される結果XMLには不要な名前空間等が含まれていますので、Replace関数で手っ取り早く、名前空間を削除し、というルート要素を作成します。
XElement.ParseメソッドでresultResponseデータを文字列として読み取り、resultXmldocで参照します。
要素の値を取得して、変数distanceに格納します。distanceTextBlockに距離を表示し、距離を読み上げるsyokoVoiceタスクを実行します。
非同期処理で行われるため、メソッドの先頭にAsyncを追加します。

  Private Async Sub OkButton3_Click(sender As Object, e As RoutedEventArgs) Handles OkButton3.Click
 
    If arriveTextBox.Text = String.Empty Then
      Dim message As New MessageDialog("到着点を入力してください。")
      Await message.ShowAsync
      Exit Sub
    End If
    myArriveLatitude = String.Empty
    myArriveLongitude = String.Empty
    Dim arrivedMyUri As String = String.Format("http://www.geocoding.jp/api/?v=1.1&q={0}", Uri.EscapeDataString(arriveTextBox.Text))
 
    Dim myHttpClient2 As New HttpClient
    Dim myArriveResponse = Await myHttpClient2.GetStringAsync(New Uri(arrivedMyUri, UriKind.Absolute))
    
    Dim arriveXmldoc As XElement = XElement.Parse(myArriveResponse)
    myArriveLatitude = arriveXmldoc.Descendants("coordinate").Elements("lat").Value
    myArriveLongitude = arriveXmldoc.Descendants("coordinate").Elements("lng").Value
    
    Dim TotalCoordinate = myStartLongitude & "," & myStartLatitude & " " & myMiddleStartLongitude & "," & myMiddleStartLatitude & " " & myArriveLongitude & "," & myArriveLatitude

    Dim myPin2 As New Pushpin
    myPin2.Background = New SolidColorBrush(Colors.Navy)
    Dim endLocation = New Location(CDbl(myArriveLatitude), CDbl(myArriveLongitude))
 
    Dim myArriveStackPanel As New StackPanel
    With myArriveStackPanel
      .Margin = New Thickness(5)
      .Background = New SolidColorBrush(Colors.Pink)
    End With
  
    Dim arriveTextBlock As New TextBlock
    With arriveTextBlock
      .FontSize = 20
      .Padding = New Thickness(5)
      .Text = "【到着点】=" & arriveTextBox.Text
      .Foreground = New SolidColorBrush(Colors.Navy)
    End With
 
    myArriveStackPanel.Children.Add(arriveTextBlock)
    MapLayer.SetPosition(myArriveStackPanel, endLocation)
    myMap.Children.Add(myArriveStackPanel)
 
    MapLayer.SetPosition(myPin2, endLocation)
    myMap.Children.Add(myPin2)
 
    myMap.SetView(endLocation, 6)
    myMapPolyline2 = New MapPolyline
 
    Dim myShapeLayer As New MapShapeLayer
    myMapPolyline2.Color = Colors.Red
    myMapPolyline2.Locations.Add(middleLocation)
    myMapPolyline2.Locations.Add(endLocation)
    
    myMapPolyline2.Width = 4
    myShapeLayer.Shapes.Add(myMapPolyline2)
    myMap.ShapeLayers.Add(myShapeLayer)
 
    Dim distanceUri = String.Format("http://distance.search.olp.yahooapis.jp/OpenLocalPlatform/V1/distance?coordinates={0}&appid={1}", TotalCoordinate, YahooAppID)
  
    Dim myHttpClient3 As New HttpClient
    Dim resultResponse = Await myHttpClient3.GetStringAsync(New Uri(distanceUri, UriKind.Absolute))
 
    resultResponse = resultResponse.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 resultXmldoc As XElement = XElement.Parse(resultResponse)
    Dim distance = resultXmldoc.Descendants("Distance").Value
    OkButton3.IsEnabled = False
    distanceTextBlock.Text = "出発点から到着点までは、直線的に約" & distance & "Kmあります。"
    readingText = distanceTextBlock.Text
    Await syokoVoice()
  End Sub

[クリア]アイコンがタップされた時の処理

引かれていた直線をクリアし、すべてを最初に読み込まれた状態にします。但し、各入力ボックスには、最後に入力された住所が残ったままになりますので、各自が調べたい住所を入力してください。

  Private Sub deleteButton_Click(sender As Object, e As RoutedEventArgs) Handles deleteButton.Click
    If myMapPolyline Is Nothing = False Then myMapPolyline.Locations.Clear()
    If myMapPolyline2 Is Nothing = False Then myMapPolyline2.Locations.Clear()
    firstLocation = Nothing
    middleLocation = Nothing
    endLocation = Nothing
    myMap.Children.Clear()
 
    distanceTextBlock.Text = String.Empty
    OkButton.IsEnabled = True
    OkButton2.IsEnabled = False
    OkButton3.IsEnabled = False
    Exit Sub
  End Sub

結果をキャラクタが音声で喋る処理

MediaElement型のmyMedia変数を宣言し、MediaElement1で初期化しておきます。
音声機能へのアクセスを提供する、新しいSpeechSynthesizerのインスタンス、synthオブジェクトを作成します。
SynthesizeTextToStreamAsyncメソッドで、指定した文字列から、音声出力を非同期に生成します。
SetSourceメソッドで、指定されたストリームおよびMIME型を使用してSourceプロパティを設定します。Playメソッドで音声を再生します。
音声にどんな言語で、どのような声で喋らすかは、SpeechSynthesizerのVoiceプロパティで参照できます。下記のURLを参照してください。
> SpeechSynthesizer.Voice | voice property

上記URLを見るとJapanese JA は性別が「Female」で、名前は「Haruka」という女性が読み上げるようです。
非同期処理で行われるため、メソッドの先頭にAsyncを追加します。

  Private Async Function syokoVoice() As Task
    Dim myMedia As MediaElement = Me.MediaElement1
    Dim synth = New Windows.Media.SpeechSynthesis.SpeechSynthesizer
    Dim stream = Await synth.SynthesizeTextToStreamAsync(readingText)
    myMedia.SetSource(stream, stream.ContentType)
    myMedia.Play()
  End Function
End Class

今回はここまでです。それでは、また次回の記事でお会いしましょう。

  • 目的地までの距離を計算してキャラクターが音声で教えてくれるアプリ

    『Windows 8.1+Visual Studio 2013によるWindows ストア・アプリ開発実例集』 第3回のサンプルプログラムです。
薬師寺国安事務所

薬師寺国安事務所代表。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メルマガ会員のサービス内容を見る

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