先日 DevFest Japan 2014 Summer - Google I/O 2014 報告会 信州会場 に参加した際、 @openspc さんによるGoogleMap + GeoJSONのプレゼンがありました。
どんなものかと気になったため、公式サイトを眺めてみたところ、
Google Maps JavaScript API v3にて提供され、GoogleMapにGeoJSONデータを表示することができる
表示させるには、
loadGeoJson()
メソッドで外部サイトやローカルファイルからGeoJSONデータを読み込むか、addGeoJson()
メソッドでGeoJSONオブジェクトを渡すGoogle Static Maps API V2にはそのような記述がなかったため、あくまでもJavaScript API で使うっぽい
ということが分かりました。
JavaScriptというとWebブラウザを使えばいろいろと試せそうでしたが、せっかくなので最近やっているC#のWPFで扱うことができないかを調べてみました。
すると、WebBrowserコントロールを使えばHTMLやJavaScriptをウィンドウの中に表示させられることが分かりました。
そこで、WPFのWebBrowserコントロールの勉強がてら、C# + WPF で Google Map JavaScript API v3のGeoJSONを試してみることにしました。
なお、WPFでGoogle Mapを表示する方法については、以下がとても参考になりました。ありがとうございました。
WPF で Google Map | アカベコマイリ
環境
- Windows7 x64
- IE11 インストール済
- .NET Framework 4.5
- 以前と同じく、MVVMパターンで作る (ただし、今回はModelは存在しない)
- DynamicJSON 1.2.0.0
2015/12/10 追記 ここから
公式Blogに以下の記事が掲載されましたので、Microsoft.TeamFoundation.MVVM
の使用前に記事を確認してみてください。
Microsoft.TeamFoundation.MVVM 名前空間の利用について - Visual Studio サポート チーム blog - Site Home - MSDN Blogs
2015/12/10 追記 ここまで
ViewModel
ViewModelのベースクラス
ViewModelは、以前と同じく Microsoft.TeamFoundation.MVVM.ViewModelBase
を継承したものを使うので、Microsoft.TeamFoundation.Controls
の参照を追加しておきます。
今回は手抜きをして以前作った VMBase.cs
をそのまま持ってきました。そのため、今回特に使わないINotifyDataErrorInfo
インタフェースも実装されています。
CSharp-Sample/MVVMApp/MVVMApp/VMBase.cs - thinkAmi/CSharp-Sample - GitHub
ViewModel
この時点のViewModelの実装は、
- WebBrowserコントロールにバインドするためのプロパティ
Uri
を用意 - コンストラクタで、WebBrowserに表示するローカルのhtmlファイルのパスをUrlに渡す
だけになります。
class MainWindowViewModel : VMBase { public MainWindowViewModel() { Uri = String.Format( "file://{0}GeoJsonLoad.html", AppDomain.CurrentDomain.BaseDirectory ); } private string _uri; public string Uri { get { return _uri; } set { _uri = value; RaisePropertyChanged(); } } }
WebBrowserUtilityクラス
WebBrowserのSourceプロパティへデータバインディングするために、依存プロパティ用にWebBrowserUtilityクラスを作成します。
内容は参考にしたものと同じになります。
View (MainWindow.xaml)
以前と同様に
- xmlnsにプロジェクトの名前空間を追加
- Window.DataContextに、ViewModelクラスを指定
します。
また、WebBrowser要素には、先ほど作成したWebBrowserUtilityクラスにあるプロパティ(ここではSource)を指定してデータバインディングします。
<Grid>
<WebBrowser local:WebBrowserUtility.Source="{Binding Uri}" />
</Grid>
WebBrowserコントロールに表示するHTMLの作成
HTMLの中でJavaScriptを記述し、GeoJsonのデータを取り込みます。
今回は以下の3つの方法を試してみました。
- 外部のGeoJsonデータを読み込み、loadGeoJson()を使って表示
- JavaScript内のGeoJsonデータを読み込み、addGeoJson()を使って表示
- C#でGeoJsonデータを作成し、addGeoJson()を使って表示
なお、差が分かるように、それぞれのHTMLではzoomレベルを変えるなどしています。
各HTMLの共通の設定
ヘッダに、以下を追加します。
- 「セキュリティ保護のため~」のエラーを回避するため、
<!-- saved from url=(0017)http://localhost/ -->
を追加 - GoogleMapをWebBrowserコントロール全体に表示するために、スタイルを指定
<html lang="ja" xmlns="http://www.w3.org/1999/xhtml"> <!-- saved from url=(0017)http://localhost/ --> ... <head> <style type="text/css"> html, body { height: 100%; margin:0px; } #map { height: 100%; overflow:auto; } </style> ... </head>
また、HTML内のJavaScriptで以下のようなエラーが発生することがあります。
- loadGeoJson()で外部のGeoJSONデータを読み込むと、「アクセスが拒否されました」エラー
- JSON.parse()を使うと、「'JSON'は定義されていません (JSON is undefined)」エラー
原因は、通常WebBrowserコントロールはIE7相当であり、IE7にはJSONオブジェクトなどが存在しないため、発生しています。
手元はIE11なので、WebBrowserコントロールをIE11相当で動かすことができますが、方法としては、
- headに
<meta http-equiv="X-UA-Compatible" content="IE=11" />
を追加 - レジストリを修正
<アプリ名>,exe
をエントリに追加- VisualStudioのデバッグ実行で確認する場合、
<アプリ名>.vshost.exe
のエントリ追加も必要
のどちらかが必要になります。
- c# - Regarding IE9 WebBrowser control - Stack Overflow
- Internet Feature Controls (B..C) (Internet Explorer)
- WebBrowserコントロールのIEバージョン - (。・ω・。)ノ・☆':;':
- Web Browser Control – Specifying the IE Version - Rick Strahl's Web Log
なお、前者のX-UA-Compatible
を使う方法はIE11以降では非推奨になっていることに注意します。
ドキュメント モードの非推奨 - MSDN
あとは、忘れずに各HTMLをプロジェクトに含めた上で、プロパティを以下のようになっているか確認します。
プロパティ名 | 設定内容 |
---|---|
ビルドアクション | コンテンツ |
出力ディレクトリにコピー | 常にコピーする |
外部のGeoJsonデータを読み込み、loadGeoJson()を使って表示する方法(loadGeoJson.html)
この場合は、Google Maps JavaScript API v3 のサンプル通りに実装します。
map.data.loadGeoJson('https://storage.googleapis.com/maps-devrel/google.json');
JavaScript内でGeoJsonデータを作成し、addGeoJson()を使って表示
addGeoJson()を使う場合は、addGeoJsonにGeoJsonデータのオブジェクトを渡せば表示することができます。
// https://storage.googleapis.com/maps-devrel/google.json にあるGeoJsonデータから、改行・スペースを削除したもの var j = '..' // 長いので省略 map.data.addGeoJson(JSON.parse(j));
C#でGeoJsonデータを作成し、addGeoJson()を使って表示
内容は上記のaddGeoJsonと似ていますが、GeoJsonデータはC#にて作成し、C#からJavaScriptへデータを渡して表示するのを試してみます。
WPFでC#からJavaScriptへとデータを渡すためには、System.Windows.Controls.ObjectForScripting
を使います。
WebBrowser.ObjectForScripting プロパティ - MSDN
ただ、ObjectForScriptingは依存プロパティではないことからデータバインディングできないため、同じサイトの別記事を参考に、添付プロパティを使ってデータバインディングができるようにします。
WPF で Google Map その 2 | アカベコマイリ
全体では以下の実装を行い、C#で作ったJSON文字列をJavaScript側に渡してGoogleMap上に表示させています。
Mapクラスを作成
このクラスで、
[ComVisible(true)]
属性をクラスに付ける- GeoJSON文字列を返すプロパティを用意する (C#オブジェクトからJSON文字列へシリアライズするために、DynamicJSONを使用)
- INotifyPropertyChangedインタフェースを実装する
なお、Googleという文字を描くGeoJsonオブジェクトを作成するのが手間だったので、中央にピンを立てるようなGeoJSONにしています。
[ComVisible(true)] public class Map : INotifyPropertyChanged { string _geoJson; public string GeoJson { get { if (!string.IsNullOrEmpty(_geoJson)) return _geoJson; var j = new { type = "FeatureCollection", features = new[] { new { type = "Feature", property = new {}, geometry = new { type = "Point", coordinates = new [] { 137.883, -28} } } } }; // DynamicJSONでJSON文字列化 return DynamicJson.Serialize(j); } set { this._geoJson = value; RaisePropertyChanged(); } } // INotifyPropertyChangedインタフェースの実装 public event PropertyChangedEventHandler PropertyChanged; public void RaisePropertyChanged([CallerMemberName]string propertyName = "") { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } }
MainWindowViewModelクラスに、Map型のプロパティを作成するとともに、コンストラクタで初期化
Uriプロパティと同じなので、省略します。
WebBrowserUtilityクラスに、ObjectForScriptingプロパティへデータバインディングするための依存プロパティを追加
上記の記事通りなので、省略します。
MainWindow.xamlのWebBrowser要素に、データバインディングの記述を追加
local:WebBrowserUtility.ObjectForScripting="{Binding GoogleMap}"
HTMLのJavaScriptに、C#のプロパティを参照して表示するコードを追加
window.external.GeoJson
でC#のGeoJSONを返すプロパティを参照します (ここでは、ObjectForScriptingプロパティにデータバインディングされているMapクラスのGeoJsonプロパティ)。
その後、JSON.parse()
でJSONオブジェクトにして、Google Maps JavaScript API v3に渡して表示します。
var j = JSON.parse(window.external.GeoJson); map.data.addGeoJson(j);
できなかったこと - ローカルの geo.json ファイルを読み込んで表示すること
ローカルにgeo.jsonファイルを作り、ビルド時に常にコピーするように設定したものの、map.data.loadGeoJson('geo.json');
としても「アクセスが拒否されました」というエラーになりました。
ローカルファイルに対するCORSのやり方が分からなかったので、今回はローカルのgeo.jsonファイルを使うのは諦めました。
Google Maps Tutorials — Google Developers
ソースコード
GitHubに上げました。
CSharp-Sample/WebbrowserGeoJson at master · thinkAmi/CSharp-Sample
HTMLは3種類ありますが、MainWindowViewModelのコンストラクタで切り替えをハードコーディングしているので(手抜き)、1種類のみ表示されます。