Skip to main content

Earth Animations for Education from NASA

August 16, 2005


The Scientific Visualization Studio
SVS Animations in Your Software
   EarthFlicks, the Example Application
Step 1: Determining What's Available
   Describing a Capabilities Entry
Step 2: Forming Requests and Retrieving Images
   Threaded Retrieval
Step 3: From Image to Texture Map
Step 4: Displaying and Animating the Images
Playing with the Globe

You've seen the beautiful images of Earth from space taken by NASA
astronauts and satellites. By viewing a series of images one after
another like frames of a movie, we can watch changes to the Earth as
they happened. You can follow hurricanes moving towards land and
observe ice caps contracting. You can replay and study changes due to
weather, natural events, and human activities. NASA has assembled
dozens of such animations, all free. This article describes how
to write Java software that uses the
OpenGL graphics interface to display these images on a 3D globe.

The Scientific Visualization Studio

A team of expert scientists, engineers, and artists at NASA's Scientific
Visualization Studio (SVS) carefully compose these animations. This group
performs their magic at NASA's Goddard Space Flight Center in Maryland.
They have been creating animated visualizations of Earth and space science
data for over 15 years. They select images from a seemingly infinite
collection of NASA imagery. They design each animation to illustrate a
particular phenomenon or event. Here are two examples:

Figure 1

Figure 1.
African fires during 2002

Figure 2

Figure 2.
Atmospheric water vapor during 1998

Recently, SVS established an image server to make some of the best earth
science animations available programmatically over the internet to software
applications. Currently about 80 are available; that will grow to over 130 by
October, 2005. You can see the growing list at the
SVS server site.
The full SVS catalog contains over 2500 animations and is at
the SVS home page.

SVS animations dynamically illustrate the formation of what's shown
in these static images: The fires in Africa progress southwards with
the season. The vapor and rain (yellow) flow across the globe as the
days progress. You can see more single shots like those shown
above at the SVS website, but they're really meant to be seen
"in motion," wrapped around a 3D Earth. The SVS website doesn't do
that. You need a software program running on a local computer to get
the full effect. You also need the ability to interact with the model
by turning the world around and zooming in and out. In this article, I
describe a bare-bones program, in order to keep the
code easily understandable. The best program for viewing SVS
animations is the free and open source
program NASA World

SVS Animations in Your Software

SVS imagery is designed to be used in educational software. The web service
responds to HTTP requests by returning an image. There's a strict
protocol and request language, with the request parameters tacked on
to what we'd consider a conventional URL.

Four steps are required to retrieve and display SVS
animations in 3D:

  1. Query the SVS server to determine the animations that are
    available from the server's table of contents.
  2. Form and send requests and retrieve the images of the
    animation. Each image requires a separate request.
  3. Convert the images to texture maps so they can be wrapped around
    a globe using OpenGL.
  4. Display the images in sequence to "play" the animation.
EarthFlicks, the Example Application

The code I'll use to demonstrate and explain all this is a small
Java application called EarthFlicks, which is available for download at
the end of this article. EarthFlicks creates two windows: one for
selecting and retrieving animations from SVS, the other for showing
and playing them. Figure 3 shows what it looks like:

Figure 3

Figure 3.

Selecting an animation in the table and clicking the Import button
causes EarthFlicks to retrieve and cache locally all the images for
that animation. When they are subsequently played, EarthFlicks
recognizes them in the cache and draws the images from there, rather
than from the SVS server.

EarthFlicks has been kept as simple as possible in order to keep
its code clear and useful in a tutorial. However, retrieving and
playing SVS animations requires a lot of code; more than I would have
guessed before I started writing EarthFlicks. Not all of that code can be
listed in this article. What are listed are important details unique to
the task. The rest is documented in the source files themselves, which
are available at the location mentioned above.

Step 1: Determining What's Available

As of July 2005, SVS holds 90 animations and 25 static
images, such as the Blue Marble imagery of a
cloudless Earth and really cool composites of the Earth at
night. This collection is growing and is expected to contain 130
animations by October 2005.

The server advertises the available animations in
the href="">
capabilities document
. This XML document includes the names of the
images, URLs for requesting them, the image formats it can provide them
in, descriptions of their sizes, regions of the Earth, and other
information that's useful or not, depending on how the images are to be
used. WMS is an ISO standard for web services that serve maps and
other geographic information. The specification is at the
specification page
. There's also a tutorial I wrote
at The
Code Project
for C# programmers, but
describes WMS in general. The standard is in its third revision,
1.3.0, which was final in 2004. Industry and government have been
using WMS for about three years.

WMS defines two primary requests and their parameters. One request,
GetCapabilities, asks for a server's table of contents,
as above. The other, GetMap, asks for a specific
image. The first part of either request is the server's address and
the path to its WMS web service. This is everything before the
question mark in the link above. The second part, the URL's query
string, contains the actual request and its parameters. An image request is
similar to the capabilities request, but has many more parameters, as I
describe below.

The simplicity of this scheme really impresses me. It makes it
possible to use the standard network-software infrastructure that a
run-time platform already provides. The classes and libraries a
programmer needs to issue the requests are merely the ones they'd use
to create and send any HTTP request. In Java, that's simply the class.

Describing a Capabilities Entry

An image request needs some of the information included in the capabilities
document. Example 1 shows an excerpt from the SVS server's capabilities document.
It describes just one of the available animations, which is a sequence of images
showing the smoke flow from large
Southern California fires in the winter of 2003. One frame of that animation is shown
in Figure 4.

Figure 4

Figure 4.
Aerosols from California fires

Example 1.
The partial Layer entry for the California Fires animation

<Layer opaque="0" noSubsets="1"
    fixedWidth="640" fixedHeight="384">
  <Title>Aerosols from 2003 Southern California Fires
      (640x384 Animation)</Title>
  <Dimension name="time" units="ISO8601"

Animations and single images are called
layers in WMS. The attributes of the Layer element
indicate that its images contain transparent pixels (opaque=0),
that images must be requested only at the latitudes and longitudes given in the
EX_GeographicBoundingBox element
(noSubsets=1), and that the server will always return an
image that is 640 pixels wide and 384 pixels high
(fixedWidth=640 and fixedHeight=384).

The Name element uniquely identifies this sequence of
images on the server. This is not a name you'd show an application
user, however. That's why the layer definition also contains
a Title element. Some image sequences span the entire
globe, but others, like this one, focus on a particular region. That
region is identified by latitude and longitude in the
element EX_GeographicBoundingBox.

So where is the sequence that forms the animation? That is
implied in the Dimension element. Notice that the dimension element's
body is a string with three parts separated by slashes


The first part indicates the first date and time of the sequence,
the second part the final date and time, and the third part specifies
the period (P) of the images between. In this case, the
period is one day (P1D). What all this tells us is that
the server holds separate images corresponding to every day from
October 23, 2003 to November 1, 2003. We
can request any or all of those.

Other animations might have dimensions in days, hours, months, or
minutes, or a combination of those. They may have dimensions other
than time, too, but I don't go into that here.
The full syntax for dimensions is
described in the WMS specification.

Example 1 shows only some of the information about
layer 3132_21145. The full entry in the capabilities
document also contains an Abstract element that gives a more
elaborate textual description of the connection between the heavy
rainfall and the mudslides. Also there are keywords, attribution (to
NASA), a URL to the logo to go with that, and a URL to retrieve a
legend image identifying the meaning of the colors in the images. The
SVS server documentation has more detailed examples and a description of
layer definitions. That documentation is at
the SVS
document site
.The EarthFlicks source code contains classes to
download, parse, and make sense of the server's capabilities

Step 2: Forming Requests and Retrieving Images

The individual images of an SVS animation are retrieved one by one
with a separate WMS GetMap request for each image in the sequence. In
most cases, those requests vary only in the date and time of the image
they ask for.

Example 2 contains another excerpt from the SVS capabilities
document. It describes an animation illustrating the changes in the
global biosphere between August 1997 and July 2003. Notice the
Dimension element, which is much more complex than the
one described in the previous section. Here, there are seven date
ranges rather than one; each is separated by a comma. The seven ranges
each specify contiguous dates, but those ranges are not contiguous
with each other. They are in chronological order,
however. Note, too, that no times are given in these dimensions, only

Example 2.
The partial Layer entry for the Global Biosphere animation

<Layer opaque="1" noSubsets="1"
    fixedWidth="2048" fixedHeight="1024">
  <Title>Global Biosphere from August, 1997 to July, 2003
      (2048x1024 Animation)</Title>
  <Dimension name="time" units="ISO8601"

The WMS request for the first image in the sequence of Table 2 is this:

If you click this link, you should eventually see a map of the
world with some pretty colors on it, as shown in Figure
5. It's the first image we're going to wrap around a globe later. The
image is three and a half megabytes in size, so it takes a while to download
if you're on a slow connection.

Figure 5

Figure 5.
The first image of the Global Biosphere animation

The WMS request for this image is very similar to that for the
California Fires sequence of the previous section. The parameter names
are the same, but the values differ for LAYERS, WIDTH, HEIGHT, BBOX,
and TIME. The layer name, image width, image height, and latitudes and
longitudes are different, and the request asks for a time specific to
this animation.

The request to retrieve the next image in the sequence is this one:
    SERVICE=WMS&REQUEST=GetMap&LAYERS= 2914_17554&FORMAT=image/png&

The only difference from the request for the first image is
the TIME, which is eight days later than the previous
time. We know to ask for this date because the layer's period
specification in the first range of the dimension element
is P8D. (See Table 2.) SVS will currently only return
images in PNG format. Even so, that format must be included in the map
request: FORMAT=image/png.

EarthFlicks retrieves all of an animation's images by looping
through its dimensions and issuing a request for each
image. It uses an internal class named MapRequest, which
is passed the individual parameter values and then creates the full
URL for the request. Images are retrieved by creating
a object and invoking
its openConnection() method. See the EarthFlicks
Retriever class in Example 3 for the code that does this.

Threaded Retrieval

As you've seen, images can be multi-megabytes of data. Retrieving
them is not something to be doing in an application's user-interface
thread. EarthFlicks performs the retrieval in a separate
thread. The Retriever class establishes the connection
for each image's retrieval and reads the returned stream to capture and
save the image bits. Example 3 shows the run() method of
the Retriever class, where this is done.

Example 3.
The run() method of the EarthFlicks Retriever class

package EarthFlicks;

public abstract class Retriever implements Runnable
  public class Status
    public static final int NOT_STARTED = 0;
    public static final int CONNECTING = 1;
    public static final int RETRIEVING = 2;
    public static final int POSTPROCESSING = 3;
    public static final int DONE = 4;
    public static final int INTERRUPTED = 10;
    public static final int ERROR = 11;

  // These  change with each call to start(),
  // some change as retrieval progresses.
  private int status = Retriever.Status.NOT_STARTED;
  private int bytesRead;
  private int contentLength;
  private String contentType;
  private Exception error;
  private Thread thread;
  protected String destination;

  // These variables are set by the client.
  private url;
  private Object clientObject;
  protected RetrieverClient client;

  public void start()
    this.thread = new Thread(this);

  public void run()
      this.status = Status.CONNECTING;

      if (Controller.getCaches().containsUrl(this.getUrl()))
      { destFile =
        this.destination = destFile.getPath();
        this.contentLength = (int)destFile.length();
        this.bytesRead = this.contentLength;
        this.status = Status.POSTPROCESSING;
        // Retrieve the contents.
        int numBytesRead = 0; connection =
        connection.setAllowUserInteraction(true); incoming =
        this.contentLength = connection.getContentLength();
        this.contentType = connection.getContentType(); destFile
        destFile.deleteOnExit(); // It's copied later.
        this.destination = destFile.getPath(); outgoing =

        this.status = Status.RETRIEVING;

        byte[] buffer = new byte[4096];

          while (!Thread.currentThread().isInterrupted()
             && numBytesRead >= 0)
            numBytesRead =;
            if (numBytesRead > 0)
              this.bytesRead += numBytesRead;
              outgoing.write(buffer, 0, numBytesRead);
          if (incoming != null)
            try {incoming.close();}
            catch ( e) {};
          if (outgoing != null)
            try {outgoing.close();}
            catch ( e) {};
        this.status = Status.POSTPROCESSING;
    catch (Exception e)
      this.status = Status.ERROR;
      this.error = e;
      if (Thread.currentThread().isInterrupted())
        this.status = Status.INTERRUPTED;
      if (this.status == Status.POSTPROCESSING)
        this.status = Status.DONE;

Before issuing an HTTP request to the server to retrieve an image,
the run() method checks to see whether that image is in a
local image cache that EarthFlicks maintains. Many of the animations
consist of tens or hundreds of images. To play an animation at any
useful frequency those images must be local, and probably on the
computer's local disk rather than even a fast local network.
The code for the image cache is included in this article's code

Notice that the run() method of Retriever
includes calls to methods
named begin(), update(),
and end(). These methods cause invocation of callback
methods in the retriever's client, the code that created and started
the retriever, so that the client code can update progress bars and
status readouts and otherwise monitor the retrieval. Example 4 below
shows these method implementations in Retriever. Since
the retrieval is running in a thread separate from the UI, the
callbacks cannot be invoked from the retrieval thread. They must be
invoked from the UI thread if the client is to safely update the user
interface. They are therefore only requested by Retriever to be
invoked later in the UI thread. The methods that actually invoke the
client's callbacks
are doBegin(), doUpdate(),
and doEnd(), also listed in Example 4.

Example 4.
The callback methods of the EarthFlicks Retriever class

private void begin()
    final RetrieverEvent event = this.makeEvent();
        new Runnable()
          public void run() {doBegin(event);}

  private void update()
    final RetrieverEvent event = this.makeEvent();
        new Runnable()
          public void run() {doUpdate(event);}

  private void end()
    final RetrieverEvent event = this.makeEvent();
        new Runnable()
          public void run() {doEnd(event);}

  protected void doBegin(RetrieverEvent event)
    if (this.getClient() != null)

  protected void doUpdate(RetrieverEvent event)
    if (this.getClient() != null)

  protected void doEnd(RetrieverEvent event)
    if (this.getClient() != null)

Step 3: From Image to Texture Map

At the point an image is fully retrieved, it could be read and
translated by the javax.imageio.ImageIO class and
displayed in a window as a java.awt.Image. But it would
be only two-dimensional, like the pictures above. The SVS images are
most compelling when wrapped around a 3D globe. That requires
conversion of the image to a texture map that can be displayed with
OpenGL using the Java bindings for OpenGL API ( href="">JOGL

Two things must be done to convert a PNG image to a texture map:

  1. Put the image into a format that OpenGL recognizes. Unfortunately, PNG is not one of those.
  2. Ensure that the image width and height are each a power of two.

OpenGL wants RGB or RGBA images. They can be in any one of many
possible bit depths and color precisions. In EarthFlicks I convert the
PNG images to 3- or 4-component unsigned-byte format, using
3-component RGB for images without transparency and 4-component RGBA
for images with transparency. Whether or not the PNG image contains
transparency values is indicated in its image file.

EarthFlicks opens the image with
the javax.imageio.ImageIO class, which copies the image
into a
java.awt.image.BufferedImage object, and then reads the buffered image
one pixel at a time and writes to a new file an unsigned-byte for each of the
pixel's red, green, blue, and alpha components. The code is shown in Example 5,
which lists a method in the MapRetriever class derived from the
Retriever class described in the previous section.

Example 5.
MapRetriever code converting a retrieved image to RGB or RGBA

  private void convertToRgb(RetrieverEvent event) throws Exception
    // Read the image.
    java.awt.image.BufferedImage image =;

    // Transform the image's orientation to that of OpenGL.
    java.awt.geom.AffineTransform xform =
      java.awt.geom.AffineTransform.getScaleInstance(1, -1);
    xform.translate(0, -image.getHeight(null));
    java.awt.image.AffineTransformOp op = new
    image = op.filter(image, null);

    // Convert the image to RGB or RGBA.
    java.awt.image.Raster raster = image.getRaster();
    int width = image.getWidth();
    int height = image.getHeight();
    java.awt.image.ColorModel colorModel = image.getColorModel();
    byte[] convertedImage = null;

    String suffix = "." + Integer.toString(width) + "x"
      + Integer.toString(height);
    if (colorModel.hasAlpha())
      suffix += ".rgba";
      int size = 4 * width * height;
      convertedImage = new byte[size];
      int index = 0;
      for (int y = 0; y < height; y++)
        for (int x = 0; x < width; x++)
          Object pixel = raster.getDataElements(x, y, null);
          byte red = (byte)colorModel.getRed(pixel);
          byte green = (byte)colorModel.getGreen(pixel);
          byte blue = (byte)colorModel.getBlue(pixel);
          byte alpha = (byte)colorModel.getAlpha(pixel);
          convertedImage[index++] = red;
          convertedImage[index++] = green;
          convertedImage[index++] = blue;
          convertedImage[index++] = alpha;
      suffix += ".rgb";
      int size = 3 * width * height;
      convertedImage = new byte[size];
      int index = 0;
      for (int y = 0; y < height; y++)
        for (int x = 0; x < width; x++)
          Object pixel = raster.getDataElements(x, y, null);
          byte red = (byte)colorModel.getRed(pixel);
          byte green = (byte)colorModel.getGreen(pixel);
          byte blue = (byte)colorModel.getBlue(pixel);
          convertedImage[index++] = red;
          convertedImage[index++] = green;
          convertedImage[index++] = blue;

    // Save the converted image to a file. newDest ="EFL_", suffix);
    newDest.deleteOnExit(); // Cached later if needed. outgoing = null;

      outgoing = new;
      if (outgoing != null)
        try {outgoing.close();}
        catch ( e) {};

    this.destination = newDest.getPath();

Performing the conversion this way and forming unsigned-byte color
components eliminates any headaches from dealing with format
variations. The ImageIO class takes care of converting the colors from
whatever form they're in to simple red, green, blue, and alpha
eight-bit values. In fact, this scheme will work with any file format
that ImageIO can read, not just PNG. The disadvantage to this scheme
is that it's probably the slowest way to do it. Some format variants
map one to one with RGB or RGBA when read by ImageIO, but EarthFlicks
makes no attempt to detect or take advantage of those situations.

In Example 5, the image is also "flipped" so that the origin is at
the lower left rather than the original upper left. Most image formats
have an upper-left origin, but OpenGL assumes a lower-left.

The result of this conversion is an RGB or RGBA image captured in
a BufferedImage and ready for its dimensions to be scaled
to powers of two if necessary, using
OpenGL's glScaleImage() function. This function scales an
image to any power of two, but EarthFlicks has it scale to the next
largest for the dimension (360 goes to 512, for example). The
conversion and scaling steps produce an RGB or RGBA image suitable for
OpenGL to use as a texture map. EarthFlicks saves these images as
files on the local disk, with a filename suffix of .rgb
or .rgba. Their widths and heights are encoded in their
file names.

Step 4: Displaying and Animating the Images

SVS images typically do not come with an underlying image of the
Earth's surface showing through. This is not a problem for images that
completely cover the globe. But many SVS animations make no sense
unless they're superimposed on a globe with base imagery of Earth
terrain. EarthFlicks addresses this by implementing an Earth model
that's covered with NASA's Blue Marble imagery, shown in Figure 6.

Figure 6

Figure 6.
The Blue Marble imagery

Several resolutions of the Blue Marble imagery are
available. EarthFlicks uses three of those, automatically switching to
the most appropriate and efficient as the user zooms in and out.

The SVS images can
be overlaid onto the Earth's surface. There are many ways to do
this. I chose to create a solid sphere matching the base globe, and
apply the SVS images as texture maps to that. The code for this is in
the EarthFlicks Shell class, in the Globe
package. That class computes the vertices of triangle strips to
represent the surface, and assigns appropriate texture coordinates to
fit each SVS image to the correct latitudes and longitudes.

Applying the texture to the shell is both simplified and
complicated by the texture cache EarthFlicks uses. To best use the
computer's graphics memory, EarthFlicks maintains a cache that keeps
the most recently used textures in memory. This cache is responsible
for opening texture files when they are first needed, managing them in memory,
and binding them to OpenGL texture
identifiers. The TextureCache class does all of this. The
code for that class also shows how EarthFlicks specifies textures and
makes them current in OpenGL.

I've found that the fastest way to get an image from disk to memory
is by memory mapping the image
file. Java's java.nio.MappedByteBuffer makes this very
easy. EarthFlicks maintains a singleton mapped-file cache that maps
and manages memory-mapped image files. The implementation is in
the MappedFileCache class in the Globe

This mapped-file cache is the third cache I've described for
EarthFlicks. To recap, there's one cache to hold
the converted images to be used as texture maps permanently on disk.
There's another cache
to manage run-time texture loading and binding to OpenGL. Finally, there's
a third cache to manage memory-mapped image files.

Playing with the Globe

At this point, there exist sequences of images from the SVS
server converted from PNG to images that can be used by OpenGL as
texture maps, all cached on a local disk. There's also code to map
these images into memory, bind them to OpenGL, and display them on a
globe with a pretty Blue Marble background surface. But these images
do not "play" as animations yet.

Playing the images is probably the simplest part of all. The ImagePlayer class in EarthFlicks accepts an
image sequence and plays it by setting a timer and displaying
consecutive images of the animation at each timer
tick. Its start() method kicks off the animation. When
all the images in the sequence have been displayed, ImagePlayer
invokes a callback to EarthFlicks' Controller class to return the user
interface to the pre-animation state, ready for the animation to be
run again.

If an image sequence is specific to a region of the
globe, ImagePlayer positions the viewpoint over that
region before playing the animation. It also has methods to stop,
pause, and resume the animation, and to remove it from the globe.

One last thing to do is provide a means for the user to turn the
globe and zoom in and out. EarthFlicks implements a very simple
mechanism for this: a rectangular region defined by latitude and
longitude is defined as a viewing window onto the globe. Moving this
region makes the globe appear to rotate. Expanding or contracting it
has the effect of zooming in and out. This is not a full 3D
manipulation model that allows the user to "fly around" in any
orientation. The user is always looking directly at the globe, and the
viewing direction is always perpendicular to the Earth's surface. If
EarthFlicks were to more realistically represent the Earth by applying
surface elevation, this manipulation model would be inadequate.

This manipulation scheme does have a couple of advantages: it allows
for a very quick, high-level clipping scheme to avoid drawing regions
of the globe that are not in view. And it keeps the user oriented to
the globe so that "up" is always north.


The SVS animations are designed to be used in visualization
programs for education. I've described in this article how to retrieve
the images composing those animations and display them on a 3D model
of the Earth. There isn't room here to describe or even list all the
code involved in doing that, so I've tried to identify, explain, and
illustrate the tasks unique to this type of application and the SVS
imagery. Much of this information, of course, can also apply to images
and image sequences available from other WMS servers.


The author can be reached at

Creation of this article and the example application was funded by NASA.

width="1" height="1" border="0" alt=" " />
Tom Gaskins is the technical director for NASA Learning Technologies.