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

Google Earth Tour

Jun 21, 2011 at 2:41 PM

Hi,

First of all, congratulations for the excellent job! Your library is so easy to use!

Despite the ease of use, I ran into a few difficulties when trying to create a Google Earth Tour.

First, as stated on http://code.google.com/apis/kml/documentation/kmlreference.html#kmlextensions, this URI: "xmlns:gx="http://www.google.com/kml/ext/2.2" must be added to the <kml> element, in order to get this:

<kml xmlns="http://www.opengis.net/kml/2.2"
 xmlns:gx="http://www.google.com/kml/ext/2.2">

Second, all elements that have the gx-prefix (see http://code.google.com/apis/kml/documentation/kmlreference.html#kmlextensions, again) appear without the "gx:" prefix, although they are not part of the OGC KML standard.

Without the "gx:" prefix added to the extension tags and the namespace URI added to the <kml> element, Google Earth does not play the tours properly (i.e. not at all), although it does not complain about wrong syntax (even if configured to do so).

How can I add the URI and the prefixes without having to post-process the kml files I'm generating?

Best regards,

Andrei

 

Coordinator
Jun 21, 2011 at 8:47 PM

Thanks for the kind comments.

The library should support some of the Google extensions in the SharpKml.Dom.GX namespace. Looking at the example from the TourPrimitive (which doesn't actually work in Google Earth 6.0.3.2197, perhaps because the namespace for gx isn't specified in the example?) I was able to get the following to work in Google Earth (though maybe not the best example of how to organise your code - I tried to copy the example as close as I could).

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

class Program
{
    static void Main(string[] args)
    {
        var animationUpdate = new SharpKml.Dom.GX.AnimatedUpdate();
        animationUpdate.Duration = 6.5;
        animationUpdate.Update = new Update();
        animationUpdate.Update.AddUpdate(new ChangeCollection
        {
            new IconStyle { TargetId = "iconstyle", Scale = 10.0 }
        });

        var flyTo = new SharpKml.Dom.GX.FlyTo();
        flyTo.Duration = 4.1;
        flyTo.View = new Camera
        {
            Longitude = 170.157,
            Latitude = -43.671,
            Altitude = 9700,
            Heading = -6.333,
            Tilt = 33.5,
            Roll = 0
        };

        var wait = new SharpKml.Dom.GX.Wait();
        wait.Duration = 2.4;

        var tour = new SharpKml.Dom.GX.Tour();
        tour.Name = "Play me!";
        tour.Playlist = new SharpKml.Dom.GX.Playlist();
        tour.Playlist.AddTourPrimitive(animationUpdate);
        tour.Playlist.AddTourPrimitive(flyTo);
        tour.Playlist.AddTourPrimitive(wait);

        var document = new Document();
        document.Name = "gx:AnimatedUpdate example";
        document.Open = true;

        document.AddStyle(
            new Style
            {
                Icon = new IconStyle
                {
                    Id = "iconstyle",
                    Scale = 1.0
                }
            });

        document.AddFeature(
            new Placemark
            {
                Id = "mountainpin1",
                Name = "New Zealand's Southern Alps",
                StyleUrl = new Uri("#style", UriKind.Relative),
                Geometry =
                    new Point
                    {
                        Coordinate = new Vector(-43.605, 170.144, 0)
                    }
            });

        document.AddFeature(tour);

        // Quick way to save the output
        var kml = new Kml { Feature = document };
        KmlFile.Create(kml, false).Save("output.kml");
    }
}
Jun 22, 2011 at 10:49 PM

Sam, thanks for your quick answer. You're faster than a normal helpdesk :-)

The sample code from the TourPrimitive you mentioned is indeed not working. They miss what I mentioned in my first post, namely the second attribute of the <kml> element, which comes on the second line in this snippet:

<kml xmlns="http://www.opengis.net/kml/2.2"
 xmlns:gx="http://www.google.com/kml/ext/2.2">

Another thing they're missing is the "id" attribute for the style. Their declaration should look like this:

<Style id = "style">

If you add these two modifications their example will work. In fact I remember I encountered the same example in their tutorials and it worked at that time, so I looked back and I found it here, where they teach about tour updates. The code given there contains the two corrections I mentioned and works properly. They just forgot to also update the example in the reference documentation.

Coming to your C# code, I compiled/ran it and the output suffers more or less from the same problem as my outputs.

First of all, there are the two problems mentioned above.

Second, all Google extension elements miss the "gx:" prefix. For example, your program outputs <Tour> instead of <gx:Tour>

Third, your code adds an xmlns attribute to the <Tour> element, making it <Tour xmlns="http://www.google.com/kml/ext/2.2"> . The tour will work only after deleting the xmlns attribute.

There is also a fourth problem I encountered with SharpKML, but not in this code. Namely, the <gx:flyToMode> element is written as <gx:FlyToMode>, which is incorrect, as KML is case sensitive. The tour won't work with capital F, unfortunately.

My questions remain therefore the same:

1) How to add the xmlns:gx attribute to the <kml> element?

2) How to add the "gx:" prefixes to the non-standard Google extensions?

3) This is new, but it's still a problem: How to avoid adding the xmlns attribute to the <gx:Tour> element?

4) I put this here again, for the record: How can I correct the <gx:FlyToMode> element to start with lower case: <gx:flyToMode>?

Coordinator
Jun 23, 2011 at 1:51 AM
Edited Jun 23, 2011 at 1:52 AM

I ran the output of the C# code that I posted though Google Earth (6.0.3.2197) and it worked fine as it is valid KML, though it might look a bit different to what you're expecting. KML extends XML, which has namespaces. The xmlns attribute specifies the default namespace for the element (and children) if no prefixes are found. However, if you're determined to use the gx prefix then you've got a couple of options (though they're both the same really). You can either change the Namespaces property of the Element class (Dom/Element.cs, line 51) from internal to public or you can use reflection to get hold of it (shown here).

using System;
using System.Reflection;
using System.Xml;
using SharpKml.Base;
using SharpKml.Dom;

class Program
{
    static void Main(string[] args)
    {
        var flyTo = new SharpKml.Dom.GX.FlyTo();
        flyTo.Mode = SharpKml.Dom.GX.FlyToMode.Bounce;

        var tour = new SharpKml.Dom.GX.Tour();
        tour.Playlist = new SharpKml.Dom.GX.Playlist();
        tour.Playlist.AddTourPrimitive(flyTo);

        var kml = new Kml();
        kml.Feature = tour;
        AddNamespace(kml, "gx", "http://www.google.com/kml/ext/2.2");

        Serializer serializer = new Serializer();
        serializer.Serialize(kml);
        Console.WriteLine(serializer.Xml);
    }

    static void AddNamespace(Element element, string prefix, string uri)
    {
        // The Namespaces property is marked as internal.
        PropertyInfo property = typeof(Element).GetProperty(
            "Namespaces",
            BindingFlags.Instance | BindingFlags.NonPublic);

        var namespaces = (XmlNamespaceManager)property.GetValue(element, null);
        namespaces.AddNamespace(prefix, uri);
    }
}

This will output the elements with the gx prefix (and also shows that when the FlyToMode enum is serialized it will change its case)

<?xml version="1.0" encoding="utf-16"?>
<kml xmlns:gx="http://www.google.com/kml/ext/2.2"
 xmlns="http://www.opengis.net/kml/2.2">
  <gx:Tour>
    <gx:Playlist>
      <gx:FlyTo>
        <gx:flyToMode>bounce</gx:flyToMode>
      </gx:FlyTo>
    </gx:Playlist>
  </gx:Tour>
</kml>

Hope it helps to answer your questions.

Jun 23, 2011 at 2:03 PM

Thanks a lot, this answers my questions, indeed.

As a side note, the code from your first post does not work properly, although I have the same version of Google Earth (6.0.3.2197). I tried it on two computers, one of which having a fresh install of Google Earth. Only the FlyTo part works. If one also wants to see the pin growing (which is part of the tour, too), he has to make the modifications I indicated in my previous posts.

A very curious thing that happened yesterday was that Google Earth was not sensitive to the gradual changes I manually made to the KML output of your code from the first post (the "output.kml" file). I already had another similar file elsewhere which I knew it was ok, and I was gradually modifying "output.kml" to see which minimal changes needed to be done in order to make it work. At some point I made all the changes, so the two files were identical, but Google Earth wouldn't play "output.kml" properly. Restarting Google Earth (without saving "output.kml" to My Places) had no effect. Copying "output.kml" outside bin/Debug was the only move that made Google Earth accept the file...

Yet another thing happened yesterday: after freshly installing Google Earth, I turned on the error handling (Tools->Options->General->"KmL Error Handling") and restarted GE. Upon restarting, GE complained (if I remember correctly) that a "<styleUrl>" belonging to "My Places" (which was generated on the first run) was an "unknown element". I inspected the file where "My Places" was saved and indeed there was this bogus <styleUrl> element wrongly placed either right before or right after a "</FlyTo>" element, so I deleted it and GE did not complain anymore after restart. The same error also happened before in the non-fresh install of GE, when turning on the error handling. At that time I thought it was my fault as I had already added other stuff to "My Places" (but not manually, so I was still a bit surprised).

All this reminds me of some other case, where I had to load the same file twice before being able to play the tour.

I suspect GE does not clean its internal memory properly, so things can still hang in which affect consequent operations, though a restart should normally reset everything. This hypothesis makes sense when thinking that GE needs to cache a lot of data for efficiency reasons. Who knows, maybe it also preserves some cache on the disk, between two starts...

Well, maybe some things also happened because of my limited experience with XML/KML, but I'm still pretty sure that GE is not always behaving consistently. Otherwise it's a great product, no question about that.

Sorry for this long post, I just wanted to share you some of my experiences with GE while using SharpKML.

Thank you again for your prompt and helpful answers, and for sharing SharpKML with us. Kudos!

Coordinator
Jun 24, 2011 at 12:43 AM

No need to apologise for the post - all feedback is welcome.

I've never used Google Earth so when I loaded the file I saw it had loaded the "Play Me!" (which I didn't see in the original example on the KML Reference page) so assumed it had worked, sorry about the confusion.

I've just been playing with it and the only way to get it to enlarge the icon is to use the gx prefix you mentioned. I'm pretty confident this is an issue with how Google Earth handles namespaces in XML - SharpKml uses the built in XML library of the .NET Framework so I don't think the problem is there. Therefore, I won't be making any changes to the library to accommodate this as I want to keep it compliant with the Open Geospatial Consortium KML 2.2 standard, though I might add an extension method that will let you add namespaces and prefixes to an element (I tried to get the Google extension elements to register and use the gx prefix but it doesn't seem to help unless the prefix is registered on the root Kml element, so I don't think there's an automatic process of adding the prefix when needed.)

Aug 14, 2012 at 5:37 PM

Thanks so much for all your effort.

This is indeed a very usefull library.

As an update.

I've discovered that the original problem with the code sample was a missing style ID.

document.AddStyle(
   new Style
   {
       Id = "style0",
       Icon = new IconStyle
       {
           Id = "iconstyle",
           Scale = 1.0
       }
   });
 
document.AddFeature(
   new Placemark
   {
       Id = "mountainpin1",
       Name = "New Zealand's Southern Alps",
       StyleUrl = new Uri("#style0", UriKind.Relative),
       Geometry =
           new Point
           {
               Coordinate = new Vector(-43.605, 170.144, 0)
           }
  });


This works fine for me with proper animation of the pin size.