This project has moved. For the latest updates, please go here.

How to traverse the KML tree?

Oct 10, 2011 at 2:24 PM
Edited Oct 12, 2011 at 12:27 AM

I have a set of KMZ files containing polygonal boundaries (outer and inner loops).

Some of these loops contain many more points than are necessary for viewing at any useful resolution (zoom level).

I wish to read the KMZ file, simplify the loops, and write the new KML file otherwise unaltered.

I am happy writing the simplify algorithm for the loops. That is not the subject of my question. (but for those interested, http://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm) and yes I know it is available in various GIS tools.

I had intended to use LoadKml to get the KMLRoot, and then effectively do a traversal of the tree, writing out the nodes as is, as I went along except for polygon loops, that I would modify and then write out.

This approach meant I could recurse through the structure and except for the polygon loops, would never have to worry too much about state or memory consumption (at least at the level I would be writing it).

I was going to use the list of Children of each node to recurse through the structure.

Unfortunately, the Children is a protected Property, so I can't use it. However, Flatten() produces (I believe) a flattened list of all sub nodes.

At a pinch I can use this, but at this point it seems Flatten will lose me some information that I wanted, effectively some of the information regarding parents and children that is useful at the node level for traversing the tree.

At the minute I'm surprised and frustrated that I can't traverse the tree using the Children Property. If there is an easy work around then I'm interested, and if there is a good reason for hiding the Children then I'm also interested.

I believe SharpKml should be the right library for me to use once I've got used to it, but at the moment I keep running into problems.

So far, @samcragg has been extremely responsive, for which I'm very grateful. I hope I can continue to use the library.

Coordinator
Oct 11, 2011 at 5:28 PM

When I designed the library I based it on the C++ version by Google (libkml). In that version there is no way to access the children from an Element and when creating the C# version I decided that Children should be an implementation detail, and as such hidden from the default public interface. However, there's no reason that you can't change the modifier and recompile the library or simply use reflection to get access to it :)

Having said that, I don't think you should need to access the children directly. The Parser class has an event called ElementAdded that might be able to help out. Here's an example, with the Kml taken from the Kml Reference:

using System;
using SharpKml.Base;
using SharpKml.Dom;

class Program
{
    static void Main(string[] args)
    {
        const string Xml =
@"<?xml version='1.0' encoding='UTF-8'?>
<kml xmlns='http://www.opengis.net/kml/2.2'>
<Document>
  <name>Polygon.kml</name>
  <open>0</open>
  <Placemark>
    <name>hollow box</name>
    <Polygon>
      <extrude>1</extrude>
      <altitudeMode>relativeToGround</altitudeMode>
      <outerBoundaryIs>
        <LinearRing>
          <coordinates>
            -122.366278,37.818844,30
            -122.365248,37.819267,30
            -122.365640,37.819861,30
            -122.366669,37.819429,30
            -122.366278,37.818844,30
          </coordinates>
        </LinearRing>
      </outerBoundaryIs>
      <innerBoundaryIs>
        <LinearRing>
          <coordinates>
            -122.366212,37.818977,30
            -122.365424,37.819294,30
            -122.365704,37.819731,30
            -122.366488,37.819402,30
            -122.366212,37.818977,30
          </coordinates>
        </LinearRing>
      </innerBoundaryIs>
    </Polygon>
  </Placemark>
</Document>
</kml>";

        var parser = new Parser();
        parser.ElementAdded += OnElementAdded;
        parser.ParseString(Xml, true);

        // Display the result.
        var serializer = new Serializer();
        serializer.Serialize(parser.Root);
        Console.WriteLine(serializer.Xml);
    }

    private static void OnElementAdded(object sender, ElementEventArgs e)
    {
        // See if the element being added is a Polygon
        Polygon polygon = e.Element as Polygon;
        if (polygon != null)
        {
            // We'll simplify the coordinates of the outer and inner boundaries.
            SimplifyCoordinates(polygon.OuterBoundary.LinearRing.Coordinates);
            foreach (var inner in polygon.InnerBoundary)
            {
                SimplifyCoordinates(inner.LinearRing.Coordinates);
            }
        }
    }

    private static void SimplifyCoordinates(CoordinateCollection coordinates)
    {
        // Create a copy of the collection so we can clear the original
        // and add back the 'simplified' values.
        Vector[] original = new Vector[coordinates.Count];
        coordinates.CopyTo(original, 0);

        coordinates.Clear();

        // Here you'd put your real simplification logic - for an example I'm
        // just going to round to the nearet integer value.
        for (int i = 0; i < original.Length; i++)
        {
            var simplified = new Vector(
                Math.Round(original[i].Latitude),
                Math.Round(original[i].Longitude),
                Math.Round(original[i].Altitude.GetValueOrDefault()));
            coordinates.Add(simplified);
        }
    }
}

Hope it helps or at least guides you to a solution, but let me know if there are any difficulties.

Oct 11, 2011 at 11:06 PM

Thanks for the clear response, both for the reasoning behind the current implementation, and for the code example. Very thorough, clear and informative.

Feb 29, 2012 at 2:19 PM

In libkml you can easily loop through children of any container node:


ContainerPtr container = kmldom::AsContainer(some_feature);
for( size_t i = 0; i < container->get_feature_array_size(); i++)
{
    const FeaturePtr current_feature = container->get_feature_array_at(i);
    //...
}

It would definitely be nice to have a means of traversing an element's children (and none of its children's children) as in some cases that is what you want and the current solution (which I am guessing at, below) is terribly inefficient.

 


Document doc = ...;// some document
foreach (Element element in doc.Flatten().Where(x => x.Parent == doc))
{
      // Do stuff...
}

Coordinator
Feb 29, 2012 at 3:20 PM

The library was designed to make moving from libkml easy, hopefully! However, certain aspects have been changed to better fit in with C# idioms, such as using IEnumerable instead of the get_xxx_size/get_xxx_at methods of the C++ version. Therefore, the C# equivalent of what you posted would be:

var container = someFeature as Container;
if (container != null)
{
    foreach (var currentFeature in container.Features)
    {
        // Code goes here
    }
}
Let me know if you get stuck (though you're probably better starting a new thread).

Feb 29, 2012 at 3:47 PM

Excellent. I was having a hard time finding that myself.