「デスクトップ地図アプリ開発」シリーズの第9弾の記事です。
今回は、第8弾で作成した地図アプリに、レイヤー リストの機能を追加します。本記事で紹介する機能のサンプル コードは ESRIジャパン GitHub で公開しています。
マップ上にあるすべてのレイヤーを一覧表示し、各レイヤーのフィーチャのシンボルが何を表しているかを示すための凡例も一緒に表示します。また、レイヤー名のチェックボックスをオン/オフして、レイヤーの表示/非表示を切り替える機能も実装します。
1. MainWindow.xaml に、レイヤー リストを表示するレイアウトを作成します。TreeView コントロールを使用してレイヤーと凡例が階層で表示されるように実装し、TreeView の中ではレイヤー名を表示するための 「Binding LayerName」 と凡例の画像を表示するための 「Binding SymbolImage」を設定します。
MainWindow.xaml
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.2*"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="0.8*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TreeView Name="ContentsTree" Grid.Column="0" ItemsSource="{Binding Layers, Source={StaticResource MapViewModel}}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:LayerInfo}" ItemsSource="{Binding SymbolMembers}">
<StackPanel Orientation="Horizontal">
<CheckBox Margin="1" IsChecked="{Binding LayerChecked}" Click="LayerCheckBox_Click" Tag="{Binding LayerID}">
<TextBlock Text="{Binding LayerName}" />
</CheckBox>
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:SymbolInfo}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding SymbolImage}" Margin="0,0,5,0" />
<TextBlock Text="{Binding SymbolName}" />
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Center"/>
2. これまでに追加したコントロールに対してカラムを設定します(各コントロールに Grid.Column="2" を追加)。
MainWindow.xaml
<esri:MapView x:Name="MainMapView" Grid.Column="2" Map="{Binding Map, Source={StaticResource MapViewModel}}" ・・・・/>
<esri:OverviewMap x:Name="OverviewMapView" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="10,10,10,30"/>
<StackPanel Grid.Column="2" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="5">
・・・・
</StackPanel>
<Border Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Top" Background="White" BorderThickness="0.5" BorderBrush="Gray" Margin="10" Padding="10" Width="250">
・・・・
</Border>
3. Visual Studio のメニューから [ツール] → [NuGet パッケージ マネージャー] → [ソリューションの NuGet パッケージの管理] をクリックして、System.Drawing.Common NuGet パッケージをインストールします。
4. MapViewModel.cs に、手順 1 で定義したレイヤー リストに表示するためのレイヤー名やシンボル情報などをバインドするクラスを定義します。
MapViewModel.cs
namespace mapApp
{
// レイヤー リストに表示するレイヤー名や凡例画像などの DataContext プロパティ
public class LayerInfo
{
public LayerInfo()
{
this.SymbolMembers = new ObservableCollection<SymbolInfo>();
}
public string? LayerName { get; set; }
public string? LayerID { get; set; }
public bool? LayerChecked { get; set; }
public ObservableCollection<SymbolInfo>? SymbolMembers { get; set; }
}
public class SymbolInfo
{
public System.Windows.Media.Imaging.BitmapFrame.BitmapFrame? SymbolImage { get; set; }
public string? SymbolName { get; set; }
}
5. TreeView コントロールのコンテンツとして設定するデータコレクションをバインドするクラスを定義します。
MapViewModel.cs
public class MapViewModel : INotifyPropertyChanged
{
///中略///
private ObservableCollection<LayerInfo>? _layers;
public ObservableCollection<LayerInfo>? Layers
{
get { return _layers; }
set
{
_layers = value;
OnPropertyChanged();
}
}
6. 次に、マップに追加されているレイヤー情報を取得して、TreeView に表示する処理を作成します。下記のコードでは Map オブジェクト(GeoModel を継承)から Layer オブジェクトのリストを取得して、そこからレイヤー名、シンボル情報を取得しています。取得した情報をもとにレイヤー情報を格納する LayerInfo オブジェクトのリストを新たに作成し、TreeView にバインドします。
MapViewModel.cs
public async void CreateToc()
{
long layerCount = 0;
// TreeView にバインドする値を保持するためのレイヤー情報を格納する LayerInfo のコレクションを作成する
Layers = new ObservableCollection<LayerInfo>();
// マップに格納されているレイヤーの一覧を取得する
foreach (var layer in Map.AllLayers)
{
// マップに格納されているレイヤーにユニークな ID を設定する(レイヤーの表示/非表示機能を実装する際に使用する)
layer.Id = layerCount.ToString();
// レイヤー名 (Layer オブジェクトから取得) とレイヤー ID をセットして、LayerInfo オブジェクトを作成する
LayerInfo layerInfo = new LayerInfo() {
LayerName = layer.Name,
LayerID = layer.Id,
LayerChecked = layer.IsVisible
};
// レイヤーに設定されているシンボル情報 (シンボル画像とその名称) を取得する
var legendInfos = await layer.GetLegendInfosAsync();
if (legendInfos != null)
{
foreach (LegendInfo item in legendInfos)
{
// シンボルを BitmapFrame 型に変換し画像として取得する
Symbol symbol = item.Symbol!;
RuntimeImage swatch = await symbol!.CreateSwatchAsync(1 * 96);
Stream imageData = await swatch.GetEncodedBufferAsync();
System.Drawing.Image image = Bitmap.FromStream(imageData);
// メモリストリームに BitmapFrame を保存して LayerInfo オブジェクトにシンボルの画像と名称をセットする
using (Stream stream = new MemoryStream())
{
image.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
stream.Seek(0, SeekOrigin.Begin);
BitmapFrame symbolImage = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
SymbolInfo symbolInfo = new SymbolInfo() {
SymbolImage = symbolImage,
SymbolName = item.Name
};
layerInfo.SymbolMembers!.Insert(0, symbolInfo);
}
}
}
// TreeView コンテンツのコレクションの一番上に LayerInfo オブジェクトを追加する
Layers.Insert(0, layerInfo);
layerCount = layerCount + 1;
}
}
Tips: レイヤー名は Map クラスが持つ AllLayers プロパティを使用することで取得でき、シンボルの情報は Layer クラスが持つ GetLegendInfosAsync() メソッドで取得できます。GetLegendInfosAsync() ではレイヤーに設定されている各シンボルとその名称のセットが返されます。
7. マップのロードが完了した後に、前の手順で作成した CreateToc() メソッドを呼び出します。
MapViewModel.cs
private async Task SetupMap()
{
///中略///
await Map.LoadAsync();
// レイヤー リストを作成する
CreateToc();
8. ここまでで、マップに追加されている各レイヤーの情報を表示するレイヤー リストを作成できました。
9. 次に、ベースマップのレイヤー名が英語(World_Basemap_v2/Elevation)で表示されているので、CreateToc() メソッドを呼び出す前に Layer オブジェクトの Name プロパティを設定して、日本語で表示されるように修正します。
MapViewModel.cs
// マップをロードする
await Map.LoadAsync();
// ベースマップのレイヤー名を変更
localizedBasemap.BaseLayers[0].Name = "陰影起伏図";
localizedBasemap.BaseLayers[1].Name = "地形図";
// レイヤー リストを作成する
CreateToc();
10. アプリを実行して、ベースマップのレイヤー名が日本語で表示されているか確認します。
11. 次に、各レイヤー名の左横に表示されているチェックボックスをクリックして、レイヤーの表示/非表示を切り替える機能を実装していきます。CheckBox コントロールに Click イベントを作成します(Click="LayerCheckBox_Click" を追加)。
MainWindows.xaml
<CheckBox Margin="1" IsChecked="{Binding LayerChecked}" Click="LayerCheckBox_Click" Tag="{Binding LayerID}">
<TextBlock Text="{Binding LayerName}" />
</CheckBox>
12. MainWindows.xaml.cs にチェックボックスのイベントハンドラーを実装します。
MainWindows.xaml.cs
// レイヤー リストのチェックボックスのイベント ハンドラー
private void LayerCheckBox_Click(object sender, RoutedEventArgs e)
{
var checkBox = (CheckBox)sender;
var currentMapViewModel = (MapViewModel)this.FindResource("MapViewModel");
// レイヤーの表示/非表示を切り替える
currentMapViewModel.setVisibleLayer(checkBox.Tag.ToString()!);
}
13. レイヤーの表示/非表示を切り替える setVisivleLayer メソッドを定義します。チェックボックスの tag プロパティに格納したレイヤー ID と Map オブジェクトに格納されているレイヤーの ID(手順 6 で設定)を比較し、 ID が一致したレイヤーの表示・非表示を切り替えます。
MapViewModel.cs
// レイヤーの表示/非表示の切替え
public void setVisibleLayer(string checkedLayerId)
{
foreach (Layer layer in Map.AllLayers)
{
if (layer.Id == checkedLayerId)
{
if (layer.IsVisible) {
// レイヤーが表示されている場合は非表示にする
layer.IsVisible = false;
} else {
// レイヤーが非表示の場合は表示する
layer.IsVisible = true;
}
}
}
}
Tips: レイヤーの表示/非表示は、Layer クラスが持つ IsVisible プロパティを使って切り替えを行います。マップの作成方法によりデフォルトではレイヤー ID を保有していないレイヤーも存在するため、手順 6 で新たにレイヤーにユニークな ID を設定してレイヤーの識別を行っています。
14. アプリを実行してレイヤーの表示/非表示を切替えてみましょう。
第9弾「レイヤー リストの作成」ではマップに含まれる各レイヤーの情報を表示するレイヤー リストの作成方法をご紹介しました。次回の第9弾では、開発したアプリを配布するために必要なライセンス認証について紹介します。