WinFormsでKMLファイルを読み込んでPlacemarkだけ取得する

# KML

> KML（ケイエムエル）は、アプリケーション・プログラムにおける三次元地理空間情報の表示の管理などを目的とした情報をXMLで記述するものである。2008年4月にKML2.2版は、そのままOpen Geospatial Consortium, Inc (OGC) という地理情報システムのオープンソース化を目指す団体の規格にOGC KMLとして取り入れられた[1]。 

引用：https://ja.wikipedia.org/wiki/KML

# Google Earth からKMLファイルを出力

![2018-08-02_15h35_11.jpg](https://qiita-image-store.s3.amazonaws.com/0/78572/1cc488ca-2c55-ac25-b128-f0393beba75a.jpeg)

ピンを2つたてました。（スカイツリー左右）

KMLとして出力してファイルの中身をみてみます。

```xml:スカイツリー.kml
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom">
<Document>
	<name>スカイツリー.kml</name>
～～省略～～
	<Folder>
		<name>スカイツリー</name>
		<open>1</open>
		<Placemark>
			<name>スカイツリー左</name>
			<LookAt>
				<longitude>139.8110927515624</longitude>
				<latitude>35.71549218701607</latitude>
				<altitude>31.79836753088749</altitude>
				<heading>-0.01903135137708219</heading>
				<tilt>73.44894794879946</tilt>
				<range>1973.689702402252</range>
				<gx:altitudeMode>relativeToSeaFloor</gx:altitudeMode>
			</LookAt>
			<styleUrl>#m_ylw-pushpin</styleUrl>
			<Point>
				<gx:drawOrder>1</gx:drawOrder>
				<coordinates>139.8096615295515,35.70967775354634,0</coordinates>
			</Point>
		</Placemark>
		<Placemark>
			<name>スカイツリー右</name>
			<LookAt>
				<longitude>139.8110927515624</longitude>
				<latitude>35.71549218701607</latitude>
				<altitude>31.79836753088749</altitude>
				<heading>-0.01903135137708219</heading>
				<tilt>73.44894794879946</tilt>
				<range>1973.689702402252</range>
				<gx:altitudeMode>relativeToSeaFloor</gx:altitudeMode>
			</LookAt>
			<styleUrl>#m_ylw-pushpin</styleUrl>
			<Point>
				<gx:drawOrder>1</gx:drawOrder>
				<coordinates>139.8114844589048,35.70980495245687,0</coordinates>
			</Point>
		</Placemark>
```


どうやらピンの位置情報は Placemark タグに記述されてるようですね。
さらに言うとPlacemark-Pointにピンの座標が入っています。
（LookAtはスカイツリーの座標っぽいです。）

ここを取得しようというのが目的。

# ライブラリを探してみる

大先輩方が既に便利なもの作ってくれているだろうと思って探してみるとこれぞC#erみたいな（意味不明
ライブラリ発見（MITライセンス）

[SharpKml.Core - github](https://github.com/samcragg/sharpkml "SharpKml")
[SharpKml.Core - LICENSE](https://github.com/samcragg/sharpkml/blob/master/LICENSE)

SharpKml と SharpKml.Coreがありますが、.Coreのほうを使います。

# インストール

Nugetでインストール

![2018-08-02_11h57_00.jpg](https://qiita-image-store.s3.amazonaws.com/0/78572/f24561fd-c2b3-2ac2-6ff7-1a6b92968636.jpeg)

# 読み込み

よくあるボタン押下→ファイル参照ダイアログでファイル指定→指定されたファイルを読み込む
っていうのをやってみます。

※Examplesがちゃんとあるのでそれ参考に作っただけですけどね！

KML読み込み部分

※ログ出力は一般的な？ロギングライブラリ [NLog](http://nlog-project.org/) を使用。

```cs:KmlReader.cs
	public class KmlReader
	{
		private static readonly ILogger _log = LogManager.GetCurrentClassLogger();

		public List<Coordinate> Read( string filename )
		{
			try
			{
				using( var stream = File.Open( filename, FileMode.Open ) )
				{
					var file = KmlFile.Load( stream );
					var kml = file.Root as KML;
					if( kml == null )
						return null;

					var placemarks = new List<Placemark>();
					ExtractPlacemarks( kml.Feature, placemarks );
					return placemarks.Select( ToCoordinate ).ToList();
				}
			}
			catch( Exception ex )
			{
				_log.Debug( ex, "KMLファイル読み込みでエラー" );
				return null;
			}
		}

		private void ExtractPlacemarks( Feature feature, List<Placemark> placemarks )
		{
			var placemark = feature as Placemark;
			if( placemark != null )
			{
				placemarks.Add( placemark );
			}
			else
			{
				var container = feature as Container;
				if( container != null )
				{
					foreach( var f in container.Features )
						ExtractPlacemarks( f, placemarks );
				}
			}
		}

		private Coordinate ToCoordinate( Placemark placemark )
		{
			var point = placemark.Geometry as Point;
			if( point == null )
				return null;

			return new Coordinate
			{
				Name = placemark.Name,
				Latitude = point.Coordinate.Latitude,
				Longitude = point.Coordinate.Longitude,
				Altitude = point.Coordinate.Altitude ?? 0
			};
		}
    }
```

KmlFile.Load で取得したKMLオブジェクトだとよく分からない状態なので、
SharpKml.DOM名前空間にいるそれっぽいクラスにキャスト（※）してそれっぽいやつを取り出します。
※as でやるとセーフキャスト。キャストできないときにExceptionを投げずにnullとなります。


必要な情報だけにするために
ToCoordinateメソッドでPlacemarkから名称と座標だけのCoordinateクラスに射影(変換)しています。

```cs:Coordinate.cs
	public class Coordinate
	{
		/// <summary>
		/// Placemark名称
		/// </summary>
		public string Name { get; set; }

		/// <summary>
		/// 緯度
		/// </summary>
		public double Latitude { get; set; }

		/// <summary>
		/// 経度
		/// </summary>
		public double Longitude { get; set; }

		/// <summary>
		/// 高度
		/// </summary>
		public double Altitude { get; set; }
	}
```

実際にファイルを指定して読み込んでみましょう。

OpenFileDialogを使用します。
毎回インスタンス生成するのはめんどくさいので自分でクラス作ってそっから呼び出します。

```cs:FileSelector.cs
	public class FileSelector
	{
		public string Select()
		{
			var dialog = new OpenFileDialog();
			dialog.FileName = "";
			dialog.Filter = "KMLファイル(*.kml)|*.kml|すべてのファイル(*.*)|*.*";
			dialog.Title = "KMLファイルを選択してください。";

			return dialog.ShowDialog() == DialogResult.Cancel
				       ? ""
				       : dialog.FileName;
		}
	}
```

適当にボタンのClickイベントハンドラなどから呼び出します。

```cs
	private void buttonKmlReader_Click( object sender, EventArgs e )
	{
		var fileSelector = new FileSelector();
		var path = fileSelector.Select();
		if( string.IsNullOrEmpty( path ) )
			return;

		var kmlReader = new KmlReader();
		var coordinates = kmlReader.Read( path );
		if( coordinates == null )
			return;

		foreach( var coordinate in coordinates )
		{
			Console.WriteLine( coordinate.Name );
			Console.WriteLine( $"lat: {coordinate.Latitude}, lng: {coordinate.Longitude}" );
			Console.WriteLine( "===========================" );
		}
	}
```

![2018-08-02_15h48_12.jpg](https://qiita-image-store.s3.amazonaws.com/0/78572/1a0f9093-6324-ac2d-1efa-36d4db40466f.jpeg)

多少誤差が出ますがちゃんと取れてますね。

おしまい

この他のKMLファイル操作はSharpKmlのgithubページのExamplesみながらやってみて下さい。