フリーハンドで書いた住所を認識してBing Map上に表示する

2012年11月20日(火)
薬師寺 国安

Bing Maps Keyの取得方法

Bing Mapsを使用するには下記URLのBing Maps Account Centerに行って専用のライセンスキーを取得する必要があります。
→ Bing Maps Account Center

Windows Live IDを持っている方はSign Inします。持ってない方はCreateからWindows Live IDを作成してSign Inしてください。筆者はSign Inから入ります(図11)。

図11:Bing Maps Account Center でSign Inする(クリックして拡大)

表示される画面の左にあるCreate or view keysをクリックします(図12)。

図12:Create or view keysをクリックする(クリックして拡大)

Create keyの画面が表示されますので、必要な項目を入力してSubmitしてください。筆者はすでにキーを持っていますので、下記にキーが表示されています(図13)。Key typeはBasicとなっています。BasicでPublic websiteの場合は、「アプリケーションが制限なしに利用され、500,000 のトランザクションの任意の種類の 12 ヶ月の期間内を超えない、公開ウェブサイトです。」となっているようです。

図13:筆者のApplication Keyが表示されている(クリックして拡大)

リスト2のレイアウトは図14のようになります。

図14:各コントロールを配置した(クリックして拡大)

次に、ソリューションエクスプローラー内のMainWindow.xamlを展開して表示される、MainWindow.xaml.vbをダブルクリックしてリスト3のコードを記述します。前回の「フリーハンドの文字を認識する」と同じコードと解説は省略しています。

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

リスト3 (MainWindow.xaml.vb)

~コード略~

MapPosition構造体で、文字列型のmyLatitude、myLogitude、myAddressを定義しておきます。

Public Structure MapPosition
  Public myLatitude As String
  Public myLogitude As String
  Public myAddress As String
End Structure

MapPosition構造体のメンバ変数myMapPositionを宣言します。

Dim myMapPosition As MapPosition

[住所検索]ボタンをクリックした時の処理

[戻る]ボタンの使用を可能にします。[クリア]ボタンの使用を不可とします。Frameを表示します。

InkManagerのGetStrokesメソッドで、InkManager を管理するコレクション内のすべての InkStroke オブジェクトを取得し、変数myStrokeに格納します。InkStrokeのコレクションの数だけ繰り返し処理を行います。すべてのInkStrokeを選択状態にします。

文字列変数myRecNameを「Microsoft 日本語手書き認識エンジン」と初期化しておきます。

英語の認識エンジンを使用する場合は「Microsoft English (US) Handwriting Recognizer」と指定します。

InkManagerのGetRecognizersメソッドで、手書き認識エンジンのコレクションを取得します。

取得したコレクションの数だけ反復処理を行います。変数myRecNameの値が、手書き認識エンジンのコレクションのNameと一致する場合は、その手書き認識エンジンを、SetDefaultRecognizerメソッドで規定値として設定します。「Microsoft 日本語手書き認識エンジン」が規定値に設定されます。

RecognizeAsyncメソッドで一つ、または複数のInkStrokeオブジェクトの手書き文字認識を実行します。InkRecognitionTarget.Allで、ストロークコレクション内のすべてのストロークを認識エンジンに渡します。UpdateRecognitionResultsメソッドで、潜在的なテキストのコレクションが、手書きの認識からマッチするよう更新します。

認識された手書きの結果テキストのコレクション内を反復処理しながら、以下の処理を行います。

InkRecognitionResult.GetTextCandidatesメソッドで、手書き文字認識と一致する可能性のある候補として識別された、文字列のコレクションを取得して、変数myTextに格納します。認識された文字列を非表示となっているTextBoxに表示します。画面上では見えません。

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

非表示となっているTextBoxの値を引数に、文字列としての
String.Format("http://www.geocoding.jp/api/?v=1.1&q={0}", TextBox1.Text)
を作成して変数myUriに格納しておきます。GeocodingのWeb APIを使用しています。Geocodingについては、下記のサイトを参照してください。
Geocoding

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

HttpClientクラスのGetStringAsyncメソッドで、指定したURIにGET送信し、非同期操作で応答本体を文字列として受け取り、変数myResponseに格納します。返された結果XMLをResultプロパティで受け取り、変数myContentに格納しておきます。XElement.Parseメソッドで、文字列として読み込まれたXML(myContent)を読み取ります。

MapPosition構造体のmyLatitudeに要素の子要素の値(緯度)を指定します。myLogitudeに要素の値(経度)を指定します。myAddressに非表示となっているTextBoxの値を指定します(認識された住所)。FrameのNavigateメソッドで、構造体オブジェクトであるmyMapPositionを引数に、BingMapsPageに遷移します。Frameを前面に出します。

Private Async Sub recognizeButton_Click(sender As Object, e As RoutedEventArgs) Handles recognizeButton.Click
backButton.IsEnabled = True
  clearButton.IsEnabled = False
  myFrame.Visibility = Xaml.Visibility.Visible
  Dim myStroke = myInkManager.GetStrokes
  For i As Integer = 0 To myStroke.Count - 1
    myStroke(i).Selected = True
  Next
 
  Dim myRecName = "Microsoft 日本語手書き認識エンジン"
  Dim myRecognizer = myInkManager.GetRecognizers
 
  For i As Integer = 0 To myRecognizer.Count - 1
    If myRecName = myRecognizer(i).Name Then
      myInkManager.SetDefaultRecognizer(myRecognizer(i))
    End If
  Next
 
  Dim result As IReadOnlyList(Of InkRecognitionResult) = Await myInkManager.RecognizeAsync(InkRecognitionTarget.All)
  myInkManager.UpdateRecognitionResults(result)
 
  Dim myAlternate = String.Empty
  For Each myResult In result
    Dim myText = myResult.GetTextCandidates
    myAlternate = myAlternate & " " & myText(0)
    TextBox1.Text = myAlternate
  Next
 
  Dim myUri As String = String.Format("http://www.geocoding.jp/api/?v=1.1&q={0}", TextBox1.Text)
 
  Dim myHttpClient As New HttpClient
  Dim myResponse = myHttpClient.GetStringAsync(New Uri(myUri, UriKind.Absolute))
  Dim myContent = myResponse.Result
 
  Dim xmldoc As XElement = XElement.Parse(myContent)
  myMapPosition.myLatitude = xmldoc.Descendants("coordinate").Elements("lat").Value
  myMapPosition.myLogitude = xmldoc.Descendants("coordinate").Elements("lng").Value
  myMapPosition.myAddress = TextBox1.Text
 
  myFrame.Navigate(GetType(BingMapsPage), myMapPosition)
  myFrame.SetValue(Canvas.ZIndexProperty, 10)
End Sub

[戻る]ボタンがクリックされた時の処理

Frameを非表示にします。[クリア]ボタンの使用を可能にします。Frameを背面に移動します。

  Private Sub backButton_Click(sender As Object, e As RoutedEventArgs) Handles backButton.Click
    myFrame.Visibility = Xaml.Visibility.Collapsed
    clearButton.IsEnabled = True
    myFrame.SetValue(Canvas.ZIndexProperty, 0)
  End Sub

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

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

リスト4 (BingMapsPage.xaml.vb)

Option Strict On

Bing Mapsに関するクラスの含まれるBing.Maps名前空間をインポートします。

Imports Bing.Maps

コア システムの機能とその UI についてのランタイム情報にアクセスするアプリケーションを提供するクラスの含まれる、Windows.UI名前空間をインポートします。描く円の色を指定する場合等に、この名前空間に含まれるクラスを使用します。

Imports Windows.UI

図形に関するクラスの含まれる、Windows.UI.Xaml.Shapes名前空間をインポートします。

Imports Windows.UI.Xaml.Shapes

Public NotInheritable Class BingMapsPage

  Inherits Page

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

MainMageから送られた値(e.Parameter)をMapPosition構造体に変換し、myResultで参照します。

myLatitude変数に構造体のmyLatitudeの値(緯度)を格納します。myLongitude変数に構造体のmyLongitudeの値(経度)を格納します。myAddress変数に構造体のmyAddressの値(住所)を格納します。

MapのCenterプロパティにDouble型に変換したmyLatitudeとmyLongitudeの値を指定します。addressTextBoxに住所を表示します。地図のタイプをBirdseyeに指定しておきます。Birdseyeとは読んで字のごとく鳥の目線で見た地図のモードといったところでしょうか。

新しいEllipseのインスタンスmyEllipseオブジェクトを作成します。FillプロパティにCrimsonを指定し、Crimson色で塗りつぶします。枠線の太さを3に指定し、枠線の色をNavyに指定します。WidthとHeightは30とします。直径30pixelの円が描かれます。

新しいStackPanelのインスタンスmyStackpanelオブジェクトを作成します。Marginに5を指定して余白を設けます。背景色にNavyを指定します。初期状態ではmyStackPanelオブジェクトを非表示としておきます。

次に、TextBlockの新しいインスタンスmyTextBlockオブジェクトを作成します。文字色をRedに指定します。文字サイズは24、TextプロパティにはmyAddress変数の値を指定します。myStackPanelオブジェクトにmyTextBlockオブジェクトを追加します。

MapLayerクラスのSetPositionメソッドで、マップレイヤー内に要素の位置を設定します。この場合、myLatitudeとmyLongitudeの位置に円をセットします。MapLayerクラスは、地図上の要素の位置を保持しているマップレイヤーを表すクラスです。MapにAddメソッドでmyEllipseオブジェクトを追加します。

同様に、MapLayerクラスのSetPositionメソッドで、マップレイヤー内に要素の位置を設定します。この場合、myLatitudeとmyLongitudeの位置にStackPanelをセットします。MapにAddメソッドでmyStackPanelオブジェクトを追加します。

円がタップされた時はmyStackPanelオブジェクトを表示します。住所の書かれたTextBlockが表示されます。myStackPanelオブジェクトがタップされた時は、myStackPanelオブジェクトを非表示にします。住所の表示されたTextBlockも非表示になります。

  Protected Overrides Sub OnNavigatedTo(e As Navigation.NavigationEventArgs)
    Dim myResult = DirectCast(e.Parameter, MapPosition)
    Dim myLatitude = myResult.myLatitude
    Dim myLongitude = myResult.myLogitude
    Dim myAddress = myResult.myAddress
 
    myMap.Center = New Location(CDbl(myLatitude), CDbl(myLongitude))
    addressTextBox.Text = myAddress
 
    myMap.MapType = MapType.Birdseye
    Dim myEllipse As New Ellipse
    With myEllipse
      .Fill = New SolidColorBrush(Colors.Crimson)
      .StrokeThickness = 3
      .Stroke = New SolidColorBrush(Colors.Navy)
      .Width = 30
      .Height = 30
    End With
    Dim myStackPanel As New StackPanel
    myStackPanel.Margin = New Thickness(5)
    myStackPanel.Background = New SolidColorBrush(Colors.Navy)
    myStackPanel.Visibility = Xaml.Visibility.Collapsed
    Dim myTextBlock As New TextBlock
    myTextBlock.Foreground = New SolidColorBrush(Colors.Red)
    myTextBlock.FontSize = 24
    myTextBlock.Text = myAddress
    myStackPanel.Children.Add(myTextBlock)
 
    MapLayer.SetPosition(myEllipse, New Location(CDbl(myLatitude), CDbl(myLongitude)))
    myMap.Children.Add(myEllipse)
 
    MapLayer.SetPosition(myStackPanel, New Location(CDbl(myLatitude), CDbl(myLongitude)))
    myMap.Children.Add(myStackPanel)
 
    AddHandler myEllipse.Tapped, Sub()
                         myStackPanel.Visibility = Xaml.Visibility.Visible
                         End Sub
 
    AddHandler myStackPanel.Tapped, Sub()
                         myStackPanel.Visibility = Xaml.Visibility.Collapsed
                         End Sub
  End Sub
End Class

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

  • フリーハンドで住所を書いてBing Map上に表示するサンプルアプリ

薬師寺国安事務所

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

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