Map App SDKのアップデート
今回は、
これまで使用してきたMap App SDKおよび開発用のMap AppのMap app test toolが、
今回の更新でVisual StudioのMap Appプロジェクトテンプレートを使用した場合、
この更新に合わせて、

さらに、
以前のSDKをアンインストール後、
パネルのイベント
前回、
- リンクのクリック
(HyperlinkButton_ Click) - リストボックスのドロップ
(PhotoListBox_ Drop) - ボタンのクリック
(Button_ Click)
前回に作成済みのMyPhotoPanelクラス、
ボタンのクリック
まず、
- ファイルを開くダイアログを表示
- 選択された写真
(JPEG) ファイルを読み込み、 PhotoEntityオブジェクトを生成 - PhotoEntity オブジェクトをPhotoItemsコレクションに追加
- PhotoEntityオブジェクトをレイヤーに追加
- 追加した地点へ地図を移動
PhotoEntityは前回作成したエンティティ クラス、
上記内容のコードを記述すると次のようになります。
private void Button_Click(object sender, RoutedEventArgs e)
{
// ファイルを開くダイアログを表示
var dialog = new OpenFileDialog();
dialog.Filter = "写真ファイル(*.jpg, *.jpeg)|*.jpg;*.jpeg";
if (dialog.ShowDialog() != true)
{
return;
}
try
{
// PhotoEntity 作成
var entity = CreateEntity(dialog.File); // (CreateEntity は後で作成)
if (entity != null)
{
// コレクションに追加
PhotoItems.Add(entity);
// レイヤーに追加
this.plugin.MainLayer.Entities.Add(entity);
// 追加した地点へ地図を移動
this.plugin.DefaultMap.SetView(entity, 15);
}
}
catch (Exception)
{
// Do nothing
}
}
CreateEntityメソッドは後で作成します。
リストボックスへファイルのドロップ
続いてListBoxのDropイベント処理です。Silverlight 4からWebブラウザーにドロップされたファイルを開くことができるようになっています。ボタンクリック処理と同様に、
ドロップを許可するには、
private void PhotoListBox_Drop(object sender, DragEventArgs e)
{
try
{
var files = (FileInfo[])e.Data.GetData(DataFormats.FileDrop);
PhotoEntity lastEntity = null;
foreach (var f in files)
{
var entity = CreateEntity(f);
if (entity != null)
{
lastEntity = entity;
PhotoItems.Add(entity);
this.plugin.MainLayer.Entities.Add(entity);
}
}
// 最後に追加した写真の場所へ移動
if (lastEntity != null)
{
this.plugin.DefaultMap.SetView(lastEntity, 15);
}
}
catch (Exception)
{
// Do nothing
}
}
複数のファイルがドロップされた場合も対応しています。そのためボタンクリック時とは少し記述が異なりますが、
Silverlightアプリケーションでは、
リンクのクリック
最後にHyperlinkButtonのClickイベントの処理です。ListBox内の各アイテムはひとつのHyperlinkButtonがあり、
private void HyperlinkButton_Click(object sender, RoutedEventArgs e)
{
var entity = (PhotoEntity)((HyperlinkButton)sender).DataContext;
this.plugin.DefaultMap.SetView(entity, 15);
}
少しわかりにくいかもしれませんが、
以上でイベント処理の記述は完了です。
エンティティの作成
次にユーザーが選択した写真ファイルから、
private PhotoEntity CreateEntity(FileInfo file)
{
var entity = new PhotoEntity();
entity.Name = file.Name; // 名前はファイル名を使用
entity.Id = Guid.NewGuid().ToString(); // ID は GUID を使用
// (ここに処理を追記)
return entity;
}
サムネイル・経緯度情報の取得
PhotoEntityオブジェクトの作成には次の処理が必要です。
- ファイルに含まれるサムネイルの取得
- ファイルに含まれる経緯度情報の取得
最近のデジタルカメラで撮影した写真には、
また、
Exifデータから情報の取得は、
それでは、
var info = ExifLib.ExifReader.ReadJpeg(file);
if (info.GpsLatitudeRef == ExifLib.ExifGpsLatitudeRef.Unknown ||
info.ThumbnailData == null)
{
// 経緯度情報・サムネイルが含まれていない場合は null を返す
return null;
}
最初にGPS経緯度情報の取得です。Exifでは経緯度は、
// 経緯度を Bing Maps で使用する形式に変換
var latitude = info.GpsLatitude[0] +
info.GpsLatitude[1] / 60.0 +
info.GpsLatitude[2] / 3600.0;
if (info.GpsLatitudeRef == ExifLib.ExifGpsLatitudeRef.South)
{
latitude = -latitude;
}
var longitude = info.GpsLongitude[0] +
info.GpsLongitude[1] / 60.0 +
info.GpsLongitude[2] / 3600.0;
if (info.GpsLongitudeRef == ExifLib.ExifGpsLongitudeRef.West)
{
longitude = -longitude;
}
var location = new Location(latitude, longitude);
サムネイルの取得は次のようになります。
// 画像の読み込み
var img = new BitmapImage();
using (var ms = new MemoryStream(info.ThumbnailData))
{
img.SetSource(ms);
}
取得した情報を、
entity.BitmapImage = img;
// プッシュピンの作成
var pin = plugin.PushpinFactoryContract.CreateStandardPushpin(location);
entity.Primitive = pin;
// MediaPushpin にする場合
// var image = new Image() { Source = entity.BitmapImage };
// var pin = plugin.PushpinFactoryContract.CreateMediaPushpin(location, (Entity ey) =>
// {
// return image;
// });
実行と確認
以上でエンティティ作成部分ができ、

写真を追加すると、
ポップアップ
次は、
ポップアップの見た目は、
<UserControl x:Class="MyPhotoMapApp.PopupControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid x:Name="LayoutRoot" Background="White">
<Image Source="{Binding BitmapImage}" Margin="10">
<Image.Effect>
<DropShadowEffect BlurRadius="5" ShadowDepth="1" Direction="0" Color="Gray" />
</Image.Effect>
</Image>
</Grid>
</UserControl>
ポップアップを登録するメソッドは、
private void RegisterPopup(PhotoEntity entity)
{
this.plugin.PopupContract.Register(entity, (PopupStateChangeContext context) =>
{
context.Title = entity.Name;
if (context.State == PopupState.Normal)
{
context.Content = new PopupControl() { DataContext = entity };
}
});
}
このメソッドをCreateEntityメソッド内で呼び出すように編集すればポップアップがアプリケーションで有効になります。
private PhotoEntity CreateEntity(FileInfo file)
{
// (省略)
// (追加)
// ポップアップの登録
RegisterPopup(entity);
return entity;
}
設定の保存
最後に新しいContractを紹介します。Map Appの状態や設定を保存できるようにしてみましょう。
分離ストレージ
.NET FrameworkやSilverlightでは、
Map Appでもこの分離ストレージが利用できます。ただし、
[ImportSingle("Microsoft/IsolatedStorageContract", ImportLoadPolicy.Synchronous)]
public IsolatedStorageContract IsolatedStorageContract { get; set; }
分離ストレージを使用したデータは、
My Photo Map Appに追加された写真の保存
今回のアプリケーションでは分離ストレージに、
保存する内容は、
- ID
- 名前
- 経緯度
- サムネイル画像
サムネイル画像は写真ごとに別のファイルとして、
分離ストレージにファイルを作成するには、
private PluginIsolatedStorageFile IsolatedStorageFile
{
get
{
return this.plugin.IsolatedStorageContract.GetPluginSpecificIsolatedStorageFile(this.plugin.Token);
}
}
ちなみにファイルを作成して設定を保存する方法以外にもPluginIsolatedStorageSettingsクラスを使用した方法もあります。次のように、
var settings = this.plugin.IsolatedStorageContract.GetPluginSpecificIsolatedStorageSettings(this.plugin.Token);
// 設定の保存
settings["key"] = "value";
// 設定の参照
string value;
if (settings.TryGetValue<string>("key", out value))
{
// Do something
}
保存処理
それでは、
<?xml version="1.0" encoding="utf-8"?>
<photos>
<photo id="1a1618d9-a138-4f61-9161-278dd6d5c65c"
name="DSC00001.JPG"
latitude="36.087925"
longitude="139.7249733" />
<photo id="a03D363a-7a2B-42fc-8d53-f3e78a59d2b6"
name="DSC00002.JPG"
latitude="36.077934"
longitude="139.719575" />
</photos>
XMLファイルを作成する部分のメソッドは次のように記述します。
private void Save()
{
var stream = IsolatedStorageFile.CreateFile("photos.xml");
var writer = System.Xml.XmlWriter.Create(stream);
writer.WriteStartDocument();
writer.WriteStartElement("photos");
foreach (var item in PhotoItems)
{
writer.WriteStartElement("photo");
writer.WriteStartAttribute("id");
writer.WriteString(item.Id);
writer.WriteEndAttribute();
writer.WriteStartAttribute("name");
writer.WriteString(item.Name);
writer.WriteEndAttribute();
// 経緯度の書き込み
var pin = (PointPrimitive)item.Primitive;
writer.WriteStartAttribute("latitude");
writer.WriteString(pin.Location.Latitude.ToString());
writer.WriteEndAttribute();
writer.WriteStartAttribute("longitude");
writer.WriteString(pin.Location.Longitude.ToString());
writer.WriteEndAttribute();
writer.WriteEndElement(); // </photo>
}
writer.WriteEndElement(); // </photos>
writer.Close();
stream.Close();
}
このSaveメソッドを呼び出す場所は、
後は、
// 画像の読み込み
var img = new BitmapImage();
using (var ms = new MemoryStream(info.ThumbnailData))
{
img.SetSource(ms);
// (以下を追記)
// ID の名前で画像を保存
using (var f = IsolatedStorageFile.CreateFile(entity.Id))
{
ms.WriteTo(f);
}
}
保存部分は以上です。分離ストレージに保存したファイルは、
読み取り処理
実行時に、
public void Load()
{
if (!this.IsolatedStorageFile.FileExists("photos.xml"))
{
return;
}
try
{
var stream = this.IsolatedStorageFile.OpenFile("photos.xml", FileMode.Open);
var reader = System.Xml.XmlReader.Create(stream, new System.Xml.XmlReaderSettings());
while (reader.Read())
{
if (reader.NodeType == System.Xml.XmlNodeType.Element &&
reader.LocalName == "photo")
{
var entity = new PhotoEntity();
reader.MoveToAttribute("id");
entity.Id = reader.Value;
reader.MoveToAttribute("name");
entity.Name = reader.Value;
var location = new Location();
reader.MoveToAttribute("latitude");
location.Latitude = Convert.ToDouble(reader.Value);
reader.MoveToAttribute("longitude");
location.Longitude = Convert.ToDouble(reader.Value);
// プッシュピン作成
entity.Primitive = this.plugin.PushpinFactoryContract.CreateStandardPushpin(location);
// 画像読み込み
entity.BitmapImage = new BitmapImage();
entity.BitmapImage.SetSource(IsolatedStorageFile.OpenFile(entity.Id, FileMode.Open, FileAccess.Read));
this.plugin.MainLayer.Entities.Add(entity); // レイヤーに追加
PhotoItems.Add(entity); // コレクションに追加
RegisterPopup(entity); // ポップアップ登録
}
}
}
catch (Exception)
{
// Do nothing
}
}
このLoadメソッドを呼び出すタイミングですが、
public override void Initialize()
{
base.Initialize();
this.MainLayer = new MyPhotoLayer(this.Token, this);
((MyPhotoPanel)this.MainLayer.Panel).Load(); // 追加
}
以上でMy Photo Map Appの完成です。実行して動作を確認してみてください。うまく動いたでしょうか?
おわりに
My Photo Map Appの作成は、