Google Earth API

DOM Part II: Using the GEarthExtensions Utility Library for Easy DOM Manipulation

Roman Nurik, Google Geo APIs Team
August 2009

Contents

Objective & Prerequisites

In Part I: An Introduction to the Earth API Document Object Model, I discuss the HTML and KML Document Object Models (DOMs) and their relation to the Earth API. In this article, we'll learn about the GEarthExtensions utility library's method of simplifying DOM traversals and how we can easily perform common DOM search and manipulation operations on entire KML document trees using this method.

This article assumes you are familiar with JavaScript and know the basics of the Earth API. It also assumes you have an understanding of the concepts discussed in Part I.

Depth-first traversal, or 'walking the DOM' in the Earth API

Many common operations in the Earth API require running a computation over (potentially) the entire KML DOM structure of either the plugin root object (GEPlugin) or KML content loaded via fetchKml or parseKml.

Since KML is hierarchical and thus tree-like, it is possible to perform such a computation using a depth-first tree traversal. To enact this traversal, we can use a variety of Earth API DOM access methods discussed in previous sections as well as in the object containers section of the Earth API documentation, such as GESchemaObjectContainer.getChildNodes and the more specialized KmlPlacemark.getGeometry.

Note: For more information on loading KML content in the Earth API, read An Overview of Using KML in the Earth API.

Abstraction in the GEarthExtensions utility library

The GEarthExtensions Earth API utility library provides a variety of helper functions that developers can use to simplify their application code. The gex.dom.walk helper method in this library is specifically designed to simplify DOM traversal in Earth API applications.

Before we dive into an example of what this simplified DOM traversal code looks like, let's see all the complex code behind gex.dom.walk that you don't need to worry about thanks to the utility library:

/**
 * Performs a depth-first tree traversal on options.rootObject,
 * calling options.visitCallback on every KmlFeature and KmlGeometry in the
 * hierarchy. Minor elements such as <name> and <coordinates>, for
 * example, are not traversed.
 */
GEarthExtensions.prototype.dom.walk = function(options) {

  // Define code to be called for each node in the tree.
  var _visit = function(object) {
    // Visit the current node.
    var retValue = options.visitCallback.call(object);

    // Stop the traversal if the visit callback returns false.
    if (retValue === false)
      return false;

    // After visiting this node, see if it has children, and visit them.
    var objectContainer = null;
    if ('getFeatures' in object) {
      // This <Document> or <Folder> can have child features.
      objectContainer = object.getFeatures();
    } else if ('getGeometry' in object && object.getGeometry()) {
      // This feature has a geometry child.
      _visit(object.getGeometry());
    } else if ('getGeometries' in object) {
      // This <MultiGeometry> can have child geometries.
      objectContainer = object.getGeometries();
    } else if ('getOuterBoundary' in object) {
      // This <Polygon> can have outer and inner boundary geometries.
      if (object.getOuterBoundary())
        _visit(object.getOuterBoundary(), contextArgument.child);
      objectContainer = object.getInnerBoundaries();
    }

    // Iterate and visit the object's children if it is a container.
    if (objectContainer && objectContainer.hasChildNodes()) {
      var childNodes = objectContainer.getChildNodes();
      var numChildNodes = childNodes.getLength();

      for (var i = 0; i < numChildNodes; i++) {
        var child = childNodes.item(i);
        if (!_visit(child, contextArgument.child))
          return false;
      }
    }

    return true;
  };

  // Begin the traversal.
  _visit(options.rootObject);
};

The outer method performs a depth-first tree traversal on options.rootObject, calling a nested, recursive function _visit for each KmlFeature and KmlGeometry under the root object's DOM hierarchy. _visit in turn calls the user defined callback function options.visitCallback, which contains the code for the desired DOM operation.

Sample usage

As a developer using the utility library, however, the code you need to use in your own application for a DOM traversal can be quite simple. For example, the snippet below shows a basic DOM traversal that counts the number of line strings in the plugin:

// Instantiate the utility library.
var gex = new GEarthExtensions(ge);
var numLines = 0;

gex.dom.walk({
  rootObject: ge,
  visitCallback: function() {
    // 'this' is the current DOM node being visited.
    if ('getType' in this && this.getType() == 'KmlLineString')
      ++numLines;
  }
});

alert('There are ' + numLines + ' lines.');

Common patterns and use cases

Below are several commonly used operations that can be modeled as DOM traversal problems and implemented using gex.dom.walk.

Finding an object by its ID

One oftenly requested feature is a function to retrieve an object by its KML ID, similar to document.getElementById in JavaScript. Although this is already available in the aforementioned utility library as gex.dom.getObjectById, let's take a look at how to implement this using only the gex.dom.walk function.

The below code assumes that the following global variables are already defined:

  • gex: an instance of GEarthExtensions
  • ge: an instance of GEPlugin, loaded via google.earth.createInstance
/**
 * Returns the object in the DOM with the given ID, or null if
 * none was found.
 */
function getObjectById(id) {
  var foundObject = null;

  // Traverse the DOM, looking for the object with the given ID.
  gex.dom.walk({
    rootObject: ge,
    visitCallback: function() {
      if ('getId' in this && this.getId() == id) {
        foundObject = this;
        return false;  // Stop the walk.
      }
    }
  });

  return foundObject;
}

Finding all objects of a given type

Another common use case for DOM traversal is to retrieve all objects of a given object type (such as KmlTour). The below code shows how to play the nth tour in a loaded KML file.

/**
 * Plays the nth (0-based) tour in the file at the given KML URL; indexing
 * is depth-first.
 */
function playTour(kmlUrl, index) {
  google.earth.fetchKml(ge, kmlUrl, function(fetchedKmlObject) {
    if (!fetchedKmlObject)
      return;

    var tours = [];

    // Find all the tours.
    gex.dom.walk({
      rootObject: fetchedKmlObject,
      visitCallback: function() {
        if ('getType' in this && this.getType() == 'KmlTour')
          tours.push(this);
      }
    });

    // Play the nth tour
    if (index < tours.length) {
      ge.getTourPlayer().setTour(tours[index]);
      ge.getTourPlayer().play();
    }
  });
}

Applying a styleUrl to every placemark in a document

Lastly, to change the styleUrl for each placemark inside a given document or folder:

/**
 * Sets the style URL of each placemark under the given root document or folder.
 */
function updateStyleUrls(container, styleUrl) {
  gex.dom.walk({
    rootObject: container,
    visitCallback: function() {
      if ('getType' in this && this.getType() == 'KmlPlacemark')
        this.setStyleUrl(styleUrl);
    }
  });
}

Traversal performance

A non-negligible cost is incurred the first time a node in the DOM tree is visited because a JavaScript object corresponding to that node must be initialized. Thus, in practice, DOM traversals over large KML files can take a long time and large amounts of memory if care is not taken to limit the scope of the search.

In the actual gex.dom.walk implementation (versus the simplified version above), it is possible to selectively prevent descending into a given node's subtree by setting a walkChildren flag to false from within the visit callback.

For more details on walkChildren and precise documentation for gex.dom.walk, see the relevant GEarthExtensions API reference.

Next steps

This article discussed the GEarthExtensions utility library for the Google Earth API, which has a dom.walk method that simplifies common DOM traversal problems such as finding elements of a given type or by ID.

To learn more about the utility library discussed in this article, visit the GEarthExtensions homepage.

Authentication required

You need to be signed in with Google+ to do that.

Signing you in...

Google Developers needs your permission to do that.