Search code examples
c#xmlxml-parsingkml

How can I parse a KML file to retrieve coordinate points from a Placemark element?


I have a KML file that contains GPS coordinates as well as the bearing of each coordinate towards the next. I want to retrieve each coordinate and each bearing and store them in separate lists. One containing strictly coordinates and one containing the bearing for each.

For the sake of the problem, I will only list 2 Placemark elements from my file. My KML file looks like this:

<?xml version="1.0" encoding="utf-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
  <Folder>
    <Placemark>
      <name>Coordinate0</name>
      <Point>
        <coordinates>44.601788291074442,-88.048173934221268</coordinates>
      </Point>
    </Placemark>

   <!-- more coordinates and bearings fill these gaps -->

   <Placemark>
      <name>Bearing200</name>
      <Point>
        <coordinates>284.25265584263923,0</coordinates>
      </Point>
    </Placemark>
  </Folder>
</kml>

I've tried the following:

 //Function that parses through the file and pulls out coordinates and bearings for each coordinate

private void ButtonTestParser_Click(object sender, EventArgs e)
{
    //load the xml file
    XDocument xmlFile = XDocument.Load("Test.kml");
    //get every placemark element in the document
    var placemarks = (from x in xmlFile.Descendants()
                      where x.Name.LocalName == "Placemark"
                  select new XElement(x)).ToList();
    //loop through each placemark and separate it into coordinates and bearings
    List<string> coordinates = new List<string>();
    string coordinate = "";
    foreach(var point in placemarks)
    {
        coordinate = (from x in point.Descendants("coordinates")
                      select x).First().Value;
        coordinates.Add(coordinate);
    }
}

I get the following error:

System.InvalidOperationException: 'Sequence contains no elements'


Solution

  • Your problem is here

    coordinate = (from x in point.Descendants("coordinates")
                  select x).First().Value;
    

    If you look at the definition of Descendants you will see that it takes an XName, not a string.

    An XName is just the namespace and the element name concatenated together.

    So an easy fix is to write your code as:

    var ns = xmlFile.Root.Name.Namespace;
    
    coordinate = (from x in point.Descendants(ns + "coordinates")
                  select x).First().Value;
    

    Note that you can use the same technique to fix your earlier code:

    var placemarks = (from x in xmlFile.Descendants(ns + "Placemark")
    

    I've taken the liberty of rewriting your program slightly, as you don't need all that LINQ select...from... code either:

    //load the xml file
    var document = XDocument.Load(@"C:\temp\bob.xml");
    var ns = document.Root.Name.Namespace;
    //get every placemark element in the document
    var placemarks = document.Descendants(ns + "Placemark");
    
    //loop through each placemark and separate it into coordinates and bearings
    var coordinates = new List<string>();
    var coordinate = "";
    foreach (var point in placemarks)
    {
        coordinate = point.Descendants(ns + "coordinates").First().Value;
        coordinates.Add(coordinate);
    }