Leap Motion実用サンプル(Visual Basic編)
Leap MotionでBing Mapsを扱う
リスト内に表示された住所項目をLeap Motionによりタッチすることで、Web上のサービス「Bing Maps」での地図検索を行うサンプル・アプリを作ってみよう。
今回はWPFでBing Mapsを扱い、Leap Motionで空中タッチしたリスト内の住所に、ズームインしてピンを立てるアプリだ。さっそく作成手順を説明していこう。
まずWPFプロジェクトを作成しよう
今回のLeap MotionアプリもWPFで作成する。
これには、Visual Studio 2012(以下、VS 2012)のIDEを起動して、メニューバーから[ファイル]-[新規作成]-[プロジェクト]と選択して、それにより表示される[新しいプロジェクト]ダイアログで「Visual Basic」のテンプレートから「WPF アプリケーション」を選択する。[名前]欄には、ここでは「BingMaps_LeapMotion」と指定する。
WPFの基本的な作成手順は、第1回と同じ手順となるので、説明を割愛する。具体的な手順は、第1回の「参照の追加」「プロジェクトのルートに「LeapCSharp.dll」と「Leapd.dll」を追加する」「プロパティを設定する」を参考にしてほしい。
NuGetパッケージの管理
[ソリューション エクスプローラー]内の[参照設定]項目の右クリック・メニューにある[NuGet パッケージの管理]から、今回は、ちょっと多くのコンポーネントをインストールする必要がある。間違えないように、インストールしてほしい。
Bing Maps WPF Controlをインストールする
[NuGet パッケージの管理]ダイアログの検索欄に「Bing Maps WPF」と入力すると、「Bing Maps WPF Control」が表示されるので、[インストール]ボタンをクリックする。筆者の場合はすでにインストール済みであるため、次の画面例では、「インストール済み」を示す緑色のチェックが表示されている。
Bing Maps WPF Controlを[参照の追加]する
[参照の追加]画面で、[参照]ボタンをクリックして、プロジェクトの物理フォルダーと同じ場所に作成された「packages\BingMapsWpfControl.1.0.0\lib\net40-client」フォルダー内にある「Microsoft.Maps.MapControl.WPF.dll」ファイルを指定して追加する。これにより[ソリューション エクスプローラー]内の[参照設定]項目内に「Microsoft.Maps.MapControl.WPF」アセンブリが追加されているのを確認する。
[ツールボックス]にBing Maps WPF Controlのコントロールを追加する
[ソリューション エクスプローラー]上で「MainWindow.xaml」ファイルを選択した状態で、[ツールボックス]の[すべての WPF コントロール]上で右クリックすると表示されるコンテキスト・メニューの[アイテムの選択]をクリックする。[ツールボックス アイテムの選択]ダイアログに[WPF コンポーネント]の一覧が表示される。[参照]ボタンで、再び「packages\BingMapsWpfControl.1.0.0\lib\net40-client」フォルダー内にある「Microsoft.Maps.MapControl.WPF.dll」ファイルを指定する。[OK]ボタンをクリックしてダイアログを閉じると、[ツールボックス]に次の画面のようにMap関連のコントロールが追加される。
Microsoft HTTP Client Librariesをインストールする
WPFではデフォルトではHttpClientクラス(System.Net.Http名前空間)が使用できない。そこで、NuGetからMicrosoft HTTP Client Librariesをインストールする。
[NuGet パッケージの管理]ダイアログの検索欄に「Microsoft HTTP Client」と入力すると、「Microsoft HTTP Client Libraries」が表示されるので、[インストール]をクリックする。筆者の場合はすでにインストール済みであるため、次の画面のように緑色のチェックが表示されている。
Microsoft ASP.NET Web API Client Librariesをインストールする
これも同様の手順で「Microsoft ASP.NET Web API」を検索し、「Microsoft ASP.NET Web API Client Libraries」をインストールする。これについても筆者の場合はインストール済みのため、緑色のチェックが表示されている(次の画面を参照)。
以上でNuGetによるコンポーネントのインストールは完了だ。
XMLファイルの追加
リスト表示される住所一覧のデータはXMLファイルで用意しておく。
VS 2012のメニューバーから[プロジェクト]-[新しい項目の追加]と選択する。これにより[新しい項目の追加]ダイアログが表示されるので、左側で[データ]を選択して、右側から[XML ファイル]テンプレートを選択し、[名前]欄に「address.xml」と指定して、実際にファイルを追加する。
XMLエディターが起動するので、リスト1の内容のXMLコードを記述する。なお、読者自身でさらに追加したい住所があれば、このXMLファイルに住所を追加してほしい。
<?xml version="1.0" encoding="utf-8" ?> <住所> <住所名>愛媛県松山市道後緑台</住所名> <住所名>京都市伏見区深草塚本町</住所名> <住所名>愛媛県宇和島市和霊元町</住所名> <住所名>東京都千代田区</住所名> <住所名>北海道札幌市</住所名> <住所名>神奈川県川崎市</住所名> <住所名>神戸市西区</住所名> <住所名>東京都豊島区</住所名> <住所名>栃木県宇都宮市</住所名> <住所名>島根県松江市</住所名> <住所名>京都市右京区</住所名> </住所> |
address.xmlファイルは、.EXEファイルと同じフォルダーに常に配置したいので、[ソリューション エクスプローラー]で「address.xml」項目の右クリック・メニューで[プロパティ]を実行し、[プロパティ]ウィンドウの[ビルド アクション]の値を「コンテンツ」に、また[出力ディレクトリにコピー]の値を「常にコピーする」に変更しておこう。
Bing Maps キーの取得
Bing Mapsを使用するためには、「Bing Maps Account Center」より取得したKeyが必要だ。Keyを持っていない場合は、こちらから取得しておこう。
今回のLeap Motionアプリについて
冒頭でも説明したが、今回のアプリは、リストボックスに表示させた住所を選択して空中タッチすると、Bing Maps上の、その住所の位置にズームインするアプリだ(次の画面を参照)。[+]/[-]ボタンでBing Mapsの拡大/縮小も可能だ。
画面のレイアウト(MainWindow.xaml)
デフォルトで配置されているGridコントロールをCanvasコントロールに変えておく。Canvasコントロールの方が、座標値が取得しやすいからだ。
デザイン画面上に、「myMap」という名前のMapコントロールを1個、「ListBox1」という名前のListBoxコントロールを1個、StackPanelコントロールを1個配置する。そして、StackPanelコントロールの子要素としてButtonコントロールを2個配置し、それぞれの名前を「plusButton」「minusButton」とする。さらに最前面に「paintCanvas」という名前のInkPresenterコントロールを配置する。
詳しいレイアウト内容は次の画面を参考にしてほしい。
下記の4つの手順で5つのコントロールを配置する。
- 1「ListBox1」リストボックスを配置する。
- 2その右隣りにMapコントロール「myMap」を配置する。
- 3「myMap」内に[+]ボタンと[-]ボタンを配置する(このボタンは、地図を拡大・縮小するためのもの)。
- 4一番手前にInkPresenterコントロールを配置する。
なお、実際に[ツールボックス]からMapコントロールを配置すると、自動的にXAMLコード内に「xmlns:WPF="clr-namespace:Microsoft.Maps.MapControl.WPF;assembly=Microsoft.Maps.MapControl.WPF"」という名前空間が追加される。
上記のレイアウトを行った結果、書き出されるXAMLコードは次のとおり。
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:WPF="clr-namespace:Microsoft.Maps.MapControl.WPF;assembly=Microsoft.Maps.MapControl.WPF" x:Class="MainWindow" Title="MainWindow" Height="1080" Width="1920" WindowState="Maximized"> <Canvas>
<WPF:Map Height="651" Canvas.Left="741" Canvas.Top="43" Width="1161" CredentialsProvider="<Bing Maps Centerで取得した各自のキー>" x:Name="myMap" AnimationLevel="Full"> <WPF:Map.Effect> <DropShadowEffect ShadowDepth="10"/> </WPF:Map.Effect> </WPF:Map>
<ListBox Height="875" Canvas.Left="23" Canvas.Top="43" Width="702" x:Name="ListBox1" FontSize="36"/>
<StackPanel Height="147" Canvas.Left="760" Canvas.Top="128" Width="115"> <Button x:Name="plusButton" Content="+" FontSize="25" Width="80" Height="61" Margin="18,5,17,5"/> <Button x:Name="minusButton" Content="-" FontSize="25" Width="80" Height="61" Margin="18,5,17,5"/> </StackPanel>
<InkPresenter x:Name="paintCanvas" />
</Canvas> </Window> |
上記のコードでMapコントロールのCredentialsProviderプロパティには「Bing Maps Account Center」より取得したKeyを記述する。Keyを記述しないとBing Mapsは動作しないので、注意してほしい。
MapコントロールのAnimationLevelプロパティに「Full」を指定しておくと、選択した住所にズームインする場合、アニメーションを伴って表示される。
また、MapコントロールのEffectプロパティに「DropShadowEffect」を指定して、地図に陰影を付けている。陰影の深さは、ShadowDepthプロパティで指定できる。ここでは「10」を指定している。
プログラム・コード(MainWindow.xaml.vb)
では、次にプログラム・コード(MainWindows.xaml.vbファイル)を見ていこう。
プログラム・コードも、タッチ処理以外は第1回と基本的に同じ内容となるので、説明を割愛する。まずは第1回の「名前空間の読み込み」「メンバー変数の宣言」「Updateメソッドの処理」の開発手順を参考にタッチ処理の前までを実装してほしい。相違点として、下記の9点を修正してほしい。
まず名前空間の宣言
(1)WPFでBing Mapsを扱うために、Microsoft.Maps.MapControl.WPF名前空間を読み込む
(2)次に、WPFでHttpClientクラスを使用するため、System.Net.Http名前空間を読み込んでおく
次はメンバー変数の宣言
(3)今回はWin32 APIを使用する。リストボックスに表示された住所を選択する場合に必要となる。次のリストのようにSetCursorPos関数とapimouse_event関数のAPIを使用する
Private Declare Function SetCursorPos Lib "user32" (x As Integer, y As Integer) As Boolean Private Declare Function apimouse_event Lib "user32" Alias "mouse_event" (ByVal dwFlags As Int32, ByVal dx As Int32, ByVal dy As Int32, ByVal cButtons As Int32, ByVal dwExtraInfo As Int32) As Boolean Private Const MOUSEEVENTF_LEFTDOWN = &H2 Private Const MOUSEEVENTF_LEFTUP = &H4 |
(4)XML要素を表すXElementクラス型のメンバー変数「Private xmldoc As XElement」を宣言する
(5)文字列型のリストである「Private AddressList As New List(Of String)」を宣言する
(6)リストボックスで選択された住所を格納するメンバー変数「Private selectAddress As String」を宣言する
(7)緯度を格納するメンバー変数「Private myAddressLatitude As String」を宣言する
(8)経度を格納するメンバー変数「Private myAddressLongitude As String」を宣言する
(9)「Private Message As String」と「Private Index As Integer」は削除する
MainWindow_Loadedメソッドの処理
XElement.Loadメソッドでaddress.xmlファイルを読み込む。Descendantsメソッドで、読み込んだXMLデータにおける全ての子孫要素「<住所名>」の内容を、変数「result」に格納しながら、AddメソッドでAddressListオブジェクトに<住所名>要素の内容を追加していく。ListBox1コントロールのItemsSourceプロパティにAddressListオブジェクトを指定すると、リストボックスに住所の一覧が表示される。
AddHandlerステートメントを使って、構成ツリーのオブジェクトがレンダリングされる直前に発生するCompositionTarget.RenderingイベントにUpdateイベント・ハンドラを追加する。
インク・ストローク(=System.Windows.Ink名前空間のStrokeクラスで表現される、WPF上でのインクの線)の外観を指定するDrawingAttributessクラス(System.Windows.Ink名前空間)のインスタンス「touchIndicatorメンバー変数」のWidthプロパティとHeightプロパティにそれぞれ「15」を指定する。スタイラスの形状を指定するStylusTipプロパティに「Ellipse」を指定して円形とする。これらの指定により、Leap Motionの上で指をかざすと、かざした指の本数に応じて15pxの円が表示される。
具体的には以下のようなコードになる。
Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
xmldoc = XElement.Load("address.xml")
' <住所名>要素の内容をAddressListオブジェクトに追加していく For Each result In From c In xmldoc.Descendants("住所名") Select c AddressList.Add(result.Value) Next
' <住所名>要素の内容を保持しているAddressListオブジェクトを、ListBoxの ' ItemsSourceプロパティに指定する ListBox1.ItemsSource = AddressList
AddHandler CompositionTarget.Rendering, AddressOf Update touchIndicator.Width = 15 touchIndicator.Height = 15
' スタイラスの形状に直径15pixcelの円を指定する touchIndicator.StylusTip = StylusTip.Ellipse
End Sub |
Updateメソッドの処理(タッチ処理部分)
第1回でも説明したが、Leap Motionのタッチ処理について簡単に説明しておこう。
次の図に示すように、Leap Motionでは手前側が「ホバー状態(hovering)」、奥側が「タッチ状態(touching)」を表す。空間の範囲は前後「1」~「-1」となっている。
まずホバーの場合は、表示されている円の色がBlueになり、タッチ・ポイントのX/Y座標をメンバー変数「x」/「y」に格納する。
Win32 APIの「apimouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0)」関数を実行して、マウスの左ボタンを離した際のイベントを発行し、そのイベント処理を実行させる。
そして、画面に表示され指の個数を取得して、メンバー変数「FingersCount」に格納しておく。
具体的には下記のコードのようになる。
For Each Pointable As Pointable In leap.Frame.Pointables ……コード略(前述)…… If Pointable.TouchDistance > 0 AndAlso Pointable.TouchZone <> Global.Leap.Pointable.Zone.ZONENONE Then touchIndicator.Color = Colors.Blue x = touchPoint.X y = touchPoint.Y ' Win32 APIを実行する apimouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0) FingersCount = leap.Frame.Fingers.Count ……コード略(続きは後述)…… End If Next |
次にタッチした際の処理になる。タッチした場合は、表示されている円の色がRedに変わる。
画面上に指が1本表示されている場合はWin32 APIの「SetCursorPos(x,y)」関数を実行して、指定した座標にマウス・カーソルを移動し、マウス・クリックをシミュレートする「apimouse_event(MOUSEEVENTF_LEFTDOWN,0, 0, 0, 0)」関数を実行する。これにより、画面に表示されるLeap Motionのタッチ・ポイントとマウス・カーソルが同じ位置に表示されるようになる。
このコードは次のようになる。
If Pointable.TouchDistance > 0 AndAlso Pointable.TouchZone <> Global.Leap.Pointable.Zone.ZONENONE Then ……コード略(前述)…… ElseIf Pointable.TouchDistance <= 0 Then touchIndicator.Color = Colors.Red If FingersCount = 1 Then ' Win32 APIを実行する SetCursorPos(x, y) apimouse_event(MOUSEEVENTF_LEFTDOWN,0, 0, 0, 0) End If
' タッチ対象外 Else touchIndicator.Color = Colors.Gold End If |
次にリストボックスに表示されている住所が選択されたときの処理を行う。
ListBox1コントロールのSelectionChangedイベント・ハンドラを追加して、そのメソッド内に以下を実装する。
HttpClientクラスのインスタンス「clientオブジェクト」を作成する。また、PushPinクラスのインスタンス「myPinオブジェクト」を作成する。
リストボックスで選択中のインデックスに該当する<住所名>要素の値を取得して、メンバー変数「selectAddress」に格納する(=住所の選択)。
「GeocodingのAPI」を使用して、選択した住所のリクエストを送る。具体的には、HttpClientオブジェクトのGetStringAsyncメソッドで指定したURIにGET要求を送信し、非同期操作で応答本体を文字列として取得する。
取得した文字列はXML形式になっているので、XElement.Parseメソッドで文字列としてXMLデータを読み込む。
読み込んだXMLデータから緯度の値を取得してメンバー変数「myAddressLatitude」に格納しておく。同様に、経度の値も取得して、メンバー変数「myAddressLongitude」に格納しておく。
Labelコントロールのインスタンス「myLabeオブジェクト」を作成し、そのContentプロパティに、選択した住所を格納しているselectAddress変数の値を指定する。さらに、文字色に「Navy」、背景色に「Beige」を指定し、文字サイズを「20」とする。
Locationクラス(Microsoft.Maps.MapControl.WPF名前空間)のインスタンス「myLocationオブジェクト」を作成する。そのLatitudeプロパティとLongitudeプロパティに、メンバー変数「myAddressLatitude」と「myAddressLongitude」の値をそれぞれ指定する。myPinオブジェクトのLocationプロパティに、myLocationオブジェクトを指定し、さらに「MapLayer.SetPosition(myLabel, myLocation)」メソッドを呼び出すと、該当する位置にピンが立ち、選択した住所名が表示される。
最後に、Mapコントロール「myMap」のSetViewメソッドで目的地にズームインする。myMapにAddメソッドでピンを追加し、myLabelオブジェクト(=住所名)を追加する。
以上の実装内容で次のようなコードになる。
Private Sub ListBox1_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles ListBox1.SelectionChanged
Dim client As New HttpClient Dim myPin As New Pushpin
' リストボックスのSelectedIndexに該当する<住所名>を取得する selectAddress = xmldoc.Descendants("住所名")(ListBox1.SelectedIndex).Value
' GeoCoodingのAPIを使用する Dim addressUri = String.Format("http://www.geocoding.jp/api/?v=1.1&q={0}", selectAddress) Dim myResponse = client.GetStringAsync(New Uri(addressUri, UriKind.Absolute)) Dim myContent = myResponse.Result
' 結果XMLをParseメソッドで文字列として読み込む Dim doc = XElement.Parse(myContent)
' 緯度と経度を取得する myAddressLatitude = doc.Descendants("coordinate").Elements("lat").Value myAddressLongitude = doc.Descendants("coordinate").Elements("lng").Value
' Labelを設定する Dim myLabel As New Label With myLabel .Content = selectAddress .Foreground = New SolidColorBrush(Colors.Navy) .Background = New SolidColorBrush(Colors.Beige) .FontSize = 20 End With
Dim myLocation As New Location
' Locationに緯度と経度を指定する myLocation.Latitude = myAddressLatitude myLocation.Longitude = myAddressLongitude myPin.Location = myLocation
' 住所名を指定した位置にセットする MapLayer.SetPosition(myLabel, myLocation)
' 指定した位置にズームインする myMap.SetView(myLocation, 16)
' 地図にピンと住所名を表示する myMap.Children.Add(myPin) myMap.Children.Add(myLabel)
End Sub |
もうひとつ、[+]ボタンと[-]ボタンがタップされたときの処理も実装しておこう。
[+]ボタン・クリックで地図を拡大させ、[-]ボタン・クリックで地図を縮小させるので、下記のようなコードを記述すればよい。
Private Sub plusButton_Click(sender As Object, e As RoutedEventArgs) Handles plusButton.Click myMap.ZoomLevel += 1 End Sub
Private Sub minusButton_Click(sender As Object, e As RoutedEventArgs) Handles minusButton.Click myMap.ZoomLevel -= 1 End Sub |
以上で完成だ。このサンプルのコードは、下記のリンク先よりダウンロードできる*1。
- *1サンプルをダウンロードして動かす場合は、「LeapCSharp.NET4.0.dll」や「LeapCSharp.dll」、「Leap.dll」を読者自身のフォルダー内にあるDLLファイルに指定し直さなければ動かない可能性があるので、動かない場合は再指定していただきたい。
■
今回はこれで終わりだ。WPFでもBing Mapsを扱えるし、HttpClientクラスも使用できる。
Win32 APIとLeap Motionを絡めれば、今回のようなアプリでも、意外と簡単に作成できることがお分かりになったのではないかと思う。いろいろなアイデアで、ぜひ、独創的なアプリ作成に挑戦していただきたい。
では、また次回の記事でお会いしよう。
※以下では、本稿の前後を合わせて5回分(第3回~第7回)のみ表示しています。
連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
3. Leap Motionによる、WPFアプリ上に動的に作成したオブジェクトのイベント処理
Leap Motionの動くサンプルを実際に作ってみる。今回はWPFの画面上に動的に作成したオブジェクトを、Leap Motionによる操作でアニメーションさせるアプリを作成。
4. Leap Motionでパーティクルを使用して軌跡に無数の円や画像を表示する
さまざまな色の粒子(パーティクル)が、Leap Motionによる手の指の動きに合わせて飛び散りながら追従するサンプル・アプリを作ってみよう。
5. 【現在、表示中】≫ Leap MotionでBing Mapsを扱う
リスト内に表示された住所項目をLeap Motionによりタッチすることで、Web上のサービス「Bing Maps」での地図検索を行うサンプル・アプリを作ってみよう。
7. 3D回転で表示される画像をLeap Motionで切り替える
画像が立体的に回転して表示され、Leap Motionで任意の画像を空中タッチすると、アニメーションを伴ってその画像が大きく表示されるサンプルWPFアプリを作ってみよう。