ComponentOne Studioの地図コンポーネントを使えば、このような煩雑は不要で「Any CPU」でデバッグ実行が可能です。
Windowsストアアプリの下準備
Windowsストアアプリのテンプレートを使って新規プロジェクトを作成すると、ツールボックスには「C1 XAML 8.1 Controls」タブがすでに作成されていて、Windowsストアアプリで使えるコンポーネントが表示されています。
今回使う地図コンポーネントは「C1Maps」コンポーネントなのでツールボックスにあることを確認したら、Windowsストアアプリの下準備を開始しましょう。
プロジェクト構成の準備
今回作成するWindowsストアアプリはModel、ViewModel、Viewのクラス構成をとり、Modelにロジック、Viewに画面デザイン、その両者をViewModelで接続することで画面定義とロジックコードの結合を疎にして、それぞれの役目が分かりやすくまとまるように考慮しています。
そのため、Windowsストアアプリのテンプレートから新規プロジェクトを作成した直後の状態から、Modelsフォルダ、ViewModelsフォルダ、Viewsフォルダを作成して、それぞれのフォルダに、AEDModelクラス、MainViewModelクラス、MainPage.xamlを配置します。
追加ライブラリの準備
今回使用するオープンデータはREST/JSON形式でデータを取得するので、JSONデータの取り扱いが楽になるようにJSON.NETライブラリをnugetから導入します。
データ取得ロジックの実装
サンプルアプリで使用するオープンデータは、Microsoft Azure Mobile Service上で動作しているAED検索オープンデータを使用します 。
Imports System.Collections.ObjectModel
Imports System.ComponentModel
Imports System.Net
Imports System.Runtime.CompilerServices
Imports Newtonsoft.Json
Namespace Models
Public Class AedInfo
Public Property Id As Long
Public Property LocationName As String '/* 場所_地名【名称】*/
Public Property FullAddress As String '/* 住所_住所【住所】*/
Public Property Perfecture As String '/* 構造化住所_都道府県 */
Public Property City As String '/* 構造化住所_市区町村 */
Public Property AddressArea As String '/* 構造化住所_町名 */
Public Property AddressCode As String '/* 住所コード */
Public Property Latitude As Single '/* 緯度経度座標系_緯度 */
Public Property Longitude As Single '/* 緯度経度座標系_経度 */
Public Property FacilityId As String '/* 公共設備_ID */
Public Property FacilityName As String '/* 公共設備_名称 */
Public Property FacilityPlace As String '/* 公共設備_設置場所【設置場所】※受付横とか */
Public Property ScheduleDayType As String '/* 公共設備_利用可能時間【利用可能時間】 */
Public Property PhotoOfAedUrl As String '/* 公共設備_写真URL【写真】 */
Public Property FacilityNote As String '/* 公共設備_補足【補足】 */
Public Property LatLng As Point
End Class
Public Class TCity
Public Property Perfecture As String '/* 構造化住所_都道府県 */
Public Property City As String '/* 構造化住所_市区町村 */
Private _Items = New ObservableCollection(Of AedInfo)
Public Property Items As ObservableCollection(Of AedInfo)
Get
Return Me._Items
End Get
Set(value As ObservableCollection(Of AedInfo))
Me._Items = value
End Set
End Property
End Class
Public Class AedModel
Implements INotifyPropertyChanged
Public Sub New()
End Sub
Private _City = New TCity
Public Property City As TCity
Get
Return Me._City
End Get
Set(value As TCity)
Me._City = value
OnPropertyChanged()
End Set
End Property
Public Async Function SelectData(perfectureName As String, cityName As String) As Task
Dim targetAeds = New ObservableCollection(Of AedInfo)
Try
Dim urlString = String.Format(
"https://aed.azure-mobile.net/api/aedinfo/{0}/{1}/",
perfectureName,
cityName)
Dim request = CType((WebRequest.Create(urlString)), HttpWebRequest)
request.Method = "GET"
request.ContentType = "application/x-www-form-urlencoded"
Try
Dim response = CType((Await request.GetResponseAsync()), HttpWebResponse)
Dim responseDataStream = New System.IO.StreamReader(response.GetResponseStream())
Dim responseResult = responseDataStream.ReadToEnd()
Dim json = JsonConvert.DeserializeObject(Of IEnumerable(Of AedInfo))(responseResult)
Dim aeds = From item In json Order By item.FullAddress
targetAeds = New ObservableCollection(Of AedInfo)
For Each aed In aeds
aed.LatLng = New Point(aed.Longitude, aed.Latitude)
targetAeds.Add(aed)
Next
Catch ex As Exception
OnFaild("NetworkError" + ex.Message)
End Try
Catch ex As Exception
OnFaild("NetworkError" + ex.Message)
Finally
Me.City = New TCity With {.Perfecture = perfectureName,
.City = cityName, .Items = targetAeds}
End Try
End Function
Public Event Faild(sender As Object, e As String)
Protected Sub OnFaild(line As String)
RaiseEvent Faild(Me, line)
End Sub
Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
Protected Sub OnPropertyChanged(<CallerMemberName> Optional propertyName As String = Nothing)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
End Namespace
C1Mapsコンポーネントの配置
ツールボックスからC1MapsコンポーネントをXAMLエディタ上にドラッグ&ドロップします。これだけで必要な参照設定が行われます。
画面デザイン
サンプル画面の構成は、左に地図、右に一覧表の2カラム構成になっていて、検索ボタンをクリックすると特定市区町村のAED情報を検索表示します。
:
(中略)
:
<Grid Grid.Row="1" Margin="15,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Margin="20,0,20,0">
<Viewbox VerticalAlignment="Top" HorizontalAlignment="Left">
<Custom:C1Maps x:Name="C1Maps1"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Zoom="13"
Width="480" Height="600">
<Custom:C1Maps.Resources>
<DataTemplate x:Key="PinStyle">
<Custom:C1VectorPlacemark
Fill="Red" Stroke="Red"
LabelPosition="Top"
Foreground="Red"
GeoPoint="{Binding LatLng}"
Geometry="M7.4166234,16.197001L13.…(中略)…z">
<Custom:C1VectorPlacemark.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="0.5" ScaleY="0.5"/>
<TranslateTransform Y="-20" X="-10"/>
</TransformGroup>
</Custom:C1VectorPlacemark.RenderTransform>
</Custom:C1VectorPlacemark>
</DataTemplate>
</Custom:C1Maps.Resources>
<Custom:C1Maps.Source>
<Custom:VirtualEarthRoadSource ApplicationId="{x:Null}" />
</Custom:C1Maps.Source>
<Custom:C1VectorLayer ItemsSource="{Binding City.Items}"
ItemTemplate="{StaticResource PinStyle}"
LabelVisibility="Visible"/>
</Custom:C1Maps>
</Viewbox>
</StackPanel>
:
(中略)
:
C1Mapsコンポーネントで緯度経度に合わせてマークを描画するには、
- 緯度経度が含まれたコレクションをItemsSourceプロパティにBindingする
- マーク描画用スタイルをItemTemplateに指定する
- マーク描画用スタイルの中で、C1VectorPlacemarkのGeoPointに緯度経度をBindingする
- マーク自体はC1VectorPlacemarkのGeometryで形を指定する
という手順で行います。
コードビハインド側でループを回して1点1点位置を指定して描画する必要はありません。
検索イベント時のコードの記述
MainPage.xamlのコードビハインド側に検索用のロジックを一部記載します。
Namespace Views
Public NotInheritable Class MainPage
Inherits Page
:
(中略)
:
Public Sub New()
InitializeComponent()
:
(中略)
:
Me.DataContext = App.MainVM
End Sub
Private Async Sub NavigationHelper_LoadState(sender As Object, e As Common.LoadStateEventArgs)
Try
Await App.MainVM.GetItems("愛知県", "豊田市")
SetCenterPos()
Me.C1Maps1.Zoom -= 1
Me.C1Maps1.Zoom += 1
Catch ex As Exception
End Try
:
(中略)
:
End Namespace
NavigationHelper_LoadStateプロシージャの中で、GetItemsメソッドによりデータを取得後にZoomプロパティに-1および+1を実行しているのは、強制的に再描画を発生させて正しく画面が表示されるようにするためです。これは本来不要な処理ですが、ComponentOne Studio 2014 v2では描画方法が変わったのか、このような操作を行わないとマークが画面に表示されません。
ここまでできたら、後はMainViewModelクラスを記述すれば出来上がりです。
サンプル実行
使用する地図を切り替える
C1Mapsが通常使用する地図はBing Mapsですが、これを他の地図に切り替えることもできます。
C1Mapsでは、巨大な地図画像を例えば256ピクセルx256ピクセルの小さなタイルに分割しておき、描画に必要な箇所のタイルだけを読み込むことでスムーズな地図描画を実現しています。このタイルを読み込む部分がC1MultiScaleTileSourceのGetTileLayersメソッドになります。
このGetTileLayersメソッドをオーバーライドして、本来のBing MapsではなくOpenStreetMapの画像を渡すようにすれば地図を切り替えることができます。具体的には、C1MapsのSourceプロパティにGetTileLayersメソッドをオーバーライドしたC1MultiScaleTileSourceを設定することになります。
Namespace Views
Public NotInheritable Class MainPage
Inherits Page
:
(中略)
:
Private Async Sub NavigationHelper_LoadState(sender As Object, e As Common.LoadStateEventArgs)
Try
Await App.MainVM.GetItems("愛知県", "豊田市")
Me.C1Maps1.Source = New OfflineMapsSource
SetCenterPos()
Me.C1Maps1.Zoom -= 1
Me.C1Maps1.Zoom += 1
Catch ex As Exception
End Try
End Sub
:
(中略)
:
Public Class OfflineMapsSource
Inherits C1.Xaml.Maps.C1MultiScaleTileSource
Private Const uriFormat As String = "http://a.tile.openstreetmap.org/{Z}/{X}/{Y}.png"
Sub New()
MyBase.New(&H8000000, &H8000000, &H100, &H100, 0)
End Sub
Protected Overrides Sub GetTileLayers(tileLevel As Integer,
tilePositionX As Integer,
tilePositionY As Integer,
tileImageLayerSources As IList(Of Object))
If (tileLevel > 8) Then
Dim zoom = tileLevel - 8
Dim Uri = uriFormat
Uri = Uri.Replace("{X}", tilePositionX.ToString())
Uri = Uri.Replace("{Y}", tilePositionY.ToString())
Uri = Uri.Replace("{Z}", zoom.ToString())
tileImageLayerSources.Add(New Uri(Uri))
End If
End Sub
End Class
End Class
End Namespace
GetTileLayersはタイルが必要になった時に呼び出されるので、その中でa.tile.openstreetmap.orgを呼び出してタイルを取得します。
まとめ
ComponentOne Studioの地図コンポーネントが通常利用している地図は、本文中でも触れていますがBing Mapsです。しかし、Bing Maps SDKは使っていないため、プラットフォームに「Any CPU」が指定できます。これはBing Maps SDKが内部的にC++のモジュールを使っているがためにプラットフォームを明示的に指定しなければならないのとは対照的に、ComponentOne Studioの地図コンポーネントがとてもWindowsストアアプリにフィットした実装になっているともいえるでしょう。
またBing Maps SDKでも可能なのですが、ComponentOne Studioの地図コンポーネントが使用する地図を切り替えられることも、より日本に即した地図を選択することでアプリの見やすさを向上できるなど、幅広く応用が効きます。
標準の方法があるけれど、それを置き換えて機能アップするという市販コンポーネントを選択する利点をぜひ体験してみてください。





