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

次に、ソリューション・エクスプローラー内の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で参照します。
非同期処理で行われるため、メソッドの先頭に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回のサンプルプログラムです。


