Serving BIL Elevation Data with Geoserver

I’ve recently been looking into how to use the GeoServer map server to serve elevation data to the NASA World Wind virtual globe. It is possible to do this, but there are some complications. In this article I’m going to explain some of the issues.

Update: World Wind recently added support for consuming elevation data from a Web Coverage Service (WCS), which gets around many of the challenges of working with BIL. For many applications this may be a better solution than using WMS and BIL. See my post on working with WCS elevation data in GeoServer for more details and some caveats.

Update: I recently submitted a GeoServer patch that adds a configuration panel to the BIL plugin. This allows the server administrator to configure GeoServer to work with World Wind without any client side configuration. See DDS/BIL Plugin Documentation for details.

World Wind retrieves elevation data on demand from a Web Map Service (WMS), and expects this data to be available in the Band Interleaved Line (BIL) format. GeoServer, out the box, does not support output in BIL, but there is a plugin that adds support for this format. So the steps to serve elevation to World Wind are

  1. Install the GeoServer BIL plugin.
  2. Configure elevation raster data as a layer in GeoServer.
  3. Configure an elevation model in World Wind to connect to the server.

Let’s look at an example of how to do this, and then discuss some of the pitfalls. GeoServer comes with a sample data set of elevation data for Spearfish, South Dakota. This data is provided as a GeoTIFF, and we will use the BIL plugin to convert it on-the-fly to BIL. First we need to configure the server to serve this data as BIL, and then connect to the WMS layer using World Wind.

  1. Download GeoServer and unzip the archive. I’ll refer to the directory of the unzipped GeoServer install as GEOSERVER_ROOT
  2. Download the DDS/BIL plugin and extract the archive into GEOSERVER/webapps/geoserver/WEB-INF/lib.
  3. Start the server by running GEOSERVER/bin/startup.sh.
  4. Configure an elevation model in World Wind to use the layer served by GeoServer. Here’s one way to do this:
// Parse the capabilities document from the WMS
final String GET_CAPABILITIES_URL = "http://localhost:8080/geoserver/wms?request=getCapabilities";
WMSCapabilities caps = WMSCapabilities.retrieve(new URI(GET_CAPABILITIES_URL));
caps.parse();

// Configure parameters for the Spearfish elevation model.
AVList params = new AVListImpl();
params.setValue(AVKey.LAYER_NAMES, "sf:sfdem");
params.setValue(AVKey.IMAGE_FORMAT, "application/bil32");
params.setValue(AVKey.BYTE_ORDER, AVKey.BIG_ENDIAN);
params.setValue(AVKey.MISSING_DATA_SIGNAL, -9.999999933815813E36);

Factory factory = (Factory) WorldWind.createConfigurationComponent(AVKey.ELEVATION_MODEL_FACTORY);
final ElevationModel spearfish = (ElevationModel) factory.createFromConfigSource(caps, params);

SwingUtilities.invokeLater(new Runnable()
{
    public void run()
    {
        // Get the WorldWindow's current elevation model.
        Globe globe = AppFrame.this.getWwd().getModel().getGlobe();

        // Replace elevation model with imported elevations. This makes the elevation 0 everywhere
        // except in the region imported, so it is easy to tell that the elevations are being pulled
        // from geoserver. For production use create a CompoundElevationModel and add the new elevations
        // to the compound model.
        globe.setElevationModel(spearfish);

        // Set the view to look at the imported elevations.
        Position spearfishSouthDakota = Position.fromDegrees(44.4709, -103.6812, 10000);
        getWwd().getView().setEyePosition(spearfishSouthDakota);
   }
});

(Full example code available on github.)

The example above is sufficient to load elevations from GeoServer. However, you will notice that the example required several parameters to be configured on the World Wind side. Unfortunately, the client needs three pieces of information to interpret the elevation data, and GeoServer does not provide this info. Specifically, we need to know the data type, the byte order, and the missing data value of the BIL files. To understand these settings it is helpful to understand the details of the BIL file format.

What is BIL anyway?

BIL stands for Band Interleaved Line, and is the file format that World Wind uses to store elevation data. More generally, BIL is a simple data format for multi-band raster data. There is a good description of the file format in the ArcGIS User Manual.

BIL files are often accompanied by a text header file that specifies the data type, missing data value, geographic extent, band names, etc. There are (at least) two different formats for these files: the ESRI header format and the ENVI header format. Without a header file a BIL file is just an opaque bunch of bytes and the client must know the data type, array dimensions and so on in order to interpret the file. When GeoServer returns BIL data to World Wind it provides only the raw bytes, not the header file. So the programmer needs to provide the missing information.

Data type

When World Wind asks a WMS server for a map it specifies what type of file the server should return. There are several MIME types used for BIL files:

  • image/bil
  • application/bil
  • application/bil16
  • application/bil32

If you use the application/bil16 or application/bil32 then World Wind can infer the data type from the MIME type (either 16 bit integer or 32 bit floating point). However, World Wind assumes that image/bil and application/bil are 16 bit integer, which is not always the case for data served by GeoServer. To avoid data type mismatches it’s best to explicitly request either application/bil16 or application/bil32.

params.setValue(AVKey.IMAGE_FORMAT, "application/bil32");

Byte order

As well as the data type, World Wind needs to know the byte order (big or little endian) of the BIL files. If not specified, World Wind will assume that the data is little endian. However, the GeoServer BIL plugin produces files in big endian order (network byte order). So the client code must specify this byte order:

params.setValue(AVKey.BYTE_ORDER, AVKey.BIG_ENDIAN);

Missing data signal

Missing data value in GeoServer interface

Refer to the “Coverage Band Details” section in the GeoServer Layer editor to find the missing data value for your elevation layer.

Elevation data sets often include a special value that marks pixels for which no data is available. Data might be unavailable because the data set covers only a certain area, or because the elevation could not be measured for some pixels. World Wind needs to know that it should ignore these values.

The BIL plugin does not set the missing data value, so whatever missing data value is used in the layer source data will be passed to the generated BIL files. In the case of the Spearfish DEM, this value is -9.999999933815813E36. You can see the missing data value in the GeoServer admin interface under the layer editor page. You can also find the missing data value using the gdalinfo command line tool.

By default World Wind uses -9999 as the missing data value. If this is not the value set in your elevation source data you will need to either set the missing data value in your layer parameters or change the missing data value in the source data.

params.setValue(AVKey.MISSING_DATA_SIGNAL, -9.999999933815813E36);

Changing the missing data value using GDAL

It is straight forward to change the missing data value of a raster file using the gdalwarp tool. To translate the Spearfish DEM to use -9999 as the missing data value (the World Wind default):

gdalwarp -dstnodata -9999 sfdem.tif sfdem_out.tif

Summary

It is possible to configure GeoServer to serve elevation data in a format that World Wind can consume, but it requires some configuration on both sides. If elevations are not rendered correctly you have probably misconfigured either the data type, byte order, or missing data settings in client code.

This entry was posted in Geospatial and tagged , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *