Index: /geovis/branches/1.0/CmdProc.cpp
===================================================================
--- /geovis/branches/1.0/CmdProc.cpp	(revision 6666)
+++ /geovis/branches/1.0/CmdProc.cpp	(revision 6666)
@@ -0,0 +1,213 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2004-2013  HUBzero Foundation, LLC
+ *
+ * Author: George A. Howlett <gah@purdue.edu>
+ */
+
+#include <cstring>
+#include <tcl.h>
+
+#include "CmdProc.h"
+
+using namespace GeoVis;
+
+/**
+ * Performs a linear search on the array of command operation
+ * specifications to find a partial, anchored match for the given
+ * operation string.
+ *
+ * \return If the string matches unambiguously the index of the specification in
+ * the array is returned.  If the string does not match, even as an
+ * abbreviation, any operation, -1 is returned.  If the string matches,
+ * but ambiguously -2 is returned.
+ */
+static int
+LinearOpSearch(CmdSpec *specs, int low, int high, const char *string,
+               int length)
+{
+    CmdSpec *specPtr;
+    char c;
+    int nMatches, last;
+    int i;
+
+    c = string[0];
+    nMatches = 0;
+    last = -1;
+    for (specPtr = specs+low, i = low; i <= high; i++, specPtr++) {
+        if ((c == specPtr->name[0]) && 
+            (strncmp(string, specPtr->name, length) == 0)) {
+            last = i;
+            nMatches++;
+            if (length == specPtr->minChars) {
+                break;
+            }
+        }
+    }
+    if (nMatches > 1) {
+        return -2;      /* Ambiguous operation name */
+    } 
+    if (nMatches == 0) {
+        return -1;      /* Can't find operation */
+    } 
+    return last;        /* Op found. */
+}
+
+/**
+ * Performs a binary search on the array of command operation
+ * specifications to find a partial, anchored match for the given
+ * operation string.  If we get to a point where the sample string
+ * partially matches an entry, we then revert to a linear search
+ * over the given range to check if it's an exact match or
+ * ambiguous (example: "color" with commands color and colormode).
+ *
+ * \return If the string matches unambiguously the index of the specification in
+ * the array is returned.  If the string does not match, even as an
+ * abbreviation, any operation, -1 is returned.  If the string matches,
+ * but ambiguously -2 is returned.
+ */
+static int
+BinaryOpSearch(CmdSpec *specs, int low, int high, const char *string,
+               int length)
+{
+    char c;
+
+    c = string[0];
+    while (low <= high) {
+        CmdSpec *specPtr;
+        int compare;
+        int median;
+    
+        median = (low + high) >> 1;
+        specPtr = specs + median;
+
+        /* Test the first character */
+        compare = c - specPtr->name[0];
+        if (compare == 0) {
+            /* Now test the entire string */
+            compare = strncmp(string, specPtr->name, length);
+        }
+        if (compare < 0) {
+            high = median - 1;
+        } else if (compare > 0) {
+            low = median + 1;
+        } else {
+            if (length < specPtr->minChars) {
+                /* Verify that the string is either ambiguous or an exact
+                 * match of another command by doing a linear search over the
+                 * given interval. */
+                return LinearOpSearch(specs, low, high, string, length);
+            }
+            return median;  /* Op found. */
+        }
+    }
+    return -1;          /* Can't find operation */
+}
+
+/**
+ * \brief Find the command operation given a string name.
+ * 
+ * This is useful where a group of command operations have the same argument 
+ * signature.
+ *
+ * \return If found, a pointer to the procedure (function pointer) is returned.
+ * Otherwise NULL is returned and an error message containing a list of
+ * the possible commands is returned in interp->result.
+ */
+Tcl_ObjCmdProc *
+GeoVis::GetOpFromObj(Tcl_Interp *interp,	/* Interpreter to report errors to */
+                     int nSpecs,		/* Number of specifications in array */
+                     CmdSpec *specs,		/* Op specification array */
+                     int operPos,		/* Position of operation in argument
+                                                 * list. */
+                     int objc,			/* Number of arguments in the argument
+                                                 * vector.  This includes any prefixed
+                                                 * arguments */ 
+                     Tcl_Obj *const *objv,	/* Argument vector */
+                     int flags)
+{
+    CmdSpec *specPtr;
+    char *string;
+    int length;
+    int n;
+
+    if (objc <= operPos) {  /* No operation argument */
+        if (interp != NULL) {
+            Tcl_AppendResult(interp, "wrong # args: ", (char *)NULL);
+        usage:
+            Tcl_AppendResult(interp, "should be one of...", (char *)NULL);
+            for (n = 0; n < nSpecs; n++) {
+                int i;
+
+                Tcl_AppendResult(interp, "\n  ", (char *)NULL);
+                for (i = 0; i < operPos; i++) {
+                    Tcl_AppendResult(interp, Tcl_GetString(objv[i]), " ", 
+                                     (char *)NULL);
+                }
+                specPtr = specs + n;
+                Tcl_AppendResult(interp, specPtr->name, " ", specPtr->usage,
+                                 (char *)NULL);
+            }
+        }
+        return NULL;
+    }
+    string = Tcl_GetStringFromObj(objv[operPos], &length);
+    if (flags & CMDSPEC_LINEAR_SEARCH) {
+        n = LinearOpSearch(specs, 0, nSpecs - 1, string, length);
+    } else {
+        n = BinaryOpSearch(specs, 0, nSpecs - 1, string, length);
+    }
+    if (n == -2) {
+        if (interp != NULL) {
+            char c;
+
+            Tcl_AppendResult(interp, "ambiguous", (char *)NULL);
+            if (operPos > 2) {
+                Tcl_AppendResult(interp, " ", Tcl_GetString(objv[operPos - 1]), 
+                                 (char *)NULL);
+            }
+            Tcl_AppendResult(interp, " operation \"", string, "\" matches: ",
+                             (char *)NULL);
+
+            c = string[0];
+            length = strlen(string);
+            for (n = 0; n < nSpecs; n++) {
+                specPtr = specs + n;
+                if ((c == specPtr->name[0]) &&
+                    (strncmp(string, specPtr->name, length) == 0)) {
+                    Tcl_AppendResult(interp, " ", specPtr->name, (char *)NULL);
+                }
+            }
+        }
+        return NULL;
+
+    } else if (n == -1) {   /* Can't find operation, display help */
+        if (interp != NULL) {
+            Tcl_AppendResult(interp, "bad", (char *)NULL);
+            if (operPos > 2) {
+                Tcl_AppendResult(interp, " ", Tcl_GetString(objv[operPos - 1]), 
+                                 (char *)NULL);
+            }
+            Tcl_AppendResult(interp, " operation \"", string, "\": ", (char *)NULL);
+            goto usage;
+        }
+        return NULL;
+    }
+    specPtr = specs + n;
+    if ((objc < specPtr->minArgs) || 
+        ((specPtr->maxArgs > 0) && (objc > specPtr->maxArgs))) {
+        if (interp != NULL) {
+            int i;
+
+            Tcl_AppendResult(interp, "wrong # args: should be \"", (char *)NULL);
+            for (i = 0; i < operPos; i++) {
+                Tcl_AppendResult(interp, Tcl_GetString(objv[i]), " ", 
+                                 (char *)NULL);
+            }
+            Tcl_AppendResult(interp, specPtr->name, " ", specPtr->usage, "\"",
+                             (char *)NULL);
+        }
+        return NULL;
+    }
+    return specPtr->proc;
+}
Index: /geovis/branches/1.0/CmdProc.h
===================================================================
--- /geovis/branches/1.0/CmdProc.h	(revision 6666)
+++ /geovis/branches/1.0/CmdProc.h	(revision 6666)
@@ -0,0 +1,49 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2004-2013  HUBzero Foundation, LLC
+ *
+ * Author: George A. Howlett <gah@purdue.edu>
+ */
+
+#ifndef GEOVIS_CMDPROC_H
+#define GEOVIS_CMDPROC_H
+
+#include <tcl.h>
+
+namespace GeoVis {
+
+/**
+ * \brief Structure to specify a set of operations for a Tcl command.
+ *
+ * This is passed to the GetOpFromObj procedure to look
+ * for a function pointer associated with the operation name.
+ */
+struct CmdSpec {
+    const char *name;		/**< Name of operation */
+    int minChars;		/**< Minimum # characters to disambiguate */
+    Tcl_ObjCmdProc *proc;
+    int minArgs;		/**< Minimum # args required */
+    int maxArgs;		/**< Maximum # args required */
+    const char *usage;		/**< Usage message */
+};
+
+enum CmdSpecIndex {
+    CMDSPEC_ARG0,		/**< Op is the first argument. */
+    CMDSPEC_ARG1,		/**< Op is the second argument. */
+    CMDSPEC_ARG2,		/**< Op is the third argument. */
+    CMDSPEC_ARG3,		/**< Op is the fourth argument. */
+    CMDSPEC_ARG4		/**< Op is the fifth argument. */
+};
+
+#define CMDSPEC_LINEAR_SEARCH	1
+#define CMDSPEC_BINARY_SEARCH	0
+
+extern Tcl_ObjCmdProc *
+GetOpFromObj(Tcl_Interp *interp, int nSpecs, 
+             CmdSpec *specs, int operPos, int objc, Tcl_Obj *const *objv, int flags);
+
+#define NumCmdSpecs(s) (sizeof(s) / sizeof(GeoVis::CmdSpec))
+
+}
+
+#endif
Index: /geovis/branches/1.0/ColorMap.cpp
===================================================================
--- /geovis/branches/1.0/ColorMap.cpp	(revision 6666)
+++ /geovis/branches/1.0/ColorMap.cpp	(revision 6666)
@@ -0,0 +1,81 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2015  HUBzero Foundation, LLC
+ *
+ * Author: Leif Delgass <ldelgass@purdue.edu>
+ */
+#include "ColorMap.h"
+
+using namespace GeoVis;
+
+void
+ColorMap::renderColorMap(osg::TransferFunction1D *map, int width, int height,
+                         osg::Image *imgData,
+                         bool opaque, float bgColor[3],
+                         bool bgr,
+                         int bytesPerPixel)
+{
+    int size = bytesPerPixel * width * height;
+    if (imgData->getTotalSizeInBytes() != (unsigned int)size) {
+        GLenum pixelFormat;
+        if (bytesPerPixel == 3) {
+            pixelFormat = bgr ? GL_BGR : GL_RGB;
+        } else {
+            pixelFormat = bgr ? GL_BGRA: GL_RGBA;
+        }; 
+        imgData->allocateImage(width, height, 1, pixelFormat, GL_UNSIGNED_BYTE, 1);
+    }
+    unsigned char *dst = imgData->data();
+    //unsigned char *src = map->data();
+    float min = map->getMinimum();
+    float max = map->getMaximum();
+    if (height > width) {
+        for (int i = 0; i < height; i++) {
+            float x = (float)i/(height-1);
+            float val = min + (max - min) * x;
+            osg::Vec4f rgba = map->getColor(val);
+            float opacity = rgba[3];
+            unsigned char color[3];
+            if (opaque) {
+                color[0] = (unsigned char)(255. * (bgr ? rgba[2] : rgba[0]));
+                color[1] = (unsigned char)(255. * rgba[1]);
+                color[2] = (unsigned char)(255. * (bgr ? rgba[0] : rgba[2]));
+            } else {
+                color[0] = (unsigned char)(255. * (bgColor[0] * (1.0 - opacity) + (bgr ? rgba[2] : rgba[0]) * opacity));
+                color[1] = (unsigned char)(255. * (bgColor[1] * (1.0 - opacity) + rgba[1] * opacity));
+                color[2] = (unsigned char)(255. * (bgColor[2] * (1.0 - opacity) + (bgr ? rgba[0] : rgba[2]) * opacity));
+            }
+            for (int j = 0; j < width; j++) {
+                memcpy(dst, color, 3);
+                dst += 3;
+                if (bytesPerPixel == 4) {
+                    *(dst++) = (unsigned char)(255. * (opaque ? 1.0 : opacity));
+                }
+            }
+        }
+    } else {
+        for (int i = 0; i < height; i++) {
+            for (int j = 0; j < width; j++) {
+                float x = (float)j/(width-1);
+                float val = min + (max - min) * x;
+                osg::Vec4f rgb = map->getColor(val);
+                double opacity = rgb[3];
+                unsigned char color[3];
+                if (opaque) {
+                    color[0] = (unsigned char)(255. * (bgr ? rgb[2] : rgb[0]));
+                    color[1] = (unsigned char)(255. * rgb[1]);
+                    color[2] = (unsigned char)(255. * (bgr ? rgb[0] : rgb[2]));
+                } else {
+                    color[0] = (unsigned char)(255. * (bgColor[0] * (1.0 - opacity) + (bgr ? rgb[2] : rgb[0]) * opacity));
+                    color[1] = (unsigned char)(255. * (bgColor[1] * (1.0 - opacity) + rgb[1] * opacity));
+                    color[2] = (unsigned char)(255. * (bgColor[2] * (1.0 - opacity) + (bgr ? rgb[0] : rgb[2]) * opacity));
+                }
+                memcpy(dst, color, 3);
+                dst += 3;
+                if (bytesPerPixel == 4) {
+                    *(dst++) = (unsigned char)(255. * (opaque ? 1.0 : opacity));
+                }
+            }
+        }
+    }
+}
Index: /geovis/branches/1.0/ColorMap.h
===================================================================
--- /geovis/branches/1.0/ColorMap.h	(revision 6666)
+++ /geovis/branches/1.0/ColorMap.h	(revision 6666)
@@ -0,0 +1,25 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2015  HUBzero Foundation, LLC
+ *
+ * Author: Leif Delgass <ldelgass@purdue.edu>
+ */
+#ifndef GEOVIS_COLORMAP_H
+#define GEOVIS_COLORMAP_H
+
+#include <osg/TransferFunction>
+
+namespace GeoVis {
+
+class ColorMap {
+public:
+    static void renderColorMap(osg::TransferFunction1D *map, int width, int height,
+                               osg::Image *imgData,
+                               bool opaque, float bgColor[3],
+                               bool bgr = false,
+                               int bytesPerPixel = 3);
+};
+
+}
+
+#endif
Index: /geovis/branches/1.0/FileUtil.cpp
===================================================================
--- /geovis/branches/1.0/FileUtil.cpp	(revision 6666)
+++ /geovis/branches/1.0/FileUtil.cpp	(revision 6666)
@@ -0,0 +1,75 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2014  HUBzero Foundation, LLC
+ *
+ * Author: Leif Delgass <ldelgass@purdue.edu>
+ */
+
+#include <cstring>
+#include <cstdio>
+#include <cerrno>
+
+#include <string>
+
+#include <osgDB/FileUtils>
+#include <osgDB/FileNameUtils>
+
+#include "FileUtil.h"
+#include "Trace.h"
+
+using namespace GeoVis;
+
+void DirectoryVisitor::traverse(const std::string& path)
+{
+    if (osgDB::fileType(path) == osgDB::DIRECTORY) {
+        if (handleDir(path)) {
+            osgDB::DirectoryContents files = osgDB::getDirectoryContents(path);
+            for (osgDB::DirectoryContents::const_iterator f = files.begin(); f != files.end(); ++f) {
+                if (f->compare(".") == 0 || f->compare("..") == 0)
+                    continue;
+
+                std::string filepath = osgDB::concatPaths(path, *f);
+                traverse(filepath);
+            }
+        }
+        handlePostDir(path);
+    } else if (osgDB::fileType(path) == osgDB::REGULAR_FILE) {
+        handleFile(path);
+    }
+}
+
+void GeoVis::removeDirectory(const char *path)
+{
+    TRACE("%s", path);
+
+    CollectFilesVisitor cfv;
+    cfv.traverse(std::string(path));
+    for (std::vector<std::string>::const_iterator itr = cfv.filenames.begin();
+         itr != cfv.filenames.end(); ++itr) {
+        if (remove(itr->c_str()) < 0) {
+            ERROR("remove: %s", strerror(errno));
+        }
+    }
+    for (std::vector<std::string>::const_iterator itr = cfv.dirnames.begin();
+         itr != cfv.dirnames.end(); ++itr) {
+        if (remove(itr->c_str()) < 0) {
+            ERROR("remove: %s", strerror(errno));
+        }
+    }
+}
+
+/**
+ * \brief Get the file path part of a local:// URL
+ * 
+ * This implementation assumes a relative path if there is no leading slash,
+ * rather than assuming there is a server name.
+ */
+std::string GeoVis::getLocalFilePath(const std::string& url)
+{
+    std::string::size_type pos(url.find("://"));
+
+    if (pos != std::string::npos) {
+        return url.substr(pos+3, std::string::npos);
+    }
+    return url;
+}
Index: /geovis/branches/1.0/FileUtil.h
===================================================================
--- /geovis/branches/1.0/FileUtil.h	(revision 6666)
+++ /geovis/branches/1.0/FileUtil.h	(revision 6666)
@@ -0,0 +1,53 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2014  HUBzero Foundation, LLC
+ *
+ * Author: Leif Delgass <ldelgass@purdue.edu>
+ */
+#ifndef GEOVIS_FILEUTIL_H
+#define GEOVIS_FILEUTIL_H
+
+#include <string>
+
+#include <osgEarth/FileUtils>
+
+namespace GeoVis {
+
+class DirectoryVisitor : public osgEarth::DirectoryVisitor
+{
+public:
+    DirectoryVisitor()
+    {}
+
+    virtual void handlePostDir(const std::string& path)
+    {}
+
+    virtual void traverse(const std::string& path);
+};
+
+class CollectFilesVisitor : public DirectoryVisitor
+{
+public:
+    CollectFilesVisitor()
+    {}
+
+    virtual void handleFile(const std::string& path)
+    {
+        filenames.push_back(path);
+    }
+
+    virtual void handlePostDir(const std::string& path)
+    {
+        dirnames.push_back(path);
+    }
+
+    std::vector<std::string> filenames;
+    std::vector<std::string> dirnames;
+};
+
+extern void removeDirectory(const char *path);
+std::string getLocalFilePath(const std::string& url);
+
+}
+
+#endif
Index: /geovis/branches/1.0/IData.cpp
===================================================================
--- /geovis/branches/1.0/IData.cpp	(revision 6666)
+++ /geovis/branches/1.0/IData.cpp	(revision 6666)
@@ -0,0 +1,275 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2015 HUBzero Foundation, LLC
+ *
+ * Author: Leif Delgass <ldelgass@purdue.edu>
+ */
+#include <string>
+#include <iostream>
+#include <sstream>
+#include <cstring>
+#include <cstdlib>
+#include <cstddef>
+
+#include <curl/curl.h>
+#include <osgEarth/JsonUtils>
+
+#include "IData.h"
+#include "Trace.h"
+
+static const char *commandNames[] = {
+    "list",
+    "get",
+    "contents",
+    "setglobalmeta",
+    "delete",
+    "upload",
+    "finishupload",
+    "checksum"
+};
+#if 0
+static const char *sharingValues[] = {
+    "no",
+    "read-only",
+    "read-write"
+};
+#endif
+
+static const char *defaultHubURL = "https://mygeohub.org";
+static const std::string servicePath = "/api/idata/collection/";
+static std::string serviceURL;
+static std::string iDataUsername;
+
+using namespace IData;
+using namespace osgEarth;
+
+void IData::iDataInit(const char *username, const char *hubURL)
+{
+    TRACE("user: %s hub: %s", username, hubURL);
+    static bool first = true;
+    std::ostringstream oss;
+    std::string hubHost;
+    if (hubURL != NULL) {
+        hubHost = hubURL;
+    } else {
+        hubHost = defaultHubURL;
+    }
+    oss << hubHost << servicePath;
+    serviceURL = oss.str();
+    iDataUsername = username;
+    if (first) {
+        curl_global_init(CURL_GLOBAL_DEFAULT);
+        first = false;
+    }
+}
+
+void IData::iDataCleanup()
+{
+    curl_global_cleanup();
+}
+
+static std::string urlEncode(const std::string& str)
+{
+    std::string retStr;
+    CURL *curl = curl_easy_init();
+    if (curl) {
+        char *encoded = curl_easy_escape(curl, str.c_str(), str.length());
+        retStr = encoded;
+        curl_free(encoded);
+        curl_easy_cleanup(curl);
+    }
+    return retStr;
+}
+
+static std::string formatQuery(Command cmd, std::vector<std::string> &params)
+{
+    std::ostringstream oss;
+    oss << serviceURL << commandNames[cmd] << "?username=" << iDataUsername;
+    for (std::vector<std::string>::iterator itr = params.begin();
+         itr != params.end(); ++itr) {
+        oss << "&" << *itr << "=";
+        itr++;
+        oss << urlEncode(*itr);
+    }
+
+    return oss.str();
+}
+
+static size_t writeData(void *buffer, size_t size, size_t nmemb, void *userp)
+{
+    size_t numBytes = size * nmemb;
+    Buffer *out = static_cast<Buffer *>(userp); 
+    out->data = realloc(out->data, out->size + numBytes);
+    memcpy((char *)out->data + out->size, buffer, numBytes);
+    out->size += numBytes;
+    return numBytes;
+}
+
+void IData::getCollectionList(std::vector<Collection>& list)
+{
+    std::vector<std::string> params;
+    std::string query = formatQuery(COMMAND_LIST, params);
+    CURL *curl = curl_easy_init();
+    if (curl) {
+        curl_easy_setopt(curl, CURLOPT_URL, query.c_str()); 
+        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeData);
+        Buffer buff;
+        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buff);
+        CURLcode ret = curl_easy_perform(curl);
+        if (ret == CURLE_OK) {
+            //std::cout.write((char *)buff.data, buff.size);
+            Json::Reader reader;
+            Json::Value result;
+            const char *begin = (const char *)buff.data;
+            const char *end = begin + buff.size;
+            reader.parse(begin, end, result, false);
+            for (size_t i = 0; i < result["collections"].size(); i++) {
+                Collection collection;
+                Json::Value& val = result["collections"][i];
+                collection.name = val["name"].asString();
+                collection.shared = val["shared"].asString();
+                collection.id = atoi(val["id"].asCString());
+                list.push_back(collection);
+            }
+        }
+        curl_easy_cleanup(curl);
+    }
+}
+
+void IData::getContents(int collectionID, const char *path,
+                        std::vector<DirectoryItem>& contents)
+{
+    std::vector<std::string> params;
+    {
+        std::ostringstream oss;
+        oss << collectionID;
+        params.push_back("id");
+        params.push_back(oss.str());
+    }
+    if (path) {
+        params.push_back("path");
+        params.push_back(path);
+    }
+    std::string query = formatQuery(COMMAND_CONTENTS, params);
+    CURL *curl = curl_easy_init();
+    if (curl) {
+        curl_easy_setopt(curl, CURLOPT_URL, query.c_str()); 
+        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeData);
+        Buffer buff;
+        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buff);
+        CURLcode ret = curl_easy_perform(curl);
+        if (ret == CURLE_OK) {
+            std::cout.write((char *)buff.data, buff.size);
+            Json::Reader reader;
+            Json::Value result;
+            const char *begin = (const char *)buff.data;
+            const char *end = begin + buff.size;
+            reader.parse(begin, end, result, false);
+            for (size_t i = 0; i < result["contents"].size(); i++) {
+                DirectoryItem dir;
+                Json::Value& val = result["contents"][i];
+                dir.name = val["name"].asString();
+                dir.isDir = (val["dir-or-file"].asInt() == TYPE_DIRECTORY);
+                if (!dir.isDir) {
+                    dir.type = val["type"].asString();
+                    dir.size = atol(val["size"].asCString());
+                    dir.ctime = val["ctime"].asString();
+                    dir.doi = val["doi"].asString();
+                }
+                contents.push_back(dir);
+            }
+        }
+        curl_easy_cleanup(curl);
+    }
+}
+
+void IData::getFile(int collectionID, const char *doi, Buffer *buff)
+{
+    std::vector<std::string> params;
+    {
+        std::ostringstream oss;
+        oss << collectionID;
+        params.push_back("id");
+        params.push_back(oss.str());
+    }
+    if (doi) {
+        params.push_back("doi");
+        params.push_back(doi);
+    }
+    std::string query = formatQuery(COMMAND_GET, params);
+    CURL *curl = curl_easy_init();
+    if (curl) {
+        curl_easy_setopt(curl, CURLOPT_URL, query.c_str()); 
+        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeData);
+        curl_easy_setopt(curl, CURLOPT_WRITEDATA, buff);
+        CURLcode ret = curl_easy_perform(curl);
+        if (ret == CURLE_OK) {
+            //std::cout.write((char *)buff->data, buff->size);
+        }
+        curl_easy_cleanup(curl);
+    }
+}
+
+void setFileMetadata(int collectionID, const char *doi, const char *name, const char *value)
+{
+    std::vector<std::string> params;
+    {
+        std::ostringstream oss;
+        oss << collectionID;
+        params.push_back("id");
+        params.push_back(oss.str());
+    }
+    if (doi) {
+        params.push_back("doi");
+        params.push_back(doi);
+    }
+    if (name) {
+        params.push_back(name);
+        params.push_back(value);
+    }
+    std::string query = formatQuery(COMMAND_SETGLOBALMETA, params);
+    CURL *curl = curl_easy_init();
+    if (curl) {
+        curl_easy_setopt(curl, CURLOPT_URL, query.c_str()); 
+        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeData);
+        Buffer buff;
+        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buff);
+        CURLcode ret = curl_easy_perform(curl);
+        if (ret == CURLE_OK) {
+            //std::cout.write((char *)buff->data, buff->size);
+        }
+        curl_easy_cleanup(curl);
+    }
+}
+
+void deleteFile(int collectionID, const char *path, const char *filename)
+{
+    std::vector<std::string> params;
+    {
+        std::ostringstream oss;
+        oss << collectionID;
+        params.push_back("id");
+        params.push_back(oss.str());
+    }
+    if (path) {
+        params.push_back("path");
+        params.push_back(path);
+    }
+    if (filename) {
+        params.push_back("filename");
+        params.push_back(filename);
+    }
+    std::string query = formatQuery(COMMAND_DELETE, params);
+    CURL *curl = curl_easy_init();
+    if (curl) {
+        curl_easy_setopt(curl, CURLOPT_URL, query.c_str()); 
+        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeData);
+        Buffer buff;
+        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buff);
+        CURLcode ret = curl_easy_perform(curl);
+        if (ret == CURLE_OK) {
+            //std::cout.write((char *)buff->data, buff->size);
+        }
+        curl_easy_cleanup(curl);
+    }
+}
Index: /geovis/branches/1.0/IData.h
===================================================================
--- /geovis/branches/1.0/IData.h	(revision 6666)
+++ /geovis/branches/1.0/IData.h	(revision 6666)
@@ -0,0 +1,79 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2015  HUBzero Foundation, LLC
+ *
+ * Author: Leif Delgass <ldelgass@purdue.edu>
+ */
+#ifndef GEOVIS_IDATA_H
+#define GEOVIS_IDATA_H
+
+#include <vector>
+#include <string>
+
+namespace IData {
+
+enum Command {
+    COMMAND_LIST,
+    COMMAND_GET,
+    COMMAND_CONTENTS,
+    COMMAND_SETGLOBALMETA,
+    COMMAND_DELETE,
+    COMMAND_UPLOAD,
+    COMMAND_FINISHUPLOAD,
+    COMMAND_CHECKSUM
+};
+
+/// For "shared" property of Collection
+enum Shared {
+    NOT_SHARED,
+    READ_ONLY,
+    READ_WRITE
+};
+
+/// For "dir-or-file" property of DirectoryItem
+enum DirectoryItemType {
+    TYPE_DIRECTORY = 1,
+    TYPE_FILE = 2
+};
+
+class Collection {
+public:
+    std::string name;
+    std::string shared; // "no", "read-only","read-write"
+    unsigned int id;
+};
+
+class DirectoryItem {
+public:
+    DirectoryItem() :
+        size(0)
+    {}
+    std::string name;
+    bool isDir;
+    std::string type;
+    size_t size;
+    std::string ctime;
+    std::string doi;
+};
+
+class Buffer {
+public:
+    Buffer() :
+      data(0), size(0)
+    {}
+    ~Buffer() { free(data); }
+    void *data;
+    size_t size;
+};
+
+extern void iDataInit(const char *username, const char *hubURL = NULL);
+extern void iDataCleanup();
+extern void getCollectionList(std::vector<Collection>& list);
+extern void getContents(int collectionID, const char *path, std::vector<DirectoryItem>& contents);
+extern void getFile(int collectionID, const char *doi, Buffer *buff);
+extern void deleteFile(int collectionID, const char *path, const char *filename);
+extern void setFileMetadata(int collectionID, const char *doi, const char *name, const char *value);
+
+}
+
+#endif
Index: /geovis/branches/1.0/Icons.cpp
===================================================================
--- /geovis/branches/1.0/Icons.cpp	(revision 6666)
+++ /geovis/branches/1.0/Icons.cpp	(revision 6666)
@@ -0,0 +1,37 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2016 HUBzero Foundation, LLC
+ *
+ * Author: Leif Delgass <ldelgass@purdue.edu>
+ */
+
+#include "Icons.h"
+
+using namespace GeoVis;
+
+std::tr1::unordered_map<std::string, std::string> GeoVis::g_icons;
+
+void GeoVis::initIconMap()
+{
+    g_icons["pin"] = ICON_PIN;
+    g_icons["pin1"] = ICON_PIN1;
+    g_icons["pin2"] = ICON_PIN2;
+    g_icons["pin3"] = ICON_PIN3;
+    g_icons["pin4"] = ICON_PIN4;
+    g_icons["pin5"] = ICON_PIN5;
+    g_icons["pin6"] = ICON_PIN6;
+    g_icons["pin7"] = ICON_PIN7;
+    g_icons["pin8"] = ICON_PIN8;
+    g_icons["pin9"] = ICON_PIN9;
+    g_icons["pin10"] = ICON_PIN10;
+}
+
+std::string GeoVis::getIconFile(const char *name)
+{
+    IconHashmap::iterator itr = g_icons.find(name);
+    if (itr == g_icons.end()) {
+        return "";
+    } else {
+        return itr->second;
+    }
+}
Index: /geovis/branches/1.0/Icons.h
===================================================================
--- /geovis/branches/1.0/Icons.h	(revision 6666)
+++ /geovis/branches/1.0/Icons.h	(revision 6666)
@@ -0,0 +1,36 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2016 HUBzero Foundation, LLC
+ *
+ * Author: Leif Delgass <ldelgass@purdue.edu>
+ */
+
+#ifndef GEOVIS_ICONS_H
+#define GEOVIS_ICONS_H
+
+#include <tr1/unordered_map>
+#include <string>
+
+#define ICON_PIN "placemark32.png"
+#define ICON_PIN1 "pin01.png"
+#define ICON_PIN2 "pin02.png"
+#define ICON_PIN3 "pin03.png"
+#define ICON_PIN4 "pin04.png"
+#define ICON_PIN5 "pin05.png"
+#define ICON_PIN6 "pin06.png"
+#define ICON_PIN7 "pin07.png"
+#define ICON_PIN8 "pin08.png"
+#define ICON_PIN9 "pin09.png"
+#define ICON_PIN10 "pin10.png"
+
+namespace GeoVis {
+
+typedef std::tr1::unordered_map<std::string, std::string> IconHashmap;
+extern IconHashmap g_icons;
+
+extern void initIconMap();
+extern std::string getIconFile(const char *name);
+
+}
+
+#endif
Index: /geovis/branches/1.0/Makefile.in
===================================================================
--- /geovis/branches/1.0/Makefile.in	(revision 6666)
+++ /geovis/branches/1.0/Makefile.in	(revision 6666)
@@ -0,0 +1,241 @@
+DEBUG			= #yes
+DEBUG_WRITE_FRAME_FILE  = #yes
+TRACE			= #yes
+TRACE_FRAME		= #yes
+NEW_ANNOTATION_API	= #yes
+USE_OFFSCREEN_RENDERING	= #yes
+USE_THREADS		= yes
+USE_READ_THREAD         = #yes
+USE_CACHE               = yes
+USE_THROTTLING_SLEEP    = #yes
+SLEEP_AFTER_QUEUE_FRAME = yes
+QUEUE_ONLY_ONE_FRAME    = yes
+USE_REX                 = #yes
+
+bindir          = @bindir@
+datadir         = @datadir@
+datarootdir     = @datarootdir@
+exec_prefix     = @exec_prefix@
+includedir      = @includedir@
+libdir          = @libdir@
+mandir          = @mandir@
+prefix          = @prefix@
+srcdir          = @srcdir@
+
+CC		= @CC@
+CXX		= @CXX@
+CFLAGS		= @CFLAGS@
+CXXFLAGS	= @CXXFLAGS@
+
+VPATH           = $(srcdir)
+
+INSTALL         = @INSTALL@
+INSTALL_PROGRAM = ${INSTALL} -m 0755
+INSTALL_DATA    = ${INSTALL} -m 0644
+INSTALL_SCRIPT  = ${INSTALL} -m 0644
+MKDIR_P		= @MKDIR_P@
+
+SVN_VERSION	= $(shell svnversion $(srcdir) | sed 's/Unversioned directory/unknown/')
+STATSDIR	= @STATSDIR@
+RESOURCES_DIR	= @RESOURCES_DIR@
+
+GL_LIB_SPEC	= -lGL -lm
+PTHREAD_LIB_SPEC= -lpthread
+CURL_LIB_SPEC	= -lcurl
+
+TCL_LIB_SPEC    = @TCL_LIB_SPEC@
+TCL_INC_SPEC    = @TCL_INC_SPEC@
+
+OSG_LIB_DIR     = @OSG_LIB_DIR@
+OSG_INC_SPEC    = @OSG_INC_SPEC@
+OSG_LIB_SPEC	= -L${OSG_LIB_DIR} \
+		-losgText \
+		-losgDB \
+		-losgViewer \
+		-losgGA \
+		-losg \
+		-lOpenThreads
+
+OSGEARTH_LIB_DIR  = $(OSG_LIB_DIR)
+OSGEARTH_INC_SPEC = $(OSG_INC_SPEC)
+OSGEARTH_LIB_SPEC = -L${OSG_LIB_DIR} \
+		  -losgEarthFeatures \
+		  -losgEarthAnnotation \
+		  -losgEarthSymbology \
+		  -losgEarthUtil \
+		  -losgEarth
+
+LD_RUN_PATH	= $(OSG_LIB_DIR):$(libdir)
+
+LIBS		= \
+		$(OSGEARTH_LIB_SPEC) \
+		$(OSG_LIB_SPEC) \
+		$(TCL_LIB_SPEC) \
+		$(GL_LIB_SPEC) \
+		$(CURL_LIB_SPEC) \
+		$(PTHREAD_LIB_SPEC) \
+		-Wl,-rpath,$(LD_RUN_PATH) \
+		-Wl,--enable-new-dtags
+
+INCLUDES	= \
+		-I$(srcdir) \
+		$(OSGEARTH_INC_SPEC) \
+		$(OSG_INC_SPEC) \
+		$(TCL_INC_SPEC)
+
+EXTRA_CXXFLAGS	= -Wall
+EXTRA_CFLAGS	= -Wall
+DEFINES		= -DSVN_VERSION=\"$(SVN_VERSION)\" -DSTATSDIR=\"$(STATSDIR)\"
+ifdef DEBUG
+DEFINES 	+= -DDEBUG
+CXXFLAGS	= -O0 -g
+endif
+ifdef DEBUG_WRITE_FRAME_FILE
+DEFINES 	+= -DDEBUG_WRITE_FRAME_FILE
+endif
+ifdef TRACE
+DEFINES		+= -DWANT_TRACE
+endif
+ifdef TRACE_FRAME
+DEFINES		+= -DWANT_FRAME
+endif
+ifdef NEW_ANNOTATION_API
+DEFINES		+= -DNEW_ANNOTATION_API
+endif
+ifdef USE_OFFSCREEN_RENDERING
+DEFINES         += -DUSE_OFFSCREEN_RENDERING
+endif
+ifdef USE_THREADS
+DEFINES         += -DUSE_THREADS
+endif
+ifdef USE_READ_THREAD
+DEFINES         += -DUSE_READ_THREAD
+endif
+ifdef USE_CACHE
+DEFINES         += -DUSE_CACHE
+endif
+ifdef USE_THROTTLING_SLEEP
+DEFINES         += -DUSE_THROTTLING_SLEEP
+endif
+ifdef SLEEP_AFTER_QUEUE_FRAME
+DEFINES         += -DSLEEP_AFTER_QUEUE_FRAME
+endif
+ifdef QUEUE_ONLY_ONE_FRAME
+DEFINES         += -DQUEUE_ONLY_ONE_FRAME
+endif
+ifdef USE_REX
+DEFINES         += -DUSE_REX
+endif
+
+CXX_SWITCHES	= $(CXXFLAGS) $(EXTRA_CXXFLAGS) $(DEFINES) $(INCLUDES)
+CC_SWITCHES	= $(CFLAGS) $(EXTRA_CFLAGS) $(DEFINES) $(INCLUDES)
+
+SERVER_SRCS	= \
+		CmdProc.cpp \
+		ColorMap.cpp \
+		FileUtil.cpp \
+		Icons.cpp \
+		IData.cpp \
+		Picker.cpp \
+		Placard.cpp \
+		PPMWriter.cpp \
+		ReadBuffer.cpp \
+		Renderer.cpp \
+		RendererCmd.cpp \
+		RenderServer.cpp \
+		ScaleBar.cpp \
+		Stats.cpp \
+		TGAWriter.cpp \
+		Trace.cpp \
+		Util.cpp
+
+ifdef USE_THREADS
+SERVER_SRCS+= \
+	ResponseQueue.cpp
+ifdef USE_READ_THREAD
+SERVER_SRCS+= \
+	CommandQueue.cpp
+endif
+endif
+
+SERVER_OBJS=$(SERVER_SRCS:.cpp=.o)
+SERVER_OBJS+= md5.o
+SERVER=geovis
+IDATATEST=idatatest
+
+resources	= \
+		$(srcdir)/resources/pin01.png \
+		$(srcdir)/resources/pin02.png \
+		$(srcdir)/resources/pin03.png \
+		$(srcdir)/resources/pin04.png \
+		$(srcdir)/resources/pin05.png \
+		$(srcdir)/resources/pin06.png \
+		$(srcdir)/resources/pin07.png \
+		$(srcdir)/resources/pin08.png \
+		$(srcdir)/resources/pin09.png \
+		$(srcdir)/resources/pin10.png \
+		$(srcdir)/resources/placemark32.png \
+		$(srcdir)/resources/world.tif
+
+.PHONY: all docs install install-resources clean clean-docs distclean
+
+all: $(SERVER)
+
+$(IDATATEST): idatatest.o IData.o
+	$(CXX) -o $@ $^ $(LIBS)
+
+docs:
+	$(MKDIR_P) -m 0755 docs/doxygen
+	doxygen
+
+$(SERVER): $(SERVER_OBJS)
+	$(CXX) -o $@ $(SERVER_OBJS) $(LIBS)
+
+install: install-server install-resources
+
+install-server: all
+	$(INSTALL_PROGRAM) $(SERVER) $(bindir)
+
+install-resources:
+	$(MKDIR_P) $(RESOURCES_DIR)
+	@for i in $(resources) ; do \
+	    echo "installing $$i..." ; \
+	    $(INSTALL_DATA) $$i $(RESOURCES_DIR) ; \
+	done
+
+%.o: %.cpp
+	$(CXX) $(CXX_SWITCHES) -c $< -o $@
+
+%.o: %.c
+	$(CC) $(CC_SWITCHES) -c $< -o $@
+
+clean:
+	$(RM) *~ *.o *.csv make.log $(SERVER) $(IDATATEST)
+
+clean-docs:
+	$(RM) -r docs
+
+distclean: clean clean-docs
+	$(RM) Makefile Doxyfile install.log
+	$(RM) -r resources
+
+CmdProc.o: CmdProc.h
+ColorMap.o: ColorMap.h
+CommandQueue.o: CommandQueue.h Trace.h
+FileUtil.o: FileUtil.h
+Icons.o: Icons.h
+IData.o: IData.h
+idatatest.o: IData.h
+md5.o: md5.h
+Picker.o: Picker.h Renderer.h Trace.h Placard.h
+Placard.o: Placard.h Trace.h
+PPMWriter.o: PPMWriter.h ResponseQueue.h Trace.h
+ReadBuffer.o: ReadBuffer.h Trace.h
+Renderer.o: Renderer.h Trace.h Picker.h Placard.h MouseCoordsTool.h ScaleBar.h FileUtil.h Util.h Icons.h
+RendererCmd.o: Renderer.h ReadBuffer.h ResponseQueue.h Trace.h CmdProc.h PPMWriter.h TGAWriter.h Placard.h
+RenderServer.o: RenderServer.h RendererCmd.h Renderer.h ReadBuffer.h ResponseQueue.h Trace.h PPMWriter.h TGAWriter.h Stats.h
+ResponseQueue.o: ResponseQueue.h Trace.h
+ScaleBar.o: ScaleBar.h Trace.h
+Stats.o: Stats.h RenderServer.h Trace.h md5.h
+Trace.o: Trace.h
+Util.o: Util.h
Index: /geovis/branches/1.0/MouseCoordsTool.h
===================================================================
--- /geovis/branches/1.0/MouseCoordsTool.h	(revision 6666)
+++ /geovis/branches/1.0/MouseCoordsTool.h	(revision 6666)
@@ -0,0 +1,142 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2014  HUBzero Foundation, LLC
+ *
+ * Author: Leif Delgass <ldelgass@purdue.edu>
+ */
+
+#ifndef GEOVIS_MOUSE_COORDS_TOOL_H
+#define GEOVIS_MOUSE_COORDS_TOOL_H
+
+#include <osgEarth/MapNode>
+
+#include <osgEarthUtil/Common>
+#include <osgEarthUtil/Controls>
+#include <osgEarthUtil/Formatter>
+
+#include <osgGA/GUIEventHandler>
+
+#include "Trace.h"
+
+namespace GeoVis {
+
+class MouseCoordsCallback : public osgEarth::Util::MouseCoordsTool::Callback
+{
+public:
+    MouseCoordsCallback(osgEarth::Util::Controls::LabelControl *label,
+                        osgEarth::Util::Formatter *formatter) :
+        osgEarth::Util::MouseCoordsTool::Callback(),
+        _label(label),
+        _formatter(formatter),
+        _havePoint(false)
+    {
+        if (dynamic_cast<osgEarth::Util::MGRSFormatter *>(formatter) != NULL) {
+            _prefix = "MGRS: ";
+        } else {
+            _prefix = "Lat/Long: ";
+        }
+    }
+
+    void set(const osgEarth::GeoPoint& mapCoords, osg::View *view, osgEarth::MapNode *mapNode)
+    {
+        TRACE("%g %g %g", mapCoords.x(), mapCoords.y(), mapCoords.z());
+        if (_label.valid()) {
+            if (mapNode->getMap()->getNumElevationLayers() > 0) {
+                _label->setText(osgEarth::Stringify()
+                                << _prefix
+                                <<  _formatter->format(mapCoords)
+                                << "\nElev: " << mapCoords.z() << " m");
+            } else {
+                _label->setText(osgEarth::Stringify()
+                                << _prefix
+                                <<  _formatter->format(mapCoords));
+            }
+        }
+        _pt = mapCoords;
+        _havePoint = true;
+    }
+
+    void reset(osg::View *view, osgEarth::MapNode *mapNode)
+    {
+        TRACE("Out of range");
+        // Out of range of map extents
+        if (_label.valid()) {
+            _label->setText(osgEarth::Stringify()
+                            << _prefix
+                            << "N/A");
+        }
+        _havePoint = false;
+    }
+
+    bool report(double *x, double *y, double *z)
+    {
+        if (_havePoint) {
+            *x = _pt.x();
+            *y = _pt.y();
+            *z = _pt.z();
+            _havePoint = false;
+            return true;
+        }
+        return false;
+    }
+
+    osgEarth::Util::Controls::LabelControl *getLabel()
+    { return _label.get(); }
+
+    osgEarth::Util::Formatter *getFormatter()
+    { return _formatter.get(); }
+
+private:
+    osg::observer_ptr<osgEarth::Util::Controls::LabelControl> _label;
+    osg::ref_ptr<osgEarth::Util::Formatter> _formatter;
+    std::string _prefix;
+    bool _havePoint;
+    osgEarth::GeoPoint _pt;
+};
+
+/**
+ * Tool that prints the map coordinates under the mouse into a 
+ * LabelControl.
+ */
+class MouseCoordsTool : public osgEarth::Util::MouseCoordsTool
+{
+public:
+    MouseCoordsTool(osgEarth::MapNode* mapNode) :
+        osgEarth::Util::MouseCoordsTool(mapNode)
+    {}
+
+    virtual ~MouseCoordsTool()
+    {}
+
+    virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
+    {
+        if (ea.getEventType() == ea.PUSH ||
+            ea.getEventType() == ea.RELEASE ||
+            ea.getEventType() == ea.MOVE ||
+            ea.getEventType() == ea.DRAG) {
+            // mouse Y from OSG is 1 at bottom of window to +height at top of window
+            // mouse Y from TK  is 0 at top of window to +(height-1) at bottom of window
+            //TRACE("coords: %g,%g", ea.getX(), ea.getY());
+            osg::Vec3d world;
+            if (_mapNode->getTerrain()->getWorldCoordsUnderMouse(aa.asView(), ea.getX(), ea.getY(), world)) {
+                osgEarth::GeoPoint map;
+                map.fromWorld(_mapNode->getMapSRS(), world);
+
+                for (osgEarth::Util::MouseCoordsTool::Callbacks::iterator i = _callbacks.begin();
+                     i != _callbacks.end(); ++i) {
+                    i->get()->set(map, aa.asView(), _mapNode);
+                }
+            } else {
+                for (osgEarth::Util::MouseCoordsTool::Callbacks::iterator i = _callbacks.begin();
+                     i != _callbacks.end(); ++i) {
+                    i->get()->reset(aa.asView(), _mapNode);
+                }
+            }
+        }
+        return false;
+    }
+};
+
+}
+
+#endif
Index: /geovis/branches/1.0/PPMWriter.cpp
===================================================================
--- /geovis/branches/1.0/PPMWriter.cpp	(revision 6666)
+++ /geovis/branches/1.0/PPMWriter.cpp	(revision 6666)
@@ -0,0 +1,171 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2004-2013  HUBzero Foundation, LLC
+ *
+ * Author: George A. Howlett <gah@purdue.edu>
+ */
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <cerrno>
+#include <sys/uio.h>
+
+#include "Trace.h"
+#include "PPMWriter.h"
+#ifdef USE_THREADS
+#include "ResponseQueue.h"
+#endif
+
+using namespace GeoVis;
+
+#ifdef USE_THREADS
+
+/**
+ * \brief Writes image data as PPM binary data to the client.
+ *
+ * The PPM binary format is very simple.
+ *
+ *     P6 w h 255\n
+ *     3-byte RGB pixel data.
+ *
+ * The client (using the TkImg library) will do less work to unpack this 
+ * format, as opposed to BMP or PNG.
+ *
+ * Note that currently the image data has bottom to top scanlines.  This 
+ * routine could be made even simpler (faster) if the image data had top 
+ * to bottom scanlines.
+ *
+ * \param[in] queue Pointer to ResponseQueue to write to
+ * \param[in] cmdName Command name to send (byte length will be appended)
+ * \param[in] data Image data
+ * \param[in] width Width of image in pixels
+ * \param[in] height Height of image in pixels
+ */
+void
+GeoVis::queuePPM(ResponseQueue *queue, const char *cmdName, 
+                 const unsigned char *data, int width, int height)
+{
+#define PPM_MAXVAL 255
+    char header[200];
+
+    FRAME("Entering (%dx%d)\n", width, height);
+    // Generate the PPM binary file header
+    snprintf(header, sizeof(header), "P6 %d %d %d\n", width, height, 
+             PPM_MAXVAL);
+
+    size_t headerLength = strlen(header);
+    size_t dataLength = width * height * 3;
+
+    char command[200];
+    snprintf(command, sizeof(command), "%s %lu\n", cmdName, 
+             (unsigned long)headerLength + dataLength);
+
+    size_t cmdLength;
+    cmdLength = strlen(command);
+
+    size_t length;
+    unsigned char *mesg;
+
+    length = headerLength + dataLength + cmdLength;
+    mesg = (unsigned char *)malloc(length);
+    if (mesg == NULL) {
+        ERROR("can't allocate %ld bytes for the image message", length);
+        return;
+    }
+    memcpy(mesg, command, cmdLength);
+    memcpy(mesg + cmdLength, header, headerLength);
+
+    size_t bytesPerRow = width * 3;
+    unsigned char *destRowPtr = mesg + length - bytesPerRow;
+    int y;
+    const unsigned char *srcRowPtr = data;
+    for (y = 0; y < height; y++) {
+        memcpy(destRowPtr, srcRowPtr, bytesPerRow);
+        srcRowPtr += bytesPerRow;
+        destRowPtr -= bytesPerRow;
+    }
+
+    Response *response;
+    if (strncmp(cmdName, "nv>legend", 9) == 0) {
+        response = new Response(Response::LEGEND);
+    } else {
+        response = new Response(Response::IMAGE);
+    }
+    response->setMessage(mesg, length, Response::DYNAMIC);
+    queue->enqueue(response);
+    FRAME("Leaving (%dx%d)\n", width, height);
+}
+#else
+
+/**
+ * \brief Writes image data as PPM binary data to the client.
+ *
+ * The PPM binary format is very simple.
+ *
+ *     P6 w h 255\n
+ *     3-byte RGB pixel data.
+ *
+ * The client (using the TkImg library) will do less work to unpack this 
+ * format, as opposed to BMP or PNG.
+ *
+ * Note that currently the image data has bottom to top scanlines.  This 
+ * routine could be made even simpler (faster) if the image data had top 
+ * to bottom scanlines.
+ *
+ * \param[in] fd File descriptor that will be written to
+ * \param[in] cmdName Command name to send (byte length will be appended)
+ * \param[in] data Image data
+ * \param[in] width Width of image in pixels
+ * \param[in] height Height of image in pixels
+ */
+void
+GeoVis::writePPM(int fd, const char *cmdName, 
+                 const unsigned char *data, int width, int height)
+{
+#define PPM_MAXVAL 255
+    char header[200];
+
+    FRAME("Entering (%dx%d)\n", width, height);
+    // Generate the PPM binary file header
+    snprintf(header, sizeof(header), "P6 %d %d %d\n", width, height,
+             PPM_MAXVAL);
+
+    size_t headerLength = strlen(header);
+    size_t dataLength = width * height * 3;
+
+    char command[200];
+    snprintf(command, sizeof(command), "%s %lu\n", cmdName, 
+             (unsigned long)headerLength + dataLength);
+
+    size_t nRecs = height + 2;
+
+    struct iovec *iov;
+    iov = (struct iovec *)malloc(sizeof(struct iovec) * nRecs);
+
+    // Write the command, then the image header and data.
+    // Command
+    iov[0].iov_base = command;
+    iov[0].iov_len = strlen(command);
+    // Header of image data
+    iov[1].iov_base = header;
+    iov[1].iov_len = headerLength;
+
+    // Image data
+    size_t bytesPerRow = width * 3;
+    int y;
+    unsigned char *srcRowPtr = const_cast<unsigned char *>(data);
+    for (y = height + 1; y >= 2; y--) {
+        iov[y].iov_base = srcRowPtr;
+        iov[y].iov_len = bytesPerRow;
+        srcRowPtr += bytesPerRow;
+    }
+    if (writev(fd, iov, nRecs) < 0) {
+        ERROR("write failed: %s\n", strerror(errno));
+    }
+    free(iov);
+
+    FRAME("Leaving (%dx%d)\n", width, height);
+}
+
+#endif /*USE_THREADS*/
Index: /geovis/branches/1.0/PPMWriter.h
===================================================================
--- /geovis/branches/1.0/PPMWriter.h	(revision 6666)
+++ /geovis/branches/1.0/PPMWriter.h	(revision 6666)
@@ -0,0 +1,26 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2004-2013  HUBzero Foundation, LLC
+ *
+ * Author: George A. Howlett <gah@purdue.edu>
+ */
+
+#ifndef GEOVIS_PPMWRITER_H
+#define GEOVIS_PPMWRITER_H
+
+#ifdef USE_THREADS
+#include "ResponseQueue.h"
+#endif
+
+namespace GeoVis {
+#ifdef USE_THREADS
+extern
+void queuePPM(ResponseQueue *queue, const char *cmdName, 
+              const unsigned char *data, int width, int height);
+#else
+extern
+void writePPM(int fd, const char *cmdName, 
+              const unsigned char *data, int width, int height);
+#endif
+}
+#endif
Index: /geovis/branches/1.0/Picker.cpp
===================================================================
--- /geovis/branches/1.0/Picker.cpp	(revision 6666)
+++ /geovis/branches/1.0/Picker.cpp	(revision 6666)
@@ -0,0 +1,295 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2015  HUBzero Foundation, LLC
+ *
+ * Author: Leif Delgass <ldelgass@purdue.edu>
+ */
+#include <vector>
+#include <cstdio>
+#include <cassert>
+
+#include <osgEarth/Registry>
+#include <osgEarth/ShaderGenerator>
+#include <osgEarth/ObjectIndex>
+#include <osgEarthFeatures/Feature>
+#include <osgEarthFeatures/FeatureIndex>
+#include <osgEarthFeatures/FeatureSourceIndexNode>
+#include <osgEarthAnnotation/AnnotationNode>
+#include <osgEarthAnnotation/LabelNode>
+#include <osgEarthSymbology/Color>
+#include <osgEarthSymbology/Style>
+#include <osgEarthSymbology/TextSymbol>
+
+#include "Picker.h"
+#include "Placard.h"
+#include "Renderer.h"
+#include "RendererCmd.h"
+#include "Trace.h"
+
+using namespace GeoVis;
+
+static const char *highlightVert =
+    "#version 130\n"
+    "uniform uint objectid_to_highlight; \n"
+    "uint oe_index_objectid;      // Stage global containing object id \n"
+    "flat out int selected; \n"
+    "void checkForHighlight(inout vec4 vertex) \n"
+    "{ \n"
+    "    selected = (objectid_to_highlight > 1u && objectid_to_highlight == oe_index_objectid) ? 1 : 0; \n"
+    "} \n";
+
+static const char *highlightFrag =
+    "#version 130\n"
+    "flat in int selected; \n"
+    "void highlightFragment(inout vec4 color) \n"
+    "{ \n"
+    "    if ( selected == 1 ) \n"
+    "        color.rgb = mix(color.rgb, clamp(vec3(0.5,0.5,2.0)*(1.0-color.rgb), 0.0, 1.0), 0.5); \n"
+    "} \n";
+
+static osg::Uniform *s_highlightUniform = NULL;
+
+static void installHighlighter(osg::StateSet *stateSet, int attrLocation)
+{
+    TRACE("GLSL Version: %s", GLSL_VERSION_STR);
+    // This shader program will highlight the selected object.
+    osgEarth::VirtualProgram *vp = osgEarth::VirtualProgram::getOrCreate(stateSet);
+    vp->setFunction("checkForHighlight",  highlightVert, osgEarth::ShaderComp::LOCATION_VERTEX_CLIP);
+    vp->setFunction("highlightFragment",  highlightFrag, osgEarth::ShaderComp::LOCATION_FRAGMENT_COLORING);
+
+    // Since we're accessing object IDs, we need to load the indexing shader as well:
+    osgEarth::Registry::objectIndex()->loadShaders(vp);
+
+    // A uniform that will tell the shader which object to highlight:
+    if (s_highlightUniform == NULL) {
+        s_highlightUniform = new osg::Uniform("objectid_to_highlight", 0u);
+    }
+    stateSet->addUniform(s_highlightUniform);
+}
+
+void GeoVis::setHighlightByObjectID(osgEarth::ObjectID id)
+{
+    if (s_highlightUniform != NULL) {
+        s_highlightUniform->set(id);
+    }
+}
+
+void GeoVis::clearHighlight()
+{
+    if (s_highlightUniform != NULL) {
+        s_highlightUniform->set(0U);
+    }
+}
+
+HoverCallback::HoverCallback(Renderer *renderer) :
+    _renderer(renderer)
+{
+    installHighlighter(renderer->getMapNode()->getOrCreateStateSet(),
+                       osgEarth::Registry::objectIndex()->getObjectIDAttribLocation());
+}
+
+void HoverCallback::onHit(osgEarth::ObjectID id)
+{
+    // First see whether it's a feature:
+    osgEarth::Features::FeatureIndex *index = osgEarth::Registry::objectIndex()->get<osgEarth::Features::FeatureIndex>(id);
+    osgEarth::Features::Feature *feature = index ? index->getFeature(id) : 0L;
+    TRACE("Hover hit");
+    if (feature) {
+        TRACE("Hit feature ID: %lu (of %d), oid: %lu", feature->getFID(), index->size(), id);
+        const osgEarth::Features::AttributeTable &attrs = feature->getAttrs();
+        for (osgEarth::Features::AttributeTable::const_iterator itr = attrs.begin(); itr != attrs.end(); ++itr) {
+            TRACE(" attr: %s", itr->first.c_str());
+            switch (itr->second.first) {
+            case osgEarth::Features::ATTRTYPE_STRING:
+                TRACE(" value: %s", itr->second.getString().c_str());
+                break;
+            case osgEarth::Features::ATTRTYPE_INT:
+                TRACE(" value: %d", itr->second.getInt());
+                break;
+            case osgEarth::Features::ATTRTYPE_DOUBLE:
+                TRACE(" value: %g", itr->second.getDouble());
+                break;
+            case osgEarth::Features::ATTRTYPE_BOOL:
+                TRACE(" value: %s", itr->second.getBool() ? "true" : "false");
+                break;
+            default:
+                TRACE(" value: unknown type");
+            }
+        }
+        //TRACE("Feature name: %s", feature->getString("name").c_str());
+    } else {
+        osgEarth::Annotation::AnnotationNode* anno =
+            osgEarth::Registry::objectIndex()->get<osgEarth::Annotation::AnnotationNode>(id);
+
+        std::set<osgEarth::Annotation::AnnotationNode*> toUnHover;
+        std::set<osgEarth::Annotation::AnnotationNode*>& hovered = _renderer->getHovered();
+        for (std::set<osgEarth::Annotation::AnnotationNode*>::iterator itr = hovered.begin();
+             itr != hovered.end(); ++itr) {
+            toUnHover.insert(*itr);
+        }
+        if (anno != NULL) {
+            TRACE("Hit AnnotationNode: %p", anno);
+#if 0
+            if (anno->getDecoration().empty() &&
+                hovered.find(anno) == hovered.end()) {
+                hovered.insert(anno);
+                anno->setDecoration("hover");
+                _renderer->eventuallyRender();
+            }
+            toUnHover.erase(anno);
+#endif
+        }
+#if 0
+        for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = toUnHover.begin();
+             itr != toUnHover.end(); ++itr) {
+            hovered.erase(*itr);
+            (*itr)->clearDecoration();
+            _renderer->eventuallyRender();
+        }
+#endif
+    }
+    setHighlightByObjectID(id);
+}
+
+void HoverCallback::onMiss()
+{
+    TRACE("Hover miss");
+    std::set<osgEarth::Annotation::AnnotationNode*> toUnHover;
+    std::set<osgEarth::Annotation::AnnotationNode*>& hovered = _renderer->getHovered();
+    for (std::set<osgEarth::Annotation::AnnotationNode*>::iterator itr = hovered.begin();
+         itr != hovered.end(); ++itr) {
+        toUnHover.insert(*itr);
+    }
+    for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = toUnHover.begin();
+         itr != toUnHover.end(); ++itr) {
+        hovered.erase(*itr);
+#ifndef NEW_ANNOTATION_API
+        (*itr)->clearDecoration();
+#endif
+        _renderer->eventuallyRender();
+    }
+    clearHighlight();
+}
+
+bool HoverCallback::accept(const osgGA::GUIEventAdapter& ea, const osgGA::GUIActionAdapter& aa)
+{
+    // Pick whenever mouse moves
+    return ea.getEventType() == ea.MOVE;
+}
+
+SelectCallback::SelectCallback(Renderer *renderer) :
+    _renderer(renderer),
+    _modifier(false)
+{
+    installHighlighter(renderer->getMapNode()->getOrCreateStateSet(),
+                       osgEarth::Registry::objectIndex()->getObjectIDAttribLocation());
+    installHighlighter(renderer->getPlaceNodes()->getOrCreateStateSet(),
+                       osgEarth::Registry::objectIndex()->getObjectIDAttribLocation());
+}
+
+void SelectCallback::onHit(osgEarth::ObjectID id)
+{
+    // First see whether it's a feature:
+    osgEarth::Features::FeatureIndex *index = osgEarth::Registry::objectIndex()->get<osgEarth::Features::FeatureIndex>(id);
+    osgEarth::Features::Feature *feature = index ? index->getFeature(id) : 0L;
+    TRACE("Select hit oid: %lu at %g,%g", id, _pickPoint.x(), _pickPoint.y());
+    if (feature) {
+        osgEarth::Features::FeatureSourceIndex *fsi = dynamic_cast<osgEarth::Features::FeatureSourceIndex *>(index);
+        std::string layerName;
+        if (fsi) {
+            osgEarth::Features::FeatureSource *source = fsi->getFeatureSource();
+            if (source) {
+                layerName = source->getFeatureSourceOptions().name().get();
+            }
+        }
+        TRACE("Hit FID: %lu (of %d) layer: %s", feature->getFID(), index->size(), layerName.c_str());
+
+        std::vector<unsigned long> fids;
+        fids.push_back(feature->getFID());
+        _renderer->selectFeatures(fids, layerName.c_str(), !_modifier);
+        _renderer->addPlacard(_pickPoint, feature, layerName.c_str());
+
+        char mesg[256];
+        if (_modifier) {
+            // Check if already selected
+            snprintf(mesg, sizeof(mesg), "nv>select feature add {%lu} {%s}\n", feature->getFID(), layerName.c_str());
+        } else {
+            snprintf(mesg, sizeof(mesg), "nv>select feature set {%lu} {%s}\n", feature->getFID(), layerName.c_str());
+        }
+        size_t length = strlen(mesg);
+        queueResponse(mesg, length, Response::VOLATILE, Response::DATA);
+    } else {
+        _renderer->clearSelection();
+        osgEarth::Annotation::AnnotationNode* anno =
+            osgEarth::Registry::objectIndex()->get<osgEarth::Annotation::AnnotationNode>(id);
+        if (anno != NULL) {
+            TRACE("Hit AnnotationNode: %p, \"%s\"", anno, anno->getName().c_str());
+            char mesg[256];
+            snprintf(mesg, sizeof(mesg), "nv>select annotation {%s}\n", anno->getName().c_str());
+            size_t length = strlen(mesg);
+            queueResponse(mesg, length, Response::VOLATILE, Response::DATA);
+        }
+    }
+    setHighlightByObjectID(id);
+    _renderer->pickPending(false);
+    _renderer->eventuallyRender();
+}
+
+void SelectCallback::onMiss()
+{
+    TRACE("Select miss");
+    _renderer->clearSelection();
+
+    static const char *mesg = "nv>select clear\n";
+    static const size_t length = strlen(mesg);
+    queueResponse(mesg, length, Response::STATIC, Response::DATA);
+
+    _renderer->pickPending(false);
+    _renderer->eventuallyRender();
+}
+
+bool SelectCallback::accept(const osgGA::GUIEventAdapter& ea, const osgGA::GUIActionAdapter& aa)
+{
+    if (ea.getEventType() == ea.PUSH) {
+        _x = ea.getX();
+        _y = ea.getY();
+        // Modifier-click?
+        _modifier = (ea.getModKeyMask() & osgGA::GUIEventAdapter::MODKEY_CTRL) != 0;
+        return false;
+    } else if (ea.getEventType() == ea.RELEASE) {
+        // Don't pick if mouse was dragged more than 2 pixels
+        if (fabsf(ea.getX()-_x) > 2 || fabsf(ea.getY()-_y) > 2) {
+            return false;
+        } else {
+            _renderer->mapMouseCoords(ea.getX(), ea.getY(), _pickPoint, false);
+            _renderer->pickPending(true);
+            TRACE("Pending pick at %g %g", ea.getX(), ea.getY());
+            return true;
+        }
+    }
+    return false;
+}
+
+void DeleteCallback::onHit(osgEarth::ObjectID id)
+{
+    // First see whether it's a feature:
+    osgEarth::Features::FeatureIndex *index = osgEarth::Registry::objectIndex()->get<osgEarth::Features::FeatureIndex>(id);
+    osgEarth::Features::Feature *feature = index ? index->getFeature(id) : 0L;
+
+    if (feature) {
+        TRACE("Hit feature ID: %lu, oid: %lu name: %s", feature->getFID(), id);
+        TRACE("Feature name: %s", feature->getString("name").c_str());
+    } else {
+        TRACE("Picker hit!");
+        osgEarth::Annotation::AnnotationNode* anno =
+            osgEarth::Registry::objectIndex()->get<osgEarth::Annotation::AnnotationNode>(id);
+        std::set<osgEarth::Annotation::AnnotationNode*>& hovered = _renderer->getHovered();
+#ifndef NEW_ANNOTATION_API
+        anno->clearDecoration();
+#endif
+        _renderer->getPlaceNodes()->removeChild(anno);
+        if (hovered.find(anno) != hovered.end()) {
+            hovered.erase(anno);
+        }
+    }
+}
Index: /geovis/branches/1.0/Picker.h
===================================================================
--- /geovis/branches/1.0/Picker.h	(revision 6666)
+++ /geovis/branches/1.0/Picker.h	(revision 6666)
@@ -0,0 +1,61 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2015  HUBzero Foundation, LLC
+ *
+ * Author: Leif Delgass <ldelgass@purdue.edu>
+ */
+#ifndef GEOVIS_PICKER_H
+#define GEOVIS_PICKER_H
+
+#include <osgEarth/Picker>
+#include <osgEarth/ObjectIndex>
+
+namespace GeoVis {
+
+class Renderer;
+
+struct HoverCallback : public osgEarth::Picker::Callback {
+public:
+    HoverCallback(Renderer *renderer);
+
+    void onHit(osgEarth::ObjectID id);
+    void onMiss();
+    bool accept(const osgGA::GUIEventAdapter& ea, const osgGA::GUIActionAdapter& aa);
+
+private:
+    Renderer *_renderer;
+};
+
+struct SelectCallback : public osgEarth::Picker::Callback {
+public:
+    SelectCallback(Renderer *renderer);
+
+    void onHit(osgEarth::ObjectID id);
+    void onMiss();
+    bool accept(const osgGA::GUIEventAdapter& ea, const osgGA::GUIActionAdapter& aa);
+
+private:
+    Renderer *_renderer;
+    float _x, _y;
+    bool _modifier;
+    osgEarth::GeoPoint _pickPoint;
+};
+
+struct DeleteCallback : public osgEarth::Picker::Callback {
+public:
+    DeleteCallback(Renderer *renderer) :
+        _renderer(renderer)
+    {}
+
+    void onHit(osgEarth::ObjectID id);
+
+private:
+    Renderer *_renderer;
+};
+
+extern void setHighlightByObjectID(osgEarth::ObjectID id);
+extern void clearHighlight();
+
+}
+
+#endif
Index: /geovis/branches/1.0/Placard.cpp
===================================================================
--- /geovis/branches/1.0/Placard.cpp	(revision 6666)
+++ /geovis/branches/1.0/Placard.cpp	(revision 6666)
@@ -0,0 +1,371 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2015  HUBzero Foundation, LLC
+ *
+ * Author: Leif Delgass <ldelgass@purdue.edu>
+ */
+
+#include <string>
+#include <sstream>
+
+#include <osg/Depth>
+#include <osgText/Text>
+#include <osgEarthAnnotation/AnnotationUtils>
+#include <osgEarth/Registry>
+#include <osgEarth/ShaderGenerator>
+#ifdef NEW_ANNOTATION_API
+#include <osgEarth/ScreenSpaceLayout>
+#include <osgEarthSymbology/BBoxSymbol>
+#else
+#include <osgEarth/Decluttering>
+#endif
+#include <osgEarthSymbology/PolygonSymbol>
+#include <osgEarthSymbology/TextSymbol>
+#include <osgEarthSymbology/Fill>
+
+#include "Placard.h"
+#include "Trace.h"
+
+#define LC "[PlacardNode] "
+
+using namespace GeoVis;
+
+Placard::Placard() :
+    _enabled(true)
+{
+    osgEarth::Symbology::TextSymbol *ts = _textStyle.getOrCreateSymbol<osgEarth::Symbology::TextSymbol>();
+    //ts->halo()->color() = osgEarth::Symbology::Color(1, 1, 1);
+    //ts->halo()->width() = 2;
+    ts->fill()->color() = osgEarth::Symbology::Color(0, 0, 0);
+    ts->size() = 14;
+    ts->alignment() = osgEarth::Symbology::TextSymbol::ALIGN_LEFT_BASE_LINE;
+    ts->pixelOffset() = osg::Vec2s(20, 0);
+    ts->declutter() = false;
+    //ts->encoding() = osgEarth::Symbology::TextSymbol::ENCODING_UTF8;
+    double charSize = ts->size().isSet() ? ts->size()->eval() : 14.0;
+    _padding = (float)(charSize / 2.0);
+
+#ifdef NEW_ANNOTATION_API
+    osgEarth::Symbology::BBoxSymbol *bbs = _textStyle.getOrCreateSymbol<osgEarth::Symbology::BBoxSymbol>();
+    bbs->fill() = osgEarth::Symbology::Color(0.75, 0.75, 0.75, 0.75);
+    bbs->margin() = _padding;
+#else
+    // Set backdrop quad color as PolygonSymbol fill
+    osgEarth::Symbology::PolygonSymbol *ps = _textStyle.getOrCreateSymbol<osgEarth::Symbology::PolygonSymbol>();
+    ps->fill() = osgEarth::Symbology::Color(0.75, 0.75, 0.75, 0.75);
+#endif
+}
+
+PlacardLabelNode::PlacardLabelNode(osgEarth::MapNode *mapNode,
+                                   const osgEarth::GeoPoint& position,
+                                   const Placard& placardConf,
+                                   const osgEarth::Features::AttributeTable &attrs) :
+    osgEarth::Annotation::LabelNode(mapNode, position, placardConf.getStyle()),
+    _placardConf(placardConf),
+    _attrs(attrs)
+{
+    setConfig(placardConf);
+}
+
+void
+PlacardLabelNode::setConfig(const Placard& placardConf)
+{
+    _placardConf = placardConf;
+    std::ostringstream oss;
+    for (size_t i = 0; i < placardConf.getNumEntries(); i++) {
+        std::string name, label;
+        placardConf.getEntry(i, name, label);
+        oss << label << ": ";
+        TRACE(" attr: %s '%s'", name.c_str(), label.c_str());
+        osgEarth::Features::AttributeTable::const_iterator itr = _attrs.find(name);
+        if (itr == _attrs.end())
+            continue;
+        switch (itr->second.first) {
+        case osgEarth::Features::ATTRTYPE_STRING:
+            TRACE(" value: %s", itr->second.getString().c_str());
+            oss << itr->second.getString() << std::endl;
+            break;
+        case osgEarth::Features::ATTRTYPE_INT:
+            TRACE(" value: %d", itr->second.getInt());
+            oss << itr->second.getInt() << std::endl;
+            break;
+        case osgEarth::Features::ATTRTYPE_DOUBLE:
+            TRACE(" value: %g", itr->second.getDouble());
+            oss << itr->second.getDouble() << std::endl;
+            break;
+        case osgEarth::Features::ATTRTYPE_BOOL:
+            TRACE(" value: %s", itr->second.getBool() ? "true" : "false");
+            oss << (itr->second.getBool() ? "true" : "false") << std::endl;
+            break;
+        default:
+            break;
+        }
+    }
+    setText(oss.str());
+}
+
+PlacardNode::PlacardNode(osgEarth::MapNode *mapNode,
+                         const osgEarth::GeoPoint& position,
+                         const Placard& placardConf,
+                         const osgEarth::Features::AttributeTable &attrs) :
+#ifdef NEW_ANNOTATION_API
+    osgEarth::Annotation::GeoPositionNode(mapNode, position),
+#else
+    osgEarth::Annotation::OrthoNode(mapNode, position),
+#endif
+    _placardConf(placardConf),
+    _attrs(attrs)
+{
+    setConfig(placardConf);
+    init(placardConf.getStyle());
+}
+
+void
+PlacardNode::setConfig(const Placard& placardConf)
+{
+    _placardConf = placardConf;
+    std::ostringstream oss;
+    for (size_t i = 0; i < placardConf.getNumEntries(); i++) {
+        std::string name, label;
+        placardConf.getEntry(i, name, label);
+        oss << label << ": ";
+        TRACE(" attr: %s '%s'", name.c_str(), label.c_str());
+        osgEarth::Features::AttributeTable::const_iterator itr = _attrs.find(name);
+        if (itr == _attrs.end())
+            continue;
+        switch (itr->second.first) {
+        case osgEarth::Features::ATTRTYPE_STRING:
+            TRACE(" value: %s", itr->second.getString().c_str());
+            oss << itr->second.getString() << std::endl;
+            break;
+        case osgEarth::Features::ATTRTYPE_INT:
+            TRACE(" value: %d", itr->second.getInt());
+            oss << itr->second.getInt() << std::endl;
+            break;
+        case osgEarth::Features::ATTRTYPE_DOUBLE:
+            TRACE(" value: %g", itr->second.getDouble());
+            oss << itr->second.getDouble() << std::endl;
+            break;
+        case osgEarth::Features::ATTRTYPE_BOOL:
+            TRACE(" value: %s", itr->second.getBool() ? "true" : "false");
+            oss << (itr->second.getBool() ? "true" : "false") << std::endl;
+            break;
+        default:
+            break;
+        }
+    }
+    setText(oss.str());
+}
+
+void
+PlacardNode::init( const osgEarth::Symbology::Style& style )
+{
+#ifdef NEW_ANNOTATION_API
+    osgEarth::ScreenSpaceLayout::activate(this->getOrCreateStateSet());
+#endif
+    _backdropGeode = new osg::Geode();
+    _backdropGeode->setComputeBoundingSphereCallback(new osgEarth::Annotation::ControlPointCallback());
+#ifdef NEW_ANNOTATION_API
+    getPositionAttitudeTransform()->addChild( _backdropGeode.get() );
+#else
+    getAttachPoint()->addChild(_backdropGeode.get());
+#endif
+    osg::StateSet* stateSet = _backdropGeode->getOrCreateStateSet();
+    stateSet->setAttributeAndModes( new osg::Depth(osg::Depth::ALWAYS, 0, 1, false), 1 );
+
+    _geode = new osg::Geode();
+
+    // ensure that (0,0,0) is the bounding sphere control/center point.
+    // useful for things like horizon culling.
+    _geode->setComputeBoundingSphereCallback(new osgEarth::Annotation::ControlPointCallback());
+
+#ifdef NEW_ANNOTATION_API
+    getPositionAttitudeTransform()->addChild( _geode.get() );
+#else
+    getAttachPoint()->addChild( _geode.get() );
+#endif
+    stateSet = _geode->getOrCreateStateSet();
+    stateSet->setAttributeAndModes( new osg::Depth(osg::Depth::ALWAYS, 0, 1, false), 1 );
+
+    setStyle( style );
+}
+
+void
+PlacardNode::setText( const std::string& text )
+{
+    if ( !_dynamic && getNumParents() > 0 )
+    {
+        OE_WARN << LC << "Illegal state: cannot change a LabelNode that is not dynamic" << std::endl;
+        return;
+    }
+
+    _text = text;
+    if (!_geode.valid())
+        return;
+
+    osgText::Text* d = dynamic_cast<osgText::Text*>(_geode->getDrawable(0));
+    if ( d )
+    {
+        d->setText( text );
+        d->dirtyDisplayList();
+    }
+
+    if (!_backdropGeode.valid())
+        return;
+
+    osg::Geometry *bd = dynamic_cast<osg::Geometry*>(_backdropGeode->getDrawable(0));
+    if (bd) {
+        osg::BoundingBox bbox = d->getBoundingBox();
+        _backdropGeode->removeDrawables( 0, _backdropGeode->getNumDrawables() );
+        const osgEarth::Symbology::PolygonSymbol* gs = _style.get<osgEarth::Symbology::PolygonSymbol>();
+        osg::Vec4 color(0.75, 0.75, 0.75, 0.75);
+        if (gs && gs->fill().isSet()) {
+            color = gs->fill().get().color();
+        }
+        osg::Drawable *bd = osgEarth::Annotation::AnnotationUtils::create2DQuad(bbox, _placardConf.getPadding(), color);
+        _backdropGeode->addDrawable(bd);
+    }
+}
+
+void
+PlacardNode::setStyle( const osgEarth::Symbology::Style& style )
+{
+    if ( !_dynamic && getNumParents() > 0 )
+    {
+        OE_WARN << LC << "Illegal state: cannot change a LabelNode that is not dynamic" << std::endl;
+        return;
+    }
+#ifndef NEW_ANNOTATION_API
+    this->clearDecoration();
+#endif
+    _backdropGeode->removeDrawables( 0, _backdropGeode->getNumDrawables() );
+
+    _geode->removeDrawables( 0, _geode->getNumDrawables() );
+
+    _style = style;
+
+    const osgEarth::Symbology::TextSymbol* symbol = _style.get<osgEarth::Symbology::TextSymbol>();
+
+    if ( _text.empty() )
+        _text = symbol->content()->eval();
+
+    osg::Drawable* t = osgEarth::Annotation::AnnotationUtils::createTextDrawable( _text, symbol, osg::Vec3(0,0,0) );
+    _geode->addDrawable(t);
+    _geode->setCullingActive(false);
+
+    const osgEarth::Symbology::PolygonSymbol* gs = _style.get<osgEarth::Symbology::PolygonSymbol>();
+    osg::Vec4 color(0.75, 0.75, 0.75, 0.75);
+    if (gs && gs->fill().isSet()) {
+        color = gs->fill().get().color();
+    }
+
+    osg::BoundingBox bbox = t->getBoundingBox();
+    osg::Drawable *bd = osgEarth::Annotation::AnnotationUtils::create2DQuad(bbox, _placardConf.getPadding(), color);
+    _backdropGeode->addDrawable(bd);
+    _backdropGeode->setCullingActive(false);
+
+    applyStyle( _style );
+
+    setLightingIfNotSet( false );
+
+    osgEarth::Registry::shaderGenerator().run(
+        this,
+        "osgEarth.PlacardNode",
+        osgEarth::Registry::stateSetCache() );
+#ifdef NEW_ANNOTATION_API
+    // Set to max priority to draw on top in decluttering sort
+    setPriority(FLT_MAX);
+    // setPriority calls updateLayoutData
+    //updateLayoutData();
+#endif
+}
+
+#ifdef NEW_ANNOTATION_API
+void
+PlacardNode::setPriority(float value)
+{
+    osgEarth::Annotation::GeoPositionNode::setPriority(value);
+    updateLayoutData();
+}
+
+void
+PlacardNode::updateLayoutData()
+{
+    if (!_geode.valid())
+        return;
+
+    if (!_dataLayout.valid())
+    {
+        _dataLayout = new osgEarth::ScreenSpaceLayoutData();
+    }
+
+    // re-apply annotation drawable-level stuff as neccesary.
+    for (unsigned i = 0; i < _backdropGeode->getNumDrawables(); ++i)
+    {
+        _backdropGeode->getDrawable(i)->setUserData(_dataLayout.get());
+    }
+    // re-apply annotation drawable-level stuff as neccesary.
+    for (unsigned i = 0; i < _geode->getNumDrawables(); ++i)
+    {
+        _geode->getDrawable(i)->setUserData(_dataLayout.get());
+    }
+
+    _dataLayout->setPriority(getPriority());
+    const osgEarth::Symbology::TextSymbol *ts = getStyle().get<osgEarth::Symbology::TextSymbol>();
+    if (ts)
+    {
+        _dataLayout->setPixelOffset(ts->pixelOffset().get());
+    }
+}
+#else
+void
+PlacardNode::setAnnotationData( osgEarth::Annotation::AnnotationData* data )
+{
+    osgEarth::Annotation::OrthoNode::setAnnotationData( data );
+
+    if (!_geode.valid())
+        return;
+
+    // override this method so we can attach the anno data to the drawables.
+    for(unsigned i=0; i<_geode->getNumDrawables(); ++i)
+    {
+        _geode->getDrawable(i)->setUserData( data );
+    }
+}
+#endif
+
+void
+PlacardNode::setDynamic( bool dynamic )
+{
+#ifdef NEW_ANNOTATION_API
+    osgEarth::Annotation::GeoPositionNode::setDynamic( dynamic );
+#else
+    osgEarth::Annotation::OrthoNode::setDynamic( dynamic );
+#endif
+    osgText::Text* d = dynamic_cast<osgText::Text*>(_geode->getDrawable(0));
+    if ( d )
+    {
+        d->setDataVariance( dynamic ? osg::Object::DYNAMIC : osg::Object::STATIC );
+    }
+    osg::Geometry *bd = dynamic_cast<osg::Geometry*>(_backdropGeode->getDrawable(0));
+    if (bd) {
+        bd->setDataVariance( dynamic ? osg::Object::DYNAMIC : osg::Object::STATIC );
+    }
+}
+
+osgEarth::Config
+PlacardNode::getConfig() const
+{
+#ifdef NEW_ANNOTATION_API
+    osgEarth::Config conf = osgEarth::Annotation::GeoPositionNode::getConfig();
+#else
+    osgEarth::Config conf = osgEarth::Annotation::OrthoNode::getConfig();
+#endif
+    conf.key() = "placard";
+
+    conf.add   ( "text",   _text );
+    conf.addObj( "style",  _style );
+    conf.add   ( "padding", _placardConf.getPadding() );
+
+    return conf;
+}
Index: /geovis/branches/1.0/Placard.h
===================================================================
--- /geovis/branches/1.0/Placard.h	(revision 6666)
+++ /geovis/branches/1.0/Placard.h	(revision 6666)
@@ -0,0 +1,208 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2015  HUBzero Foundation, LLC
+ *
+ * Author: Leif Delgass <ldelgass@purdue.edu>
+ */
+
+#ifndef GEOVIS_PLACARD_H
+#define GEOVIS_PLACARD_H
+
+#include <string>
+#include <vector>
+#include <algorithm>
+
+#ifdef NEW_ANNOTATION_API
+#include <osgEarth/ScreenSpaceLayout>
+#include <osgEarthAnnotation/GeoPositionNode>
+#else
+#include <osgEarthAnnotation/Decoration>
+#include <osgEarthAnnotation/OrthoNode>
+#endif
+#include <osgEarthAnnotation/LabelNode>
+#include <osgEarthFeatures/Feature>
+
+namespace GeoVis {
+
+class Placard {
+public:
+    Placard();
+    virtual ~Placard()
+    {}
+    void addAllAttributes(const osgEarth::Features::AttributeTable& attrs)
+    {
+        for (osgEarth::Features::AttributeTable::const_iterator itr = attrs.begin();
+             itr != attrs.end(); ++itr) {
+            std::string label = itr->first;
+            std::replace(label.begin(), label.end(), '_', ' ');
+            addEntry(itr->first, label);
+        }
+    }
+    void addEntry(const std::string& name)
+    {
+        addEntry(name, name);
+    }
+    void addEntry(const std::string& name, const std::string& label)
+    {
+        _attrNames.push_back(name);
+        _attrLabels.push_back(label);
+    }
+    void getEntry(int i, std::string& name, std::string& label) const
+    {
+        name = _attrNames[i];
+        label = _attrLabels[i];
+    }
+    size_t getNumEntries() const
+    {
+        return _attrNames.size();
+    }
+    void setStyle(const osgEarth::Symbology::Style& style)
+    {
+        _textStyle = style;
+    }
+    const osgEarth::Symbology::Style& getStyle() const
+    {
+        return _textStyle;
+    }
+    void setPadding(float padding)
+    {
+        _padding = padding;
+    }
+    float getPadding() const
+    {
+        return _padding;
+    }
+    bool enabled()
+    {
+        return _enabled;
+    }
+    void setEnabled(bool state)
+    {
+        _enabled = state;
+    }
+
+private:
+    std::vector<std::string> _attrNames;
+    std::vector<std::string> _attrLabels;
+    float _padding;
+    osgEarth::Symbology::Style _textStyle;
+    bool _enabled;
+};
+
+class PlacardLabelNode : public osgEarth::Annotation::LabelNode {
+public:
+    META_Node(osgEarthAnnotation, PlacardLabelNode);
+#ifndef NEW_ANNOTATION_API
+    virtual bool accept(osgEarth::Annotation::Decoration* ds, bool enable)
+    { return ds->apply(*this, enable); }
+#endif
+    PlacardLabelNode() :
+        osgEarth::Annotation::LabelNode()
+    {}
+    PlacardLabelNode(const PlacardLabelNode& other, const osg::CopyOp& op = osg::CopyOp::DEEP_COPY_ALL) :
+        osgEarth::Annotation::LabelNode(other, op),
+        _placardConf(other._placardConf)
+    {
+    }
+    PlacardLabelNode(osgEarth::MapNode *mapNode,
+                     const osgEarth::GeoPoint& position,
+                     const Placard& placardConf,
+                     const osgEarth::Features::AttributeTable &attrs);
+
+    virtual ~PlacardLabelNode()
+    {}
+
+    void setConfig(const Placard& placardConf);
+
+private:
+    Placard _placardConf;
+    osgEarth::Features::AttributeTable _attrs;
+};
+
+class PlacardNode :
+#ifdef NEW_ANNOTATION_API
+public osgEarth::Annotation::GeoPositionNode
+#else
+public osgEarth::Annotation::OrthoNode
+#endif
+{
+public:
+    META_Node(osgEarthAnnotation, PlacardNode);
+#ifndef NEW_ANNOTATION_API
+    virtual bool accept(osgEarth::Annotation::Decoration* ds, bool enable)
+    { return ds->apply(*this, enable); }
+#endif
+    PlacardNode() :
+#ifdef NEW_ANNOTATION_API
+        osgEarth::Annotation::GeoPositionNode()
+#else
+        osgEarth::Annotation::OrthoNode()
+#endif
+    {}
+    PlacardNode(const PlacardNode& other, const osg::CopyOp& op = osg::CopyOp::DEEP_COPY_ALL) :
+#ifdef NEW_ANNOTATION_API
+        osgEarth::Annotation::GeoPositionNode(other, op),
+#else
+        osgEarth::Annotation::OrthoNode(other, op),
+#endif
+        _placardConf(other._placardConf)
+    {
+    }
+    PlacardNode(osgEarth::MapNode *mapNode,
+                const osgEarth::GeoPoint& position,
+                const Placard& placardConf,
+                const osgEarth::Features::AttributeTable &attrs);
+
+    virtual ~PlacardNode()
+    {}
+
+    void setConfig(const Placard& placardConf);
+
+    /**
+     * Sets the text content.
+     */
+    void setText( const std::string& text );
+    const std::string& text() const { return _text; }
+    virtual const std::string& getText() const { return text(); }
+
+    /**
+     * Gets a copy of the text style.
+     */
+    const osgEarth::Symbology::Style& getStyle() const { return _style; }
+
+    /**
+     * Sets a new text style
+     */
+    void setStyle( const osgEarth::Symbology::Style& style );
+
+public: // Ortho|GeoPositionNode override
+#ifdef NEW_ANNOTATION_API
+    virtual void setPriority(float value);
+#else
+    virtual void setAnnotationData( osgEarth::Annotation::AnnotationData* data );
+#endif
+    virtual void setDynamic( bool value );
+
+    virtual osgEarth::Config getConfig() const;
+
+protected:
+    void init(const osgEarth::Symbology::Style& style);
+#ifdef NEW_ANNOTATION_API
+    void updateLayoutData();
+#endif
+
+    std::string                _text;
+    osgEarth::Symbology::Style _style;
+    osg::ref_ptr<osg::Geode>   _geode;
+    osg::ref_ptr<osg::Geode>   _backdropGeode;
+#ifdef NEW_ANNOTATION_API
+    osg::ref_ptr<osgEarth::ScreenSpaceLayoutData> _dataLayout;
+#endif
+
+    Placard _placardConf;
+    osgEarth::Features::AttributeTable _attrs;
+};
+
+}
+
+#endif
Index: /geovis/branches/1.0/ReadBuffer.cpp
===================================================================
--- /geovis/branches/1.0/ReadBuffer.cpp	(revision 6666)
+++ /geovis/branches/1.0/ReadBuffer.cpp	(revision 6666)
@@ -0,0 +1,185 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2004-2013  HUBzero Foundation, LLC
+ *
+ * Author: George A. Howlett <gah@purdue.edu>
+ */
+
+#include <cstdlib>
+#include <cerrno>
+#include <cstdio>
+#include <unistd.h>
+#include <cstring>
+
+#include "ReadBuffer.h"
+#include "Trace.h"
+
+using namespace GeoVis;
+
+/**
+ * \param[in] fd File descriptor to read
+ * \param[in] bufferSize Block size to use in internal buffer
+ */
+ReadBuffer::ReadBuffer(int fd, size_t bufferSize) :
+    _bufferSize(bufferSize),
+    _fd(fd),
+    _lastStatus(OK)
+{
+    _bytes = new unsigned char[_bufferSize];
+    flush();
+}
+
+ReadBuffer::~ReadBuffer()
+{
+    TRACE("Deleting ReadBuffer");
+    delete [] _bytes;
+}
+
+/**
+ * \brief Checks if a new line is currently in the buffer.
+ *
+ * \return the index of the character past the new line.
+ */
+size_t
+ReadBuffer::nextNewLine()
+{
+    /* Check for a newline in the current buffer. */
+    unsigned char *ptr = 
+        (unsigned char *)memchr(_bytes + _mark, '\n', _fill - _mark);
+    if (ptr == NULL)
+        return 0;
+    else
+        return (ptr - _bytes + 1);
+}
+
+/**  
+ * \brief Fills the buffer with available data.
+ * 
+ * Any existing data in the buffer is moved to the front of the buffer, 
+ * then the channel is read to fill the rest of the buffer.
+ *
+ * \return If an error occur when reading the channel, then ERROR is
+ * returned. ENDFILE is returned on EOF.  If the buffer can't be filled, 
+ * then CONTINUE is returned.
+ */
+ReadBuffer::BufferStatus
+ReadBuffer::doFill()
+{
+    //TRACE("Enter, mark: %lu fill: %lu", _mark, _fill);
+    if (_mark >= _fill) {
+        flush();                        /* Fully consumed buffer */
+    }
+    if (_mark > 0) {
+        /* Some data has been consumed. Move the unconsumed data to the front
+         * of the buffer. */
+        TRACE("memmove %lu bytes", _fill-_mark);
+        memmove(_bytes, _bytes + _mark, _fill - _mark);
+        _fill -= _mark;
+        _mark = 0;
+    }
+    ssize_t numRead;
+    size_t bytesLeft;
+
+    bytesLeft = _bufferSize - _fill;
+    //TRACE("going to read %lu bytes", bytesLeft);
+    numRead = read(_fd, _bytes + _fill, bytesLeft);
+    if (numRead == 0) {
+        /* EOF */
+        TRACE("EOF found reading buffer");
+        return ReadBuffer::ENDFILE;
+    }
+    if (numRead < 0) {
+        if (errno != EAGAIN) {
+            ERROR("error reading buffer: %s", strerror(errno));
+            return ReadBuffer::ERROR;
+        }
+        TRACE("Short read for buffer");
+        return ReadBuffer::CONTINUE;
+    }
+    _fill += numRead;
+    //TRACE("Read %lu bytes", numRead);
+    return ((size_t)numRead == bytesLeft) 
+        ? ReadBuffer::OK : ReadBuffer::CONTINUE;
+}
+
+/**
+ * \brief Read the requested number of bytes from the buffer.
+
+ * Fails if the requested number of bytes are not immediately 
+ * available. Never should be short. 
+ */
+ReadBuffer::BufferStatus
+ReadBuffer::followingData(unsigned char *out, size_t numBytes)
+{
+    TRACE("Enter");
+    while (numBytes > 0) {
+        size_t bytesLeft;
+
+        bytesLeft = _fill - _mark;
+        if (bytesLeft > 0) {
+            int size;
+
+            /* Pull bytes out of the buffer, updating the mark. */
+            size = (bytesLeft >  numBytes) ? numBytes : bytesLeft;
+            memcpy(out, _bytes + _mark, size);
+            _mark += size;
+            numBytes -= size;
+            out += size;
+        }
+        if (numBytes == 0) {
+            /* Received requested # bytes. */
+            return ReadBuffer::OK;
+        }
+        /* Didn't get enough bytes, need to read some more. */
+        _lastStatus = doFill();
+        if (_lastStatus == ReadBuffer::ERROR ||
+            _lastStatus == ReadBuffer::ENDFILE) {
+            return _lastStatus;
+        }
+    }
+    return ReadBuffer::OK;
+}
+
+/** 
+ * \brief Returns the next available line (terminated by a newline)
+ * 
+ * If insufficient data is in the buffer, then the channel is
+ * read for more data.  If reading the channel results in a
+ * short read, CONTINUE is returned and *numBytesPtr is set to 0.
+ */
+ReadBuffer::BufferStatus
+ReadBuffer::getLine(size_t *numBytesPtr, unsigned char **bytesPtr)
+{
+    TRACE("Enter");
+    *numBytesPtr = 0;
+    *bytesPtr = NULL;
+
+    _lastStatus = ReadBuffer::OK;
+    for (;;) {
+        size_t _newline;
+
+        _newline = nextNewLine();
+        if (_newline > 0) {
+            /* Start of the line. */
+            *bytesPtr = _bytes + _mark;
+            /* Number of bytes in the line. */
+            *numBytesPtr = _newline - _mark;
+            _mark = _newline;
+            return ReadBuffer::OK;
+        }
+        /* Couldn't find a newline, so it may be that we need to read some
+         * more. Check first that last read wasn't a short read. */
+        if (_lastStatus == ReadBuffer::CONTINUE) {
+            /* No complete line just yet. */
+            return ReadBuffer::CONTINUE;
+        }
+        /* Try to add more data to the buffer. */
+        _lastStatus = doFill();
+        if (_lastStatus == ReadBuffer::ERROR ||
+            _lastStatus == ReadBuffer::ENDFILE) {
+            return _lastStatus;
+        }
+        /* OK or CONTINUE */
+    }
+    return ReadBuffer::CONTINUE;
+}
Index: /geovis/branches/1.0/ReadBuffer.h
===================================================================
--- /geovis/branches/1.0/ReadBuffer.h	(revision 6666)
+++ /geovis/branches/1.0/ReadBuffer.h	(revision 6666)
@@ -0,0 +1,84 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2004-2013  HUBzero Foundation, LLC
+ *
+ * Author: George A. Howlett <gah@purdue.edu>
+ */
+
+#include <cstdlib>
+
+#ifndef GEOVIS_READBUFFER_H
+#define GEOVIS_READBUFFER_H
+
+namespace GeoVis {
+
+/**
+ * \brief Buffered input for reading socket
+ */
+class ReadBuffer
+{
+public:
+    enum BufferStatus {
+        ENDFILE=-1,
+        OK,
+        ERROR,
+        CONTINUE,
+    };
+
+    ReadBuffer(int fd, size_t bufferSize);
+
+    virtual ~ReadBuffer();
+
+    BufferStatus getLine(size_t *numBytesPtr, unsigned char **bytesPtr);
+
+    BufferStatus followingData(unsigned char *out, size_t numBytes);
+
+    /**
+     * \brief Check if buffer contains data ending in a newline
+     * 
+     * \return bool indicating if getLine can be called without blocking 
+     */
+    bool isLineAvailable()
+    {
+        return (nextNewLine() > 0);
+    }
+
+    /**
+     * \brief Get the file descriptor this object reads from
+     */
+    int file()
+    {
+        return _fd;
+    }
+
+    /**
+     * \brief Get the status of the last read
+     */
+    BufferStatus status()
+    {
+        return _lastStatus;
+    }
+
+private:
+    unsigned char *_bytes;		/**< New-ed buffer to hold read
+					 * characters. */
+    size_t _bufferSize;			/**< # of bytes allocated for buffer */
+    size_t _fill;			/**< # of bytes used in the buffer */
+    size_t _mark;			/**< Starting point of the unconsumed
+					 * data in the buffer. */
+    int _fd;				/**< File descriptor to get data. */
+    BufferStatus _lastStatus;           /**< Status of last read operation. */
+
+    BufferStatus doFill();
+
+    void flush()
+    {
+        _fill = _mark = 0;
+    }
+
+    size_t nextNewLine();
+};
+
+}
+
+#endif
Index: /geovis/branches/1.0/RenderServer.cpp
===================================================================
--- /geovis/branches/1.0/RenderServer.cpp	(revision 6666)
+++ /geovis/branches/1.0/RenderServer.cpp	(revision 6666)
@@ -0,0 +1,488 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2013-2015  HUBzero Foundation, LLC
+ *
+ * Author: Leif Delgass <ldelgass@purdue.edu>
+ */
+
+#include <cstdio>
+#include <cstring>
+#include <cstdlib>
+#include <cerrno>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <sys/times.h>
+
+#include <string>
+#include <sstream>
+
+#include <tcl.h>
+
+#include "Trace.h"
+#include "FileUtil.h"
+#include "ReadBuffer.h"
+#include "RenderServer.h"
+#include "RendererCmd.h"
+#include "Renderer.h"
+#include "Stats.h"
+#include "PPMWriter.h"
+#include "TGAWriter.h"
+#ifdef USE_THREADS
+#include <pthread.h>
+#include "ResponseQueue.h"
+#ifdef USE_READ_THREAD
+#include "CommandQueue.h"
+#endif
+#endif 
+
+using namespace GeoVis;
+
+Stats GeoVis::g_stats;
+
+int GeoVis::g_statsFile = -1; ///< Stats output file descriptor.
+int GeoVis::g_fdIn = STDIN_FILENO; ///< Input file descriptor
+int GeoVis::g_fdOut = STDOUT_FILENO; ///< Output file descriptor
+FILE *GeoVis::g_fOut = NULL; ///< Output file handle
+FILE *GeoVis::g_fLog = NULL; ///< Trace logging file handle
+Renderer *GeoVis::g_renderer = NULL; ///< Main render worker
+ReadBuffer *GeoVis::g_inBufPtr = NULL; ///< Socket read buffer
+#ifdef USE_THREADS
+ResponseQueue *GeoVis::g_outQueue = NULL;
+#ifdef USE_READER_THREAD
+CommandQueue *GeoVis::g_inQueue = NULL;
+#endif
+#endif
+
+static int
+queueViewpoint()
+{
+    osgEarth::Viewpoint view = g_renderer->getViewpoint();
+
+    std::ostringstream oss;
+    size_t len = 0;
+    oss << "nv>camera get "
+        << view.focalPoint()->x() << " "
+        << view.focalPoint()->y() << " "
+        << view.focalPoint()->z() << " "
+        << view.heading()->getValue() << " "
+        << view.pitch()->getValue() << " "
+        << view.range()->getValue()
+        << " {" << ((view.focalPoint()->getSRS() == NULL) ? "" : view.focalPoint()->getSRS()->getHorizInitString()) << "}"
+        << " {" << ((view.focalPoint()->getSRS() == NULL) ? "" : view.focalPoint()->getSRS()->getVertInitString()) << "}"
+        << "\n";
+    std::string ostr = oss.str();
+    len = ostr.size();
+#ifdef USE_THREADS
+    queueResponse(ostr.c_str(), len, Response::VOLATILE);
+#else 
+    ssize_t bytesWritten = SocketWrite(ostr.c_str(), len);
+
+    if (bytesWritten < 0) {
+        return TCL_ERROR;
+    }
+#endif /*USE_THREADS*/
+    return TCL_OK;
+}
+
+#ifdef USE_THREADS
+static void
+queueFrame(ResponseQueue *queue, const unsigned char *imgData)
+{
+#ifdef DEBUG_WRITE_FRAME_FILE
+
+#ifdef RENDER_TARGA
+    writeTGAFile("/tmp/frame.tga",
+                 imgData,
+                 g_renderer->getWindowWidth(),
+                 g_renderer->getWindowHeight(),
+                 TARGA_BYTES_PER_PIXEL);
+#else
+    writeTGAFile("/tmp/frame.tga",
+                 imgData,
+                 g_renderer->getWindowWidth(),
+                 g_renderer->getWindowHeight(),
+                 TARGA_BYTES_PER_PIXEL,
+                 true);
+#endif  /*RENDER_TARGA*/
+
+#else
+ 
+#ifdef RENDER_TARGA
+    queueTGA(queue, "nv>image -type image -bytes",
+             imgData,
+             g_renderer->getWindowWidth(),
+             g_renderer->getWindowHeight(),
+             TARGA_BYTES_PER_PIXEL);
+#else
+    char cmd[256];
+    snprintf(cmd, sizeof(cmd), "nv>image -type image -token %lu -bytes", g_stats.nCommands);
+    queuePPM(queue, cmd,
+             imgData,
+             g_renderer->getWindowWidth(),
+             g_renderer->getWindowHeight());
+#endif  /*RENDER_TARGA*/
+#endif  /*DEBUG_WRITE_FRAME_FILE*/
+}
+
+#else
+
+static void
+writeFrame(int fd, const unsigned char *imgData)
+{
+#ifdef DEBUG_WRITE_FRAME_FILE
+
+#ifdef RENDER_TARGA
+    writeTGAFile("/tmp/frame.tga",
+                 imgData,
+                 g_renderer->getWindowWidth(),
+                 g_renderer->getWindowHeight(),
+                 TARGA_BYTES_PER_PIXEL);
+#else
+    writeTGAFile("/tmp/frame.tga",
+                 imgData,
+                 g_renderer->getWindowWidth(),
+                 g_renderer->getWindowHeight(),
+                 TARGA_BYTES_PER_PIXEL,
+                 true);
+#endif  /*RENDER_TARGA*/
+
+#else
+
+#ifdef RENDER_TARGA
+    writeTGA(fd, "nv>image -type image -bytes",
+             imgData,
+             g_renderer->getWindowWidth(),
+             g_renderer->getWindowHeight(),
+             TARGA_BYTES_PER_PIXEL);
+#else
+    writePPM(fd, "nv>image -type image -bytes",
+             imgData,
+             g_renderer->getWindowWidth(),
+             g_renderer->getWindowHeight());
+#endif  /*RENDER_TARGA*/
+#endif  /*DEBUG_WRITE_FRAME_FILE*/
+}
+#endif /*USE_THREADS*/
+
+static int
+sendAck()
+{
+    std::ostringstream oss;
+    oss << "nv>ok -token " << g_stats.nCommands << "\n";
+    std::string ostr = oss.str();
+    int nBytes = ostr.length();
+
+    TRACE("Sending OK for commands through %lu", g_stats.nCommands);
+#ifdef USE_THREADS
+    queueResponse(ostr.c_str(), nBytes, Response::VOLATILE, Response::OK);
+#else
+    if (write(g_fdOut, ostr.c_str(), nBytes) < 0) {
+        ERROR("write failed: %s", strerror(errno));
+        return -1;
+    }
+#endif
+    return 0;
+}
+
+static void
+initService()
+{
+    g_fOut = fdopen(g_fdOut, "w");
+    // If running without socket, use stdout for debugging
+    if (g_fOut == NULL && g_fdOut != STDOUT_FILENO) {
+        g_fdOut = STDOUT_FILENO;
+        g_fOut = fdopen(g_fdOut, "w");
+    }
+
+    const char *user = getenv("USER");
+    char *logName = NULL;
+    int logNameLen = 0;
+
+    if (user == NULL) {
+        logNameLen = 19+1;
+        logName = (char *)calloc(logNameLen, sizeof(char));
+        strncpy(logName, "/tmp/geovis_log.txt", logNameLen);
+    } else {
+        logNameLen = 16+strlen(user)+4+1;
+        logName = (char *)calloc(logNameLen, sizeof(char));
+        strncpy(logName, "/tmp/geovis_log_", logNameLen);
+        strncat(logName, user, strlen(user));
+        strncat(logName, ".txt", 4);
+    }
+
+    // open log and map stderr to log file
+    g_fLog = fopen(logName, "w");
+    dup2(fileno(g_fLog), STDERR_FILENO);
+    // If we are writing to socket, map stdout to log
+    if (g_fdOut != STDOUT_FILENO) {
+        dup2(fileno(g_fLog), STDOUT_FILENO);
+    }
+
+    fflush(stdout);
+
+    // clean up malloc'd memory
+    if (logName != NULL) {
+        free(logName);
+    }
+}
+
+static void
+exitService(int code)
+{
+    TRACE("Enter");
+
+    serverStats(g_stats, code);
+
+    // close log file
+    if (g_fLog != NULL) {
+        fclose(g_fLog);
+        g_fLog = NULL;
+    }
+}
+
+#ifdef USE_THREADS
+
+#ifdef USE_READ_THREAD
+static void *
+readerThread(void *clientData)
+{
+    Tcl_Interp *interp = (Tcl_Interp *)clientData;
+
+    TRACE("Starting reader thread");
+
+    queueCommands(interp, NULL, g_inBufPtr);
+
+    return NULL;
+}
+#endif
+
+static void *
+writerThread(void *clientData)
+{
+    ResponseQueue *queue = (ResponseQueue *)clientData;
+
+    TRACE("Starting writer thread");
+    for (;;) {
+        Response *response = queue->dequeue();
+        if (response == NULL)
+            continue;
+        if (fwrite(response->message(), sizeof(unsigned char), response->length(),
+                   g_fOut) != response->length()) {
+            ERROR("short write while trying to write %ld bytes", 
+                  response->length());
+        }
+        fflush(g_fOut);
+#ifdef TRACE_RESPONSE_QUEUE
+        TRACE("Wrote response of type %d", response->type());
+#endif
+        delete response;
+        if (feof(g_fOut))
+            break;
+    }    
+    return NULL;
+}
+
+#endif  /*USE_THREADS*/
+
+int
+main(int argc, char *argv[])
+{
+    // Ignore SIGPIPE.  **Is this needed? **
+    signal(SIGPIPE, SIG_IGN);
+
+    std::string resourcePath;
+    double maxbitrate = 1.0e8;
+    long idleTimeout = -1L;
+    while (1) {
+        int c = getopt(argc, argv, "r:p:i:o:t:");
+        if (c == -1) {
+            break;
+        }
+        switch (c) {
+        case 'p':
+            resourcePath = optarg;
+            break;
+        case 'r':
+            // Option is in kbps
+            maxbitrate = 1000.0 * (double)atol(optarg);
+            break;
+        case 'i': {
+            int fd = atoi(optarg);
+            if (fd >=0 && fd < 5) {
+                g_fdIn = fd;
+            }
+        }
+            break;
+        case 'o': {
+            int fd = atoi(optarg);
+            if (fd >=0 && fd < 5) {
+                g_fdOut = fd;
+            }
+        }
+            break;
+        case 't':
+            // Idle timeout in seconds
+            idleTimeout = atol(optarg);
+            break;
+        case '?':
+            break;
+        default:
+            return 1;
+        }
+    }
+
+    initService();
+    initLog();
+
+    memset(&g_stats, 0, sizeof(g_stats));
+    gettimeofday(&g_stats.start, NULL);
+
+    TRACE("Starting GeoVis Server");
+
+    // Sanity check: log descriptor can't be used for client IO
+    if (fileno(g_fLog) == g_fdIn) {
+        ERROR("Invalid input file descriptor");
+        return 1;
+    }
+    if (fileno(g_fLog) == g_fdOut) {
+        ERROR("Invalid output file descriptor");
+        return 1;
+    }
+    TRACE("File descriptors: in %d out %d log %d", g_fdIn, g_fdOut, fileno(g_fLog));
+
+    /* This synchronizes the client with the server, so that the client 
+     * doesn't start writing commands before the server is ready. It could
+     * also be used to supply information about the server (version, memory
+     * size, etc). */
+    fprintf(g_fOut, "GeoVis %s (build %s)\n", GEOVIS_VERSION_STRING, SVN_VERSION);
+    fflush(g_fOut);
+
+    g_renderer = new Renderer();
+    g_inBufPtr = new ReadBuffer(g_fdIn, 1<<12);
+
+    g_renderer->setMaximumBitrate(maxbitrate);
+    if (resourcePath.empty()) {
+        resourcePath = "/usr/share/osgearth/data";
+    }
+    g_renderer->setResourcePath(resourcePath);
+    g_renderer->setIdleTimeout(idleTimeout);
+
+    TRACE("Default Tcl encoding dir: %s", Tcl_GetDefaultEncodingDir());
+    //Tcl_SetDefaultEncodingDir("encoding");
+
+    Tcl_Interp *interp = Tcl_CreateInterp();
+    initTcl(interp, NULL);
+
+#ifdef USE_THREADS
+    g_outQueue = new ResponseQueue();
+
+    pthread_t writerThreadId;
+    if (pthread_create(&writerThreadId, NULL, &writerThread, g_outQueue) < 0) {
+        ERROR("Can't create writer thread: %s", strerror(errno));
+    }
+#endif
+
+    osg::ref_ptr<osg::Image> imgData;
+
+    // Start main server loop
+    for (;;) {
+        struct timeval timeout;
+        g_renderer->getTimeout(&timeout);
+#ifdef SLEEP_AFTER_QUEUE_FRAME
+        g_renderer->markFrameStart();
+#endif
+        int cmdStatus = processCommands(interp, NULL, g_inBufPtr, g_fdOut, &timeout);
+        if (cmdStatus < 0)
+            break;
+
+        if (g_renderer->render()) {
+            FRAME("Rendered new frame");
+            imgData = g_renderer->getRenderedFrame();
+            if (imgData == NULL) {
+                ERROR("Empty image");
+            } else {
+                FRAME("Image: %d x %d", imgData->s(), imgData->t());
+
+                if (imgData->s() == g_renderer->getWindowWidth() &&
+                    imgData->t() == g_renderer->getWindowHeight()) {
+                    queueViewpoint();
+#ifdef USE_THREADS
+                    queueFrame(g_outQueue, imgData->data());
+#else
+                    writeFrame(g_fdOut, imgData->data());
+#endif
+                }
+                g_stats.nFrames++;
+                g_stats.nFrameBytes += imgData->s() * imgData->t() * 3;
+            }
+#ifdef SLEEP_AFTER_QUEUE_FRAME
+            g_renderer->markFrameEnd();
+#endif
+        } else {
+            //TRACE("No render required");
+            if (cmdStatus > 1) {
+                sendAck();
+            } else {
+                TRACE("No render required and status = %d", cmdStatus);
+            }
+        }
+
+#if 0
+        double x, y, z;
+        if (g_renderer->getMousePoint(&x, &y, &z)) {
+            // send coords to client
+            size_t length;
+            char mesg[256];
+
+            length = snprintf(mesg, sizeof(mesg),
+                              "nv>map coords %g %g %g\n", x, y, z);
+
+            queueResponse(mesg, length, Response::VOLATILE);
+        }
+#endif
+
+        if (g_inBufPtr->status() == ReadBuffer::ENDFILE)
+            break;
+    }
+#ifdef USE_THREADS
+    // Writer thread is probably blocked on sem_wait, so cancel instead
+    // of joining
+    if (pthread_cancel(writerThreadId) < 0) {
+        ERROR("Can't cancel writer thread: %s", strerror(errno));
+    } else {
+        TRACE("Cancelled writer thread");
+    }
+
+    TRACE("Deleting ResponseQueue");
+    delete g_outQueue;
+    g_outQueue = NULL;
+#endif
+
+    TRACE("Stopping Tcl interpreter");
+    exitTcl(interp);
+    interp = NULL;
+
+    TRACE("Deleting ReadBuffer");
+    delete g_inBufPtr;
+    g_inBufPtr = NULL;
+
+    std::string cacheDir = g_renderer->getCacheDirectory();
+    TRACE("Deleting renderer");
+    delete g_renderer;
+    g_renderer = NULL;
+    // Clean up cache directory after renderer's threads have finished
+    if (!cacheDir.empty()) {
+        removeDirectory(cacheDir.c_str());
+    }
+
+    TRACE("Exiting GeoVis Server");
+
+    closeLog();
+    exitService(0);
+
+    return 0;
+}
Index: /geovis/branches/1.0/RenderServer.h
===================================================================
--- /geovis/branches/1.0/RenderServer.h	(revision 6666)
+++ /geovis/branches/1.0/RenderServer.h	(revision 6666)
@@ -0,0 +1,51 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2013-2015  HUBzero Foundation, LLC
+ *
+ * Author: Leif Delgass <ldelgass@purdue.edu>
+ */
+
+#ifndef GEOVIS_RENDERSERVER_H
+#define GEOVIS_RENDERSERVER_H
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/time.h>
+
+namespace GeoVis {
+
+class Renderer;
+class ReadBuffer;
+class CommandQueue;
+class ResponseQueue;
+class Stats;
+
+#define GEOVIS_VERSION_STRING "0.7.4"
+
+#define MSECS_ELAPSED(t1, t2) \
+    ((t1).tv_sec == (t2).tv_sec ? (((t2).tv_usec - (t1).tv_usec)/1.0e+3) : \
+     (((t2).tv_sec - (t1).tv_sec))*1.0e+3 + (double)((t2).tv_usec - (t1).tv_usec)/1.0e+3)
+
+#define CVT2SECS(x)  ((double)(x).tv_sec) + ((double)(x).tv_usec * 1.0e-6)
+
+extern Stats g_stats;
+
+extern int g_fdIn;
+extern int g_fdOut;
+extern FILE *g_fOut;
+extern FILE *g_fLog;
+extern Renderer *g_renderer;
+extern ReadBuffer *g_inBufPtr;
+#ifdef USE_THREADS
+#ifdef USE_READ_THREAD
+extern CommandQueue *g_inQueue;
+#endif
+extern ResponseQueue *g_outQueue;
+#endif
+extern int g_statsFile;
+extern int writeToStatsFile(int f, const char *s, size_t length);
+extern int getStatsFile(const char *key = NULL);
+
+}
+
+#endif
Index: /geovis/branches/1.0/Renderer.cpp
===================================================================
--- /geovis/branches/1.0/Renderer.cpp	(revision 6666)
+++ /geovis/branches/1.0/Renderer.cpp	(revision 6666)
@@ -0,0 +1,3466 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2004-2015  HUBzero Foundation, LLC
+ *
+ * Author: Leif Delgass <ldelgass@purdue.edu>
+ */
+
+#include <cfloat>
+#include <cstring>
+#include <cassert>
+#include <cmath>
+#include <cstdlib>
+#include <cerrno>
+
+#include <limits>
+#include <set>
+#include <fstream>
+#include <sstream>
+
+#include <sys/types.h>
+#include <unistd.h> // For getpid()
+
+#include <GL/gl.h>
+
+#ifdef WANT_TRACE
+#include <sys/time.h>
+#endif
+
+#include <osgDB/WriteFile>
+#include <osgDB/FileUtils>
+#include <osgDB/FileNameUtils>
+#include <osgGA/StateSetManipulator>
+#include <osgGA/GUIEventAdapter>
+#include <osgViewer/ViewerEventHandlers>
+
+#include <osgEarth/Version>
+#include <osgEarth/Registry>
+#include <osgEarth/FileUtils>
+#include <osgEarth/Cache>
+#include <osgEarth/CachePolicy>
+#include <osgEarthDrivers/cache_filesystem/FileSystemCache>
+#include <osgEarth/MapNode>
+#include <osgEarth/TerrainEngineNode>
+#include <osgEarth/Bounds>
+#include <osgEarth/Profile>
+#include <osgEarth/Viewpoint>
+#include <osgEarth/GeoMath>
+#include <osgEarth/TerrainLayer>
+#include <osgEarth/ImageLayer>
+#include <osgEarth/ElevationLayer>
+#include <osgEarth/ModelLayer>
+#include <osgEarth/DateTime>
+#include <osgEarth/TimeControl>
+#ifdef USE_RTT_PICKER
+#include <osgEarthUtil/RTTPicker>
+#include <osgEarthFeatures/FeatureIndex>
+#else
+#include <osgEarth/IntersectionPicker>
+#endif
+#include <osgEarthUtil/GraticuleNode>
+#include <osgEarthFeatures/FeatureModelSource>
+#include <osgEarthFeatures/Feature>
+#include <osgEarthSymbology/Color>
+#include <osgEarthSymbology/Geometry>
+#include <osgEarthSymbology/Style>
+#include <osgEarthSymbology/StyleSheet>
+#include <osgEarthSymbology/IconSymbol>
+#include <osgEarthSymbology/LineSymbol>
+
+#include <osgEarthAnnotation/AnnotationNode>
+#include <osgEarthAnnotation/FeatureNode>
+#include <osgEarthAnnotation/PlaceNode>
+#ifdef NEW_ANNOTATION_API
+#include <osgEarth/ScreenSpaceLayout>
+#else
+#include <osgEarth/Decluttering>
+#include <osgEarthAnnotation/HighlightDecoration>
+#include <osgEarthAnnotation/ScaleDecoration>
+#endif
+#include <osgEarthUtil/EarthManipulator>
+#include <osgEarthUtil/Sky>
+#include <osgEarthDrivers/sky_simple/SimpleSkyOptions>
+#include <osgEarthUtil/AutoClipPlaneHandler>
+#include <osgEarthUtil/MouseCoordsTool>
+#include <osgEarthUtil/UTMGraticule>
+#include <osgEarthUtil/MGRSGraticule>
+#include <osgEarthUtil/GeodeticGraticule>
+#include <osgEarthUtil/LatLongFormatter>
+#include <osgEarthUtil/MGRSFormatter>
+#include <osgEarthUtil/GLSLColorFilter>
+#include <osgEarthUtil/VerticalScale>
+#include <osgEarthDrivers/gdal/GDALOptions>
+#include <osgEarthDrivers/engine_mp/MPTerrainEngineOptions>
+#ifdef USE_REX
+#include <osgEarthDrivers/engine_rex/RexTerrainEngineOptions>
+#endif
+
+#include "Renderer.h"
+#ifdef USE_RTT_PICKER
+#include "Picker.h"
+#endif
+#include "IData.h"
+#include "ColorMap.h"
+#if 0
+#include "SingleWindow.h"
+#endif
+#include "ScaleBar.h"
+#include "FileUtil.h"
+#include "Util.h"
+#include "Icons.h"
+#include "Trace.h"
+
+#define MSECS_ELAPSED(t1, t2) \
+    ((t1).tv_sec == (t2).tv_sec ? (((t2).tv_usec - (t1).tv_usec)/1.0e+3) : \
+     (((t2).tv_sec - (t1).tv_sec))*1.0e+3 + (double)((t2).tv_usec - (t1).tv_usec)/1.0e+3)
+
+#ifndef CACHE_DIR
+#define CACHE_DIR "/var/cache/geovis"
+#endif
+#define BASE_IMAGE "world.tif"
+#define PIN_ICON "placemark32.png"
+
+using namespace GeoVis;
+using namespace IData;
+
+Renderer::Renderer() :
+    _needsRedraw(false),
+    _windowWidth(500),
+    _windowHeight(500),
+    _idleTimeout(-1L),
+    _pickPending(false),
+    _scaleBarUnits(UNITS_METERS)
+{
+    TRACE("Enter");
+
+    initIconMap();
+    _bgColor[0] = 0;
+    _bgColor[1] = 0;
+    _bgColor[2] = 0;
+    // 100 Mbps
+    setMaximumBitrate(1.0e8);
+    _lastFrameTime = _minFrameTime;
+
+    iDataInit(getenv("USER"));
+    setCacheBaseDirectory(CACHE_DIR);
+    char *base = getenv("MAP_BASE_URI");
+    if (base != NULL) {
+        _baseURI = base;
+        TRACE("Setting base URI: %s", _baseURI.c_str());
+    }
+}
+
+osgGA::EventQueue *Renderer::getEventQueue()
+{
+    if (_viewer.valid()) {
+        osgViewer::ViewerBase::Windows windows;
+        _viewer->getWindows(windows);
+        if (windows.size() > 0) {
+            return windows[0]->getEventQueue();
+        }
+    }
+    return NULL;
+}
+
+Renderer::~Renderer()
+{
+    TRACE("Enter");
+
+    // Removes temp files
+    deleteColorMap("all");
+
+    iDataCleanup();
+
+    // Cache is deleted in main() to allow ref-counted objects to
+    // be deleted and threads to exit first
+
+    TRACE("Leave");
+}
+
+std::string Renderer::getIconFile(const char *name) const
+{
+    std::ostringstream oss;
+    oss << _resourcePath << "/" << GeoVis::getIconFile(name);
+    return oss.str();
+}
+
+std::string Renderer::getBaseImage() const
+{
+    std::ostringstream oss;
+    oss << _resourcePath << "/" << BASE_IMAGE;
+    return oss.str();
+}
+
+std::string Renderer::getPinIcon() const
+{
+    std::ostringstream oss;
+    oss << _resourcePath << "/" << PIN_ICON;
+    return oss.str();
+}
+
+void Renderer::setupCache()
+{
+    std::ostringstream dir;
+    dir << _cacheBaseDir << "/geovis_cache" << getpid();
+    _cacheDir = dir.str();
+    const char *path = _cacheDir.c_str();
+    TRACE("Cache dir: %s", path);
+    removeDirectory(path);
+    if (!osgDB::makeDirectory(_cacheDir)) {
+        ERROR("Failed to create directory '%s'", path);
+    }
+}
+
+void Renderer::initColorMaps()
+{
+    if (!_colorMaps.empty())
+        return;
+#if 0
+    osg::TransferFunction1D *defaultColorMap = new osg::TransferFunction1D;
+    defaultColorMap->allocate(256);
+    defaultColorMap->setColor(0.00, osg::Vec4f(0,0,1,1), false);
+    defaultColorMap->setColor(0.25, osg::Vec4f(0,1,1,1), false);
+    defaultColorMap->setColor(0.50, osg::Vec4f(0,1,0,1), false);
+    defaultColorMap->setColor(0.75, osg::Vec4f(1,1,0,1), false);
+    defaultColorMap->setColor(1.00, osg::Vec4f(1,0,0,1), false);
+    defaultColorMap->updateImage();
+    addColorMap("default", defaultColorMap);
+    osg::TransferFunction1D *defaultGrayColorMap = new osg::TransferFunction1D;
+    defaultGrayColorMap->allocate(256);
+    defaultGrayColorMap->setColor(0, osg::Vec4f(0,0,0,1), false);
+    defaultGrayColorMap->setColor(1, osg::Vec4f(1,1,1,1), false);
+    defaultGrayColorMap->updateImage();
+    addColorMap("grayDefault", defaultGrayColorMap);
+#endif
+}
+
+void Renderer::saveCLRFile(const std::string& path, osg::TransferFunction1D *xfer)
+{
+    std::ofstream out(path.c_str());
+    float value;
+    unsigned int r, g, b, a;
+    for (osg::TransferFunction1D::ColorMap::const_iterator itr = xfer->getColorMap().begin();
+         itr != xfer->getColorMap().end(); ++itr) {
+        value = itr->first;
+        r = (unsigned int)(255.0 * itr->second.r());
+        g = (unsigned int)(255.0 * itr->second.g());
+        b = (unsigned int)(255.0 * itr->second.b());
+        a = (unsigned int)(255.0 * itr->second.a());
+        out << value << " " << r << " " << g << " " << b << " " << a << std::endl;
+    }
+}
+
+void Renderer::getColorMapRange(const ColorMapId& id, float *min, float *max) const
+{
+    ColorMapHashmap::const_iterator itr = _colorMaps.find(id);
+    if (itr == _colorMaps.end()) {
+        ERROR("Unknown ColorMap %s", id.c_str());
+        return;
+    }
+
+    *min = itr->second->getMinimum();
+    *max = itr->second->getMaximum();
+}
+
+std::string Renderer::getColorMapFilePath(const ColorMapId& id) const
+{
+    std::ostringstream path;
+    path << "/tmp/" << id << "_" << getpid() << ".clr";
+    return path.str();
+}
+
+void Renderer::addColorMap(const ColorMapId& id, osg::TransferFunction1D *xfer)
+{
+    _colorMaps[id] = xfer;
+    saveCLRFile(getColorMapFilePath(id), xfer);
+}
+
+void Renderer::deleteColorMap(const ColorMapId& id)
+{
+    ColorMapHashmap::iterator itr;
+    bool doAll = false;
+
+    if (id.compare("all") == 0) {
+        itr = _colorMaps.begin();
+        doAll = true;
+    } else {
+        itr = _colorMaps.find(id);
+    }
+
+    if (itr == _colorMaps.end()) {
+        if (!doAll) {
+            ERROR("Unknown ColorMap %s", id.c_str());
+        }
+        return;
+    }
+
+    do {
+        TRACE("Deleting ColorMap %s", itr->first.c_str());
+        std::string path = getColorMapFilePath(itr->first);
+        if (remove(path.c_str()) < 0) {
+            ERROR("Failed to delete colormap file '%s': %s",
+                  path.c_str(), strerror(errno));
+        }
+        itr = _colorMaps.erase(itr);
+    } while (doAll && itr != _colorMaps.end());
+}
+
+void Renderer::setColorMapNumberOfTableEntries(const ColorMapId& id,
+                                               int numEntries)
+{
+    ColorMapHashmap::iterator itr;
+    bool doAll = false;
+
+    if (id.compare("all") == 0) {
+        itr = _colorMaps.begin();
+        doAll = true;
+    } else {
+        itr = _colorMaps.find(id);
+    }
+
+    if (itr == _colorMaps.end()) {
+        ERROR("Unknown ColorMap %s", id.c_str());
+        return;
+    }
+
+    do {
+        itr->second->allocate(numEntries);
+    } while (doAll && ++itr != _colorMaps.end());
+}
+
+bool
+Renderer::renderColorMap(const ColorMapId& id, int width, int height,
+                         osg::Image *imgData,
+                         bool opaque, float bgColor[3],
+                         bool bgr, int bytesPerPixel) const
+{
+    ColorMapHashmap::const_iterator itr = _colorMaps.find(id);
+    if (itr == _colorMaps.end()) {
+        ERROR("Unknown ColorMap %s", id.c_str());
+        return false;
+    }
+
+    ColorMap::renderColorMap(itr->second, width, height, imgData, opaque, bgColor, bgr, bytesPerPixel);
+    return true;
+}
+
+void Renderer::initViewer() {
+    if (_viewer.valid())
+        return;
+    _viewer = new osgViewer::Viewer();
+#if 1
+    osg::DisplaySettings *ds = _viewer->getDisplaySettings();
+    if (ds == NULL) {
+        ds = osg::DisplaySettings::instance().get();
+    }
+    ds->setDoubleBuffer(false);
+    ds->setMinimumNumAlphaBits(8);
+    ds->setMinimumNumStencilBits(8);
+    ds->setNumMultiSamples(0);
+#endif
+    _viewer->setThreadingModel(osgViewer::ViewerBase::SingleThreaded);
+    _viewer->getDatabasePager()->setUnrefImageDataAfterApplyPolicy(false, false);
+    _viewer->setReleaseContextAtEndOfFrameHint(false);
+    //_viewer->setLightingMode(osg::View::SKY_LIGHT);
+    _viewer->getCamera()->setClearColor(osg::Vec4(_bgColor[0], _bgColor[1], _bgColor[2], 1));
+    _viewer->getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+    _viewer->getCamera()->setNearFarRatio(0.00002);
+    _viewer->getCamera()->setSmallFeatureCullingPixelSize(-1.0f);
+    _stateManip = new osgGA::StateSetManipulator(_viewer->getCamera()->getOrCreateStateSet());
+    _viewer->addEventHandler(_stateManip);
+    //_viewer->addEventHandler(new osgViewer::StatsHandler());
+
+#ifdef DEBUG
+    if (_viewer->getViewerStats() != NULL) {
+        TRACE("Enabling stats");
+        _viewer->getViewerStats()->collectStats("scene", true);
+    }
+#endif
+#if 0
+    osgViewer::ViewerBase::Windows windows;
+    _viewer->getWindows(windows);
+    if (windows.size() == 1) {
+        windows[0]->setSyncToVBlank(false);
+    } else {
+        ERROR("Num windows: %lu", windows.size());
+    }
+#endif
+}
+
+void Renderer::finalizeViewer() {
+    initViewer();
+    TRACE("Before _viewer->isRealized()");
+    if (!_viewer->isRealized()) {
+        int screen = 0;
+        const char *displayEnv = getenv("DISPLAY");
+        if (displayEnv != NULL) {
+            TRACE("DISPLAY: %s", displayEnv);
+            // 3 parts: host, display, screen
+            int part = 0;
+            for (size_t c = 0; c < strlen(displayEnv); c++) {
+                if (displayEnv[c] == ':') {
+                    part = 1;
+                } else if (part == 1 && displayEnv[c] == '.') {
+                    part = 2;
+                } else if (part == 2) {
+                    screen = atoi(&displayEnv[c]);
+                    break;
+                }
+            }
+        }
+        TRACE("Using screen: %d", screen);
+#ifdef USE_OFFSCREEN_RENDERING
+#ifdef USE_PBUFFER
+        osg::ref_ptr<osg::GraphicsContext> pbuffer;
+        osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
+        traits->x = 0;
+        traits->y = 0;
+        traits->width = _windowWidth;
+        traits->height = _windowHeight;
+        traits->red = 8;
+        traits->green = 8;
+        traits->blue = 8;
+        traits->alpha = 8;
+        traits->windowDecoration = false;
+        traits->pbuffer = true;
+        traits->doubleBuffer = true;
+        traits->sharedContext = 0;
+
+        pbuffer = osg::GraphicsContext::createGraphicsContext(traits.get());
+        if (pbuffer.valid()) {
+            TRACE("Pixel buffer has been created successfully.");
+        } else {
+            ERROR("Pixel buffer has not been created successfully.");
+        }
+        osg::Camera *camera = new osg::Camera;
+        camera->setGraphicsContext(pbuffer.get());
+        //camera->getOrCreateStateSet()->setGlobalDefaults();
+        camera->setViewport(new osg::Viewport(0, 0, _windowWidth, _windowHeight));
+        GLenum buffer = pbuffer->getTraits()->doubleBuffer ? GL_BACK : GL_FRONT;
+        camera->setDrawBuffer(buffer);
+        camera->setReadBuffer(buffer);
+        _captureCallback = new ScreenCaptureCallback();
+        camera->setFinalDrawCallback(_captureCallback.get());
+        _viewer->addSlave(camera, osg::Matrixd(), osg::Matrixd());
+#else
+        _viewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
+        osg::Texture2D* texture2D = new osg::Texture2D;
+        texture2D->setTextureSize(_windowWidth, _windowHeight);
+        texture2D->setInternalFormat(GL_RGBA);
+        texture2D->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST);
+        texture2D->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST);
+
+        _viewer->getCamera()->setImplicitBufferAttachmentMask(0, 0);
+        _viewer->getCamera()->attach(osg::Camera::COLOR_BUFFER0, texture2D);
+        //_viewer->getCamera()->attach(osg::Camera::DEPTH_BUFFER, GL_DEPTH_COMPONENT24);
+        _viewer->getCamera()->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, GL_DEPTH24_STENCIL8_EXT);
+        _captureCallback = new ScreenCaptureCallback(texture2D);
+        _viewer->getCamera()->setFinalDrawCallback(_captureCallback.get());
+        _viewer->setUpViewInWindow(0, 0, _windowWidth, _windowHeight, screen);
+#endif
+#else
+        _captureCallback = new ScreenCaptureCallback();
+        _viewer->getCamera()->setFinalDrawCallback(_captureCallback.get());
+#if 1
+        _viewer->setUpViewInWindow(0, 0, _windowWidth, _windowHeight, screen);
+#else
+        SingleWindow *windowConfig = new SingleWindow(0, 0, _windowWidth, _windowHeight, screen);
+        osg::DisplaySettings *ds = windowConfig->getActiveDisplaySetting(*_viewer.get());
+        ds->setDoubleBuffer(false);
+        ds->setMinimumNumAlphaBits(8);
+        ds->setMinimumNumStencilBits(8);
+        ds->setNumMultiSamples(0);
+        windowConfig->setWindowDecoration(false);
+        windowConfig->setOverrideRedirect(false);
+        _viewer->apply(windowConfig);
+#endif
+#endif
+        _viewer->realize();
+        initColorMaps();
+        // HACK: This seems to initialize something required for properly
+        // mapping mouse coords
+        assert(getEventQueue() != NULL);
+        getEventQueue()->mouseMotion(_windowWidth/2, _windowHeight/2);
+    }
+}
+
+void Renderer::setAttribution(const std::string& attrib)
+{
+    TRACE("Setting map attribution: '%s'", attrib.c_str());
+    _attribution = attrib;
+    if (_copyrightLabel.valid()) {
+        _copyrightLabel->setText(_attribution);
+        _needsRedraw = true;
+    }
+}
+
+void Renderer::initControls()
+{
+    if (!_debugBox.valid()) {
+        _debugBox = 
+            new osgEarth::Util::Controls::HBox(osgEarth::Util::Controls::Control::ALIGN_RIGHT,
+                                               osgEarth::Util::Controls::Control::ALIGN_TOP,
+                                               osgEarth::Util::Controls::Gutter(2, 2, 2, 2), 2.0f);
+        _debugLabel = 
+            new osgEarth::Util::Controls::LabelControl("Selection: None", 12.0f);
+        _debugLabel->setForeColor(osg::Vec4f(0, 0, 0, 1));
+        _debugBox->addControl(_debugLabel.get());
+        _debugBox->setVertFill(true);
+        _debugBox->setForeColor(osg::Vec4f(0, 0, 0, 1));
+        _debugBox->setBackColor(osg::Vec4f(1, 1, 1, 0.6));
+        osgEarth::Util::Controls::ControlCanvas::getOrCreate(_viewer.get())->addControl(_debugBox.get());
+    }
+    if (_copyrightScaleBox.valid())
+        return;
+    _copyrightScaleBox = 
+        new osgEarth::Util::Controls::HBox(osgEarth::Util::Controls::Control::ALIGN_RIGHT,
+                                           osgEarth::Util::Controls::Control::ALIGN_BOTTOM,
+                                           osgEarth::Util::Controls::Gutter(2, 2, 2, 2), 2.0f);
+    _attribution = "Map data Â© OpenStreetMap";
+    _copyrightLabel =
+        new osgEarth::Util::Controls::LabelControl(_attribution, 12.0f);
+    _copyrightLabel->setForeColor(osg::Vec4f(0, 0, 0, 1));
+    _copyrightLabel->setEncoding(osgText::String::ENCODING_UTF8);
+    _scaleLabel =
+        new osgEarth::Util::Controls::LabelControl("- km", 12.0f);
+    _scaleLabel->setForeColor(osg::Vec4f(0, 0, 0, 1));
+    _scaleBar =
+        new osgEarth::Util::Controls::Frame();
+    _scaleBar->setVertFill(true);
+    _scaleBar->setForeColor(osg::Vec4f(0, 0, 0, 1));
+    _scaleBar->setBackColor(osg::Vec4f(1, 1, 1, 0.6));
+    _scaleBar->setBorderColor(osg::Vec4f(0, 0, 0 ,1));
+    _scaleBar->setBorderWidth(1.0);
+    _copyrightScaleBox->addControl(_copyrightLabel.get());
+    _copyrightScaleBox->addControl(_scaleLabel.get());
+    _copyrightScaleBox->addControl(_scaleBar.get());
+    _copyrightScaleBox->setVertFill(true);
+    _copyrightScaleBox->setForeColor(osg::Vec4f(0, 0, 0, 1));
+    _copyrightScaleBox->setBackColor(osg::Vec4f(1, 1, 1, 0.6));
+    osgEarth::Util::Controls::ControlCanvas::getOrCreate(_viewer.get())->addControl(_copyrightScaleBox.get());
+    // Install an event callback to handle scale bar updates
+    // Can't use an update callback since that will trigger
+    // constant rendering
+    _mapNode->setEventCallback(new MapNodeCallback(this));
+}
+
+void Renderer::setGraticule(bool enable, GraticuleType type)
+{
+    if (!_mapNode.valid() || !_sceneRoot.valid())
+        return;
+    if (enable) {
+        if (_graticule.valid()) {
+            _sceneRoot->removeChild(_graticule.get());
+            _graticule = NULL;
+        }
+        switch (type) {
+        case GRATICULE_UTM: {
+            osgEarth::Util::UTMGraticule *gr = new osgEarth::Util::UTMGraticule(_mapNode.get());
+            _sceneRoot->addChild(gr);
+            _graticule = gr;
+        }
+            break;
+        case GRATICULE_MGRS: {
+            osgEarth::Util::MGRSGraticule *gr = new osgEarth::Util::MGRSGraticule(_mapNode.get());
+            _sceneRoot->addChild(gr);
+            _graticule = gr;
+        }
+            break;
+        case GRATICULE_SHADER: {
+            osgEarth::Util::GraticuleOptions opt;
+            opt.color() = osgEarth::Symbology::Color(1, 1, 0);
+            opt.labelColor() = osgEarth::Symbology::Color(1, 1, 0);
+            opt.lineWidth() = 1.0;
+            osgEarth::Util::GraticuleNode *gr = new osgEarth::Util::GraticuleNode(_mapNode.get(), opt);
+            _sceneRoot->addChild(gr);
+            _graticule = gr;
+        }
+            break;
+        case GRATICULE_GEODETIC:
+        default:
+            osgEarth::Util::GeodeticGraticule *gr = new osgEarth::Util::GeodeticGraticule(_mapNode.get());
+            osgEarth::Util::GeodeticGraticuleOptions opt = gr->getOptions();
+            opt.lineStyle()->getOrCreate<osgEarth::Symbology::LineSymbol>()->stroke()->color().set(1,0,0,1);
+            gr->setOptions(opt);
+            _sceneRoot->addChild(gr);
+            _graticule = gr;
+        }
+    } else if (_graticule.valid()) {
+        _sceneRoot->removeChild(_graticule.get());
+        _graticule = NULL;
+    }
+    _needsRedraw = true;
+}
+
+void Renderer::setReadout(int x, int y)
+{
+    if (!_coordsCallback.valid() || !_mapNode.valid() || !_viewer.valid())
+        return;
+
+    osgEarth::GeoPoint mapCoord;
+    if (mapMouseCoords(x, y, mapCoord)) {
+        _coordsCallback->set(mapCoord, _viewer->asView(), _mapNode);
+    } else {
+        _coordsCallback->reset(_viewer->asView(), _mapNode);
+    }
+    _needsRedraw = true;
+}
+
+void Renderer::clearReadout()
+{
+    if (_coordsCallback.valid()) {
+        _coordsCallback->reset(_viewer->asView(), _mapNode);
+    }
+    _needsRedraw = true;
+}
+
+void Renderer::setCoordinateReadout(bool state, CoordinateDisplayType type,
+                                    int precision)
+{
+    if (!state) {
+        if (_mouseCoordsTool.valid() && _viewer.valid()) {
+            _viewer->removeEventHandler(_mouseCoordsTool.get());
+            _mouseCoordsTool = NULL;
+        }
+        if (_coordsCallback.valid()) {
+            _coordsCallback = NULL;
+        }
+        if (_coordsBox.valid() && _viewer.valid()) {
+            _coordsBox->clearControls();
+            osgEarth::Util::Controls::ControlCanvas::getOrCreate(_viewer.get())->removeControl(_coordsBox.get());
+        }
+        _coordsBox = NULL;
+    } else {
+        initMouseCoordsTool(type, precision);
+    }
+    _needsRedraw = true;
+}
+
+osgEarth::Util::MGRSFormatter::Precision
+Renderer::getMGRSPrecision(int precisionInMeters)
+{
+    switch (precisionInMeters) {
+    case 1:
+        return osgEarth::Util::MGRSFormatter::PRECISION_1M;
+    case 10:
+        return osgEarth::Util::MGRSFormatter::PRECISION_10M;
+    case 100:
+        return osgEarth::Util::MGRSFormatter::PRECISION_100M;
+    case 1000:
+        return osgEarth::Util::MGRSFormatter::PRECISION_1000M;
+    case 10000:
+        return osgEarth::Util::MGRSFormatter::PRECISION_10000M;
+    case 100000:
+        return osgEarth::Util::MGRSFormatter::PRECISION_100000M;
+    default:
+        ERROR("Invalid precision: %d", precisionInMeters);
+        return osgEarth::Util::MGRSFormatter::PRECISION_1M;
+    }
+}
+
+void Renderer::initMouseCoordsTool(CoordinateDisplayType type, int precision)
+{
+    if (!_viewer.valid())
+        return;
+    if (_mouseCoordsTool.valid()) {
+        _viewer->removeEventHandler(_mouseCoordsTool.get());
+    }
+    _mouseCoordsTool = new MouseCoordsTool(_mapNode.get());
+    osgEarth::Util::Controls::LabelControl *readout;
+    if (_coordsCallback.valid()) {
+        readout = _coordsCallback->getLabel();
+        _coordsCallback = NULL;
+    } else {
+        readout = new osgEarth::Util::Controls::LabelControl("Lat/Long: N/A", 12.0f);
+        readout->setForeColor(osg::Vec4f(0, 0, 0, 1));
+        _coordsBox =
+            new osgEarth::Util::Controls::HBox(osgEarth::Util::Controls::Control::ALIGN_LEFT,
+                                               osgEarth::Util::Controls::Control::ALIGN_TOP,
+                                               osgEarth::Util::Controls::Gutter(2, 2, 2, 2), 2.0f);
+        _coordsBox->setVertFill(true);
+        _coordsBox->setForeColor(osg::Vec4f(0, 0, 0, 1));
+        _coordsBox->setBackColor(osg::Vec4f(1, 1, 1, 0.6));
+        _coordsBox->addControl(readout);
+        osgEarth::Util::Controls::ControlCanvas::getOrCreate(_viewer.get())->addControl(_coordsBox.get());
+    }
+
+    osgEarth::Util::Formatter *formatter = NULL;
+    if (type == COORDS_MGRS) {
+        osgEarth::Util::MGRSFormatter::Precision prec =
+            osgEarth::Util::MGRSFormatter::PRECISION_1M;
+        if (precision > 0) {
+            prec = getMGRSPrecision(precision);
+        }
+        unsigned int opts = 0u;
+        formatter = new osgEarth::Util::MGRSFormatter(prec, NULL, opts);
+    } else {
+        osgEarth::Util::LatLongFormatter::AngularFormat af;
+        unsigned int opts =  osgEarth::Util::LatLongFormatter::USE_SYMBOLS;
+        switch (type) {
+        case COORDS_LATLONG_DEGREES_DECIMAL_MINUTES:
+            af = osgEarth::Util::LatLongFormatter::FORMAT_DEGREES_DECIMAL_MINUTES;
+            break;
+        case COORDS_LATLONG_DEGREES_MINUTES_SECONDS:
+            af = osgEarth::Util::LatLongFormatter::FORMAT_DEGREES_MINUTES_SECONDS;
+            break;
+        default:
+            af = osgEarth::Util::LatLongFormatter::FORMAT_DECIMAL_DEGREES;
+        }
+        osgEarth::Util::LatLongFormatter *latlong = new osgEarth::Util::LatLongFormatter(af, opts);
+        if (precision > 0) {
+            latlong->setPrecision(precision);
+        }
+        formatter = latlong;
+    }
+    _coordsCallback = new MouseCoordsCallback(readout, formatter);
+
+    _mouseCoordsTool->addCallback(_coordsCallback.get());
+    _viewer->addEventHandler(_mouseCoordsTool.get());
+}
+
+void Renderer::initEarthManipulator()
+{
+    _manipulator = new osgEarth::Util::EarthManipulator;
+#if 1
+    osgEarth::Util::EarthManipulator::Settings *settings = _manipulator->getSettings();
+
+    settings->bindMouse(osgEarth::Util::EarthManipulator::ACTION_ROTATE,
+                        osgGA::GUIEventAdapter::MIDDLE_MOUSE_BUTTON,
+                        osgGA::GUIEventAdapter::MODKEY_ALT);
+    osgEarth::Util::EarthManipulator::ActionOptions options;
+    options.clear();
+    options.add(osgEarth::Util::EarthManipulator::OPTION_CONTINUOUS, true);
+    settings->bindMouse(osgEarth::Util::EarthManipulator::ACTION_ZOOM,
+                        osgGA::GUIEventAdapter::RIGHT_MOUSE_BUTTON,
+                        osgGA::GUIEventAdapter::MODKEY_ALT, options);
+
+    if (_map->isGeocentric()) {
+        settings->setMinMaxDistance(1.0, 2.0e7);
+    } else {
+        osgEarth::GeoExtent extent = _map->getProfile()->getExtent();
+        settings->setMinMaxDistance(1.0, getMaxDistanceFromExtent(extent));
+    }
+
+    _manipulator->applySettings(settings);
+#endif
+    _viewer->setCameraManipulator(_manipulator.get());
+    _manipulator->setNode(NULL);
+    _manipulator->setNode(_sceneRoot.get());
+    //_manipulator->computeHomePosition();
+}
+
+void Renderer::loadEarthFile(const char *path)
+{
+    TRACE("Loading %s", path);
+    osg::Node *node = osgDB::readNodeFile(path);
+    if (node == NULL) {
+        ERROR("Couldn't load %s", path);
+        return;
+    }
+    osgEarth::MapNode *mapNode = osgEarth::MapNode::findMapNode(node);
+    if (mapNode == NULL) {
+        ERROR("Couldn't find MapNode");
+        return;
+    } else {
+        initViewer();
+        _sceneRoot = new osg::Group;
+        _sceneRoot->addChild(node);
+        _map = mapNode->getMap();
+    }
+    _mapNode = mapNode;
+
+    if (_clipPlaneCullCallback.valid()) {
+        _viewer->getCamera()->removeCullCallback(_clipPlaneCullCallback.get());
+        _clipPlaneCullCallback = NULL;
+    }
+    if (_map->isGeocentric()) {
+        _clipPlaneCullCallback = new osgEarth::Util::AutoClipPlaneCullCallback(mapNode);
+        _viewer->getCamera()->addCullCallback(_clipPlaneCullCallback.get());
+    }
+    _viewer->setSceneData(_sceneRoot.get());
+
+    if (_mouseCoordsTool.valid()) {
+        initMouseCoordsTool();
+    }
+    initControls();
+    initEarthManipulator();
+    
+    _viewer->home();
+    finalizeViewer();
+    _needsRedraw = true;
+}
+
+void Renderer::resetMap(osgEarth::MapOptions::CoordinateSystemType type,
+                        const osg::Vec4f& bgColor,
+                        const char *profile,
+                        double bounds[4],
+                        const char *boundsSRS)
+{
+    TRACE("Restting map with type %d, profile %s", type, profile);
+
+    osgEarth::MapOptions mapOpts;
+    mapOpts.coordSysType() = type;
+    if (profile != NULL) {
+        if (bounds != NULL) {
+            mapOpts.profile() = osgEarth::ProfileOptions();
+            if (strcmp(profile, "geodetic") == 0) {
+                mapOpts.profile()->srsString() = "epsg:4326"; // WGS84
+            } else if (strcmp(profile, "spherical-mercator") == 0) {
+                // Projection used by Google/Bing/OSM
+                // aka epsg:900913 meters in x/y
+                // aka WGS84 Web Mercator (Auxiliary Sphere)
+                // X/Y: -20037508.34m to 20037508.34m
+                mapOpts.profile()->srsString() = "epsg:3857";
+            } else {
+                mapOpts.profile()->srsString() = profile;
+            }
+            TRACE("Input bounds: %g %g %g %g srs: \"%s\"",
+                  bounds[0], bounds[1], bounds[2], bounds[3], boundsSRS);
+            if (boundsSRS != NULL) {
+                osg::ref_ptr<osgEarth::SpatialReference> fromSRS = osgEarth::SpatialReference::create(boundsSRS);
+                osg::ref_ptr<osgEarth::SpatialReference> toSRS = osgEarth::SpatialReference::create(mapOpts.profile()->srsString().get());
+                osgEarth::Bounds extents(bounds[0], bounds[1], bounds[2], bounds[3]);
+                extents.transform(fromSRS, toSRS);
+                mapOpts.profile()->bounds() = extents;
+                //mapOpts.profile()->numTilesWideAtLod0() = 1;
+                //mapOpts.profile()->numTilesHighAtLod0() = 2;
+            } else {
+                mapOpts.profile()->bounds() = 
+                    osgEarth::Bounds(bounds[0], bounds[1], bounds[2], bounds[3]);
+            }
+            TRACE("Setting profile bounds: %g %g %g %g",
+                  mapOpts.profile()->bounds()->xMin(),
+                  mapOpts.profile()->bounds()->yMin(),
+                  mapOpts.profile()->bounds()->xMax(),
+                  mapOpts.profile()->bounds()->yMax());
+        } else {
+            mapOpts.profile() = osgEarth::ProfileOptions(profile);
+        }
+    } else if (type == osgEarth::MapOptions::CSTYPE_PROJECTED) {
+        mapOpts.profile() = osgEarth::ProfileOptions("global-mercator");
+    }
+
+#ifdef USE_CACHE
+    setupCache();
+    osgEarth::Drivers::FileSystemCacheOptions cacheOpts;
+    cacheOpts.rootPath() = _cacheDir;
+    mapOpts.cache() = cacheOpts;
+#endif
+
+    initViewer();
+
+    //mapOpts.referenceURI() = _baseURI;
+    osgEarth::Map *map = new osgEarth::Map(mapOpts);
+    _map = map;
+    osgEarth::Drivers::GDALOptions bopts;
+    bopts.url() = getBaseImage();
+    addImageLayer("base", bopts);
+#ifdef USE_REX
+    osgEarth::Drivers::RexTerrainEngine::RexTerrainEngineOptions engineOpt;
+#else
+    osgEarth::Drivers::MPTerrainEngine::MPTerrainEngineOptions engineOpt;
+#endif
+    // Set background layer color
+    engineOpt.color() = bgColor;
+    //engineOpt.minLOD() = 1;
+    // Sets shader uniform for terrain renderer (config var defaults to false)
+    if (!_map->isGeocentric()) {
+        engineOpt.enableLighting() = false;
+    }
+    osgEarth::MapNodeOptions mapNodeOpts(engineOpt);
+    // Sets GL_LIGHTING state in MapNode StateSet (config var defaults to true)
+    //mapNodeOpts.enableLighting() = true;
+#if 0
+    osgEarth::TerrainOptions terrOpts;
+    //terrOpts.loadingPolicy().mapLoadingThreadsPerCore() = 1;
+    //terrOpts.loadingPolicy().numLoadingThreads() = 1;
+    //terrOpts.loadingPolicy().numCompileThreadsPerCore() = 1;
+    //terrOpts.loadingPolicy().numCompileThreads() = 1;
+    if (_map->isGeocentric()) {
+        // The default is 17 posts
+        terrOpts.tileSize() = 17;
+    } else {
+        // Can reduce if we don't have elevation data
+        terrOpts.tileSize() = 2;
+    }
+    mapNodeOpts.setTerrainOptions(terrOpts);
+#endif
+    osgEarth::MapNode *mapNode = new osgEarth::MapNode(map, mapNodeOpts);
+    _mapNode = mapNode;
+    if (_map->isGeocentric()) {
+        osgEarth::DateTime now;
+        TRACE("Creating SkyNode");
+        osgEarth::Drivers::SimpleSky::SimpleSkyOptions skyOpts;
+        skyOpts.hours() = now.hours();
+        skyOpts.ambient() = 0.2f;
+        skyOpts.atmosphericLighting() = true;
+        skyOpts.exposure() = 3.0;
+        _skyNode = osgEarth::Util::SkyNode::create(skyOpts, mapNode);
+        _skyNode->addChild(mapNode);
+        _skyNode->attach(_viewer.get(), 0);
+        _sceneRoot = new osg::Group();
+        _sceneRoot->addChild(_skyNode.get());
+        //_sceneRoot = _skyNode;
+    } else {
+        _sceneRoot = new osg::Group();
+        _sceneRoot->addChild(_mapNode.get());
+    }
+
+    if (_clipPlaneCullCallback.valid()) {
+        _viewer->getCamera()->removeCullCallback(_clipPlaneCullCallback.get());
+        _clipPlaneCullCallback = NULL;
+    }
+    if (_map->isGeocentric()) {
+        _clipPlaneCullCallback = new osgEarth::Util::AutoClipPlaneCullCallback(mapNode);
+        _viewer->getCamera()->addCullCallback(_clipPlaneCullCallback.get());
+    }
+    _viewer->setSceneData(_sceneRoot.get());
+    if (_mouseCoordsTool.valid()) {
+        initMouseCoordsTool();
+    }
+#ifdef USE_RTT_PICKER
+    if (_picker.valid()) {
+        _viewer->removeEventHandler(_picker.get());
+        _picker = NULL;
+    }
+    if (!_picker.valid()) {
+        _picker = new osgEarth::Util::RTTPicker;
+        _picker->addChild(mapNode);
+        //osg::ref_ptr<HoverCallback> callback = new HoverCallback(this);
+        osg::ref_ptr<SelectCallback> callback = new SelectCallback(this);
+        _picker->setDefaultCallback(callback.get());
+        _viewer->addEventHandler(_picker.get());
+    }
+#endif
+    initControls();
+    //_viewer->setSceneData(_sceneRoot.get());
+    initEarthManipulator();
+    _viewer->home();
+
+    finalizeViewer();
+    _needsRedraw = true;
+}
+
+void Renderer::clearMap()
+{
+    if (_map.valid()) {
+        _map->clear();
+        _needsRedraw = true;
+    }
+}
+
+void Renderer::setSkyAmbient(float value)
+{
+#if OSG_MIN_VERSION_REQUIRED(2, 7, 0)
+    if (_skyNode.valid()) {
+        _skyNode->setMinimumAmbient(osg::Vec4f(value, value, value, 1.0f));
+        _needsRedraw = true;
+    }
+#endif
+}
+
+void Renderer::setLighting(bool state)
+{
+    if (_mapNode.valid()) {
+        TRACE("Setting lighting: %d", state ? 1 : 0);
+        _mapNode->getOrCreateStateSet()
+            ->setMode(GL_LIGHTING, state ? 1 : 0);
+        _needsRedraw = true;
+    }
+}
+
+void Renderer::setViewerLightType(osg::View::LightingMode mode)
+{
+    if (_viewer.valid()) {
+        _viewer->setLightingMode(mode);
+        _needsRedraw = true;
+    }
+}
+
+void Renderer::setTerrainVerticalScale(double scale)
+{
+    if (_mapNode.valid()) {
+        if (!_verticalScale.valid()) {
+            _verticalScale = new osgEarth::Util::VerticalScale;
+            _mapNode->getTerrainEngine()->addEffect(_verticalScale);
+        }
+        _verticalScale->setScale(scale);
+        _needsRedraw = true;
+    }
+}
+
+void Renderer::setEphemerisTime(time_t utcTime)
+{
+    if (_skyNode.valid()) {
+        osgEarth::DateTime time(utcTime);
+        _skyNode->setDateTime(time);
+        _needsRedraw = true;
+    }
+}
+
+void Renderer::setEphemerisTime(int year, int month, int day, double hours)
+{
+    if (_skyNode.valid()) {
+        osgEarth::DateTime time(year, month, day, hours);
+        _skyNode->setDateTime(time);
+        _needsRedraw = true;
+    }
+}
+
+void Renderer::setTerrainColor(const osg::Vec4f& color)
+{
+    // XXX: Don't know how to change this at runtime without a map reset
+    _needsRedraw = true;
+}
+
+void Renderer::setTerrainLighting(bool state)
+{
+    if (_skyNode.valid()) {
+        _skyNode->setLighting((state ? osg::StateAttribute::ON : osg::StateAttribute::OFF));
+    } else {
+#if 1
+        if (!_mapNode.valid()) {
+            ERROR("No map node");
+            return;
+        }
+        // XXX: HACK alert
+        // Find the terrain engine container (might be above one or more decorators)
+        osg::Group *group = _mapNode->getTerrainEngine();
+        while (group->getParent(0) != NULL && group->getParent(0) != _mapNode.get()) {
+            group = group->getParent(0);
+        }
+        if (group != NULL && group->getParent(0) == _mapNode.get()) {
+            TRACE("Setting terrain lighting: %d", state ? 1 : 0);
+            if (group->getOrCreateStateSet()->getUniform("oe_mode_GL_LIGHTING") != NULL) {
+                group->getStateSet()->getUniform("oe_mode_GL_LIGHTING")->set(state);
+            } else {
+                ERROR("Can't get terrain lighting uniform");
+            }
+        } else {
+            ERROR("Can't find terrain engine container");
+        }
+#else
+        if (_stateManip.valid()) {
+            _stateManip->setLightingEnabled(state);
+        }
+#endif
+    }
+    _needsRedraw = true;
+}
+
+void Renderer::setTerrainWireframe(bool state)
+{
+    if (!_map.valid()) {
+        ERROR("No map");
+        return;
+    }
+#if 0
+    if (!_mapNode.valid()) {
+        ERROR("No map node");
+        return;
+    }
+    TRACE("Setting terrain wireframe: %d", state ? 1 : 0);
+    osg::StateSet *state = _mapNode->getOrCreateStateSet();
+    osg::PolygonMode *pmode = dynamic_cast< osg::PolygonMode* >(state->getAttribute(osg::StateAttribute::POLYGONMODE));
+    if (pmode == NULL) {
+        pmode = new osg::PolygonMode;
+        state->setAttribute(pmode);
+    }
+    if (state) {
+        pmode->setMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE);
+    } else {
+        pmode->setMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL);
+    }
+    _needsRedraw = true;
+#else
+    if (_stateManip.valid()) {
+        _stateManip->setPolygonMode(state ? osg::PolygonMode::LINE : osg::PolygonMode::FILL);
+        _needsRedraw = true;
+    }
+#endif
+}
+
+void Renderer::saveNamedViewpoint(const char *name)
+{
+    _viewpoints[name] = getViewpoint();
+}
+
+bool Renderer::removeNamedViewpoint(const char *name)
+{
+    ViewpointHashmap::iterator itr = _viewpoints.find(name);
+    if (itr != _viewpoints.end()) {
+        _viewpoints.erase(name);
+        return true;
+    } else {
+        ERROR("Unknown viewpoint: '%s'", name);
+        return false;
+    }
+}
+
+bool Renderer::restoreNamedViewpoint(const char *name, double durationSecs)
+{
+    ViewpointHashmap::iterator itr = _viewpoints.find(name);
+    if (itr != _viewpoints.end()) {
+        setViewpoint(itr->second, durationSecs);
+        return true;
+    } else {
+        ERROR("Unknown viewpoint: '%s'", name);
+        return false;
+    }
+}
+
+void Renderer::setViewpoint(const osgEarth::Viewpoint& v, double durationSecs)
+{
+    if (_manipulator.valid()) {
+        TRACE("Setting viewpoint: %g %g %g %g %g %g",
+              v.focalPoint()->x(), v.focalPoint()->y(), v.focalPoint()->z(),
+              v.heading()->as(osgEarth::Units::DEGREES),
+              v.pitch()->as(osgEarth::Units::DEGREES),
+              v.range()->as(osgEarth::Units::METERS));
+        _manipulator->setViewpoint(v, durationSecs);
+        _needsRedraw = true;
+    } else {
+        ERROR("No manipulator");
+    }
+}
+
+osgEarth::Viewpoint Renderer::getViewpoint()
+{
+    if (_manipulator.valid()) {
+        return _manipulator->getViewpoint();
+    } else {
+        // Uninitialized, invalid viewpoint
+        return osgEarth::Viewpoint();
+    }
+}
+
+static void srsInfo(const osgEarth::SpatialReference *srs)
+{
+    TRACE("SRS: %s", srs->getName().c_str());
+    TRACE("horiz: \"%s\" vert: \"%s\"", srs->getHorizInitString().c_str(), srs->getVertInitString().c_str());
+    TRACE("geographic: %d geodetic: %d projected: %d ecef: %d mercator: %d spherical mercator: %d northpolar: %d southpolar: %d userdefined: %d contiguous: %d cube: %d ltp: %d plate_carre: %d",
+          srs->isGeographic() ? 1 : 0,
+          srs->isGeodetic() ? 1 : 0,
+          srs->isProjected() ? 1 : 0,
+          srs->isECEF() ? 1 : 0,
+          srs->isMercator() ? 1 : 0,
+          srs->isSphericalMercator() ? 1 : 0,
+          srs->isNorthPolar() ? 1 : 0,
+          srs->isSouthPolar() ? 1 : 0,
+          srs->isUserDefined() ? 1 : 0,
+          srs->isContiguous() ? 1 : 0,
+          srs->isCube() ? 1 : 0,
+          srs->isLTP() ? 1 : 0,
+          srs->isPlateCarre() ? 1 : 0);
+}
+
+bool Renderer::getWorldCoords(const osgEarth::GeoPoint& mapPt, osg::Vec3d *world)
+{
+    if (!_mapNode.valid() || _mapNode->getTerrain() == NULL) {
+        ERROR("No map");
+        return false;
+    }
+    TRACE("Input SRS:");
+    srsInfo(mapPt.getSRS());
+    TRACE("Map SRS:");
+    srsInfo(_mapNode->getMapSRS());
+    bool ret = mapPt.toWorld(*world, _mapNode->getTerrain());
+    TRACE("In: %g,%g,%g Out: %g,%g,%g",
+          mapPt.x(), mapPt.y(), mapPt.z(),
+          world->x(), world->y(), world->z());
+    return ret;
+}
+
+bool Renderer::worldToScreen(const osg::Vec3d& world, osg::Vec3d *screen, bool invertY)
+{
+    if (!_viewer.valid()) {
+        ERROR("No viewer");
+        return false;
+    }
+    osg::Camera *cam = _viewer->getCamera();
+    osg::Matrixd MVP = cam->getViewMatrix() * cam->getProjectionMatrix();
+    // Get clip coords
+    osg::Vec4d pt;
+    pt = osg::Vec4d(world, 1.0) * MVP;
+    // Clip
+    if (pt.x() < -pt.w() ||
+        pt.x() > pt.w() ||
+        pt.y() < -pt.w() ||
+        pt.y() > pt.w() ||
+        pt.z() < -pt.w() ||
+        pt.z() > pt.w()) {
+        // Outside frustum
+        TRACE("invalid pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w());
+        return false;
+    }
+    TRACE("clip pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w());
+    // Perspective divide: now NDC
+    pt /= pt.w();
+    const osg::Viewport *viewport = cam->getViewport();
+#if 1
+    screen->x() = viewport->x() + viewport->width() * 0.5 + pt.x() * viewport->width() * 0.5;
+    screen->y() = viewport->y() + viewport->height() * 0.5 + pt.y() * viewport->height() * 0.5;
+    //double near = 0;
+    //double far = 1;
+    //screen->z() = (far + near) * 0.5 + (far - near) * 0.5 * pt.z();
+    screen->z() = 0.5 + 0.5 * pt.z();
+#else
+    *screen = osg::Vec3d(pt.x(), pt.y(), pt.z()) * cam->getViewport()->computeWindowMatrix();
+#endif
+    if (invertY) {
+        screen->y() = viewport->height() - screen->y();
+    }
+    TRACE("screen: %g,%g,%g", screen->x(), screen->y(), screen->z());
+    return true;
+}
+
+bool Renderer::worldToScreen(std::vector<osg::Vec3d>& coords, bool invertY)
+{
+    if (!_viewer.valid()) {
+        ERROR("No viewer");
+        return false;
+    }
+    bool ret = true;
+    osg::Camera *cam = _viewer->getCamera();
+    osg::Matrixd MVP = cam->getViewMatrix() * cam->getProjectionMatrix();
+    const osg::Viewport *viewport = cam->getViewport();
+    for (std::vector<osg::Vec3d>::iterator itr = coords.begin();
+         itr != coords.end(); ++itr) {
+        // Get clip coords
+        osg::Vec4d pt;
+        pt = osg::Vec4d(*itr, 1.0) * MVP;
+        // Clip
+        if (pt.x() < -pt.w() ||
+            pt.x() > pt.w() ||
+            pt.y() < -pt.w() ||
+            pt.y() > pt.w() ||
+            pt.z() < -pt.w() ||
+            pt.z() > pt.w()) {
+            // Outside frustum
+            TRACE("invalid pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w());
+            itr->set(std::numeric_limits<double>::quiet_NaN(),
+                     std::numeric_limits<double>::quiet_NaN(),
+                     std::numeric_limits<double>::quiet_NaN());
+            ret = false;
+            continue;
+        }
+        TRACE("clip pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w());
+        // Perspective divide: now NDC
+        pt /= pt.w();
+#if 1
+        itr->x() = viewport->x() + viewport->width() * 0.5 + pt.x() * viewport->width() * 0.5;
+        itr->y() = viewport->y() + viewport->height() * 0.5 + pt.y() * viewport->height() * 0.5;
+        //double near = 0;
+        //double far = 1;
+        //itr->z() = (far + near) * 0.5 + (far - near) * 0.5 * pt.z();
+        itr->z() = 0.5 + 0.5 * pt.z();
+#else
+        *itr = osg::Vec3d(pt.x(), pt.y(), pt.z()) * viewport->computeWindowMatrix();
+#endif
+        if (invertY) {
+            itr->y() = viewport->height() - itr->y();
+        }
+        TRACE("screen: %g,%g,%g", itr->x(), itr->y(), itr->z());
+    }
+    return ret;
+}
+
+bool Renderer::mouseToLatLong(int mouseX, int mouseY,
+                              double *latitude, double *longitude)
+{
+    if (getMapSRS() == NULL)
+        return false;
+    const osgEarth::SpatialReference *outSRS = 
+        getMapSRS()->getGeographicSRS();
+    if (outSRS == NULL)
+        return false;
+    osgEarth::GeoPoint mapPoint;
+    if (mapMouseCoords(mouseX, mouseY, mapPoint)) {
+        mapPoint = mapPoint.transform(outSRS);
+        *longitude = mapPoint.x();
+        *latitude = mapPoint.y();
+        return true;
+    } else {
+        return false;
+    }
+}
+#if 0
+bool Renderer::mapToLatLong(osgEarth::GeoPoint& mapPoint)
+{
+    if (getMapSRS() == NULL)
+        return false;
+    const osgEarth::SpatialReference *outSRS = 
+        getMapSRS()->getGeographicSRS();
+    if (outSRS == NULL)
+        return false;
+    mapPoint = mapPoint.transform(outSRS);
+    return true;
+}
+#endif
+/**
+ * \brief Map screen mouse coordinates to map coordinates
+ *
+ * This method assumes that mouse Y coordinates are 0 at the top
+ * of the screen and increase going down if invertY is set, and 
+ * 0 at the bottom and increase going up otherwise.
+ */
+bool Renderer::mapMouseCoords(float mouseX, float mouseY,
+                              osgEarth::GeoPoint& map, bool invertY)
+{
+    if (!_mapNode.valid() || _mapNode->getTerrain() == NULL) {
+        ERROR("No map");
+        return false;
+    }
+    if (!_viewer.valid()) {
+        ERROR("No viewer");
+        return false;
+    }
+    if (invertY) {
+        mouseY = ((float)_windowHeight - mouseY);
+    }
+    osg::Vec3d world;
+    if (_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), mouseX, mouseY, world)) {
+        map.fromWorld(getMapSRS(), world);
+        return true;
+    }
+    return false;
+}
+
+bool Renderer::addImageLayer(const char *name,
+                             osgEarth::TileSourceOptions& opts,
+                             unsigned int pos,
+                             bool enableCache,
+                             bool coverage,
+                             bool makeShared,
+                             bool visible,
+                             unsigned int minLOD, unsigned int maxLOD)
+{
+    if (!_map.valid()) {
+        ERROR("No map");
+        return false;
+    }
+    TRACE("layer: %s", name);
+    if (!opts.tileSize().isSet()) {
+        opts.tileSize() = 256;
+    }
+    osgEarth::ImageLayerOptions layerOpts(name, opts);
+    layerOpts.textureCompression() = osg::Texture::USE_IMAGE_DATA_FORMAT;
+    if (coverage) {
+        layerOpts.coverage() = true;
+    }
+    if (makeShared) {
+        layerOpts.shared() = true;
+    }
+    if (!enableCache) {
+        TRACE("Disabling cache for layer %s", name);
+        layerOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
+    }
+    if (!visible) {
+        layerOpts.visible() = false;
+    }
+    layerOpts.minLevel() = minLOD;
+    layerOpts.maxLevel() = maxLOD;
+    osg::ref_ptr<osgEarth::ImageLayer> layer = new osgEarth::ImageLayer(layerOpts);
+    if (pos < (unsigned int)_map->getNumImageLayers()) {
+        _map->insertImageLayer(layer.get(), pos);
+    } else {
+        _map->addImageLayer(layer.get());
+    }
+    if (layer->getTileSource() == NULL || !layer->getTileSource()->isOK()) {
+        ERROR("Failed to add image layer: %s", name);
+        _map->removeImageLayer(layer.get());
+        return false;
+    }
+    _needsRedraw = true;
+    return true;
+}
+
+void Renderer::addColorFilter(const char *name,
+                              const char *shader)
+{
+    if (!_map.valid()) {
+        ERROR("No map");
+        return;
+    }
+    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
+    if (layer == NULL) {
+        TRACE("Image layer not found: %s", name);
+        return;
+    }
+    osgEarth::Util::GLSLColorFilter *filter = new osgEarth::Util::GLSLColorFilter;
+    filter->setCode(shader);
+    //filter->setCode("color.rgb = color.r > 0.5 ? vec3(1.0) : vec3(0.0);");
+    layer->addColorFilter(filter);
+    _needsRedraw = true;
+}
+
+void Renderer::removeColorFilter(const char *name, int idx)
+{
+    if (!_map.valid()) {
+        ERROR("No map");
+        return;
+    }
+    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
+    if (layer == NULL) {
+        TRACE("Image layer not found: %s", name);
+        return;
+    }
+    if (idx < 0) {
+        while (!layer->getColorFilters().empty()) {
+            layer->removeColorFilter(layer->getColorFilters()[0]);
+        }
+    } else {
+        layer->removeColorFilter(layer->getColorFilters().at(idx));
+    }
+    _needsRedraw = true;
+}
+
+void Renderer::removeImageLayer(const char *name)
+{
+    if (!_map.valid()) {
+        ERROR("No map");
+        return;
+    }
+    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
+    if (layer != NULL) {
+        _map->removeImageLayer(layer);
+        _needsRedraw = true;
+    } else {
+        TRACE("Image layer not found: %s", name);
+    }
+}
+
+void Renderer::moveImageLayer(const char *name, unsigned int pos)
+{
+    if (!_map.valid()) {
+        ERROR("No map");
+        return;
+    }
+    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
+    if (layer != NULL) {
+        _map->moveImageLayer(layer, pos);
+        _needsRedraw = true;
+    } else {
+        TRACE("Image layer not found: %s", name);
+    }
+}
+
+void Renderer::setImageLayerOpacity(const char *name, double opacity)
+{
+    if (!_map.valid()) {
+        ERROR("No map");
+        return;
+    }
+    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
+    if (layer != NULL) {
+        layer->setOpacity(opacity);
+        _needsRedraw = true;
+    } else {
+        TRACE("Image layer not found: %s", name);
+    }
+}
+
+void Renderer::setImageLayerVisibleRange(const char *name, float min, float max)
+{
+    if (!_map.valid()) {
+        ERROR("No map");
+        return;
+    }
+    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
+    if (layer != NULL) {
+        layer->setMinVisibleRange(min);
+        layer->setMaxVisibleRange(max);
+        _needsRedraw = true;
+    } else {
+        TRACE("Image layer not found: %s", name);
+    }
+}
+
+void Renderer::setImageLayerVisibility(const char *name, bool state)
+{
+    if (!_map.valid()) {
+        ERROR("No map");
+        return;
+    }
+    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
+    if (layer != NULL) {
+        layer->setVisible(state);
+        _needsRedraw = true;
+    } else {
+        TRACE("Image layer not found: %s", name);
+    }
+}
+
+bool Renderer::layerHasSequence(const char *name)
+{
+    if (!_map.valid()) {
+        ERROR("No map");
+        return false;
+    }
+    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
+    if (layer != NULL) {
+        osgEarth::SequenceControl *seq = layer->getSequenceControl();
+        if (seq == NULL) {
+            TRACE("Image layer has no SequenceControl: %s", name);
+            return false;
+        }
+        return seq->supportsSequenceControl();
+    }
+    TRACE("Image layer not found: %s", name);
+    return false;
+}
+
+bool Renderer::sequencePause(const char *name)
+{
+    if (!_map.valid()) {
+        ERROR("No map");
+        return false;
+    }
+    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
+    if (layer != NULL) {
+        osgEarth::SequenceControl *seq = layer->getSequenceControl();
+        if (seq == NULL || !seq->supportsSequenceControl()) {
+            TRACE("Image layer has no SequenceControl: %s", name);
+            return false;
+        }
+        seq->pauseSequence();
+        _needsRedraw = true;
+        return true;
+    }
+    TRACE("Image layer not found: %s", name);
+    return false;
+}
+
+bool Renderer::sequencePlay(const char *name)
+{
+    if (!_map.valid()) {
+        ERROR("No map");
+        return false;
+    }
+    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
+    if (layer != NULL) {
+        osgEarth::SequenceControl *seq = layer->getSequenceControl();
+        if (seq == NULL || !seq->supportsSequenceControl()) {
+            TRACE("Image layer has no SequenceControl: %s", name);
+            return false;
+        }
+        seq->playSequence();
+        _needsRedraw = true;
+        return true;
+    }
+    TRACE("Image layer not found: %s", name);
+    return false;
+}
+
+bool Renderer::sequenceSeek(const char *name, unsigned int frame)
+{
+    if (!_map.valid()) {
+        ERROR("No map");
+        return false;
+    }
+    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
+    if (layer != NULL) {
+        osgEarth::SequenceControl *seq = layer->getSequenceControl();
+        if (seq == NULL || !seq->supportsSequenceControl()) {
+            TRACE("Image layer has no SequenceControl: %s", name);
+            return false;
+        }
+        seq->seekToSequenceFrame(frame);
+        _needsRedraw = true;
+        return true;
+    }
+    TRACE("Image layer not found: %s", name);
+    return false;
+}
+
+void Renderer::addElevationLayer(const char *name,
+                                 osgEarth::TileSourceOptions& opts,
+                                 unsigned int pos,
+                                 bool enableCache,
+                                 bool visible,
+                                 const char *verticalDatum,
+                                 unsigned int minLOD, unsigned int maxLOD)
+{
+    if (!_map.valid()) {
+        ERROR("No map");
+        return;
+    }
+    TRACE("layer: %s", name);
+    if (!opts.tileSize().isSet()) {
+        opts.tileSize() = 17;
+    }
+    osgEarth::ElevationLayerOptions layerOpts(name, opts);
+    if (!enableCache) {
+        layerOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
+    }
+    if (!visible) {
+        layerOpts.visible() = false;
+    }
+    // Check for vertical datum override (required for GDAL driver if not using
+    // geodetic HAE).
+    if (verticalDatum != NULL && strlen(verticalDatum) > 0) {
+        layerOpts.verticalDatum() = verticalDatum;
+    }
+    layerOpts.minLevel() = minLOD;
+    layerOpts.maxLevel() = maxLOD;
+    osgEarth::ElevationLayer *layer = new osgEarth::ElevationLayer(layerOpts);
+    _map->addElevationLayer(layer);
+    // Map API lacks an insertElevationLayer method, so need to move it
+    if (pos < (unsigned int)_map->getNumElevationLayers()) {
+        _map->moveElevationLayer(layer, pos);
+    }
+    _needsRedraw = true;
+}
+
+void Renderer::removeElevationLayer(const char *name)
+{
+    if (!_map.valid()) {
+        ERROR("No map");
+        return;
+    }
+    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
+    if (layer != NULL) {
+        _map->removeElevationLayer(layer);
+        _needsRedraw = true;
+    } else {
+        TRACE("Elevation layer not found: %s", name);
+    }
+}
+
+void Renderer::moveElevationLayer(const char *name, unsigned int pos)
+{
+    if (!_map.valid()) {
+        ERROR("No map");
+        return;
+    }
+    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
+    if (layer != NULL) {
+        _map->moveElevationLayer(layer, pos);
+        _needsRedraw = true;
+    } else {
+        TRACE("Elevation layer not found: %s", name);
+    }
+}
+
+void Renderer::setElevationLayerVisibility(const char *name, bool state)
+{
+    if (!_map.valid()) {
+        ERROR("No map");
+        return;
+    }
+    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
+    if (layer != NULL) {
+        layer->setVisible(state);
+        _needsRedraw = true;
+    } else {
+        TRACE("Elevation layer not found: %s", name);
+    }
+}
+
+void Renderer::addTerrainMaskLayer(const char *name,
+                                   osgEarth::MaskSourceOptions& opts,
+                                   unsigned int minLOD)
+{
+    if (!_map.valid()) {
+        ERROR("No map");
+        return;
+    }
+    TRACE("layer: %s", name);
+    osgEarth::MaskLayerOptions layerOpts(name, opts);
+    layerOpts.minLevel() = minLOD;
+    osgEarth::MaskLayer *layer = new osgEarth::MaskLayer(layerOpts);
+    _map->addTerrainMaskLayer(layer);
+    _needsRedraw = true;
+}
+
+void Renderer::removeTerrainMaskLayer(const char *name)
+{
+    if (!_map.valid()) {
+        ERROR("No map");
+        return;
+    }
+    osgEarth::MaskLayer *layer = getTerrainMaskLayerByName(name);
+    if (layer != NULL) {
+        _map->removeTerrainMaskLayer(layer);
+        _needsRedraw = true;
+    } else {
+        TRACE("Terrain mask layer not found: %s", name);
+    }
+}
+
+void Renderer::initAnnotations()
+{
+    if (!_annotations.valid()) {
+        _annotations = new osg::Group();
+        _annotations->setName("Annotations");
+        _sceneRoot->addChild(_annotations.get());
+        _placeNodes = new osg::Group();
+        _placeNodes->setName("Place Nodes");
+#ifdef NEW_ANNOTATION_API
+        osgEarth::ScreenSpaceLayout::activate(_placeNodes->getOrCreateStateSet());
+#else
+        osgEarth::Decluttering::setEnabled(_placeNodes->getOrCreateStateSet(), true);
+#endif
+        _annotations->addChild(_placeNodes.get());
+        if (_picker.valid()) {
+            _picker->addChild(_placeNodes.get());
+        }
+    }
+}
+
+void Renderer::setSelectMode(SelectMode mode)
+{
+    _selectMode = mode;
+    switch (_selectMode) {
+    case SELECT_OFF: {
+#ifdef USE_RTT_PICKER
+        if (_picker.valid()) {
+            _viewer->removeEventHandler(_picker.get());
+            _picker = NULL;
+        }
+#endif
+    }
+        break;
+    case SELECT_ON: {
+#ifdef USE_RTT_PICKER
+        if (!_picker.valid()) {
+            _picker = new osgEarth::Util::RTTPicker;
+            _picker->addChild(_mapNode);
+            osg::ref_ptr<SelectCallback> callback = new SelectCallback(this);
+            _picker->setDefaultCallback(callback.get());
+            _viewer->addEventHandler(_picker.get());
+        }
+#endif
+    }
+        break;
+    default:
+        ERROR("Unknown select mode");
+    }
+}
+
+void Renderer::enablePlacard(const char *layerName, bool state)
+{
+    osgEarth::ModelLayer *layer = _map->getModelLayerByName(layerName);
+    if (layer == NULL) {
+        ERROR("Unknown layer '%s'", layerName);
+        return;
+    }
+    _placardConfigs[layerName].setEnabled(state);
+}
+
+void Renderer::setPlacardConfig(const Placard& placardConf, const char *layerName)
+{
+    osgEarth::ModelLayer *layer = _map->getModelLayerByName(layerName);
+    if (layer == NULL) {
+        ERROR("Unknown layer '%s'", layerName);
+        return;
+    }
+    //PlacardNode *node;
+    //node->setConfig(placardConf);
+    _placardConfigs[layerName] = placardConf;
+}
+
+void Renderer::deselectFeatures(std::vector<unsigned long>& featureIDs, const char *layerName)
+{
+    // TODO: Take down placard?
+    TRACE("Deselect features layer '%s', num features: %u", layerName, featureIDs.size());
+    for (unsigned int i = 0; i < featureIDs.size(); i++) {
+        _selectedFeatures[layerName].erase(featureIDs.at(i));
+    }
+    FeatureSelectionHashmap::iterator itr = _selectedFeatures.find(layerName);
+    if (itr != _selectedFeatures.end()) {
+        for (FeatureSelectionHashmap::mapped_type::iterator fitr = itr->second.begin();
+             fitr != itr->second.end(); ++fitr) {
+            TRACE("Selection: %lu", *fitr);
+        }
+    } else {
+        TRACE("No selection");
+    }
+    updateDebugLabel();
+}
+
+void Renderer::selectFeatures(std::vector<unsigned long>& featureIDs, const char *layerName, bool clear)
+{
+    bool doPlacard = false;
+    TRACE("Select layer '%s', num features: %u", layerName, featureIDs.size());
+    if (clear) {
+        clearSelection();
+    }
+    if (featureIDs.size() == 0) {
+        // clear selection
+        return;
+    }
+    osgEarth::ModelLayer *layer = _map->getModelLayerByName(layerName);
+    if (layer == NULL) {
+        ERROR("Unknown layer '%s'", layerName);
+        return;
+    }
+    for (unsigned int i = 0; i < featureIDs.size(); i++) {
+        TRACE("feature ID: %u", featureIDs.at(i));
+        _selectedFeatures[layerName].insert(featureIDs.at(i));
+    }
+    FeatureSelectionHashmap::iterator itr = _selectedFeatures.find(layerName);
+    if (itr != _selectedFeatures.end()) {
+        for (FeatureSelectionHashmap::mapped_type::iterator fitr = itr->second.begin();
+             fitr != itr->second.end(); ++fitr) {
+            TRACE("Selection: %lu", *fitr);
+        }
+    } else {
+        TRACE("No selection");
+    }
+    unsigned long fid = featureIDs.at(0);
+
+    osgEarth::ModelSource *modelSource = layer->getModelSource();
+    osgEarth::Features::FeatureModelSource *fms = dynamic_cast<osgEarth::Features::FeatureModelSource *>(modelSource);
+    if (fms != NULL) {
+        osgEarth::Features::FeatureSource *fs = fms->getFeatureSource();
+        FindFeatureSourceIndexNodeVisitor visitor;
+        visitor.source = fs;
+        _sceneRoot->accept(visitor);
+        TRACE("Num FeatureSourceIndexNodes found: %lu", visitor.nodes.size());
+        for (size_t i = 0; i < visitor.nodes.size(); i++) {
+            osgEarth::Features::FeatureIndex *index = visitor.nodes[i]->getIndex();
+#if OSGEARTH_MIN_VERSION_REQUIRED(2, 8, 0)
+            osgEarth::ObjectID id = index->getObjectID(fid);
+            if (doPlacard) {
+                osgEarth::Features::Feature *feature = index->getFeature(id);
+                if (feature) {
+                    // Find feature centroid
+                    osgEarth::GeoPoint location;
+                    if (feature->getGeometry()) {
+                        osg::BoundingSphered bound;
+                        feature->getWorldBound(getMapSRS(), bound);
+                        location.set(getMapSRS(), bound.center(),
+                                     osgEarth::ALTMODE_ABSOLUTE);
+                    }
+                    addPlacard(location, feature, layerName);
+                }
+            }
+            setHighlightByObjectID(id);
+            TRACE("FID %lu = OID %d", fid, id);
+#endif
+        }
+	_needsRedraw = true;
+    }
+    updateDebugLabel();
+}
+
+void Renderer::updateDebugLabel()
+{
+    if (!_debugLabel.valid()) return;
+    std::ostringstream oss;
+    oss << "Selection:";
+    for (FeatureSelectionHashmap::iterator itr = _selectedFeatures.begin();
+         itr != _selectedFeatures.end(); ++itr) {
+        bool found = false;
+        oss << std::endl << itr->first << ":";
+        for (FeatureSelectionHashmap::mapped_type::iterator fitr = itr->second.begin();
+             fitr != itr->second.end(); ++fitr) {
+            oss << " " << *fitr;
+            found = true;
+        }
+        if (!found) {
+            oss << " None";
+        }
+    }
+    _debugLabel->setText(oss.str());
+}
+
+void Renderer::addPlacard(const osgEarth::GeoPoint& location,
+                          osgEarth::Features::Feature *feature,
+                          const char *layerName)
+{
+    if (feature == NULL) return;
+    Placard placard = getPlacardConfig(layerName);
+    if (!placard.enabled())
+        return;
+
+    const osgEarth::Features::AttributeTable &attrs = feature->getAttrs();
+    if (placard.getNumEntries() == 0) {
+        placard.addAllAttributes(attrs);
+    }
+    PlacardNode *label = 
+        new PlacardNode(_mapNode.get(), location, placard, attrs);
+#ifndef NEW_ANNOTATION_API
+    label->getOrCreateStateSet()->setRenderBinDetails(INT_MAX, "RenderBin");
+#endif
+    getAnnotations()->addChild(label);
+}
+
+void Renderer::clearSelection()
+{
+    _selectedFeatures.clear();
+    clearHighlight();
+    clearSelectionAnnotationNodes();
+    clearBoxSelection();
+    updateDebugLabel();
+    _needsRedraw = true;
+}
+
+/**
+ * \brief Remove annotation nodes created during a pick/selection
+ * 
+ * The primary purpose of this method is to take down the feature placard
+ */
+void Renderer::clearSelectionAnnotationNodes()
+{
+    osg::Group *nodes = getAnnotations();
+    std::vector<osg::Node *> toRemove;
+    for (unsigned int i = 0; i < nodes->getNumChildren(); i++) {
+        osg::Node *node = nodes->getChild(i);
+        // This can be Placard, PlacardLabel, Label, Place or Track Node
+#ifdef NEW_ANNOTATION_API
+        if (dynamic_cast<osgEarth::Annotation::GeoPositionNode *>(node) != NULL) {
+#else
+        if (dynamic_cast<osgEarth::Annotation::OrthoNode *>(node) != NULL) {
+#endif
+            toRemove.push_back(node);
+        }
+    }
+    for (std::vector<osg::Node *>::iterator itr = toRemove.begin();
+         itr != toRemove.end(); ++itr) {
+        osgEarth::Annotation::AnnotationNode *anno = dynamic_cast<osgEarth::Annotation::AnnotationNode *>(*itr);
+        if (anno != NULL) {
+            if (_hovered.find(anno) != _hovered.end()) {
+                _hovered.erase(anno);
+            }
+            if (_selected.find(anno) != _selected.end()) {
+                _selected.erase(anno);
+            }
+        }
+        nodes->removeChild(*itr);
+    }
+    _needsRedraw = true;
+}
+
+void Renderer::initBoxSelection(int x, int y)
+{
+    double latitude, longitude;
+    if (!mouseToLatLong(x, y, &latitude, &longitude)) {
+        return;
+    }
+    _anchorLat = latitude;
+    _anchorLong = longitude;
+    addRhumbBox(latitude, latitude, longitude, longitude);
+}
+
+void Renderer::updateBoxSelection(int x, int y)
+{
+    double nlat, nlong;
+    if (!mouseToLatLong(x, y, &nlat, &nlong)) {
+        return;
+    }
+    double latMin, latMax, longMin, longMax;
+    if (nlong >= _anchorLong && nlat >= _anchorLat) {
+        // +x +y
+        longMin = _anchorLong;
+        latMin = _anchorLat;
+        longMax = nlong;
+        latMax = nlat;
+    } else if (nlong < _anchorLong && nlat >= _anchorLat) {
+        // -x +y
+        longMin = nlong;
+        latMin = _anchorLat;
+        longMax = _anchorLong;
+        latMax = nlat;
+    } else if (nlong < _anchorLong && nlat < _anchorLat) {
+        // -x -y
+        longMin = nlong;
+        latMin = nlat;
+        longMax = _anchorLong;
+        latMax = _anchorLat;
+    } else {
+        // +x -y
+        longMin = _anchorLong;
+        latMin = nlat;
+        longMax = nlong;
+        latMax = _anchorLat;
+    }
+    osgEarth::Annotation::FeatureNode *node = _selectionBox.get();
+    osgEarth::Symbology::Geometry *geom = node->getFeature()->getGeometry();
+    (*geom)[0] = osg::Vec3d(longMin, latMin, 0);
+    (*geom)[1] = osg::Vec3d(longMax, latMin, 0);
+    (*geom)[2] = osg::Vec3d(longMax, latMax, 0);
+    (*geom)[3] = osg::Vec3d(longMin, latMax, 0);
+    node->init();
+#ifndef NEW_ANNOTATION_API
+    for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = _selected.begin();
+         itr != _selected.end(); ++itr) {
+        (*itr)->clearDecoration();
+    }
+#endif
+    _selected.clear();
+    SelectPlaceNodesVisitor spnv(this, latMin, latMax, longMin, longMax);
+    _placeNodes->accept(spnv);
+    _needsRedraw = true;
+}
+
+void Renderer::getBoxSelection(double *latMin, double *latMax,
+                               double *longMin, double *longMax,
+                               const osgEarth::SpatialReference *outSRS)
+{
+    osgEarth::Annotation::FeatureNode *node = _selectionBox.get();
+    if (node == NULL)
+        return;
+    osgEarth::Symbology::Geometry *geom = node->getFeature()->getGeometry();
+    if (geom == NULL)
+        return;
+    *latMin = (*geom)[0].y();
+    *latMax = (*geom)[2].y();
+    *longMin = (*geom)[0].x();
+    *longMax = (*geom)[2].x();
+    if (outSRS == NULL)
+        return;
+
+    const osgEarth::SpatialReference* fromSRS = _mapNode->getMapSRS()->getGeographicSRS();
+    osgEarth::GeoPoint pt1(fromSRS, *longMin, *latMin);
+    osgEarth::GeoPoint pt2(fromSRS, *longMax, *latMax);
+    pt1.transform(outSRS);
+    pt2.transform(outSRS);
+    *latMin = pt1.y();
+    *latMax = pt2.y();
+    *longMin = pt1.x();
+    *longMax = pt2.x();
+}
+
+void SelectPlaceNodesVisitor::apply(osg::Node& node)
+{
+    osgEarth::Annotation::PlaceNode *placeNode =
+        dynamic_cast<osgEarth::Annotation::PlaceNode*>(&node);
+    if (placeNode != NULL) {
+        const osgEarth::SpatialReference *outSRS = 
+            _renderer->getMapSRS()->getGeographicSRS();
+        osgEarth::GeoPoint pt = placeNode->getPosition();
+        pt = pt.transform(outSRS);
+        if (pt.x() >= _longMin && pt.x() <= _longMax &&
+            pt.y() >= _latMin && pt.y() <= _latMax) {
+            if (_renderer->select(placeNode)) {
+                TRACE("Select PlaceNode: %g %g n:'%s' t:'%s'",
+                     pt.x(), pt.y(), placeNode->getName().c_str(),
+                     placeNode->getText().c_str());
+#ifndef NEW_ANNOTATION_API
+                placeNode->setDecoration("select");
+#endif
+            }
+        }
+    }
+    traverse(node);
+}
+
+void Renderer::clearBoxSelection()
+{
+    if (_annotations.valid() && _selectionBox.valid()) {
+        _annotations->removeChild(_selectionBox.get());
+        _selectionBox = NULL;
+    }
+#ifndef NEW_ANNOTATION_API
+    for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = _selected.begin();
+         itr != _selected.end(); ++itr) {
+        (*itr)->clearDecoration();
+    }
+#endif
+    _selected.clear();
+    _needsRedraw = true;
+}
+
+void Renderer::addRhumbBox(double latMin, double latMax,
+                           double longMin, double longMax)
+{
+    if (!_mapNode.valid()) {
+        ERROR("No map node");
+        return;
+    }
+    initAnnotations();
+
+    if (_selectionBox.valid()) {
+        osgEarth::Symbology::Geometry *geom = _selectionBox->getFeature()->getGeometry();
+        (*geom)[0] = osg::Vec3d(longMin, latMin, 0);
+        (*geom)[1] = osg::Vec3d(longMax, latMin, 0);
+        (*geom)[2] = osg::Vec3d(longMax, latMax, 0);
+        (*geom)[3] = osg::Vec3d(longMin, latMax, 0);
+        _selectionBox->init();
+    } else {
+        const osgEarth::SpatialReference* geoSRS = _mapNode->getMapSRS()->getGeographicSRS();
+        osgEarth::Symbology::Geometry *geom = new osgEarth::Symbology::Polygon();
+        geom->push_back(osg::Vec3d(longMin, latMin, 0));
+        geom->push_back(osg::Vec3d(longMax, latMin, 0));
+        geom->push_back(osg::Vec3d(longMax, latMax, 0));
+        geom->push_back(osg::Vec3d(longMin, latMax, 0));
+        osgEarth::Symbology::Style boxStyle;
+#if 1
+        osgEarth::Symbology::PolygonSymbol *poly = boxStyle.getOrCreate<osgEarth::Symbology::PolygonSymbol>();
+        poly->fill()->color() = osgEarth::Symbology::Color::Cyan;
+        poly->fill()->color().a() = 0.5;
+#else
+        osgEarth::Symbology::LineSymbol *line = boxStyle.getOrCreate<osgEarth::Symbology::LineSymbol>();
+        line->stroke()->color() = osgEarth::Symbology::Color::Yellow;
+        line->stroke()->width() = 2.0f;
+        //line->creaseAngle() = 45.0f;
+        line->tessellation() = 10;
+#endif
+        osgEarth::Symbology::AltitudeSymbol *alt = boxStyle.getOrCreate<osgEarth::Symbology::AltitudeSymbol>();
+        alt->clamping() = osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
+        //alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
+        alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_DRAPE;
+        //alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_SCENE;
+#if 0
+        osgEarth::Symbology::RenderSymbol* rs = boxStyle.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
+        rs->depthOffset()->enabled() = true;
+        rs->depthOffset()->minBias() = 1000;
+#endif
+        osgEarth::Features::Feature *feature = new osgEarth::Features::Feature(geom, geoSRS, boxStyle);
+        //feature->geoInterp() = osgEarth::GEOINTERP_GREAT_CIRCLE;
+        feature->geoInterp() = osgEarth::GEOINTERP_RHUMB_LINE;
+        _selectionBox =
+            new osgEarth::Annotation::FeatureNode(_mapNode, feature);
+        _annotations->addChild(_selectionBox.get());
+    }
+
+    _needsRedraw = true;
+}
+
+void Renderer::addPlaceNode(double latitude, double longitude, char *labelText)
+{
+    if (!_mapNode.valid()) {
+        ERROR("No map node");
+        return;
+    }
+    initAnnotations();
+
+    const osgEarth::SpatialReference *geoSRS = _mapNode->getMapSRS()->getGeographicSRS();
+
+    osgEarth::Symbology::Style pin;
+    pin.getOrCreate<osgEarth::Symbology::IconSymbol>()->url()->setLiteral(getPinIcon());
+    osgEarth::Annotation::AnnotationNode *anno =
+        new osgEarth::Annotation::PlaceNode(_mapNode, osgEarth::GeoPoint(geoSRS, longitude, latitude), labelText, pin);
+    anno->setName(labelText);
+    _placeNodes->addChild(anno);
+#ifdef USE_RTT_PICKER
+    osgEarth::Registry::objectIndex()->tagNode(anno, anno);
+#else
+    osgEarth::Annotation::DecorationInstaller
+        highlightInstaller("select", new osgEarth::Annotation::HighlightDecoration(osg::Vec4f(1,1,0,0.5)));
+    _placeNodes->accept(highlightInstaller);
+
+    // scale labels when hovering:
+    osgEarth::Annotation::DecorationInstaller
+        scaleInstaller("hover", new osgEarth::Annotation::ScaleDecoration(1.1f));
+    _placeNodes->accept(scaleInstaller);
+#endif
+#if 0
+    writeScene("/tmp/test.osg");
+#endif
+    _needsRedraw = true;
+}
+#if 0
+void Renderer::hoverFeatureNodes(int x, int y, bool invertY)
+{
+    if (!_mapNode.valid()) {
+        ERROR("No map node");
+        return;
+    }
+    osgEarth::IntersectionPicker picker(_viewer.get(), _mapNode.get());
+    osgEarth::IntersectionPicker::Hits hits;
+    float mouseX = (float)x;
+    float mouseY = (float)y;
+    if (invertY) {
+        mouseY = ((float)_windowHeight - mouseY);
+    }
+    std::set<osgEarth::Annotation::FeatureNode*> toUnHover;
+    for (std::set<osgEarth::Annotation::FeatureNode*>::iterator itr = _hovered.begin();
+         itr != _hovered.end(); ++itr) {
+        toUnHover.insert(*itr);
+    }
+    if (picker.pick(mouseX, mouseY, hits)) {
+        TRACE("Picker hits: %d", hits.size());
+        for (osgEarth::IntersectionPicker::Hits::const_iterator hitr = hits.begin();
+             hitr != hits.end(); ++hitr) {
+            osgEarth::Annotation::FeatureNode *anno =
+                picker.getNode<osgEarth::Annotation::FeatureNode>(*hitr);
+            if (anno != NULL) {
+                TRACE("Hit FeatureNode: %p", anno);
+                if (_hovered.find(anno) == _hovered.end()) {
+                    _hovered.insert(anno);
+                    anno->setDecoration("hover");
+                    _needsRedraw = true;
+                }
+                toUnHover.erase(anno);
+            }
+        }
+#if 0
+        writeScene("/tmp/test.osgt");
+#endif
+    }
+    for (std::set<osgEarth::Annotation::FeatureNode *>::iterator itr = toUnHover.begin();
+         itr != toUnHover.end(); ++itr) {
+        _hovered.erase(*itr);
+        (*itr)->clearDecoration();
+        _needsRedraw = true;
+    }
+}
+#endif
+
+#ifdef USE_RTT_PICKER
+void Renderer::hoverPlaceNode(int x, int y, bool invertY)
+{
+    if (!_placeNodes.valid()) {
+        TRACE("No place nodes");
+        return;
+    }
+    return;
+
+    float mouseX = (float)x;
+    float mouseY = (float)y;
+    if (invertY) {
+        mouseY = ((float)_windowHeight - mouseY);
+    }
+    if (_picker->pick(_viewer.get(), mouseX, mouseY)) {
+        TRACE("Hover pick queued: %g %g", mouseX, mouseY);
+    } else {
+        TRACE("Failed to queue pick: %g %g", mouseX, mouseY);
+    }
+}
+
+void Renderer::deletePlaceNode(int x, int y, bool invertY)
+{
+    if (!_placeNodes.valid()) {
+        TRACE("No place nodes");
+        return;
+    }
+    return;
+
+    osg::ref_ptr<osgEarth::Util::RTTPicker> picker = new osgEarth::Util::RTTPicker;
+    picker->addChild(_placeNodes.get());
+    osg::ref_ptr<DeleteCallback> callback = new DeleteCallback(this);
+    float mouseX = (float)x;
+    float mouseY = (float)y;
+    if (invertY) {
+        mouseY = ((float)_windowHeight - mouseY);
+    }
+    if (picker->pick(_viewer.get(), mouseX, mouseY, callback)) {
+        TRACE("Delete pick queued: %g %g", mouseX, mouseY);
+    }
+}
+#else
+void Renderer::hoverPlaceNode(int x, int y, bool invertY)
+{
+    if (!_placeNodes.valid()) {
+        TRACE("No place nodes");
+        return;
+    }
+
+    osgEarth::IntersectionPicker picker(_viewer.get(), _placeNodes.get());
+    osgEarth::IntersectionPicker::Hits hits;
+    float mouseX = (float)x;
+    float mouseY = (float)y;
+    if (invertY) {
+        mouseY = ((float)_windowHeight - mouseY);
+    }
+    std::set<osgEarth::Annotation::AnnotationNode*> toUnHover;
+    for (std::set<osgEarth::Annotation::AnnotationNode*>::iterator itr = _hovered.begin();
+         itr != _hovered.end(); ++itr) {
+        toUnHover.insert(*itr);
+    }
+    if (picker.pick(mouseX, mouseY, hits)) {
+        TRACE("Picker hits: %d", hits.size());
+        for (osgEarth::IntersectionPicker::Hits::const_iterator hitr = hits.begin();
+             hitr != hits.end(); ++hitr) {
+            TRACE("Hit: node %p drawable %p idx %d", picker.getNode<osg::Node>(*hitr), hitr->drawable.get(), hitr->primitiveIndex);
+            osgEarth::Annotation::AnnotationNode *anno =
+                picker.getNode<osgEarth::Annotation::AnnotationNode>(*hitr);
+            if (anno != NULL && anno->getDecoration().empty()) {
+                TRACE("Hit AnnotationNode: %p", anno);
+                if (_hovered.find(anno) == _hovered.end()) {
+                    _hovered.insert(anno);
+                    anno->setDecoration("hover");
+                    _needsRedraw = true;
+                }
+                toUnHover.erase(anno);
+            }
+        }
+#if 0
+        writeScene("/tmp/test.osgt");
+#endif
+    }
+    for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = toUnHover.begin();
+         itr != toUnHover.end(); ++itr) {
+        _hovered.erase(*itr);
+        (*itr)->clearDecoration();
+        _needsRedraw = true;
+    }
+}
+
+void Renderer::deletePlaceNode(int x, int y, bool invertY)
+{
+    if (!_placeNodes.valid()) {
+        TRACE("No place nodes");
+        return;
+    }
+    osgEarth::IntersectionPicker picker(_viewer.get(), _placeNodes.get());
+    osgEarth::IntersectionPicker::Hits hits;
+    float mouseX = (float)x;
+    float mouseY = (float)y;
+    if (invertY) {
+        mouseY = ((float)_windowHeight - mouseY);
+    }
+    if (picker.pick(mouseX, mouseY, hits)) {
+        TRACE("Picker hit!");
+        // prevent multiple hits on the same instance
+        std::set<osgEarth::Annotation::AnnotationNode *> fired;
+        for (osgEarth::IntersectionPicker::Hits::const_iterator hitr = hits.begin();
+             hitr != hits.end(); ++hitr) {
+            osgEarth::Annotation::AnnotationNode *anno =
+                picker.getNode<osgEarth::Annotation::AnnotationNode>(*hitr);
+            if (anno != NULL && fired.find(anno) == fired.end()) {
+                fired.insert(anno);
+                _needsRedraw = true;
+            }
+        }
+        for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = fired.begin();
+             itr != fired.end(); ++itr) {
+            (*itr)->clearDecoration();
+            if (_hovered.find(*itr) != _hovered.end()) {
+                _hovered.erase(*itr);
+            }
+            if (_selected.find(*itr) != _selected.end()) {
+                _selected.erase(*itr);
+            }
+           _placeNodes->removeChild(*itr);
+        }
+    } else {
+        TRACE("NO Picker hits");
+    }
+#if 0
+    writeScene("/tmp/test.osg");
+#endif
+}
+#endif
+
+void Renderer::addModelLayer(const char *name,
+                             osgEarth::ModelSourceOptions& opts,
+                             unsigned int pos,
+                             bool enableCache,
+                             bool lighting,
+                             bool visible,
+                             bool terrainPatch)
+{
+    if (!_map.valid()) {
+        ERROR("No map");
+        return;
+    }
+    TRACE("layer: %s", name);
+    osgEarth::ModelLayerOptions layerOpts(name, opts);
+    if (!enableCache) {
+        TRACE("Disabling cache for layer %s", name);
+        layerOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
+    }
+    if (!visible) {
+        layerOpts.visible() = false;
+    }
+    layerOpts.lightingEnabled() = lighting;
+    if (terrainPatch) {
+        layerOpts.terrainPatch() = true;
+    }
+    osgEarth::ModelLayer *layer = new osgEarth::ModelLayer(layerOpts);
+    if (pos < (unsigned int)_map->getNumModelLayers()) {
+        _map->insertModelLayer(layer, pos);
+    } else {
+        _map->addModelLayer(layer);
+    }
+    _needsRedraw = true;
+}
+
+void Renderer::removeModelLayer(const char *name)
+{
+    if (!_map.valid()) {
+        ERROR("No map");
+        return;
+    }
+    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
+    if (layer != NULL) {
+        _map->removeModelLayer(layer);
+        _needsRedraw = true;
+    } else {
+        TRACE("Model layer not found: %s", name);
+    }
+}
+
+void Renderer::moveModelLayer(const char *name, unsigned int pos)
+{
+    if (!_map.valid()) {
+        ERROR("No map");
+        return;
+    }
+    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
+    if (layer != NULL) {
+        _map->moveModelLayer(layer, pos);
+        _needsRedraw = true;
+    } else {
+        TRACE("Model layer not found: %s", name);
+    }
+}
+
+void Renderer::setModelLayerOpacity(const char *name, double opacity)
+{
+    if (!_map.valid()) {
+        ERROR("No map");
+        return;
+    }
+    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
+    if (layer != NULL) {
+        layer->setOpacity(opacity);
+        _needsRedraw = true;
+    } else {
+        TRACE("Model layer not found: %s", name);
+    }
+}
+#if 0
+void Renderer::setModelLayerVisibleRange(const char *name, float min, float max)
+{
+    if (!_map.valid()) {
+        ERROR("No map");
+        return;
+    }
+    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
+    if (layer != NULL) {
+        layer->minVisibleRange(min);
+        layer->maxVisibleRange(max);
+        _needsRedraw = true;
+    } else {
+        TRACE("Model layer not found: %s", name);
+    }
+}
+#endif
+void Renderer::setModelLayerVisibility(const char *name, bool state)
+{
+    if (!_map.valid()) {
+        ERROR("No map");
+        return;
+    }
+    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
+    if (layer != NULL) {
+        layer->setVisible(state);
+        _needsRedraw = true;
+    } else {
+        TRACE("Model layer not found: %s", name);
+    }
+}
+
+void Renderer::setLODScale(float scale)
+{
+    if (!_viewer.valid())
+        return;
+
+    TRACE("Setting LOD scale to %g", scale);
+    _viewer->getCamera()->setLODScale(scale);
+    _needsRedraw = true;
+}
+
+/**
+ * \brief Resize the render window (image size for renderings)
+ */
+void Renderer::setWindowSize(int width, int height)
+{
+    if (_windowWidth == width &&
+        _windowHeight == height) {
+        TRACE("No change");
+        return;
+    }
+
+    TRACE("Setting window size to %dx%d", width, height);
+
+    double origBitrate = getMaximumBitrate();
+
+    _windowWidth = width;
+    _windowHeight = height;
+
+    setMaximumBitrate(origBitrate);
+
+    if (_viewer.valid()) {
+#ifdef USE_OFFSCREEN_RENDERING
+#ifdef USE_PBUFFER
+        osg::ref_ptr<osg::GraphicsContext> pbuffer;
+        osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
+        traits->x = 0;
+        traits->y = 0;
+        traits->width = _windowWidth;
+        traits->height = _windowHeight;
+        traits->red = 8;
+        traits->green = 8;
+        traits->blue = 8;
+        traits->alpha = 8;
+        traits->windowDecoration = false;
+        traits->pbuffer = true;
+        traits->doubleBuffer = true;
+        traits->sharedContext = 0;
+
+        pbuffer = osg::GraphicsContext::createGraphicsContext(traits.get());
+        if (pbuffer.valid()) {
+            TRACE("Pixel buffer has been created successfully.");
+        } else {
+            ERROR("Pixel buffer has not been created successfully.");
+        }
+        osg::Camera *camera = new osg::Camera;
+        camera->setGraphicsContext(pbuffer.get());
+        //camera->getOrCreateStateSet()->setGlobalDefaults();
+        camera->setViewport(new osg::Viewport(0, 0, _windowWidth, _windowHeight));
+        GLenum buffer = pbuffer->getTraits()->doubleBuffer ? GL_BACK : GL_FRONT;
+        camera->setDrawBuffer(buffer);
+        camera->setReadBuffer(buffer);
+        camera->setFinalDrawCallback(_captureCallback.get());
+        _viewer->addSlave(camera, osg::Matrixd(), osg::Matrixd());
+        _viewer->realize();
+#else
+        if (_captureCallback.valid()) {
+            _captureCallback->getTexture()->setTextureSize(_windowWidth, _windowHeight);
+        }
+        osgViewer::ViewerBase::Windows windows;
+        _viewer->getWindows(windows);
+        if (windows.size() == 1) {
+            windows[0]->setWindowRectangle(0, 0, _windowWidth, _windowHeight);
+        } else {
+            ERROR("Num windows: %lu", windows.size());
+        }
+#endif
+#else
+        osgViewer::ViewerBase::Windows windows;
+        _viewer->getWindows(windows);
+#if 1
+        for (osgViewer::Viewer::Windows::iterator itr = windows.begin();
+             itr != windows.end(); ++itr) {
+            osgViewer::GraphicsWindow *window = *itr;
+            window->setWindowRectangle(0, 0, _windowWidth, _windowHeight);
+            //window->grabFocusIfPointerInWindow();
+        }
+        //_viewer->getCamera()->setViewport(new osg::Viewport(0, 0, _windowWidth, _windowHeight));
+#else
+        if (windows.size() == 1) {
+            windows[0]->setWindowRectangle(0, 0, _windowWidth, _windowHeight);
+        } else {
+            ERROR("Num windows: %lu", windows.size());
+        }
+#endif
+#endif
+        // HACK: Without this, the mouse coordinate mapping uses the old size
+        // for 1 frame.
+        assert(_viewer->getEventQueue() != NULL);
+        //TRACE("Window EventQueue: %p", getEventQueue());
+        //TRACE("Viewer EventQueue: %p", _viewer->getEventQueue());
+        _viewer->getEventQueue()->windowResize(0, 0, _windowWidth, _windowHeight);
+        _needsRedraw = true;
+    }
+}
+
+/**
+ * \brief Set the orientation of the camera from a quaternion
+ * rotation
+ *
+ * \param[in] quat A quaternion with scalar part first: w,x,y,z
+ * \param[in] absolute Is rotation absolute or relative?
+ */
+void Renderer::setCameraOrientation(const double quat[4], bool absolute)
+{
+    if (_manipulator.valid()) {
+        _manipulator->setRotation(osg::Quat(quat[1], quat[2], quat[3], quat[0]));
+        _needsRedraw = true;
+    }
+}
+
+/**
+ * \brief Reset pan, zoom, clipping planes and optionally rotation
+ *
+ * \param[in] resetOrientation Reset the camera rotation/orientation also
+ */
+void Renderer::resetCamera(bool resetOrientation)
+{
+    TRACE("Enter: resetOrientation=%d", resetOrientation ? 1 : 0);
+    if (_viewer.valid()) {
+        _viewer->home();
+        _needsRedraw = true;
+    }
+}
+
+/**
+ * \brief Perform a 2D translation of the camera
+ *
+ * x,y pan amount are specified as signed absolute pan amount in viewport
+ * units -- i.e. 0 is no pan, .5 is half the viewport, 2 is twice the viewport,
+ * etc.
+ *
+ * \param[in] x Viewport coordinate horizontal panning (positive number pans
+ * camera left, object right)
+ * \param[in] y Viewport coordinate vertical panning (positive number pans 
+ * camera up, object down)
+ */
+void Renderer::panCamera(double x, double y)
+{
+    TRACE("Enter: %g %g", x, y);
+
+    if (_manipulator.valid()) {
+        // Wants mouse delta x,y in normalized screen coords
+        _manipulator->pan(x, y);
+        _needsRedraw = true;
+    }
+}
+
+void Renderer::rotateCamera(double x, double y)
+{
+    TRACE("Enter: %g %g", x, y);
+
+    if (_manipulator.valid()) {
+        _manipulator->rotate(x, y);
+        _needsRedraw = true;
+    }
+}
+
+/**
+ * \brief Dolly camera or set orthographic scaling based on camera type
+ *
+ * \param[in] y Mouse y coordinate in normalized screen coords
+ */
+void Renderer::zoomCamera(double y)
+{
+    TRACE("Enter: y: %g", y);
+
+    if (_manipulator.valid()) {
+        // +y = zoom out, -y = zoom in
+        TRACE("camDist: %g", _manipulator->getDistance());
+#ifdef LIMIT_ZOOM_BY_MAP_SCALE
+        if ((_mapScale < 0.0 && y > 0.0) ||
+            (_mapScale < 0.1 && y < 0.0) ||
+            (_mapScale > 40000.0 && y > 0.0))
+            return;
+#endif
+#if 1
+       _manipulator->zoom(0, y);
+#else
+        double dist = _manipulator->getDistance();
+        dist *= (1.0 + y);
+        _manipulator->setDistance(dist);
+#endif
+        _needsRedraw = true;
+    }
+}
+
+bool Renderer::getImageLayerExtent(const char *name, osgEarth::GeoExtent &ext)
+{
+    if (!_map.valid()) {
+        ERROR("No map");
+        return false;
+    }
+    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
+    if (layer != NULL) {
+        osgEarth::TileSource *ts = layer->getTileSource();
+        if (ts != NULL) {
+            ext = ts->getDataExtentsUnion();
+            if (!ext.isValid() && ts->getProfile() != NULL) {
+                ext = ts->getProfile()->getExtent();
+            }
+            TRACE("Image layer %s srs: %s extent: %g %g %g %g",
+                  name, ext.getSRS() ? ext.getSRS()->getName().c_str() : "nil",
+                  ext.xMin(), ext.yMin(), ext.xMax(), ext.yMax());
+            return true;
+        }
+    } else {
+        TRACE("Image layer not found: %s", name);
+    }
+    return false;
+}
+
+bool Renderer::getElevationLayerExtent(const char *name, osgEarth::GeoExtent &ext)
+{
+    if (!_map.valid()) {
+        ERROR("No map");
+        return false;
+    }
+    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
+    if (layer != NULL) {
+        osgEarth::TileSource *ts = layer->getTileSource();
+        if (ts != NULL) {
+            ext = ts->getDataExtentsUnion();
+            if (!ext.isValid() && ts->getProfile() != NULL) {
+                ext = ts->getProfile()->getExtent();
+            }
+            TRACE("Elevation Layer %s srs: %s extent: %g %g %g %g",
+                  name, ext.getSRS() ? ext.getSRS()->getName().c_str() : "nil",
+                  ext.xMin(), ext.yMin(), ext.xMax(), ext.yMax());
+            return true;
+        }
+    } else {
+        TRACE("Elevation layer not found: %s", name);
+    }
+    return false;
+}
+
+bool Renderer::getTerrainMaskLayerExtent(const char *name, osgEarth::GeoExtent &ext)
+{
+    if (!_map.valid()) {
+        ERROR("No map");
+        return false;
+    }
+    osgEarth::MaskLayer *layer = getTerrainMaskLayerByName(name);
+    if (layer != NULL) {
+        const osgEarth::SpatialReference *srs = getMapSRS();
+        osgEarth::GeoExtent newExt(srs);
+        osg::Vec3dArray *polypts = layer->getOrCreateMaskBoundary(1.0f, srs, NULL);
+        if (polypts) {
+            for (size_t i = 0; i < polypts->size(); i++) {
+                osg::Vec3d pt = (*polypts)[i];
+                newExt.expandToInclude(pt);
+            }
+        }
+        if (!newExt.isValid()) {
+            ERROR("Couldn't find extent of layer %s", name);
+            return false;
+        }
+        ext = newExt;
+        TRACE("Mask Layer %s srs: %s extent: %g %g %g %g",
+              name, ext.getSRS() ? ext.getSRS()->getName().c_str() : "nil",
+              ext.xMin(), ext.yMin(), ext.xMax(), ext.yMax());
+        return true;
+    } else {
+        TRACE("Terrain mask layer not found: %s", name);
+    }
+    return false;
+}
+
+bool Renderer::getModelLayerExtent(const char *name, osgEarth::GeoExtent &ext)
+{
+    if (!_map.valid()) {
+        ERROR("No map");
+        return false;
+    }
+    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
+    if (layer != NULL) {
+        osgEarth::ModelSource *ms = layer->getModelSource();
+        if (ms != NULL) {
+            osgEarth::DataExtentList& dataExtents = ms->getDataExtents();
+            if (dataExtents.size() > 0) {
+                ext = dataExtents[0];
+                for (unsigned int i = 1; i < dataExtents.size(); i++) {
+                    ext.expandToInclude(dataExtents[i]);
+                }
+            }
+            if (!ext.isValid()) {
+                ERROR("Couldn't find extent of layer %s", name);
+                return false;
+            }
+            TRACE("Model Layer %s srs: %s extent: %g %g %g %g",
+                  name, ext.getSRS() ? ext.getSRS()->getName().c_str() : "nil",
+                  ext.xMin(), ext.yMin(), ext.xMax(), ext.yMax());
+            return true;
+        }
+    } else {
+        TRACE("Model layer not found: %s", name);
+    }
+    return false;
+}
+
+/**
+ * \brief Set view to fit a bounding rectangle
+ *
+ * A camera distance is chosen such that the extent rectangle
+ * will fit in the vertical field of view.
+ *
+ * \param ext input GeoExtent
+ * \param durationSecs Animation duration for the view change.
+ */
+void Renderer::setViewpointFromExtent(const osgEarth::GeoExtent& ext,
+                                      double durationSecs)
+{
+    setViewpointFromRect(ext.xMin(), ext.yMin(), ext.xMax(), ext.yMax(),
+                         ext.getSRS(), durationSecs);
+}
+
+/**
+ * \brief Set view to fit a bounding rectangle
+ *
+ * A camera distance is chosen such that the extent rectangle
+ * will fit in the vertical field of view.
+ *
+ * \param xmin Minimum X coord in the given SRS
+ * \param ymin Minimum Y coord in the given SRS
+ * \param xmax Maximum X coord in the given SRS
+ * \param ymax Maximum Y coord in the given SRS
+ * \param srs Optional SRS of bounding points.  If omitted, wgs84 is assumed.
+ * \param durationSecs Animation duration for the view change.
+ */
+void Renderer::setViewpointFromRect(double xmin, double ymin,
+                                    double xmax, double ymax,
+                                    const osgEarth::SpatialReference *srs,
+                                    double durationSecs)
+{
+    if (!_viewer.valid() || !_manipulator.valid() || getMapSRS() == NULL)
+        return;
+
+    double x = 0.0, y = 0.0, distance = 0.0;
+    double x1 = xmin, y1 = ymin, x2 = xmax, y2 = ymax;
+    osgEarth::Units distanceUnits = getMapSRS()->getUnits();
+    if (getMapSRS()->isProjected() && !getMapSRS()->isPlateCarre()) {
+        TRACE("Projected");
+        osg::ref_ptr<const osgEarth::SpatialReference> fromSRS = 
+            srs ? srs : osgEarth::SpatialReference::create("wgs84");
+        fromSRS->transformExtentToMBR(getMapSRS(), x1, y1, x2, y2);
+        double height = y2 - y1;
+        double fovy, aspect, near, far;
+        _viewer->getCamera()->getProjectionMatrixAsPerspective(fovy, aspect, near, far);
+        distance = height / (2. * tan(osg::DegreesToRadians(fovy)/2.));
+        TRACE("fov: %g a: %g n: %g f: %g (%g, %g, %g, %g)", fovy, aspect, near, far, x1, y1, x2, y2);
+        x = x1 + (x2 - x1)/2.0;
+        y = y1 + (y2 - y1)/2.0;
+        TRACE("x: %g y: %g, dist: %g", x, y, distance);
+    } else if (getMapSRS()->isGeographic() && !getMapSRS()->isPlateCarre()) {
+        // World coords are ECEF
+        TRACE("Geocentric");
+        osg::ref_ptr<const osgEarth::SpatialReference> fromSRS =
+            srs ? srs : osgEarth::SpatialReference::create("wgs84");
+        fromSRS->transformExtentToMBR(getMapSRS(), x1, y1, x2, y2);
+        TRACE("(%g, %g, %g, %g)", x1, y1, x2, y2);
+        // These are angular units
+        x = x1 + (x2 - x1)/2.0;
+        y = y1 + (y2 - y1)/2.0;
+        // These will be ECEF
+        osg::Vec3d world1, world2, world3, world4;
+        // bottom
+        getMapSRS()->transformToWorld(osg::Vec3d(x, y1, 0), world1);
+        // top
+        getMapSRS()->transformToWorld(osg::Vec3d(x, y2, 0), world2);
+        // Focal point on surface
+        getMapSRS()->transformToWorld(osg::Vec3d(x, y, 0), world3);
+        // point on line between top and bottom points
+        world4 = world1 + (world2 - world1)/2.0;
+        TRACE("world1: %g,%g,%g world2: %g,%g,%g",
+             world1.x(), world1.y(), world1.z(),
+             world2.x(), world2.y(), world2.z());
+        TRACE("world3: %g,%g,%g world4: %g,%g,%g",
+             world3.x(), world3.y(), world3.z(),
+             world4.x(), world4.y(), world4.z());
+        double height = (world2 - world1).length();
+        double fovy, aspect, near, far;
+        _viewer->getCamera()->getProjectionMatrixAsPerspective(fovy, aspect, near, far);
+        distance = height / (2. * tan(osg::DegreesToRadians(fovy)/2.));
+        distance -= (world4 - world3).length();
+        if (distance < 0.0) distance = 0.0;
+        distanceUnits = osgEarth::Units::METERS;
+        TRACE("fov: %g a: %g n: %g f: %g (%g, %g, %g, %g)", fovy, aspect, near, far, x1, y1, x2, y2);
+        TRACE("x: %g y: %g, dist: %g", x, y, distance);
+    } else {
+        assert(getMapSRS()->isPlateCarre());
+        ERROR("Plate Carree not supported");
+        return;
+    }
+    TRACE("Map units: %d", getMapSRS()->getUnits().getType());
+    osgEarth::Viewpoint vpt;
+    vpt.focalPoint()->set(getMapSRS(), x, y, 0.0, osgEarth::ALTMODE_ABSOLUTE);
+    vpt.range()->set(distance, distanceUnits);
+    vpt.heading()->set(0.0, osgEarth::Units::DEGREES);
+    vpt.pitch()->set(-90.0, osgEarth::Units::DEGREES);
+
+    _manipulator->setViewpoint(vpt, durationSecs);
+    _needsRedraw = true;
+}
+
+double Renderer::getMaxDistanceFromExtent(const osgEarth::GeoExtent& extent)
+{
+    if (!_viewer.valid() || extent.getSRS() == NULL)
+        return 2.0e7;
+
+    double x = 0.0, y = 0.0, distance = 0.0;
+    double x1 = extent.xMin(), y1 = extent.yMin(), x2 = extent.xMax(), y2 = extent.yMax();
+    osgEarth::Units distanceUnits = extent.getSRS()->getUnits();
+    if (extent.getSRS()->isProjected() && !extent.getSRS()->isPlateCarre()) {
+        TRACE("Projected");
+        double height = y2 - y1;
+        double fovy, aspect, near, far;
+        _viewer->getCamera()->getProjectionMatrixAsPerspective(fovy, aspect, near, far);
+        distance = height / (2. * tan(osg::DegreesToRadians(fovy)/2.));
+        TRACE("fov: %g a: %g n: %g f: %g (%g, %g, %g, %g)", fovy, aspect, near, far, x1, y1, x2, y2);
+        x = x1 + (x2 - x1)/2.0;
+        y = y1 + (y2 - y1)/2.0;
+        TRACE("x: %g y: %g, dist: %g", x, y, distance);
+    } else if (extent.getSRS()->isGeographic() && !extent.getSRS()->isPlateCarre()) {
+        // World coords are ECEF
+        TRACE("Geocentric");
+        TRACE("(%g, %g, %g, %g)", x1, y1, x2, y2);
+        // These are angular units
+        x = x1 + (x2 - x1)/2.0;
+        y = y1 + (y2 - y1)/2.0;
+        // These will be ECEF
+        osg::Vec3d world1, world2, world3, world4;
+        // bottom
+        getMapSRS()->transformToWorld(osg::Vec3d(x, y1, 0), world1);
+        // top
+        getMapSRS()->transformToWorld(osg::Vec3d(x, y2, 0), world2);
+        // Focal point on surface
+        getMapSRS()->transformToWorld(osg::Vec3d(x, y, 0), world3);
+        // point on line between top and bottom points
+        world4 = world1 + (world2 - world1)/2.0;
+        double height = (world2 - world1).length();
+        double fovy, aspect, near, far;
+        _viewer->getCamera()->getProjectionMatrixAsPerspective(fovy, aspect, near, far);
+        distance = height / (2. * tan(osg::DegreesToRadians(fovy)/2.));
+        distance -= (world4 - world3).length();
+        if (distance < 0.0) distance = 0.0;
+        distanceUnits = osgEarth::Units::METERS;
+        TRACE("fov: %g a: %g n: %g f: %g (%g, %g, %g, %g)", fovy, aspect, near, far, x1, y1, x2, y2);
+        TRACE("x: %g y: %g, dist: %g", x, y, distance);
+    } else {
+        assert(extent.getSRS()->isPlateCarre());
+        ERROR("Plate Carree not supported");
+    }
+
+    return distance;
+}
+
+/**
+ * \brief Dolly camera to set distance from focal point
+ *
+ * \param[in] dist distance in world coordinate units (typically meters)
+ */
+void Renderer::setCameraDistance(double dist)
+{
+    TRACE("Enter: dist: %g", dist);
+
+    if (_manipulator.valid()) {
+        TRACE("camDist: %g", _manipulator->getDistance());
+
+        _manipulator->setDistance(dist);
+
+        _needsRedraw = true;
+    }
+}
+
+void Renderer::keyPress(int key)
+{
+    osgGA::EventQueue *queue = getEventQueue();
+    if (queue != NULL) {
+        queue->keyPress(key);
+        _needsRedraw = true;
+    }
+}
+
+void Renderer::keyRelease(int key)
+{
+    osgGA::EventQueue *queue = getEventQueue();
+    if (queue != NULL) {
+        queue->keyRelease(key);
+        _needsRedraw = true;
+    }
+}
+
+void Renderer::setThrowingEnabled(bool state)
+{
+    if (_manipulator.valid()) {
+        _manipulator->getSettings()->setThrowingEnabled(state);
+    }
+}
+
+void Renderer::mouseDoubleClick(int button, double x, double y)
+{
+    osgGA::EventQueue *queue = getEventQueue();
+    if (queue != NULL) {
+        queue->mouseDoubleButtonPress((float)x, (float)y, button);
+        _needsRedraw = true;
+    }
+}
+
+void Renderer::mouseClick(int button, double x, double y)
+{
+    osgGA::EventQueue *queue = getEventQueue();
+    if (queue != NULL) {
+        queue->mouseButtonPress((float)x, (float)y, button);
+        _needsRedraw = true;
+    }
+}
+
+void Renderer::mouseDrag(int button, double x, double y)
+{
+    osgGA::EventQueue *queue = getEventQueue();
+    if (queue != NULL) {
+        queue->mouseMotion((float)x, (float)y);
+        _needsRedraw = true;
+    }
+}
+
+void Renderer::mouseRelease(int button, double x, double y)
+{
+    osgGA::EventQueue *queue = getEventQueue();
+    if (queue != NULL) {
+        queue->mouseButtonRelease((float)x, (float)y, button);
+        _needsRedraw = true;
+    }
+}
+
+void Renderer::mouseMotion(double x, double y)
+{
+#if 1
+    osgGA::EventQueue *queue = getEventQueue();
+    if (queue != NULL) {
+        queue->mouseMotion((float)x, (float)y);
+        _needsRedraw = true;
+    }
+#else
+    if (_mouseCoordsTool.valid()) {
+        if (_viewer.valid() && _coordsCallback.valid()) {
+            osgEarth::GeoPoint mapPt;
+            if (mapMouseCoords((float)x, (float)y, mapPt)) {
+                _coordsCallback->set(mapPt, _viewer->asView(), _mapNode);
+            } else {
+                _coordsCallback->reset(_viewer->asView(), _mapNode);
+            }
+            _needsRedraw = true;
+        }
+    }
+#endif
+}
+
+void Renderer::mouseScroll(int direction)
+{
+    osgGA::EventQueue *queue = getEventQueue();
+    if (queue != NULL) {
+        queue->mouseScroll((direction > 0 ? osgGA::GUIEventAdapter::SCROLL_UP : osgGA::GUIEventAdapter::SCROLL_DOWN));
+        _needsRedraw = true;
+    }
+}
+
+/**
+ * \brief Set the RGB background color to render into the image
+ */
+void Renderer::setBackgroundColor(float color[3])
+{
+    _bgColor[0] = color[0];
+    _bgColor[1] = color[1];
+    _bgColor[2] = color[2];
+
+    if (_viewer.valid()) {
+        _viewer->getCamera()->setClearColor(osg::Vec4(color[0], color[1], color[2], 1));
+
+        _needsRedraw = true;
+    }
+}
+
+/**
+ * \brief Sets flag to trigger rendering next time render() is called
+ */
+void Renderer::eventuallyRender()
+{
+    _needsRedraw = true;
+}
+
+/**
+ * \brief Get a timeout for select()
+ * 
+ * If the paging thread is idle, returns <0 indicating that the
+ * select call can block until data is available.  Otherwise,
+ * if the frame render time was faster than the target frame 
+ * rate, return the remaining frame time.
+ */
+void Renderer::getTimeout(struct timeval *tv)
+{
+    if (!checkNeedToDoFrame()) {
+        // <0 means no timeout, block until socket has data
+        // If _idleTimeout is positive, server will disconnect after timeout
+        tv->tv_sec = _idleTimeout;
+        tv->tv_usec = 0L;
+    } else if (_lastFrameTime < _minFrameTime) {
+        tv->tv_sec = 0L;
+        tv->tv_usec = (long)1.0e6*(_minFrameTime - _lastFrameTime);
+    } else {
+        // No timeout (poll)
+        tv->tv_sec = tv->tv_usec = 0L;
+    }
+}
+
+/**
+ * \brief Check if paging thread is quiescent
+ */
+bool Renderer::isPagerIdle()
+{
+    if (!_viewer.valid())
+        return true;
+    else
+        return (!_viewer->getDatabasePager()->requiresUpdateSceneGraph() &&
+                !_viewer->getDatabasePager()->getRequestsInProgress());
+}
+
+/**
+ * \brief Check is frame call is necessary to render and/or update
+ * in response to events or timed actions
+ */
+bool Renderer::checkNeedToDoFrame()
+{
+    return (_needsRedraw || _pickPending ||
+            (_viewer.valid() && _viewer->checkNeedToDoFrame()));
+}
+
+/**
+ * \brief MapNode event phase
+ *
+ * This is called by the MapNode's event callback during the event
+ * traversal of the viewer
+ */
+void Renderer::mapNodeUpdate()
+{
+    computeMapScale();
+}
+
+void Renderer::markFrameStart()
+{
+    _startFrameTime = osg::Timer::instance()->tick();
+}
+
+void Renderer::markFrameEnd()
+{
+    osg::Timer_t endFrameTick = osg::Timer::instance()->tick();
+    _lastFrameTime = osg::Timer::instance()->delta_s(_startFrameTime, endFrameTick);
+    if (_lastFrameTime > _minFrameTime) {
+        FRAME("BROKE FRAME by %.2f msec", (_lastFrameTime - _minFrameTime)*1000.0f);
+    } else {
+        FRAME("Frame time: %.2f msec", _lastFrameTime*1000.0f);
+    }
+#ifdef USE_THROTTLING_SLEEP
+    if (_lastFrameTime < _minFrameTime) {
+        FRAME("Sleeping for %.2f msec", (_minFrameTime - _lastFrameTime)*1000.0f);
+        OpenThreads::Thread::microSleep(static_cast<unsigned int>(1000000.0*(_minFrameTime - _lastFrameTime)));
+    }
+#endif
+}
+
+/**
+ * \brief Cause the rendering to render a new image if needed
+ *
+ * The _needsRedraw flag indicates if a state change has occured since
+ * the last rendered frame
+ */
+bool Renderer::render()
+{
+    if (_viewer.valid() && checkNeedToDoFrame()) {
+        FRAME("Enter needsRedraw=%d",  _needsRedraw ? 1 : 0);
+        _renderStartTime = osg::Timer::instance()->tick();
+        FRAME("Before frame()");
+        _viewer->frame();
+        FRAME("After frame()");
+        _renderStopTime = osg::Timer::instance()->tick();
+        _renderTime = osg::Timer::instance()->delta_s(_renderStartTime, _renderStopTime);
+        FRAME("Render time: %g msec", _renderTime * 1000.0);
+#ifndef SLEEP_AFTER_QUEUE_FRAME
+        _lastFrameTime = _renderTime;
+#ifdef USE_THROTTLING_SLEEP
+        if (_lastFrameTime < _minFrameTime) {
+            FRAME("Sleeping for %.2f msec", (_minFrameTime - _lastFrameTime)*1000.0f);
+            OpenThreads::Thread::microSleep(static_cast<unsigned int>(1.0e6*(_minFrameTime - _lastFrameTime)));
+        }
+#endif
+#endif
+#ifdef WANT_FRAME
+        if (_viewer->getViewerStats() != NULL) {
+            _viewer->getViewerStats()->report(std::cerr, _viewer->getViewerStats()->getLatestFrameNumber());
+        }
+#endif
+        _needsRedraw = false;
+        return true;
+    } else {
+        _renderStartTime = _renderStopTime = osg::Timer::instance()->tick();
+        _renderTime = 0;
+        return false;
+    }
+}
+
+/**
+ * \brief Read back the rendered framebuffer image
+ */
+osg::Image *Renderer::getRenderedFrame()
+{
+    if (_captureCallback.valid())
+        return _captureCallback->getImage();
+    else
+        return NULL;
+}
+
+void Renderer::setScaleBar(bool state)
+{
+    if (_scaleLabel.valid()) {
+        _scaleLabel->setVisible(state);
+    }
+    if (_scaleBar.valid()) {
+        _scaleBar->setVisible(state);
+    }
+    _needsRedraw = true;
+}
+
+void Renderer::setScaleBarUnits(ScaleBarUnits units)
+{
+    _scaleBarUnits = units;
+    _needsRedraw = true;
+}
+
+/**
+ * \brief Compute the scale ratio of the map based on a horizontal center line
+ *
+ * The idea here is to take 2 screen points on a horizontal line in the center
+ * of the screen and convert to lat/long.  The lat/long coordinates are then
+ * used to compute the great circle distance (assuming spherical earth) between 
+ * the points.
+ *
+ * We could use local projected map coordinates for the distance computation,
+ * which would be faster; however, this would not show e.g. the change in 
+ * scale at different latitudes
+ */
+double Renderer::computeMapScale()
+{
+    if (!_scaleLabel.valid() || !_scaleLabel->visible()) {
+        return -1.0;
+    }
+    if (!_mapNode.valid() || _mapNode->getTerrain() == NULL) {
+        ERROR("No map");
+        return -1.0;
+    }
+    if (!_viewer.valid()) {
+        ERROR("No viewer");
+        return -1.0;
+    }
+
+    double x, y;
+    double pixelWidth = _windowWidth * 0.1 * 2.0;
+    if (pixelWidth < 10)
+        pixelWidth = 10;
+    if (pixelWidth > 150)
+        pixelWidth = 150;
+    x = (double)(_windowWidth -1)/2.0 - pixelWidth / 2.0;
+    y = (double)(_windowHeight-1)/2.0;
+
+    osg::Vec3d world1, world2;
+    if (!_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), x, y, world1)) {
+        // off map
+        TRACE("Off map coords: %g %g", x, y);
+        _scaleLabel->setText("");
+        _scaleBar->setWidth(0);
+        return -1.0;
+    }
+    x += pixelWidth;
+    if (!_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), x, y, world2)) {
+        // off map
+        TRACE("Off map coords: %g %g", x, y);
+        _scaleLabel->setText("");
+        _scaleBar->setWidth(0);
+        return -1.0;
+    }
+
+#if 0
+    TRACE("w1: %g %g %g w2: %g %g %g",
+          world1.x(), world1.y(), world1.z(),
+          world2.x(), world2.y(), world2.z());
+#endif
+
+    double meters;
+    double radius = 6378137.0;
+    if (_mapNode->getMapSRS() &&
+        _mapNode->getMapSRS()->getEllipsoid()) {
+        radius = _mapNode->getMapSRS()->getEllipsoid()->getRadiusEquator();
+    }
+    if (!_map->isGeocentric() &&
+        _mapNode->getMapSRS() &&
+        _mapNode->getMapSRS()->isGeographic()) {
+        TRACE("Map is geographic");
+        // World cords are already lat/long
+        // Compute great circle distance
+        meters =
+            osgEarth::GeoMath::distance(world1, world2, _mapNode->getMapSRS());
+    } else if (_mapNode->getMapSRS()) {
+        // Get map coords in lat/long
+        osgEarth::GeoPoint mapPoint1, mapPoint2;
+        mapPoint1.fromWorld(_mapNode->getMapSRS(), world1);
+        mapPoint1.makeGeographic();
+        mapPoint2.fromWorld(_mapNode->getMapSRS(), world2);
+        mapPoint2.makeGeographic();
+        // Compute great circle distance
+        meters =
+            osgEarth::GeoMath::distance(osg::DegreesToRadians(mapPoint1.y()),
+                                        osg::DegreesToRadians(mapPoint1.x()),
+                                        osg::DegreesToRadians(mapPoint2.y()),
+                                        osg::DegreesToRadians(mapPoint2.x()),
+                                        radius);
+    } else {
+        // Assume geocentric?
+        ERROR("No map SRS");
+        _scaleLabel->setText("");
+        _scaleBar->setWidth(0);
+        return -1.0;
+    }
+
+    double scale = meters / pixelWidth;
+    // 1mi = 5280 feet
+    //double scaleMiles = scale / 1609.344; // International mile = 1609.344m
+    //double scaleNauticalMiles = scale / 1852.0; // nautical mile = 1852m
+    //double scaleUSSurveyMiles = scale / 1609.347218694; // US survey mile = 5280 US survey feet
+    //double scaleUSSurveyFeet = scale * 3937.0/1200.0; // US survey foot = 1200/3937 m
+#if 0
+    TRACE("m: %g px: %g m/px: %g", meters, pixelWidth, scale);
+#endif
+    _mapScale = scale;
+    switch (_scaleBarUnits) {
+    case UNITS_NAUTICAL_MILES: {
+        double nmi = meters / 1852.0;
+        scale = nmi / pixelWidth;
+        nmi = normalizeScaleNauticalMiles(nmi);
+        pixelWidth = nmi / scale;
+        if (_scaleLabel.valid()) {
+            _scaleLabel->setText(osgEarth::Stringify()
+                                 << nmi
+                                 << " nmi");
+        }
+    }
+        break;
+    case UNITS_US_SURVEY_FEET: {
+        double feet = meters * 3937.0/1200.0;
+        scale = feet / pixelWidth;
+        feet = normalizeScaleFeet(feet);
+        pixelWidth = feet / scale;
+        if (_scaleLabel.valid()) {
+            if (feet >= 5280) {
+                _scaleLabel->setText(osgEarth::Stringify()
+                                     << feet / 5280.0
+                                     << " miUS");
+             } else {
+                _scaleLabel->setText(osgEarth::Stringify()
+                                     << feet
+                                     << " ftUS");
+            }
+        }
+    }
+        break;
+    case UNITS_INTL_FEET: {
+        double feet = 5280.0 * meters / 1609.344;
+        scale = feet / pixelWidth;
+        feet = normalizeScaleFeet(feet);
+        pixelWidth = feet / scale;
+        if (_scaleLabel.valid()) {
+            if (feet >= 5280) {
+                _scaleLabel->setText(osgEarth::Stringify()
+                                     << feet / 5280.0
+                                     << " mi");
+            } else {
+                _scaleLabel->setText(osgEarth::Stringify()
+                                     << feet
+                                     << " ft");
+            }
+        }
+    }
+        break;
+    case UNITS_METERS:
+    default: {
+        meters = normalizeScaleMeters(meters);
+        pixelWidth = meters / scale;
+        if (_scaleLabel.valid()) {
+            if (meters >= 1000) {
+                _scaleLabel->setText(osgEarth::Stringify()
+                                     << meters / 1000.0
+                                     << " km");
+            } else {
+                _scaleLabel->setText(osgEarth::Stringify()
+                                     << meters
+                                     << " m");
+            }
+        }
+    }
+        break;
+    }
+    if (_scaleBar.valid()) {
+        _scaleBar->setWidth(pixelWidth);
+    }
+    return scale;
+}
+
+std::string Renderer::getCanonicalPath(const std::string& url) const
+{
+    std::string retStr;
+    std::string proto = osgDB::getServerProtocol(url);
+    if (proto.empty()) {
+        retStr = osgDB::getRealPath(url);
+        if (!osgDB::fileExists(retStr)) {
+            if (!url.empty() && url[0] != '/') {
+                // Relative URL, assume local://
+                std::ostringstream oss;
+                oss << getCacheDirectory() << "/" << url;
+                retStr = oss.str();
+            } else {
+                retStr = "";
+            }
+        }
+    } else if (proto == "local") {
+        TRACE("Local protocol: '%s'", getLocalFilePath(url).c_str());
+        std::ostringstream oss;
+        oss << getCacheDirectory() << "/" << getLocalFilePath(url);
+        retStr = oss.str();
+    } else if (proto == "file") {
+        TRACE("File: '/%s'", osgDB::getServerFileName(url).c_str());
+        std::ostringstream oss;
+        oss << "/" <<  osgDB::getServerFileName(url);
+        retStr = oss.str();
+    } else if (proto == "idata") {
+        std::string fileName = osgDB::getServerFileName(url);
+        TRACE("IData protocol: coll: '%s', '%s'", osgDB::getServerAddress(url).c_str(), fileName.c_str());
+        int collection = atoi(osgDB::getServerAddress(url).c_str());
+        {
+            std::ostringstream oss;
+            oss << getCacheDirectory() << "/" << fileName;
+            retStr = oss.str();
+            std::string dirPath = osgDB::getFilePath(retStr);
+            osgDB::makeDirectory(dirPath);
+        }
+        // If this is a shape file, get auxiliary files (e.g. .dbf, .shx, etc)
+        if (osgDB::getFileExtension(fileName) == "shp") {
+            // Check for files with same basename
+            std::vector<DirectoryItem> items;
+            std::string path = osgDB::getFilePath(fileName);
+            getContents(collection, path.c_str(), items);
+            // Get name without extension
+            std::string basename = osgDB::getStrippedName(fileName);
+            for (std::vector<DirectoryItem>::const_iterator itr = items.begin();
+                 itr != items.end(); ++itr) {
+                if (!itr->isDir &&
+                    osgDB::getStrippedName(itr->name) == basename) {
+                    std::string localName;
+                    {
+                        std::ostringstream oss;
+                        oss << getCacheDirectory() << "/" << itr->name;
+                        localName = oss.str();
+                    }
+                    if (!localName.empty() && !osgDB::fileExists(localName)) {
+                        IData::Buffer buf;
+                        {
+                            std::ostringstream oss;
+                            oss << "//" << itr->name;
+                            IData::getFile(collection, oss.str().c_str(), &buf);
+                        }
+                        std::ofstream file(localName.c_str());
+                        file.write((char *)buf.data, buf.size);
+                        file.close();
+                    }
+                }
+            }
+        }
+        if (!osgDB::fileExists(retStr)) {
+            std::ostringstream oss;
+            oss << "//" << fileName;
+            IData::Buffer buf;
+            IData::getFile(collection, oss.str().c_str(), &buf);
+            std::ofstream file(retStr.c_str());
+            file.write((char *)buf.data, buf.size);
+            file.close();
+        }
+    } else {
+        TRACE("Protocol: '%s' url: '%s'", proto.c_str(), url.c_str());
+        retStr = url;
+    }
+    return retStr;
+}
+
+void Renderer::writeScene(const std::string& file)
+{
+    if (_sceneRoot.valid()) {
+        GraphPrintVisitor gpv;
+        _sceneRoot->accept(gpv);
+        //osgDB::writeNodeFile(*_sceneRoot.get(), file);
+    }
+}
Index: /geovis/branches/1.0/Renderer.h
===================================================================
--- /geovis/branches/1.0/Renderer.h	(revision 6666)
+++ /geovis/branches/1.0/Renderer.h	(revision 6666)
@@ -0,0 +1,823 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2004-2015  HUBzero Foundation, LLC
+ *
+ * Author: Leif Delgass <ldelgass@purdue.edu>
+ */
+
+#ifndef GEOVIS_RENDERER_H
+#define GEOVIS_RENDERER_H
+
+#include <sys/time.h> // For struct timeval
+#include <ctime> // For time_t
+#include <string>
+#include <vector>
+#include <tr1/unordered_map>
+#include <typeinfo>
+
+#include <osg/ref_ptr>
+#include <osg/Node>
+#include <osg/Image>
+#include <osg/TransferFunction>
+#include <osgText/String>
+#include <osgViewer/Viewer>
+#include <osgGA/StateSetManipulator>
+#include <osgUtil/IncrementalCompileOperation>
+
+#include <osgEarth/Version>
+#include <osgEarth/StringUtils>
+#include <osgEarth/Map>
+#include <osgEarth/Viewpoint>
+#include <osgEarth/ImageLayer>
+#include <osgEarth/ElevationLayer>
+#include <osgEarth/ModelLayer>
+#include <osgEarth/MaskLayer>
+#include <osgEarth/TileSource>
+#include <osgEarth/ModelSource>
+#include <osgEarth/GeoData>
+#include <osgEarthAnnotation/AnnotationNode>
+#include <osgEarthAnnotation/FeatureNode>
+#include <osgEarthUtil/Sky>
+#include <osgEarthUtil/RTTPicker>
+#include <osgEarthUtil/EarthManipulator>
+#include <osgEarthUtil/MouseCoordsTool>
+#include <osgEarthUtil/Controls>
+#include <osgEarthUtil/Formatter>
+#include <osgEarthUtil/MGRSFormatter>
+#include <osgEarthUtil/AutoClipPlaneHandler>
+#include <osgEarthUtil/VerticalScale>
+
+#define USE_RTT_PICKER
+
+#include "Types.h"
+#include "Trace.h"
+#include "MouseCoordsTool.h"
+#include "ScaleBar.h"
+#include "Placard.h"
+
+// Controls if TGA format is sent to client
+//#define RENDER_TARGA
+#define TARGA_BYTES_PER_PIXEL 3
+
+namespace GeoVis {
+
+class ScreenCaptureCallback : public osg::Camera::DrawCallback
+{
+public:
+    ScreenCaptureCallback(osg::Texture2D *texture = NULL) :
+        osg::Camera::DrawCallback(),
+        _texture(texture)
+    {
+        _image = new osg::Image;
+    }
+
+    virtual void operator()(osg::RenderInfo &renderInfo) const
+    {
+        FRAME("Enter ScreenCaptureCallback");
+        int width, height;
+        if (renderInfo.getCurrentCamera() == NULL) {
+            ERROR("No camera");
+            return;
+        }
+        if (renderInfo.getCurrentCamera()->getViewport() == NULL) {
+            ERROR("No viewport");
+            return;
+        }
+        width = (int)renderInfo.getCurrentCamera()->getViewport()->width();
+        height = (int)renderInfo.getCurrentCamera()->getViewport()->height();
+        FRAME("readPixels: %d x %d", width, height);
+#if 0 //USE_OFFSCREEN_FRAMEBUFFER
+        _image = _texture->getImage();
+#else
+#ifdef RENDER_TARGA
+        _image->readPixels(0, 0, width, height,
+                           GL_BGR, GL_UNSIGNED_BYTE);
+#else
+        _image->readPixels(0, 0, width, height,
+                           GL_RGB, GL_UNSIGNED_BYTE);
+#endif
+#endif
+    }
+
+    osg::Image *getImage()
+    {
+        return _image.get();
+    }
+
+    osg::Texture2D *getTexture()
+    {
+        return _texture.get();
+    }
+
+private:
+    osg::ref_ptr<osg::Texture2D> _texture;
+    osg::ref_ptr<osg::Image> _image;
+};
+
+/**
+ * \brief GIS Renderer
+ */
+class Renderer
+{
+public:
+    typedef std::string ColorMapId;
+    typedef std::string ViewpointId;
+
+    enum GraticuleType {
+        GRATICULE_UTM,
+        GRATICULE_MGRS,
+        GRATICULE_GEODETIC,
+        GRATICULE_SHADER
+    };
+
+    enum CoordinateDisplayType {
+        COORDS_LATLONG_DECIMAL_DEGREES,
+        COORDS_LATLONG_DEGREES_DECIMAL_MINUTES,
+        COORDS_LATLONG_DEGREES_MINUTES_SECONDS,
+        COORDS_MGRS
+    };
+
+    enum SelectMode {
+        SELECT_OFF,
+        SELECT_ON
+    };
+
+    Renderer();
+    virtual ~Renderer();
+
+    void setResourcePath(const std::string& path)
+    {
+        TRACE("Set resource path to %s", path.c_str());
+        _resourcePath = path;
+    }
+
+    void setCacheBaseDirectory(const std::string& path)
+    {
+        TRACE("Set cache base dir to %s", path.c_str());
+        _cacheBaseDir = path;
+    }
+
+    std::string getCacheDirectory() const
+    {
+        return _cacheDir;
+    }
+
+    void setupCache();
+
+    std::string getIconFile(const char *name) const;
+
+    std::string getBaseImage() const;
+
+    std::string getPinIcon() const;
+
+    void setAttribution(const std::string& attrib);
+
+    // Colormaps
+
+    void addColorMap(const ColorMapId& id, osg::TransferFunction1D *xfer);
+
+    void deleteColorMap(const ColorMapId& id);
+
+    void setColorMapNumberOfTableEntries(const ColorMapId& id, int numEntries);
+
+    bool renderColorMap(const ColorMapId& id, int width, int height,
+                        osg::Image *imgData,
+                        bool opaque, float bgColor[3],
+                        bool bgr = false,
+                        int bytesPerPixel = 3) const;
+
+    void getColorMapRange(const ColorMapId& id, float *min, float *max) const;
+
+    std::string getColorMapFilePath(const ColorMapId& id) const;
+
+    void saveCLRFile(const std::string& path, osg::TransferFunction1D *xfer);
+
+    // Scene
+
+    void loadEarthFile(const char *path);
+
+    void resetMap(osgEarth::MapOptions::CoordinateSystemType type,
+                  const osg::Vec4f& bgColor = osg::Vec4f(1,1,1,1),
+                  const char *profile = NULL,
+                  double bounds[4] = NULL,
+                  const char *boundsSRS = NULL);
+
+    void clearMap();
+
+    // Map options
+
+    void setCoordinateReadout(bool state,
+                              CoordinateDisplayType type = COORDS_LATLONG_DECIMAL_DEGREES,
+                              int precision = -1);
+
+    void setReadout(int mouseX, int mouseY);
+
+    void clearReadout();
+
+    void setScaleBar(bool state);
+
+    void setScaleBarUnits(ScaleBarUnits units);
+
+    void setGraticule(bool enable, GraticuleType type = GRATICULE_SHADER);
+
+    void setViewerLightType(osg::View::LightingMode mode);
+
+    void setLighting(bool state);
+
+    void setTerrainColor(const osg::Vec4f& color);
+
+    void setTerrainEdges(bool state) {}
+
+    void setTerrainLineColor(const osg::Vec4f& color) {}
+
+    void setTerrainLineWidth(float width) {}
+
+    void setTerrainLighting(bool state);
+
+    void setTerrainVerticalScale(double scale);
+
+    void setTerrainWireframe(bool state);
+
+    void setEphemerisTime(time_t utcTime);
+
+    void setEphemerisTime(int year, int month, int day, double hours);
+
+    void setSkyAmbient(float ambientLevel);
+
+    // Image raster layers
+
+    int getNumImageLayers() const
+    {
+        return (_map.valid() ? _map->getNumImageLayers() : 0);
+    }
+
+    void getImageLayerNames(std::vector<std::string>& names) const
+    {
+        if (_map.valid()) {
+            osgEarth::ImageLayerVector layerVector;
+            _map->getImageLayers(layerVector);
+            osgEarth::ImageLayerVector::const_iterator itr;
+            for (itr = layerVector.begin(); itr != layerVector.end(); ++itr) {
+                //osgEarth::UID uid = (*itr)->getUID();
+                names.push_back((*itr)->getName());
+            }
+        }
+    }
+
+    bool addImageLayer(const char *name,
+                       osgEarth::TileSourceOptions& opts,
+                       unsigned int pos = UINT_MAX,
+                       bool enableCache = true,
+                       bool coverage = false,
+                       bool makeShared = false,
+                       bool visible = true,
+                       unsigned int minLOD = 0,
+                       unsigned int maxLOD = 23);
+
+    void removeImageLayer(const char *name);
+
+    void moveImageLayer(const char *name, unsigned int pos);
+
+    bool getImageLayerExtent(const char *name, osgEarth::GeoExtent &ext);
+
+    void setImageLayerVisibleRange(const char *name, float min, float max);
+
+    void setImageLayerLODRange(const char *name, int min, int max);
+
+    void setImageLayerOpacity(const char *name, double opacity);
+
+    void setImageLayerVisibility(const char *name, bool state);
+
+    void addColorFilter(const char *name, const char *shader);
+
+    void removeColorFilter(const char *name, int idx = -1);
+
+    // Sequence control
+
+    bool layerHasSequence(const char *name);
+
+    bool sequencePause(const char *name);
+
+    bool sequencePlay(const char *name);
+
+    bool sequenceSeek(const char *name, unsigned int frame);
+
+    // Elevation raster layers
+
+    int getNumElevationLayers() const
+    {
+        return (_map.valid() ? _map->getNumElevationLayers() : 0);
+    }
+
+    void getElevationLayerNames(std::vector<std::string>& names) const
+    {
+        if (_map.valid()) {
+            osgEarth::ElevationLayerVector layerVector;
+            _map->getElevationLayers(layerVector);
+            osgEarth::ElevationLayerVector::const_iterator itr;
+            for (itr = layerVector.begin(); itr != layerVector.end(); ++itr) {
+                //osgEarth::UID uid = (*itr)->getUID();
+                names.push_back((*itr)->getName());
+            }
+        }
+    }
+
+    void addElevationLayer(const char *name,
+                           osgEarth::TileSourceOptions& opts,
+                           unsigned int pos = UINT_MAX,
+                           bool enableCache = true,
+                           bool visible = true,
+                           const char *verticalDatum = NULL,
+                           unsigned int minLOD = 0,
+                           unsigned int maxLOD = 23);
+
+    void removeElevationLayer(const char *name);
+
+    void moveElevationLayer(const char *name, unsigned int pos);
+
+    bool getElevationLayerExtent(const char *name, osgEarth::GeoExtent &ext);
+
+    void setElevationLayerVisibleRange(const char *name, float min, float max);
+
+    void setElevationLayerVisibility(const char *name, bool state);
+
+    // Terrain mask layers
+
+    int getNumTerrainMaskLayers() const
+    {
+        if (!_map.valid()) return 0;
+
+        osgEarth::MaskLayerVector layers;
+        _map->getTerrainMaskLayers(layers);
+        return (int)layers.size();
+    }
+
+    void getTerrainMaskLayerNames(std::vector<std::string>& names) const
+    {
+        if (_map.valid()) {
+            osgEarth::MaskLayerVector layerVector;
+            _map->getTerrainMaskLayers(layerVector);
+            osgEarth::MaskLayerVector::const_iterator itr;
+            for (itr = layerVector.begin(); itr != layerVector.end(); ++itr) {
+                //osgEarth::UID uid = (*itr)->getUID();
+                names.push_back((*itr)->getName());
+            }
+        }
+    }
+
+    osgEarth::MaskLayer *getTerrainMaskLayerByName(const std::string& name)
+    {
+        if (!_map.valid())
+            return NULL;
+
+        osgEarth::MaskLayerVector layerVector;
+        _map->getTerrainMaskLayers(layerVector);
+        osgEarth::MaskLayerVector::iterator itr;
+        for (itr = layerVector.begin(); itr != layerVector.end(); ++itr) {
+            if ((*itr)->getName() == name) {
+                return (*itr).get();
+            }
+        }
+        return NULL;
+    }
+
+    void addTerrainMaskLayer(const char *name,
+                             osgEarth::MaskSourceOptions& opts,
+                             unsigned int minLOD = 0);
+
+    void removeTerrainMaskLayer(const char *name);
+
+    bool getTerrainMaskLayerExtent(const char *name, osgEarth::GeoExtent &ext);
+
+    // Model layers
+
+    int getNumModelLayers() const
+    {
+        return (_map.valid() ? _map->getNumModelLayers() : 0);
+    }
+
+    void getModelLayerNames(std::vector<std::string>& names) const
+    {
+        if (_map.valid()) {
+            osgEarth::ModelLayerVector layerVector;
+            _map->getModelLayers(layerVector);
+            osgEarth::ModelLayerVector::const_iterator itr;
+            for (itr = layerVector.begin(); itr != layerVector.end(); ++itr) {
+                //osgEarth::UID uid = (*itr)->getUID();
+                names.push_back((*itr)->getName());
+            }
+        }
+    }
+
+    void addModelLayer(const char *name,
+                       osgEarth::ModelSourceOptions& opts,
+                       unsigned int pos = UINT_MAX,
+                       bool enableCache = true,
+                       bool lighting = true,
+                       bool visible = true,
+                       bool terrainPatch = false);
+
+    void removeModelLayer(const char *name);
+
+    void moveModelLayer(const char *name, unsigned int pos);
+
+    bool getModelLayerExtent(const char *name, osgEarth::GeoExtent &ext);
+
+    //void setModelLayerVisibleRange(const char *name, float min, float max);
+
+    void setModelLayerOpacity(const char *name, double opacity);
+
+    void setModelLayerVisibility(const char *name, bool state);
+
+    // Render window
+
+    void setWindowSize(int width, int height);
+
+    int getWindowWidth() const
+    {
+        return _windowWidth;
+    }
+
+    int getWindowHeight() const
+    {
+        return _windowHeight;
+    }
+
+    // Camera
+
+    void saveNamedViewpoint(const char *name);
+
+    bool restoreNamedViewpoint(const char *name, double durationSecs);
+
+    bool removeNamedViewpoint(const char *name);
+
+    osgEarth::Viewpoint getViewpoint();
+
+    void setViewpoint(const osgEarth::Viewpoint& v, double durationSecs = 0.0);
+
+    double getMaxDistanceFromExtent(const osgEarth::GeoExtent& extent);
+
+    void setViewpointFromExtent(const osgEarth::GeoExtent& ext,
+                                double durationSecs = 0.0);
+
+    void setViewpointFromRect(double xmin, double ymin,
+                              double xmax, double ymax,
+                              const osgEarth::SpatialReference *srs = NULL,
+                              double durationSecs = 0.0);
+
+    void resetCamera(bool resetOrientation = true);
+
+    void setCameraOrientation(const double quat[4], bool absolute = true);
+
+    void panCamera(double x, double y);
+
+    void rotateCamera(double x, double y);
+
+    void zoomCamera(double z);
+
+    void setCameraDistance(double dist);
+
+    void setLODScale(float scale);
+
+    // Keyboard events
+
+    void keyPress(int key);
+
+    void keyRelease(int key);
+
+    // Mouse events
+
+    void setThrowingEnabled(bool state);
+
+    void mouseClick(int button, double x, double y);
+
+    void mouseDoubleClick(int button, double x, double y);
+
+    void mouseDrag(int button, double x, double y);
+
+    void mouseRelease(int button, double x, double y);
+
+    void mouseMotion(double x, double y);
+
+    void mouseScroll(int direction);
+
+    void pickPending(bool value)
+    { _pickPending = value; }
+
+    // Rendering an image
+
+    void setBackgroundColor(float color[3]);
+
+    void eventuallyRender();
+
+    bool render();
+
+    osg::Image *getRenderedFrame();
+
+    bool mapMouseCoords(float mouseX, float mouseY,
+                        osgEarth::GeoPoint &pt, bool invertY = true);
+
+    double computeMapScale();
+
+    const osgEarth::SpatialReference *getMapSRS()
+    {
+        if (_mapNode.valid()) {
+            return _mapNode->getMapSRS();
+        } else {
+            return NULL;
+        }
+    }
+
+    void addPlaceNode(double latitude, double longitude, char *labelText);
+
+    void hoverPlaceNode(int x, int y, bool invertY = true);
+
+    void deletePlaceNode(int x, int y, bool invertY = true);
+
+    bool getMousePoint(double *x, double *y, double *z)
+    {
+        return (_coordsCallback.valid() && _coordsCallback->report(x, y, z));
+    }
+
+    bool mouseToLatLong(int mouseX, int mouseY,
+                        double *latitude, double *longitude);
+
+    bool getWorldCoords(const osgEarth::GeoPoint& mapPt, osg::Vec3d *world);
+
+    bool worldToScreen(const osg::Vec3d& world, osg::Vec3d *screen,
+                       bool invertY = true);
+
+    bool worldToScreen(std::vector<osg::Vec3d>& coords, bool invertY = true);
+
+    void setMaximumFrameRateInHertz(double rate)
+    {
+        if (rate > 60.0)
+            rate = 60.0;
+        if (rate < 0.25)
+            rate = 0.25;
+        _minFrameTime = 1.0/rate;
+        if (_viewer.valid()) {
+            osgUtil::IncrementalCompileOperation *op =
+                _viewer->getDatabasePager()->getIncrementalCompileOperation();
+            if (op != NULL) {
+                TRACE("Setting DB Pager target frame rate to %g", rate);
+                op->setTargetFrameRate(rate);
+            }
+        }
+        TRACE("Frame rate target: %.2f Hz", (float)getMaximumFrameRateInHertz());
+        TRACE("Frame time target: %.2f msec", _minFrameTime * 1000.0f);
+    }
+
+    void setMaximumBitrate(double bitsPerSecond)
+    {
+        TRACE("Setting max bitrate to %g", bitsPerSecond);
+        unsigned long bitsPerFrame = (_windowWidth * _windowHeight * 3 + 16) * 8;
+        double fps = bitsPerSecond / ((double)bitsPerFrame);
+        setMaximumFrameRateInHertz(fps);
+        TRACE("Bandwidth target: %.2f Mbps", (float)(getMaximumBitrate()/1.0e6));
+    }
+
+    double getMaximumFrameRateInHertz()
+    {
+        return (1.0/_minFrameTime);
+    }
+
+    double getMaximumBitrate()
+    {
+        unsigned long bitsPerFrame = (_windowWidth * _windowHeight * 3 + 16) * 8;
+        return ((double)bitsPerFrame * getMaximumFrameRateInHertz());
+    }
+
+    void markFrameStart();
+
+    void markFrameEnd();
+
+    void setIdleTimeout(long timeout)
+    {
+        if (timeout != -1L &&
+            (double)timeout < _minFrameTime) {
+            ERROR("Timeout must be more than %g sec", _minFrameTime);
+            return;
+        }
+        TRACE("Setting idle timeout to %ld sec", timeout);
+        _idleTimeout = timeout;
+    }
+
+    long getIdleTimeout()
+    {
+        return _idleTimeout;
+    }
+
+    void getTimeout(struct timeval *tv);
+
+    void mapNodeUpdate();
+
+    std::string getCanonicalPath(const std::string& url) const;
+
+    void writeScene(const std::string& file);
+
+    // Placard info window
+
+    void enablePlacard(const char *layerName, bool state);
+
+    void setPlacardConfig(const Placard& placardConf, const char *layerName);
+
+    Placard getPlacardConfig(const char *layerName)
+    {
+        Placard ret;
+        PlacardHashmap::iterator itr = _placardConfigs.find(layerName);
+        if (itr != _placardConfigs.end()) {
+            ret = itr->second;
+        }
+        return ret;
+    }
+
+    // Selection
+
+    void setSelectMode(SelectMode mode);
+
+    void selectFeatures(std::vector<unsigned long>& featureIDs,
+                        const char *layerName, bool clear = true);
+
+    void deselectFeatures(std::vector<unsigned long>& featureIDs,
+                          const char *layerName);
+
+    void addPlacard(const osgEarth::GeoPoint& location,
+                    osgEarth::Features::Feature *feature,
+                    const char *layerName);
+
+    void clearSelection();
+
+    void addRhumbBox(double latMin, double latMax,
+                     double longMin, double longMax);
+
+    void initBoxSelection(int x, int y);
+    void updateBoxSelection(int x, int y);
+    void getBoxSelection(double *latMin, double *latMax,
+                         double *longMin, double *longMax,
+                         const osgEarth::SpatialReference *outSRS = NULL);
+    void clearBoxSelection();
+
+    // These methods are based on the deprecated annotation API
+    bool select(osgEarth::Annotation::AnnotationNode *node)
+    {
+        if (_selected.find(node) == _selected.end()) {
+            _selected.insert(node);
+            return true;
+        } else {
+            return false;
+        }
+    }
+    std::set<osgEarth::Annotation::AnnotationNode*>& getHovered()
+    { return _hovered; }
+    std::set<osgEarth::Annotation::AnnotationNode*>& getSelected()
+    { return _selected; }
+
+    // Scene Graph
+
+    osg::Group *getSceneRoot()
+    { return _sceneRoot.get(); }
+    osgEarth::MapNode *getMapNode()
+    { return _mapNode.get(); }
+    osg::Group *getAnnotations()
+    {
+        initAnnotations();
+        return _annotations.get();
+    }
+    osg::Group *getPlaceNodes()
+    {
+        initAnnotations();
+        return _placeNodes.get();
+    }
+
+private:
+    typedef std::tr1::unordered_map<ColorMapId, osg::ref_ptr<osg::TransferFunction1D> > ColorMapHashmap;
+    typedef std::tr1::unordered_map<ViewpointId, osgEarth::Viewpoint> ViewpointHashmap;
+    typedef std::tr1::unordered_map<std::string, Placard> PlacardHashmap;
+    typedef std::tr1::unordered_map<std::string, std::set<unsigned long> > FeatureSelectionHashmap;
+
+    void initAnnotations();
+
+    void clearSelectionAnnotationNodes();
+
+    void initViewer();
+
+    void finalizeViewer();
+    
+    void initControls();
+
+    void updateDebugLabel();
+
+    void initEarthManipulator();
+
+    void initMouseCoordsTool(CoordinateDisplayType type = COORDS_LATLONG_DECIMAL_DEGREES,
+                             int precision = -1);
+
+    osgEarth::Util::MGRSFormatter::Precision getMGRSPrecision(int precisionInMeters);
+
+    void initColorMaps();
+
+    void initCamera();
+
+    bool isPagerIdle();
+
+    bool checkNeedToDoFrame();
+
+    osgGA::EventQueue *getEventQueue();
+
+    bool _needsRedraw;
+    int _windowWidth, _windowHeight;
+    double _mapScale;
+    float _bgColor[3];
+
+    long _idleTimeout;
+    double _minFrameTime;
+    double _lastFrameTime;
+    double _renderTime;
+    osg::Timer_t _startFrameTime;
+    osg::Timer_t _renderStartTime;
+    osg::Timer_t _renderStopTime;
+
+    ColorMapHashmap _colorMaps;
+
+    std::string _resourcePath;
+    std::string _cacheBaseDir;
+    std::string _cacheDir;
+    std::string _baseURI;
+    std::string _attribution;
+
+    osg::ref_ptr<osg::Group> _sceneRoot;
+    osg::ref_ptr<osg::Group> _graticule;
+    osg::ref_ptr<osg::Group> _annotations;
+    PlacardHashmap _placardConfigs;
+    double _anchorLat, _anchorLong;
+    SelectMode _selectMode;
+    osg::ref_ptr<osgEarth::Annotation::FeatureNode> _selectionBox;
+#ifdef USE_RTT_PICKER
+    osg::ref_ptr<osgEarth::Util::RTTPicker> _picker;
+#endif
+    bool _pickPending;
+    osg::ref_ptr<osg::Group> _placeNodes;
+    FeatureSelectionHashmap _selectedFeatures;
+    std::set<osgEarth::Annotation::AnnotationNode *> _hovered;
+    std::set<osgEarth::Annotation::AnnotationNode *> _selected;
+    osg::ref_ptr<osgEarth::MapNode> _mapNode;
+    osg::ref_ptr<osgEarth::Map> _map;
+    osg::ref_ptr<osgEarth::Util::SkyNode> _skyNode;
+    osg::ref_ptr<osgViewer::Viewer> _viewer;
+    osg::ref_ptr<ScreenCaptureCallback> _captureCallback;
+    osg::ref_ptr<osgEarth::Util::AutoClipPlaneCullCallback> _clipPlaneCullCallback;
+    osg::ref_ptr<osgEarth::Util::Controls::HBox> _coordsBox;
+    osg::ref_ptr<MouseCoordsTool> _mouseCoordsTool;
+    osg::ref_ptr<MouseCoordsCallback> _coordsCallback;
+    osg::ref_ptr<osgEarth::Util::Controls::HBox> _debugBox;
+    osg::ref_ptr<osgEarth::Util::Controls::LabelControl> _debugLabel;
+    osg::ref_ptr<osgEarth::Util::Controls::HBox> _copyrightScaleBox;
+    osg::ref_ptr<osgEarth::Util::Controls::LabelControl> _copyrightLabel;
+    osg::ref_ptr<osgEarth::Util::Controls::LabelControl> _scaleLabel;
+    osg::ref_ptr<osgEarth::Util::Controls::Frame> _scaleBar;
+    ScaleBarUnits _scaleBarUnits;
+    osg::ref_ptr<osgEarth::Util::EarthManipulator> _manipulator;
+    osg::ref_ptr<osgGA::StateSetManipulator> _stateManip;
+    osg::ref_ptr<osgEarth::Util::VerticalScale> _verticalScale;
+    ViewpointHashmap _viewpoints;
+};
+
+class MapNodeCallback : public osg::NodeCallback
+{
+public:
+    MapNodeCallback(Renderer *renderer) :
+        _renderer(renderer)
+    {}
+
+    virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
+    {
+        _renderer->mapNodeUpdate();
+        traverse(node, nv);
+    }
+private:
+    Renderer *_renderer;
+};
+
+class SelectPlaceNodesVisitor : public osg::NodeVisitor
+{
+public:
+    SelectPlaceNodesVisitor(Renderer *renderer,
+                            double latMin, double latMax,
+                            double longMin, double longMax) :
+        osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
+        _renderer(renderer),
+        _latMin(latMin), _latMax(latMax),
+        _longMin(longMin), _longMax(longMax)
+    {}
+
+    virtual void apply(osg::Node& node);
+
+private:
+    Renderer *_renderer;
+    double _latMin, _latMax, _longMin, _longMax;
+};
+
+}
+
+#endif
Index: /geovis/branches/1.0/RendererCmd.cpp
===================================================================
--- /geovis/branches/1.0/RendererCmd.cpp	(revision 6666)
+++ /geovis/branches/1.0/RendererCmd.cpp	(revision 6666)
@@ -0,0 +1,4211 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2004-2015  HUBzero Foundation, LLC
+ *
+ * Author: Leif Delgass <ldelgass@purdue.edu>
+ */
+
+#include <cstdlib>
+#include <cstdio>
+#include <cstring>
+#include <cfloat>
+#include <cerrno>
+#include <string>
+#include <sstream>
+#include <limits>
+#include <vector>
+#include <unistd.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+#include <tcl.h>
+
+#include <osgDB/FileUtils>
+#include <osgDB/FileNameUtils>
+
+#include <osgEarth/Version>
+#include <osgEarth/Registry>
+#include <osgEarth/Units>
+#include <osgEarth/Config>
+#include <osgEarthFeatures/FeatureModelSource>
+#include <osgEarthSymbology/Color>
+#include <osgEarthSymbology/Style>
+#include <osgEarthSymbology/StyleSheet>
+#include <osgEarthSymbology/CssUtils>
+#include <osgEarthSymbology/IconSymbol>
+#include <osgEarthSymbology/ModelSymbol>
+#include <osgEarthSymbology/LineSymbol>
+#include <osgEarthSymbology/RenderSymbol>
+
+#include <osgEarthDrivers/agglite/AGGLiteOptions>
+#include <osgEarthDrivers/osg/ColorRampOptions>
+#include <osgEarthDrivers/debug/DebugOptions>
+#include <osgEarthDrivers/arcgis/ArcGISOptions>
+#include <osgEarthDrivers/gdal/GDALOptions>
+#include <osgEarthDrivers/tms/TMSOptions>
+#include <osgEarthDrivers/wcs/WCSOptions>
+#include <osgEarthDrivers/wms/WMSOptions>
+#include <osgEarthDrivers/xyz/XYZOptions>
+#include <osgEarthDrivers/mask_feature/FeatureMaskOptions>
+#include <osgEarthDrivers/model_feature_geom/FeatureGeomModelOptions>
+#include <osgEarthDrivers/model_simple/SimpleModelOptions>
+#include <osgEarthDrivers/feature_ogr/OGRFeatureOptions>
+#include <osgEarthDrivers/feature_tfs/TFSFeatureOptions>
+#include <osgEarthDrivers/feature_wfs/WFSFeatureOptions>
+
+#include "Trace.h"
+#include "CmdProc.h"
+#include "ReadBuffer.h"
+#include "Types.h"
+#include "RendererCmd.h"
+#include "RenderServer.h"
+#include "Renderer.h"
+#include "IData.h"
+#include "Stats.h"
+#include "PPMWriter.h"
+#include "TGAWriter.h"
+#include "ResponseQueue.h"
+#ifdef USE_READ_THREAD
+#include "CommandQueue.h"
+#endif
+#include "Util.h"
+
+using namespace GeoVis;
+
+static int lastCmdStatus;
+
+#ifndef USE_THREADS
+static ssize_t
+SocketWrite(const void *bytes, size_t len)
+{
+    size_t ofs = 0;
+    ssize_t bytesWritten;
+    while ((bytesWritten = write(g_fdOut, (const char *)bytes + ofs, len - ofs)) > 0) {
+        ofs += bytesWritten;
+        if (ofs == len)
+            break;
+    }
+    if (bytesWritten < 0) {
+        ERROR("write: %s", strerror(errno));
+    }
+    return bytesWritten;
+}
+#endif
+
+static bool
+SocketRead(char *bytes, size_t len)
+{
+    if (len == 0) return true;
+    ReadBuffer::BufferStatus status;
+    status = g_inBufPtr->followingData((unsigned char *)bytes, len);
+    g_stats.nDataBytes += len;
+    TRACE("followingData status: %d", status);
+    return (status == ReadBuffer::OK);
+}
+
+ssize_t
+GeoVis::queueResponse(const void *bytes, size_t len, 
+                      Response::AllocationType allocType,
+                      Response::ResponseType type)
+{
+#ifdef USE_THREADS
+    Response *response = new Response(type);
+    response->setMessage((unsigned char *)bytes, len, allocType);
+    g_outQueue->enqueue(response);
+    return (ssize_t)len;
+#else
+    return SocketWrite(bytes, len);
+#endif
+}
+
+static int
+ExecuteCommand(Tcl_Interp *interp, Tcl_DString *dsPtr) 
+{
+    int result;
+#ifdef WANT_TRACE
+    char *str = Tcl_DStringValue(dsPtr);
+    std::string cmd(str);
+    cmd.erase(cmd.find_last_not_of(" \n\r\t")+1);
+    TRACE("command %lu: '%s'", g_stats.nCommands, cmd.c_str());
+#endif
+    lastCmdStatus = TCL_OK;
+    result = Tcl_EvalEx(interp, Tcl_DStringValue(dsPtr), 
+                        Tcl_DStringLength(dsPtr), 
+                        TCL_EVAL_DIRECT | TCL_EVAL_GLOBAL);
+    Tcl_DStringSetLength(dsPtr, 0);
+    if (lastCmdStatus == TCL_BREAK) {
+        return TCL_BREAK;
+    }
+    lastCmdStatus = result;
+    if (result != TCL_OK) {
+        TRACE("Error: %d", result);
+    }
+    return result;
+}
+
+static int
+GetBooleanFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, bool *boolPtr)
+{
+    int value;
+
+    if (Tcl_GetBooleanFromObj(interp, objPtr, &value) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    *boolPtr = (bool)value;
+    return TCL_OK;
+}
+#if 0
+static short
+GetShortFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, short *valuePtr)
+{
+    int value;
+
+    if (Tcl_GetIntFromObj(interp, objPtr, &value) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    *valuePtr = (short)value;
+    return TCL_OK;
+}
+#endif
+static unsigned short
+GetUShortFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, unsigned short *valuePtr)
+{
+    int value;
+
+    if (Tcl_GetIntFromObj(interp, objPtr, &value) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    *valuePtr = (unsigned short)value;
+    return TCL_OK;
+}
+
+static unsigned int
+GetUIntFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, unsigned int *valuePtr)
+{
+    long value;
+
+    if (Tcl_GetLongFromObj(interp, objPtr, &value) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    *valuePtr = (unsigned int)value;
+    return TCL_OK;
+}
+
+static int
+GetFloatFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, float *valuePtr)
+{
+    double value;
+
+    if (Tcl_GetDoubleFromObj(interp, objPtr, &value) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    *valuePtr = (float)value;
+    return TCL_OK;
+}
+
+static osgEarth::Symbology::TextSymbol::Alignment
+ParseTextAlignment(const char *str,
+                   osgEarth::Symbology::TextSymbol::Alignment defAlign =
+                   osgEarth::Symbology::TextSymbol::ALIGN_LEFT_BASE_LINE)
+{
+    osgEarth::Symbology::TextSymbol::Alignment alignment = defAlign;
+    if (str[0] == 'l' && strcmp(str, "left_top") == 0) {
+        alignment = osgEarth::Symbology::TextSymbol::ALIGN_LEFT_TOP;
+    } else if (str[0] == 'l' && strcmp(str, "left_center") == 0) {
+        alignment = osgEarth::Symbology::TextSymbol::ALIGN_LEFT_CENTER;
+    } else if (str[0] == 'l' && strcmp(str, "left_bottom") == 0) {
+        alignment = osgEarth::Symbology::TextSymbol::ALIGN_LEFT_BOTTOM;
+    } else if (str[0] == 'c' && strcmp(str, "center_top") == 0) {
+        alignment = osgEarth::Symbology::TextSymbol::ALIGN_CENTER_TOP;
+    } else if (str[0] == 'c' && strcmp(str, "center_center") == 0) {
+        alignment = osgEarth::Symbology::TextSymbol::ALIGN_CENTER_CENTER;
+    } else if (str[0] == 'c' && strcmp(str, "center_bottom") == 0) {
+        alignment = osgEarth::Symbology::TextSymbol::ALIGN_CENTER_BOTTOM;
+    } else if (str[0] == 'r' && strcmp(str, "right_top") == 0) {
+        alignment = osgEarth::Symbology::TextSymbol::ALIGN_RIGHT_TOP;
+    } else if (str[0] == 'r' && strcmp(str, "right_center") == 0) {
+        alignment = osgEarth::Symbology::TextSymbol::ALIGN_RIGHT_CENTER;
+    } else if (str[0] == 'r' && strcmp(str, "right_bottom") == 0) {
+        alignment = osgEarth::Symbology::TextSymbol::ALIGN_RIGHT_BOTTOM;
+    } else if (str[0] == 'l' && strcmp(str, "left_baseline") == 0) {
+        alignment = osgEarth::Symbology::TextSymbol::ALIGN_LEFT_BASE_LINE;
+    } else if (str[0] == 'c' && strcmp(str, "center_baseline") == 0) {
+        alignment = osgEarth::Symbology::TextSymbol::ALIGN_CENTER_BASE_LINE;
+    } else if (str[0] == 'r' && strcmp(str, "right_baseline") == 0) {
+        alignment = osgEarth::Symbology::TextSymbol::ALIGN_RIGHT_BASE_LINE;
+    } else  if (str[0] == 'l' && strcmp(str, "left_bottom_baseline") == 0) {
+        alignment = osgEarth::Symbology::TextSymbol::ALIGN_LEFT_BOTTOM_BASE_LINE;
+    } else if (str[0] == 'c' && strcmp(str, "center_bottom_baseline") == 0) {
+        alignment = osgEarth::Symbology::TextSymbol::ALIGN_CENTER_BOTTOM_BASE_LINE;
+    } else if (str[0] == 'r' && strcmp(str, "right_bottom_baseline") == 0) {
+        alignment = osgEarth::Symbology::TextSymbol::ALIGN_RIGHT_BOTTOM_BASE_LINE;
+    }
+    return alignment;
+}
+
+static osgEarth::Symbology::IconSymbol::Alignment
+ParseIconAlignment(const char *str,
+                   osgEarth::Symbology::IconSymbol::Alignment defAlign =
+                   osgEarth::Symbology::IconSymbol::ALIGN_CENTER_BOTTOM)
+{
+    osgEarth::Symbology::IconSymbol::Alignment alignment = defAlign;
+    if (str[0] == 'l' && strcmp(str, "left_top") == 0) {
+        alignment = osgEarth::Symbology::IconSymbol::ALIGN_LEFT_TOP;
+    } else if (str[0] == 'l' && strcmp(str, "left_center") == 0) {
+        alignment = osgEarth::Symbology::IconSymbol::ALIGN_LEFT_CENTER;
+    } else if (str[0] == 'l' && strcmp(str, "left_bottom") == 0) {
+        alignment = osgEarth::Symbology::IconSymbol::ALIGN_LEFT_BOTTOM;
+    } else if (str[0] == 'c' && strcmp(str, "center_top") == 0) {
+        alignment = osgEarth::Symbology::IconSymbol::ALIGN_CENTER_TOP;
+    } else if (str[0] == 'c' && strcmp(str, "center_center") == 0) {
+        alignment = osgEarth::Symbology::IconSymbol::ALIGN_CENTER_CENTER;
+    } else if (str[0] == 'c' && strcmp(str, "center_bottom") == 0) {
+        alignment = osgEarth::Symbology::IconSymbol::ALIGN_CENTER_BOTTOM;
+    } else if (str[0] == 'r' && strcmp(str, "right_top") == 0) {
+        alignment = osgEarth::Symbology::IconSymbol::ALIGN_RIGHT_TOP;
+    } else if (str[0] == 'r' && strcmp(str, "right_center") == 0) {
+        alignment = osgEarth::Symbology::IconSymbol::ALIGN_RIGHT_CENTER;
+    } else if (str[0] == 'r' && strcmp(str, "right_bottom") == 0) {
+        alignment = osgEarth::Symbology::IconSymbol::ALIGN_RIGHT_BOTTOM;
+    }
+    return alignment;
+}
+
+static osgEarth::Symbology::InstanceSymbol::Placement
+ParseInstancePlacement(const char *str,
+                       osgEarth::Symbology::InstanceSymbol::Placement defPlacement =
+                       osgEarth::Symbology::InstanceSymbol::PLACEMENT_VERTEX)
+{
+    osgEarth::Symbology::InstanceSymbol::Placement placement = defPlacement;
+    if (str[0] == 'v' && strcmp(str, "vertex") == 0) {
+        placement = osgEarth::Symbology::InstanceSymbol::PLACEMENT_VERTEX;
+    } else if (str[0] == 'c' && strcmp(str, "centroid") == 0) {
+        placement = osgEarth::Symbology::InstanceSymbol::PLACEMENT_CENTROID;
+    } else if (str[0] == 'i' && strcmp(str, "interval") == 0) {
+        placement = osgEarth::Symbology::InstanceSymbol::PLACEMENT_INTERVAL;
+    } else if (str[0] == 'r' && strcmp(str, "random") == 0) {
+        placement = osgEarth::Symbology::InstanceSymbol::PLACEMENT_RANDOM;
+    }
+    return placement;
+}
+
+static osgEarth::Symbology::AltitudeSymbol::Clamping
+ParseClamping(const char *str,
+              osgEarth::Symbology::AltitudeSymbol::Clamping defTechnique =
+              osgEarth::Symbology::AltitudeSymbol::CLAMP_NONE)
+{
+    osgEarth::Symbology::AltitudeSymbol::Clamping clamping = defTechnique;
+    if (str[0] == 'n' && strcmp(str, "none") == 0) {
+        clamping = osgEarth::Symbology::AltitudeSymbol::CLAMP_NONE;
+    } else if (str[0] == 't' && strcmp(str, "terrain") == 0) {
+        clamping = osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
+    } else if (str[0] == 'r' && strcmp(str, "relative") == 0) {
+        clamping = osgEarth::Symbology::AltitudeSymbol::CLAMP_RELATIVE_TO_TERRAIN;
+    } else if (str[0] == 'a' && strcmp(str, "absolute") == 0) {
+        clamping = osgEarth::Symbology::AltitudeSymbol::CLAMP_ABSOLUTE;
+    }
+    return clamping;
+}
+
+static osgEarth::Symbology::AltitudeSymbol::Technique
+ParseClampingTechnique(const char *str,
+                       osgEarth::Symbology::AltitudeSymbol::Technique defTechnique =
+                       osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_DRAPE)
+{
+    osgEarth::Symbology::AltitudeSymbol::Technique technique = defTechnique;
+    if (str[0] == 'd' && strcmp(str, "drape") == 0) {
+        technique = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_DRAPE;
+    } else if (str[0] == 'g' && strcmp(str, "gpu") == 0) {
+        technique = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
+    } else if (str[0] == 's' && strcmp(str, "scene") == 0) {
+        technique = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_SCENE;
+    } else if (str[0] == 'm' && strcmp(str, "map") == 0) {
+        technique = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_MAP;
+    }
+    return technique;
+}
+
+static osgEarth::Symbology::Stroke::LineCapStyle
+ParseLineCapStyle(const char *str,
+                  osgEarth::Symbology::Stroke::LineCapStyle defLineCap =
+                  osgEarth::Symbology::Stroke::LINECAP_FLAT)
+{
+    osgEarth::Symbology::Stroke::LineCapStyle lineCap = defLineCap;
+    if (str[0] == 'f' && strcmp(str, "flat") == 0) {
+        lineCap = osgEarth::Symbology::Stroke::LINECAP_FLAT;
+    } else if (str[0] == 's' && strcmp(str, "square") == 0) {
+        lineCap = osgEarth::Symbology::Stroke::LINECAP_SQUARE;
+    } else if (str[0] == 'r' && strcmp(str, "round") == 0) {
+        lineCap = osgEarth::Symbology::Stroke::LINECAP_ROUND;
+    }
+    return lineCap;
+}
+
+static osgEarth::Symbology::Stroke::LineJoinStyle
+ParseLineJoinStyle(const char *str,
+                   osgEarth::Symbology::Stroke::LineJoinStyle defLineJoin =
+                   osgEarth::Symbology::Stroke::LINEJOIN_MITRE)
+{
+    osgEarth::Symbology::Stroke::LineJoinStyle lineJoin = defLineJoin;
+    if (str[0] == 'm' &&
+       (strcmp(str, "mitre") == 0 || strcmp(str, "miter") == 0)) {
+        lineJoin = osgEarth::Symbology::Stroke::LINEJOIN_MITRE;
+    } else if (str[0] == 'r' && strcmp(str, "round") == 0) {
+        lineJoin = osgEarth::Symbology::Stroke::LINEJOIN_ROUND;
+    }
+    return lineJoin;
+}
+
+static int
+CameraDeleteViewpointOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+                        Tcl_Obj *const *objv)
+{
+    char *name = Tcl_GetString(objv[2]);
+
+    g_renderer->removeNamedViewpoint(name);
+    return TCL_OK;
+}
+
+static int
+CameraCenterOp(ClientData clientData, Tcl_Interp *interp, int objc,
+               Tcl_Obj *const *objv)
+{
+    double x, y;
+    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
+        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    double zoom = 1.0;
+    if (objc > 4) {
+        if (Tcl_GetDoubleFromObj(interp, objv[4], &zoom) != TCL_OK) {
+            return TCL_ERROR;
+        }
+    }
+    double duration = 1.0;
+    if (objc > 5) {
+        if (Tcl_GetDoubleFromObj(interp, objv[5], &duration) != TCL_OK) {
+            return TCL_ERROR;
+        }
+    }
+
+    osgEarth::Viewpoint vpt = g_renderer->getViewpoint();
+    vpt.focalPoint()->x() = x;
+    vpt.focalPoint()->y() = y;
+    //vpt.focalPoint()->z() = mapPoint.z();
+    vpt.range()->set(vpt.range()->getValue() * zoom, osgEarth::Units::METERS);
+
+    g_renderer->setViewpoint(vpt, duration);
+
+    return TCL_OK;
+}
+
+static int
+CameraGetViewpointOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+                     Tcl_Obj *const *objv)
+{
+    osgEarth::Viewpoint view = g_renderer->getViewpoint();
+
+    std::ostringstream oss;
+    size_t len = 0;
+    oss << "nv>camera get "
+        << view.focalPoint()->x() << " "
+        << view.focalPoint()->y() << " "
+        << view.focalPoint()->z() << " "
+        << view.heading()->getValue() << " "
+        << view.pitch()->getValue() << " "
+        << view.range()->getValue()
+        << " {" << ((view.focalPoint()->getSRS() == NULL) ? "" : view.focalPoint()->getSRS()->getHorizInitString()) << "}"
+        << " {" << ((view.focalPoint()->getSRS() == NULL) ? "" : view.focalPoint()->getSRS()->getVertInitString()) << "}"
+        << "\n";
+    std::string ostr = oss.str();
+    len = ostr.size();
+#ifdef USE_THREADS
+    queueResponse(ostr.c_str(), len, Response::VOLATILE);
+#else 
+    ssize_t bytesWritten = SocketWrite(ostr.c_str(), len);
+
+    if (bytesWritten < 0) {
+        return TCL_ERROR;
+    }
+#endif /*USE_THREADS*/
+    return TCL_OK;
+}
+
+static int
+CameraGoOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+           Tcl_Obj *const *objv)
+{
+    int x, y;
+    if (Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK ||
+        Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    double zoom = 1.0;
+    if (objc > 4) {
+        if (Tcl_GetDoubleFromObj(interp, objv[4], &zoom) != TCL_OK) {
+            return TCL_ERROR;
+        }
+    }
+    double duration = 1.0;
+    if (objc > 5) {
+        if (Tcl_GetDoubleFromObj(interp, objv[5], &duration) != TCL_OK) {
+            return TCL_ERROR;
+        }
+    }
+
+    osgEarth::GeoPoint mapPoint;
+    if (g_renderer->mapMouseCoords(x, y, mapPoint)) {
+        osgEarth::Viewpoint vpt = g_renderer->getViewpoint();
+        vpt.focalPoint() = mapPoint;
+        vpt.range()->set(vpt.range()->getValue() * zoom, osgEarth::Units::METERS);
+        g_renderer->setViewpoint(vpt, duration);
+    } else {
+        // Out of map bounds
+    }
+    return TCL_OK;
+}
+
+static int
+CameraOrientOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+               Tcl_Obj *const *objv)
+{
+    double quat[4];
+
+    if (Tcl_GetDoubleFromObj(interp, objv[2], &quat[0]) != TCL_OK ||
+        Tcl_GetDoubleFromObj(interp, objv[3], &quat[1]) != TCL_OK ||
+        Tcl_GetDoubleFromObj(interp, objv[4], &quat[2]) != TCL_OK ||
+        Tcl_GetDoubleFromObj(interp, objv[5], &quat[3]) != TCL_OK) {
+        return TCL_ERROR;
+    }
+
+    g_renderer->setCameraOrientation(quat);
+    return TCL_OK;
+}
+
+static int
+CameraPanOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+            Tcl_Obj *const *objv)
+{
+    double x, y;
+
+    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
+        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
+        return TCL_ERROR;
+    }
+
+    g_renderer->panCamera(x, y);
+    return TCL_OK;
+}
+
+static int
+CameraResetOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+              Tcl_Obj *const *objv)
+{
+    if (objc == 3) {
+        const char *string = Tcl_GetString(objv[2]);
+        char c = string[0];
+        if ((c != 'a') || (strcmp(string, "all") != 0)) {
+            Tcl_AppendResult(interp, "bad camera reset option \"", string,
+                         "\": should be all", (char*)NULL);
+            return TCL_ERROR;
+        }
+        g_renderer->resetCamera(true);
+    } else {
+        g_renderer->resetCamera(false);
+    }
+    return TCL_OK;
+}
+
+static int
+CameraRestoreViewpointOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+                         Tcl_Obj *const *objv)
+{
+    char *name = Tcl_GetString(objv[2]);
+
+    double duration = 0.0;
+    if (objc > 3) {
+        if (Tcl_GetDoubleFromObj(interp, objv[3], &duration) != TCL_OK) {
+            return TCL_ERROR;
+        }
+    }
+    if (!g_renderer->restoreNamedViewpoint(name, duration)) {
+        Tcl_AppendResult(interp, "camera viewpoint \"", name,
+                         "\" not found", (char*)NULL);
+        return TCL_ERROR;
+    }
+    return TCL_OK;
+}
+
+static int
+CameraRotateOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+               Tcl_Obj *const *objv)
+{
+    double x, y;
+
+    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
+        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
+        return TCL_ERROR;
+    }
+
+    g_renderer->rotateCamera(x, y);
+    return TCL_OK;
+}
+
+static int
+CameraSaveViewpointOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+                      Tcl_Obj *const *objv)
+{
+    char *name = Tcl_GetString(objv[2]);
+
+    g_renderer->saveNamedViewpoint(name);
+    return TCL_OK;
+}
+
+static int
+CameraSetByExtentOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+                    Tcl_Obj *const *objv)
+{
+    double xmin, ymin, xmax, ymax;
+    double duration = 0.0;
+    if (Tcl_GetDoubleFromObj(interp, objv[2], &xmin) != TCL_OK ||
+        Tcl_GetDoubleFromObj(interp, objv[3], &ymin) != TCL_OK ||
+        Tcl_GetDoubleFromObj(interp, objv[4], &xmax) != TCL_OK ||
+        Tcl_GetDoubleFromObj(interp, objv[5], &ymax) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    if (objc > 6) {
+        if (Tcl_GetDoubleFromObj(interp, objv[6], &duration) != TCL_OK) {
+            return TCL_ERROR;
+        }
+    }
+    osg::ref_ptr<const osgEarth::SpatialReference> srs = g_renderer->getMapSRS();
+    if (objc > 7) {
+        char *srsInit = Tcl_GetString(objv[7]);
+        if (strlen(srsInit) > 0) {
+            srs = osgEarth::SpatialReference::get(srsInit);
+        }
+    }
+    g_renderer->setViewpointFromRect(xmin, ymin, xmax, ymax, srs.get(), duration);
+    return TCL_OK;
+}
+
+static int
+CameraSetByLayerExtentOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+                         Tcl_Obj *const *objv)
+{
+    const char *name = Tcl_GetString(objv[2]);
+    double duration = 0.0;
+    if (objc > 3) {
+        if (Tcl_GetDoubleFromObj(interp, objv[3], &duration) != TCL_OK) {
+            return TCL_ERROR;
+        }
+    }
+    osgEarth::GeoExtent ext;
+    if (g_renderer->getImageLayerExtent(name, ext)) {
+        g_renderer->setViewpointFromExtent(ext, duration);
+        return TCL_OK;
+    } else if (g_renderer->getElevationLayerExtent(name, ext)) {
+        g_renderer->setViewpointFromExtent(ext, duration);
+        return TCL_OK;
+    } else if (g_renderer->getTerrainMaskLayerExtent(name, ext)) {
+        g_renderer->setViewpointFromExtent(ext, duration);
+        return TCL_OK;
+    } else if (g_renderer->getModelLayerExtent(name, ext)) {
+        g_renderer->setViewpointFromExtent(ext, duration);
+        return TCL_OK;
+    }
+    Tcl_AppendResult(interp, "Invalid layer \"", name,
+                     "\" or could not determine layer extent", (char*)NULL);
+    return TCL_ERROR;
+}
+
+static int
+CameraSetDistanceOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+                    Tcl_Obj *const *objv)
+{
+    double dist;
+
+    if (Tcl_GetDoubleFromObj(interp, objv[2], &dist) != TCL_OK) {
+        return TCL_ERROR;
+    }
+
+    g_renderer->setCameraDistance(dist);
+    return TCL_OK;
+}
+
+static int
+CameraSetViewpointOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+                     Tcl_Obj *const *objv)
+{
+    double x, y, z, heading, pitch, distance;
+    double duration = 0.0;
+    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
+        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK ||
+        Tcl_GetDoubleFromObj(interp, objv[4], &z) != TCL_OK ||
+        Tcl_GetDoubleFromObj(interp, objv[5], &heading) != TCL_OK ||
+        Tcl_GetDoubleFromObj(interp, objv[6], &pitch) != TCL_OK ||
+        Tcl_GetDoubleFromObj(interp, objv[7], &distance) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    if (objc > 8) {
+        if (Tcl_GetDoubleFromObj(interp, objv[8], &duration) != TCL_OK) {
+            return TCL_ERROR;
+        }
+    }
+    const osgEarth::SpatialReference *srs = g_renderer->getMapSRS();
+    if (objc > 9) {
+        char *srsInit = Tcl_GetString(objv[9]);
+        if (strlen(srsInit) > 0) {
+            if (objc > 10) {
+                char *vertDatum = Tcl_GetString(objv[10]);
+                srs = osgEarth::SpatialReference::get(srsInit, vertDatum);
+            } else {
+                srs = osgEarth::SpatialReference::get(srsInit);
+            }
+            if (srs == NULL) {
+                ERROR("Couldn't get SRS from init string: %s", srsInit);
+                return TCL_ERROR;
+            }
+        }
+    }
+    osgEarth::Viewpoint view;
+    view.focalPoint() = osgEarth::GeoPoint(srs, x, y, z);
+    view.heading() = osgEarth::Angle(heading, osgEarth::Units::DEGREES);
+    view.pitch() = osgEarth::Angle(pitch, osgEarth::Units::DEGREES);
+    view.range() = osgEarth::Distance(distance, osgEarth::Units::METERS);
+    g_renderer->setViewpoint(view, duration);
+    return TCL_OK;
+}
+
+static int
+CameraThrowOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+              Tcl_Obj *const *objv)
+{
+    bool state;
+
+    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
+        return TCL_ERROR;
+    }
+
+    g_renderer->setThrowingEnabled(state);
+    return TCL_OK;
+}
+
+static int
+CameraZoomOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+             Tcl_Obj *const *objv)
+{
+    double z;
+
+    if (Tcl_GetDoubleFromObj(interp, objv[2], &z) != TCL_OK) {
+        return TCL_ERROR;
+    }
+
+    g_renderer->zoomCamera(z);
+    return TCL_OK;
+}
+
+static CmdSpec cameraOps[] = {
+    {"center",  1, CameraCenterOp,           4, 6, "x y ?zoomFactor? ?duration?"},
+    {"delete",  2, CameraDeleteViewpointOp,  3, 3, "name"},
+    {"dist",    2, CameraSetDistanceOp,      3, 3, "distance"},
+    {"extent",  1, CameraSetByExtentOp,      6, 8, "xmin ymin xmax ymax ?duration? ?srs?"},
+    {"get",     2, CameraGetViewpointOp,     2, 2, ""},
+    {"go",      2, CameraGoOp,               4, 6, "x y ?zoomFactor? ?duration?"},
+    {"lextent", 1, CameraSetByLayerExtentOp, 3, 4, "layerName ?duration?"},
+    {"orient",  1, CameraOrientOp,           6, 6, "qw qx qy qz"},
+    {"pan",     1, CameraPanOp,              4, 4, "panX panY"},
+    {"reset",   4, CameraResetOp,            2, 3, "?all?"},
+    {"restore", 4, CameraRestoreViewpointOp, 3, 4, "name ?duration?"},
+    {"rotate",  2, CameraRotateOp,           4, 4, "azimuth elevation"},
+    {"save",    2, CameraSaveViewpointOp,    3, 3, "name"},
+    {"set",     2, CameraSetViewpointOp,     8, 11, "x y z heading pitch distance ?duration? ?srs? ?vertDatum?"},
+    {"throw",   1, CameraThrowOp,            3, 3, "bool"},
+    {"zoom",    1, CameraZoomOp,             3, 3, "zoomAmount"}
+};
+static int nCameraOps = NumCmdSpecs(cameraOps);
+
+static int
+CameraCmd(ClientData clientData, Tcl_Interp *interp, int objc, 
+          Tcl_Obj *const *objv)
+{
+    Tcl_ObjCmdProc *proc;
+
+    proc = GetOpFromObj(interp, nCameraOps, cameraOps,
+                        CMDSPEC_ARG1, objc, objv, 0);
+    if (proc == NULL) {
+        return TCL_ERROR;
+    }
+    return (*proc) (clientData, interp, objc, objv);
+}
+
+static int
+ClientInfoCmd(ClientData clientData, Tcl_Interp *interp, int objc, 
+              Tcl_Obj *const *objv)
+{
+    Tcl_DString ds;
+    Tcl_Obj *objPtr, *listObjPtr, **items;
+    int numItems;
+    char buf[BUFSIZ];
+    const char *string;
+    int length;
+    int result;
+    static bool first = true;
+
+    /* Client arguments. */
+    if (Tcl_ListObjGetElements(interp, objv[1], &numItems, &items) != TCL_OK ||
+        numItems % 2 != 0) {
+        return TCL_ERROR;
+    }
+    const char *username = NULL;
+    const char *hubURL = NULL;
+    //const char *session = NULL;
+    for (int i = 0; i < numItems; i+=2) {
+        const char *name = Tcl_GetString(items[i]);
+        const char *val = Tcl_GetString(items[i+1]);
+        if (strcmp(name, "user") == 0) {
+            username = val;
+        }
+        if (strcmp(name, "hub_url") == 0) {
+            hubURL = val;
+        }
+        //if (strcmp(name, "session") == 0) {
+        //    session = val;
+        //}
+    }
+    if (username != NULL) {
+        IData::iDataInit(username, hubURL);
+    }
+
+    /* First try to see if we already have an open descriptor */
+    int fd = getStatsFile();
+    if (fd < 0) {
+        /* Use the initial client key value pairs as the parts for generating
+         * a unique file name. */
+        fd = GeoVis::getStatsFile(Tcl_GetString(objv[1]));
+        if (fd < 0) {
+            Tcl_AppendResult(interp, "can't open stats file: ", 
+                             Tcl_PosixError(interp), (char *)NULL);
+            return TCL_ERROR;
+        }
+    }
+    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
+    Tcl_IncrRefCount(listObjPtr);
+    if (first) {
+        first = false;
+        objPtr = Tcl_NewStringObj("render_start", 12);
+        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
+        /* renderer */
+        objPtr = Tcl_NewStringObj("renderer", 8);
+        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
+        objPtr = Tcl_NewStringObj("geovis", 6);
+        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
+        /* pid */
+        objPtr = Tcl_NewStringObj("pid", 3);
+        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
+        Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewIntObj(getpid()));
+        /* host */
+        objPtr = Tcl_NewStringObj("host", 4);
+        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
+        gethostname(buf, BUFSIZ-1);
+        buf[BUFSIZ-1] = '\0';
+        objPtr = Tcl_NewStringObj(buf, -1);
+        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
+        /* date */
+        Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("date", 4));
+        strcpy(buf, ctime(&GeoVis::g_stats.start.tv_sec));
+        buf[strlen(buf) - 1] = '\0';
+        Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj(buf, -1));
+        /* date_secs */
+        Tcl_ListObjAppendElement(interp, listObjPtr,
+                                 Tcl_NewStringObj("date_secs", 9));
+        Tcl_ListObjAppendElement(interp, listObjPtr,
+                                 Tcl_NewLongObj(GeoVis::g_stats.start.tv_sec));
+    } else {
+        objPtr = Tcl_NewStringObj("render_info", 11);
+        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
+        struct timeval now;
+        gettimeofday(&now, NULL);
+        /* date */
+        Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("date", 4));
+        strcpy(buf, ctime(&now.tv_sec));
+        buf[strlen(buf) - 1] = '\0';
+        Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj(buf, -1));
+        /* date_secs */
+        Tcl_ListObjAppendElement(interp, listObjPtr,
+                                 Tcl_NewStringObj("date_secs", 9));
+        Tcl_ListObjAppendElement(interp, listObjPtr,
+                                 Tcl_NewLongObj(now.tv_sec));
+    }
+    /* client items */
+    for (int i = 0; i < numItems; i++) {
+        Tcl_ListObjAppendElement(interp, listObjPtr, items[i]);
+    }
+    Tcl_DStringInit(&ds);
+    string = Tcl_GetStringFromObj(listObjPtr, &length);
+    Tcl_DStringAppend(&ds, string, length);
+    Tcl_DStringAppend(&ds, "\n", 1);
+    result = GeoVis::writeToStatsFile(fd, Tcl_DStringValue(&ds), 
+                                      Tcl_DStringLength(&ds));
+    Tcl_DStringFree(&ds);
+    Tcl_DecrRefCount(listObjPtr);
+    return result;
+}
+
+static int
+ColorMapAddOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+              Tcl_Obj *const *objv)
+{
+    const char *name = Tcl_GetString(objv[2]);
+    int cmapc;
+    Tcl_Obj **cmapv = NULL;
+
+    if (Tcl_ListObjGetElements(interp, objv[3], &cmapc, &cmapv) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    if ((cmapc % 5) != 0) {
+        Tcl_AppendResult(interp, "wrong # elements in colormap: should be ",
+                         "{ value r g b a... }", (char*)NULL);
+        return TCL_ERROR;
+    }
+
+    osg::TransferFunction1D *colorMap = new osg::TransferFunction1D;
+    colorMap->allocate(256);
+
+    for (int i = 0; i < cmapc; i += 5) {
+        double val[5];
+        for (int j = 0; j < 5; j++) {
+            if (Tcl_GetDoubleFromObj(interp, cmapv[i+j], &val[j]) != TCL_OK) {
+                delete colorMap;
+                return TCL_ERROR;
+            }
+            // Need to permit un-normalized values
+            if (j > 0 && (val[j] < 0.0 || val[j] > 1.0)) {
+                Tcl_AppendResult(interp, "bad colormap value \"",
+                                 Tcl_GetString(cmapv[i+j]),
+                                 "\": should be in the range [0,1]", (char*)NULL);
+                delete colorMap;
+                return TCL_ERROR;
+            }
+        }
+        colorMap->setColor(val[0], osg::Vec4f(val[1], val[2], val[3], val[4]), false);
+    }
+
+    colorMap->updateImage();
+
+    g_renderer->addColorMap(name, colorMap);
+    return TCL_OK;
+}
+
+static int
+ColorMapDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+                 Tcl_Obj *const *objv)
+{
+    if (objc == 3) {
+        const char *name = Tcl_GetString(objv[2]);
+#if 0
+        if (strcmp(name, "default") == 0 ||
+            strcmp(name, "grayDefault") == 0) {
+            WARN("Cannot delete a default color map");
+            return TCL_ERROR;
+        }
+#endif
+        g_renderer->deleteColorMap(name);
+    } else {
+        g_renderer->deleteColorMap("all");
+    }
+
+    return TCL_OK;
+}
+
+static int
+ColorMapNumTableEntriesOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+                          Tcl_Obj *const *objv)
+{
+    int numEntries;
+    if (Tcl_GetIntFromObj(interp, objv[2], &numEntries) != TCL_OK) {
+        const char *str = Tcl_GetString(objv[2]);
+        if (str[0] == 'd' && strcmp(str, "default") == 0) {
+            numEntries = -1;
+        } else {
+            Tcl_AppendResult(interp, "bad colormap resolution value \"", str,
+                             "\": should be a positive integer or \"default\"", (char*)NULL);
+            return TCL_ERROR;
+        }
+    } else if (numEntries < 1) {
+        Tcl_AppendResult(interp, "bad colormap resolution value \"", Tcl_GetString(objv[2]),
+                         "\": should be a positive integer or \"default\"", (char*)NULL);
+        return TCL_ERROR;
+    }
+    if (objc == 4) {
+        const char *name = Tcl_GetString(objv[3]);
+
+        g_renderer->setColorMapNumberOfTableEntries(name, numEntries);
+    } else {
+        g_renderer->setColorMapNumberOfTableEntries("all", numEntries);
+    }
+    return TCL_OK;
+}
+
+static CmdSpec colorMapOps[] = {
+    {"add",    1, ColorMapAddOp,             4, 4, "colorMapName colormap"},
+    {"define", 3, ColorMapAddOp,             4, 4, "colorMapName colormap"},
+    {"delete", 3, ColorMapDeleteOp,          2, 3, "?colorMapName?"},
+    {"res",    1, ColorMapNumTableEntriesOp, 3, 4, "numTableEntries ?colorMapName?"}
+};
+static int nColorMapOps = NumCmdSpecs(colorMapOps);
+
+static int
+ColorMapCmd(ClientData clientData, Tcl_Interp *interp, int objc, 
+            Tcl_Obj *const *objv)
+{
+    Tcl_ObjCmdProc *proc;
+
+    proc = GetOpFromObj(interp, nColorMapOps, colorMapOps,
+                        CMDSPEC_ARG1, objc, objv, 0);
+    if (proc == NULL) {
+        return TCL_ERROR;
+    }
+    return (*proc) (clientData, interp, objc, objv);
+}
+
+static int
+FilePutOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+          Tcl_Obj *const *objv)
+{
+    long size;
+    const char *path = Tcl_GetString(objv[2]);
+    if (Tcl_GetLongFromObj(interp, objv[4], &size) != TCL_OK ||
+        size < 1) {
+        return TCL_ERROR;
+    }
+    char *data = (char *)malloc((size_t)size);
+    if (!SocketRead(data, size)) {
+        free(data);
+        return TCL_ERROR;
+    }
+    if (g_renderer->getCacheDirectory().empty()) {
+        g_renderer->setupCache();
+    }
+    std::string dirPath = osgDB::getFilePath(path);
+    std::string fileName = osgDB::getSimpleFileName(path);
+    std::ostringstream fullPath;
+    fullPath << g_renderer->getCacheDirectory() << "/" << dirPath;
+    if (!dirPath.empty()) {
+        TRACE("Make dir: %s", fullPath.str().c_str());
+        osgDB::makeDirectory(fullPath.str());
+        fullPath << "/";
+    }
+    fullPath << fileName;
+    FILE *fp = fopen(fullPath.str().c_str(), "wb");
+    if (fp == NULL) {
+        free(data);
+        return TCL_ERROR;
+    }
+    int bytesWritten = fwrite(data, 1, (size_t)size, fp);
+    fclose(fp);
+    free(data);
+    TRACE("Wrote %d bytes to %s", bytesWritten, fullPath.str().c_str());
+    return (bytesWritten == size) ? TCL_OK : TCL_ERROR;
+}
+
+static CmdSpec fileOps[] = {
+    {"put",    1, FilePutOp, 5, 5, "path type size"}
+};
+static int nFileOps = NumCmdSpecs(fileOps);
+
+static int
+FileCmd(ClientData clientData, Tcl_Interp *interp, int objc, 
+          Tcl_Obj *const *objv)
+{
+    Tcl_ObjCmdProc *proc;
+
+    proc = GetOpFromObj(interp, nFileOps, fileOps,
+                        CMDSPEC_ARG1, objc, objv, 0);
+    if (proc == NULL) {
+        return TCL_ERROR;
+    }
+    return (*proc) (clientData, interp, objc, objv);
+}
+
+static int
+ImageFlushCmd(ClientData clientData, Tcl_Interp *interp, int objc, 
+              Tcl_Obj *const *objv)
+{
+    lastCmdStatus = TCL_BREAK;
+    return TCL_OK;
+}
+
+static int
+KeyPressOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+           Tcl_Obj *const *objv)
+{
+    int key;
+    if (Tcl_GetIntFromObj(interp, objv[2], &key) != TCL_OK) {
+        return TCL_ERROR;
+    }
+
+    g_renderer->keyPress(key);
+    return TCL_OK;
+}
+
+static int
+KeyReleaseOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+               Tcl_Obj *const *objv)
+{
+    int key;
+    if (Tcl_GetIntFromObj(interp, objv[2], &key) != TCL_OK) {
+        return TCL_ERROR;
+    }
+
+    g_renderer->keyRelease(key);
+    return TCL_OK;
+}
+
+static CmdSpec keyOps[] = {
+    {"press",    1, KeyPressOp,       3, 3, "key"},
+    {"release",  1, KeyReleaseOp,     3, 3, "key"},
+};
+static int nKeyOps = NumCmdSpecs(keyOps);
+
+static int
+KeyCmd(ClientData clientData, Tcl_Interp *interp, int objc, 
+         Tcl_Obj *const *objv)
+{
+    Tcl_ObjCmdProc *proc;
+
+    proc = GetOpFromObj(interp, nKeyOps, keyOps,
+                        CMDSPEC_ARG1, objc, objv, 0);
+    if (proc == NULL) {
+        return TCL_ERROR;
+    }
+    return (*proc) (clientData, interp, objc, objv);
+}
+
+static int
+LegendCmd(ClientData clientData, Tcl_Interp *interp, int objc, 
+          Tcl_Obj *const *objv)
+{
+    if (objc < 4) {
+        Tcl_AppendResult(interp, "wrong # args: should be \"",
+                Tcl_GetString(objv[0]), " colormapName width height\"", (char*)NULL);
+        return TCL_ERROR;
+    }
+    const char *colorMapName = Tcl_GetString(objv[1]);
+
+    int width, height;
+    if (Tcl_GetIntFromObj(interp, objv[2], &width) != TCL_OK ||
+        Tcl_GetIntFromObj(interp, objv[3], &height) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    bool opaque = true;
+    if (objc > 4) {
+        if (GetBooleanFromObj(interp, objv[4], &opaque) != TCL_OK) {
+            return TCL_ERROR;
+        }
+    }
+    float bgColor[3];
+    if (objc > 5) {
+        if (GetFloatFromObj(interp, objv[5], &bgColor[0]) != TCL_OK ||
+            GetFloatFromObj(interp, objv[6], &bgColor[1]) != TCL_OK ||
+            GetFloatFromObj(interp, objv[7], &bgColor[2]) != TCL_OK) {
+            return TCL_ERROR;
+        }
+    } else {
+        memset(bgColor, 0, sizeof(float)*3);
+    }
+    osg::ref_ptr<osg::Image> imgData = new osg::Image();
+
+#if defined(RENDER_TARGA) || defined(DEBUG)
+    if (!g_renderer->renderColorMap(colorMapName, width, height, imgData, opaque,
+                                    bgColor, true)) {
+        Tcl_AppendResult(interp, "Color map \"",
+                         colorMapName, "\" was not found", (char*)NULL);
+        return TCL_ERROR;
+    }
+#else
+    if (!g_renderer->renderColorMap(colorMapName, width, height, imgData, opaque,
+                                    bgColor)) {
+        Tcl_AppendResult(interp, "Color map \"",
+                         colorMapName, "\" was not found", (char*)NULL);
+        return TCL_ERROR;
+    }
+#endif
+
+#ifdef DEBUG
+# ifdef RENDER_TARGA
+    writeTGAFile("/tmp/legend.tga", imgData->data(), width, height,
+                 TARGA_BYTES_PER_PIXEL);
+# else
+    writeTGAFile("/tmp/legend.tga", imgData->data(), width, height,
+                 TARGA_BYTES_PER_PIXEL, true);
+# endif
+#else
+    char cmd[256];
+    float min, max;
+    g_renderer->getColorMapRange(colorMapName, &min, &max);
+    snprintf(cmd, sizeof(cmd), "nv>legend {%s} %g %g", colorMapName, min, max);
+
+# ifdef USE_THREADS
+#  ifdef RENDER_TARGA
+    queueTGA(g_outQueue, cmd, imgData->data(), width, height,
+             TARGA_BYTES_PER_PIXEL);
+#  else
+    queuePPM(g_outQueue, cmd, imgData->data(), width, height);
+#  endif
+# else
+#  ifdef RENDER_TARGA
+    writeTGA(g_fdOut, cmd, imgData->data(), width, height,
+             TARGA_BYTES_PER_PIXEL);
+#  else
+    writePPM(g_fdOut, cmd, imgData->data(), width, height);
+#  endif
+# endif // USE_THREADS
+#endif // DEBUG
+
+    return TCL_OK;
+}
+
+static int
+MapAttributionOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+                 Tcl_Obj *const *objv)
+{
+    int len;
+    char *str = Tcl_GetStringFromObj(objv[2], &len);
+#if 0
+    Tcl_DString attrib;
+    Tcl_DStringInit(&attrib);
+    Tcl_Encoding encoding = Tcl_GetEncoding(interp, "identity");
+    Tcl_ExternalToUtfDString(encoding, str, len, &attrib);
+    Tcl_FreeEncoding(encoding);
+    str = Tcl_DStringValue(&attrib);
+#endif
+    g_renderer->setAttribution(str);
+#if 0
+    Tcl_DStringFree(&attrib);
+#endif
+    return TCL_OK;
+}
+
+static int
+MapBoxClearOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+              Tcl_Obj *const *objv)
+{
+    g_renderer->clearBoxSelection();
+    return TCL_OK;
+}
+
+static int
+MapBoxEndOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+            Tcl_Obj *const *objv)
+{
+    int x, y;
+    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
+        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    g_renderer->updateBoxSelection(x, y);
+
+    // Notify client of selected region
+    double latMin, latMax, longMin, longMax;
+    latMin = latMax = longMin = longMax = 0;
+    g_renderer->getBoxSelection(&latMin, &latMax, &longMin, &longMax);
+
+    std::ostringstream oss;
+    oss << "nv>select region "
+        << longMin << " "
+        << latMin << " "
+        << longMax << " "
+        << latMax << "\n";
+    std::string ostr = oss.str();
+    size_t len = ostr.size();
+    queueResponse(ostr.c_str(), len, Response::VOLATILE);
+    return TCL_OK;
+}
+
+static int
+MapBoxInitOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+             Tcl_Obj *const *objv)
+{
+    int x, y;
+    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
+        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    g_renderer->initBoxSelection(x, y);
+    return TCL_OK;
+}
+
+static int
+MapBoxUpdateOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+               Tcl_Obj *const *objv)
+{
+    int x, y;
+    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
+        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    g_renderer->updateBoxSelection(x, y);
+    return TCL_OK;
+}
+
+static CmdSpec mapBoxOps[] = {
+    {"clear",   1, MapBoxClearOp,   3, 3, ""},
+    {"end",     1, MapBoxEndOp,     5, 5, "x y"},
+    {"init",    1, MapBoxInitOp,    5, 5, "x y"},
+    {"update",  1, MapBoxUpdateOp,  5, 5, "x y"},
+};
+static int nMapBoxOps = NumCmdSpecs(mapBoxOps);
+
+static int
+MapBoxOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+         Tcl_Obj *const *objv)
+{
+    Tcl_ObjCmdProc *proc;
+
+    proc = GetOpFromObj(interp, nMapBoxOps, mapBoxOps,
+                        CMDSPEC_ARG2, objc, objv, 0);
+    if (proc == NULL) {
+        return TCL_ERROR;
+    }
+    return (*proc) (clientData, interp, objc, objv);
+}
+
+static int
+MapCoordsOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+            Tcl_Obj *const *objv)
+{
+    int tokenLength;
+    const char *token = Tcl_GetStringFromObj(objv[2], &tokenLength);
+    int numCoords;
+    Tcl_Obj **coords;
+    if (Tcl_ListObjGetElements(interp, objv[3], &numCoords, &coords) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    if (numCoords == 0) {
+        Tcl_AppendResult(interp, "no x,y coordinates in list", (char *)NULL);
+        return TCL_ERROR;
+    }
+    if (numCoords & 1) {
+        Tcl_AppendResult(interp, "odd number of x, y coordinates in list",
+                         (char *)NULL);
+        return TCL_ERROR;
+    }
+    const osgEarth::SpatialReference *outSRS = NULL;
+    if (objc > 4) {
+        std::string srsInit;
+        std::string verticalDatum;
+        srsInit = Tcl_GetString(objv[4]);
+        if (objc > 5) {
+            verticalDatum = Tcl_GetString(objv[5]);
+        }
+        outSRS = osgEarth::SpatialReference::get(srsInit, verticalDatum);
+        if (outSRS == NULL) {
+            Tcl_AppendResult(interp, "bad SRS \"", srsInit.c_str(), "\"",
+                             (char*)NULL);
+            return TCL_ERROR;
+        }
+    }
+    Tcl_Obj *listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
+    std::vector<osg::Vec3d> coordVec;
+    for (int i = 0; i < numCoords; i += 2) {
+        int x, y;
+        if ((Tcl_GetIntFromObj(interp, coords[i], &x) != TCL_OK) ||
+            (Tcl_GetIntFromObj(interp, coords[i+1], &y) != TCL_OK)) {
+            return TCL_ERROR;
+        }
+        osgEarth::GeoPoint mapPoint;
+        if (g_renderer->mapMouseCoords(x, y, mapPoint)) {
+            // altmode absolute
+            coordVec.push_back(osg::Vec3d(mapPoint.x(), mapPoint.y(), mapPoint.z()));
+        } else {
+            coordVec.push_back(osg::Vec3d(std::numeric_limits<double>::quiet_NaN(),
+                                          std::numeric_limits<double>::quiet_NaN(),
+                                          std::numeric_limits<double>::quiet_NaN()));
+        }
+    }
+    if (outSRS != NULL) {
+        g_renderer->getMapSRS()->transform(coordVec, outSRS);
+    } else {
+        outSRS = g_renderer->getMapSRS();
+    }
+    for (std::vector<osg::Vec3d>::iterator itr = coordVec.begin();
+         itr != coordVec.end(); ++itr) {
+        Tcl_Obj *objPtr = Tcl_NewDoubleObj(itr->x());
+        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
+        objPtr = Tcl_NewDoubleObj(itr->y());
+        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
+        objPtr = Tcl_NewDoubleObj(itr->z());
+        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
+    }
+    // send coords to client
+    int listLength;
+    const char *string = Tcl_GetStringFromObj(listObjPtr, &listLength);
+    size_t length = listLength + tokenLength + 
+        outSRS->getHorizInitString().length() +
+        outSRS->getVertInitString().length() +
+        25;
+    char *mesg = new char[length];
+    length = snprintf(mesg, length, "nv>map coords %s {%s} {%s} {%s}\n", token, string,
+                      outSRS->getHorizInitString().c_str(),
+                      outSRS->getVertInitString().c_str());
+    Tcl_DecrRefCount(listObjPtr);
+    queueResponse(mesg, length, Response::VOLATILE);
+    delete [] mesg;
+    return TCL_OK;
+}
+
+static int
+MapGraticuleOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+               Tcl_Obj *const *objv)
+{
+    bool state;
+    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    if (objc > 3) {
+        Renderer::GraticuleType type;
+        char *typeStr = Tcl_GetString(objv[3]);
+        if (typeStr[0] == 'g' && strcmp(typeStr, "geodetic") == 0) {
+            type = Renderer::GRATICULE_GEODETIC;
+        } else if (typeStr[0] == 's' && strcmp(typeStr, "shader") == 0) {
+            type = Renderer::GRATICULE_SHADER;
+        } else if (typeStr[0] == 'u' && strcmp(typeStr, "utm") == 0) {
+            type = Renderer::GRATICULE_UTM;
+        } else if (typeStr[0] == 'm' && strcmp(typeStr, "mgrs") == 0) {
+            type = Renderer::GRATICULE_MGRS;
+        } else {
+            return TCL_ERROR;
+        }
+        g_renderer->setGraticule(state, type);
+    } else {
+        g_renderer->setGraticule(state);
+    }
+    return TCL_OK;
+}
+
+static int
+MapLayerAddOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+              Tcl_Obj *const *objv)
+{
+    char *name = Tcl_GetString(objv[3]);
+    char *type = Tcl_GetString(objv[4]);
+    unsigned int pos = UINT_MAX;
+    bool cache = true;
+    bool visible = true;
+    if (type[0] == 'i' && strcmp(type, "image") == 0) {
+        bool ret;
+        char *driver = Tcl_GetString(objv[5]);
+        std::string url;
+        if (objc > 6) {
+            char *urlIn = Tcl_GetString(objv[6]);
+            url = g_renderer->getCanonicalPath(std::string(urlIn));
+            if (url.empty()) {
+                Tcl_AppendResult(interp, "file not found: \"",
+                                 urlIn, "\"", (char*)NULL);
+                return TCL_ERROR;
+            }
+        }
+        if (objc > 7) {
+            if (GetBooleanFromObj(interp, objv[7], &cache) != TCL_OK) {
+                return TCL_ERROR;
+            }
+        }
+        bool coverage = false;
+        if (objc > 8) {
+            if (GetBooleanFromObj(interp, objv[8], &coverage) != TCL_OK) {
+                return TCL_ERROR;
+            }
+        }
+        bool shared = false;
+        unsigned int minLOD = 0;
+        unsigned int maxLOD = 23;
+        float minRange = 0.f;
+        float maxRange = FLT_MAX;
+        if (driver[0] == 'a' && strcmp(driver, "agglite") == 0) {
+            osgEarth::Drivers::AGGLiteOptions aggOpts;
+
+            const char *featureDriver = Tcl_GetString(objv[9]);
+            const char *format = Tcl_GetString(objv[10]);
+            if (format && strlen(format) > 0 &&
+                strcmp(format, "json") != 0 && strcmp(format, "gml") != 0) {
+                Tcl_AppendResult(interp, "unknown format \"", format,
+                                 "\": should be 'json' or 'gml'", (char*)NULL);
+                return TCL_ERROR;
+            }
+            const char *typeName = Tcl_GetString(objv[11]);
+
+            long ssSize;
+            if (Tcl_GetLongFromObj(interp, objv[12], &ssSize) != TCL_OK) {
+                return TCL_ERROR;
+            }
+            char *styleSheetStr = (char *)malloc((size_t)(ssSize+1L));
+            if (!SocketRead(styleSheetStr, ssSize)) {
+                free(styleSheetStr);
+                return TCL_ERROR;
+            }
+            styleSheetStr[ssSize] = '\0';
+            osgEarth::Config styleConf("style", styleSheetStr);
+            free(styleSheetStr);
+            styleConf.add("type", "text/css");
+            TRACE("style CSS: %s", styleConf.value().c_str());
+            osgEarth::Config stylesheetConf;
+            stylesheetConf.add(styleConf);
+            aggOpts.styles() = new osgEarth::Symbology::StyleSheet(stylesheetConf);
+
+            aggOpts.optimizeLineSampling() = true;
+            //aggOpts.gamma() = 1.3;
+
+            if (objc > 13) {
+                long scriptSize;
+                if (Tcl_GetLongFromObj(interp, objv[13], &scriptSize) != TCL_OK) {
+                    return TCL_ERROR;
+                }
+                char *scriptStr = NULL;
+                if (scriptSize > 0) {
+                    scriptStr = (char *)malloc((size_t)scriptSize);
+                    if (!SocketRead(scriptStr, scriptSize)) {
+                        free(scriptStr);
+                        return TCL_ERROR;
+                    }
+                }
+                std::string scripts(scriptStr, scriptSize);
+                free(scriptStr);
+                if (!scripts.empty()) {
+                    TRACE("script: %s", scripts.c_str());
+                    osg::ref_ptr<osgEarth::Symbology::StyleSheet::ScriptDef> scriptDef = 
+                        new osgEarth::Symbology::StyleSheet::ScriptDef(scripts);
+                    aggOpts.styles()->setScript(scriptDef.get());
+                } else {
+                    TRACE("no script");
+                }
+            }
+            if (objc > 14) {
+                long selectorsSize;
+                if (Tcl_GetLongFromObj(interp, objv[14], &selectorsSize) != TCL_OK) {
+                    return TCL_ERROR;
+                }
+                char *selectorsStr = (char *)malloc((size_t)selectorsSize);
+                if (!SocketRead(selectorsStr, selectorsSize)) {
+                    free(selectorsStr);
+                    return TCL_ERROR;
+                }
+                Tcl_Obj *selectorsObj = Tcl_NewStringObj(selectorsStr, selectorsSize);
+                free(selectorsStr);
+                int numSelectors;
+                Tcl_Obj **selectors;
+                if (Tcl_ListObjGetElements(interp, selectorsObj, &numSelectors, &selectors) != TCL_OK) {
+                    return TCL_ERROR;
+                }
+                for (int i = 0; i < numSelectors; i++) {
+                    int numFields;
+                    Tcl_Obj **fields;
+                    if (Tcl_ListObjGetElements(interp, selectors[i], &numFields, &fields) != TCL_OK ||
+                        numFields % 2 != 0) {
+                        return TCL_ERROR;
+                    }
+                    std::map<std::string, std::string> fieldMap;
+                    std::map<std::string, std::string>::iterator itr;
+                    for (int f = 0; f < numFields; f+=2) {
+                        char *name = Tcl_GetString(fields[f]);
+                        char *value = Tcl_GetString(fields[f+1]);
+                        fieldMap[name] = value;
+                        TRACE("selector[%s] = %s", name, value);
+                    }
+                    osgEarth::Symbology::StyleSelector ss;
+                    itr = fieldMap.find("name");
+                    if (itr != fieldMap.end()) {
+                        ss.name() = itr->second;
+                    }
+                    itr = fieldMap.find("style");
+                    if (itr != fieldMap.end()) {
+                        ss.styleName() = itr->second;
+                    }
+                    itr = fieldMap.find("styleExpression");
+                    if (itr != fieldMap.end()) {
+                        TRACE("selector: %s", itr->second.c_str());
+                        ss.styleExpression() = osgEarth::Symbology::StringExpression(itr->second);
+                    }
+                    itr = fieldMap.find("query");
+                    if (itr != fieldMap.end()) {
+                        osgEarth::Symbology::Query query;
+                        query.expression() = itr->second;
+                        itr = fieldMap.find("queryOrderBy");
+                        if (itr != fieldMap.end()) {
+                            query.orderby() = itr->second;
+                        }
+                        itr = fieldMap.find("queryBounds");
+                        if (itr !=  fieldMap.end()) {
+                            double xmin, ymin, xmax, ymax;
+                            if (sscanf(itr->second.c_str(), "%lf %lf %lf %lf", &xmin, &ymin, &xmax, &ymax) != 4) {
+                                return TCL_ERROR;
+                            }
+                            TRACE("query bounds: %g %g %g %g", xmin, ymin, xmax, ymax);
+                            query.bounds() = osgEarth::Bounds(xmin, ymin, xmax, ymax);
+                        }
+                        ss.query() = query;
+                    }
+                    aggOpts.styles()->selectors().push_back(ss);
+                }
+                Tcl_DecrRefCount(selectorsObj);
+            }
+            if (featureDriver[0] == 'd' && strcmp(featureDriver, "db") == 0) {
+                osgEarth::Drivers::OGRFeatureOptions opts;
+                opts.name() = name;
+                // Get unmodified connection string
+                opts.connection() = Tcl_GetString(objv[6]);
+                opts.layer() = typeName;
+                aggOpts.featureOptions() = opts;
+            } else if (featureDriver[0] == 'o' && strcmp(featureDriver, "ogr") == 0) {
+                osgEarth::Drivers::OGRFeatureOptions opts;
+                opts.name() = name;
+                if (osgDB::getLowerCaseFileExtension(url) == "csv") {
+                    url = loadCSVLayer(url.c_str());
+                }
+                opts.url() = url;
+                aggOpts.featureOptions() = opts;
+            } else if (featureDriver[0] == 'o' && strcmp(featureDriver, "tfs") == 0) {
+                osgEarth::Drivers::TFSFeatureOptions opts;
+                opts.name() = name;
+                opts.url() = url;
+                opts.format() = format;
+                aggOpts.featureOptions() = opts;
+            } else if (featureDriver[0] == 'o' && strcmp(featureDriver, "wfs") == 0) {
+                osgEarth::Drivers::WFSFeatureOptions opts;
+                opts.name() = name;
+                opts.url() = url;
+                opts.outputFormat() = format;
+                opts.typeName() = typeName;
+                aggOpts.featureOptions() = opts;
+            } else {
+                Tcl_AppendResult(interp, "unknown feature driver \"", driver,
+                                 "\": should be 'db', 'ogr', 'tfs', or 'wfs'", (char*)NULL);
+                return TCL_ERROR;
+            }
+            ret = g_renderer->addImageLayer(name, aggOpts, pos, cache, coverage, shared, visible, minLOD, maxLOD);
+        } else if (driver[0] == 'c' && strcmp(driver, "colorramp") == 0) {
+            osgEarth::Drivers::ColorRampOptions colorRampOpts;
+            char *edriver = Tcl_GetString(objv[9]);
+            char *profile = Tcl_GetString(objv[10]);
+            char *colormap = Tcl_GetString(objv[11]);
+            if (edriver[0] == 'g' && strcmp(edriver, "gdal") == 0) {
+                osgEarth::Drivers::GDALOptions opts;
+                opts.url() = url;
+                osgEarth::ElevationLayerOptions elevOpts(name, opts);
+                if (!cache) {
+                    elevOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
+                }
+                if (profile != NULL) {
+                    elevOpts.driver()->profile() = osgEarth::ProfileOptions(profile);
+                }
+#if 0
+                {
+                    osgEarth::ProfileOptions profile;
+                    profile.srsString() = srsString;
+                    profile.vsrsString() = vsrsString;
+                    profile.bounds() = osgEarth::Bounds(xmin, ymin, xmax, ymax);
+                    elevOpts.driver()->profile() = profile;
+                }
+#endif
+                colorRampOpts.elevationLayer() = elevOpts;
+            } else if (edriver[0] == 't' && strcmp(edriver, "tms") == 0) {
+                osgEarth::Drivers::TMSOptions opts;
+                //char *tmsType = Tcl_GetString(objv[9]);
+                //char *format = Tcl_GetString(objv[10]);
+                opts.url() = url;
+                //opts.tmsType() = tmsType;
+                //opts.format() = format;
+                osgEarth::ElevationLayerOptions elevOpts(name, opts);
+                if (!cache) {
+                    elevOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
+                }
+                if (profile != NULL) {
+                    elevOpts.driver()->profile() = osgEarth::ProfileOptions(profile);
+                }
+                colorRampOpts.elevationLayer() = elevOpts;
+            }
+            colorRampOpts.ramp() = g_renderer->getColorMapFilePath(colormap);
+            ret = g_renderer->addImageLayer(name, colorRampOpts, pos, cache, coverage, shared, visible, minLOD, maxLOD);
+        } else if (driver[0] == 'd' && strcmp(driver, "debug") == 0) {
+            osgEarth::Drivers::DebugOptions opts;
+            ret = g_renderer->addImageLayer(name, opts, pos, cache, coverage, shared, visible, minLOD, maxLOD);
+        } else if (driver[0] == 'a' && strcmp(driver, "arcgis") == 0) {
+            osgEarth::Drivers::ArcGISOptions opts;
+            opts.url() = url;
+            if (objc > 9) {
+                opts.token() = Tcl_GetString(objv[9]);
+            }
+            if (objc > 10) {
+                opts.layers() = Tcl_GetString(objv[10]);
+            }
+            //opts.format() = Tcl_GetString(objv[11]);
+            ret = g_renderer->addImageLayer(name, opts, pos, cache, coverage, shared, visible, minLOD, maxLOD);
+            g_renderer->setImageLayerVisibleRange(name, minRange, maxRange);
+        } else if (driver[0] == 'g' && strcmp(driver, "gdal") == 0) {
+            osgEarth::Drivers::GDALOptions opts;
+            opts.url() = url;
+            ret = g_renderer->addImageLayer(name, opts, pos, cache, coverage, shared, visible, minLOD, maxLOD);
+            g_renderer->setImageLayerVisibleRange(name, minRange, maxRange);
+        } else if (driver[0] == 't' && strcmp(driver, "tms") == 0) {
+            osgEarth::Drivers::TMSOptions opts;
+            opts.url() = url;
+            //opts.tmsType() = Tcl_GetString(objv[9]);
+            //opts.format() = Tcl_GetString(objv[10]);
+            ret = g_renderer->addImageLayer(name, opts, pos, cache, coverage, shared, visible, minLOD, maxLOD);
+            g_renderer->setImageLayerVisibleRange(name, minRange, maxRange);
+        } else if (driver[0] == 'w' && strcmp(driver, "wms") == 0) {
+            osgEarth::Drivers::WMSOptions opts;
+            char *wmsLayers = Tcl_GetString(objv[9]);
+            char *format = Tcl_GetString(objv[10]);
+            bool transparent;
+            if (GetBooleanFromObj(interp, objv[11], &transparent) != TCL_OK) {
+                return TCL_ERROR;
+            }
+            opts.url() = url;
+            opts.layers() = wmsLayers;
+            opts.format() = format;
+            opts.transparent() = transparent;
+            if (objc > 12) {
+                char *times = Tcl_GetString(objv[12]);
+                opts.times() = times;
+            }
+            if (objc > 13) {
+                double frameSeconds;
+                if (Tcl_GetDoubleFromObj(interp, objv[13], &frameSeconds) != TCL_OK) {
+                    return TCL_ERROR;
+                }
+                opts.secondsPerFrame() = frameSeconds;
+            }
+
+            ret = g_renderer->addImageLayer(name, opts, pos, cache, coverage, shared, visible, minLOD, maxLOD);
+            g_renderer->setImageLayerVisibleRange(name, minRange, maxRange);
+        } else if (driver[0] == 'x' && strcmp(driver, "xyz") == 0) {
+            osgEarth::Drivers::XYZOptions opts;
+            opts.url() = url;
+            opts.profile() = osgEarth::ProfileOptions("global-mercator");
+            //bool invertY = false;
+            //opts.invertY() = invertY;
+            //opts.format() = Tcl_GetString(objv[9]);
+            ret = g_renderer->addImageLayer(name, opts, pos, cache, coverage, shared, visible, minLOD, maxLOD);
+            g_renderer->setImageLayerVisibleRange(name, minRange, maxRange);
+        } else {
+            Tcl_AppendResult(interp, "unknown image driver \"", driver,
+                             "\": should be 'debug', 'gdal', 'tms', 'wms' or 'xyz'", (char*)NULL);
+            return TCL_ERROR;
+        }
+        if (!ret) {
+            Tcl_AppendResult(interp, "Failed to add image layer \"", name, "\"", (char*)NULL);
+            return TCL_ERROR;
+        }
+    } else if (type[0] == 'e' && strcmp(type, "elevation") == 0) {
+        char *driver = Tcl_GetString(objv[5]);
+        char *urlIn = Tcl_GetString(objv[6]);
+        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
+        if (url.empty()) {
+            Tcl_AppendResult(interp, "file not found: \"",
+                             urlIn, "\"", (char*)NULL);
+            return TCL_ERROR;
+        }
+        if (objc > 7) {
+            if (GetBooleanFromObj(interp, objv[7], &cache) != TCL_OK) {
+                return TCL_ERROR;
+            }
+        }
+        // GDAL does not report vertical datum
+        // Options: geodetic (default), egm84, egm96, egm2008
+        const char *verticalDatum = NULL;
+        if (objc > 8) {
+            verticalDatum = Tcl_GetString(objv[8]);
+        }
+        int minLOD = 0;
+        int maxLOD = 23;
+        if (driver[0] == 'g' && strcmp(driver, "gdal") == 0) {
+            osgEarth::Drivers::GDALOptions opts;
+            opts.url() = url;
+            g_renderer->addElevationLayer(name, opts, pos, cache, visible, verticalDatum, minLOD, maxLOD);
+        } else if (driver[0] == 't' && strcmp(driver, "tms") == 0) {
+            osgEarth::Drivers::TMSOptions opts;
+            opts.url() = url;
+            //opts.tmsType() = Tcl_GetString(objv[8]);
+            //opts.format() = Tcl_GetString(objv[9]);
+            g_renderer->addElevationLayer(name, opts, pos, cache, visible, verticalDatum, minLOD, maxLOD);
+        } else if (driver[0] == 'w' && strcmp(driver, "wcs") == 0) {
+            osgEarth::Drivers::WCSOptions opts;
+            opts.url() = url;
+            if (objc > 9) {
+                opts.identifier() = Tcl_GetString(objv[9]);
+            }
+            if (objc > 10) {
+                // default = 'm'
+                opts.elevationUnit() = Tcl_GetString(objv[10]);
+            }
+            if (objc > 11) {
+                // default = 'image/GeoTIFF'
+                opts.format() = Tcl_GetString(objv[11]);
+            }
+            //opts.srs() = Tcl_GetString(objv[12]);
+            //opts.rangeSubset() = Tcl_GetString(objv[13]);
+            g_renderer->addElevationLayer(name, opts, pos, cache, visible, verticalDatum, minLOD, maxLOD);
+        } else {
+            Tcl_AppendResult(interp, "unknown elevation driver \"", driver,
+                             "\": should be 'gdal' or 'tms'", (char*)NULL);
+            return TCL_ERROR;
+        }
+    } else if (type[0] == 'f' && strcmp(type, "feature") == 0) {
+        // Generic feature geometry layer
+        char *driver = Tcl_GetString(objv[5]);
+        char *format = Tcl_GetString(objv[6]);
+        if (format && strlen(format) > 0 &&
+            strcmp(format, "json") != 0 && strcmp(format, "gml") != 0) {
+            Tcl_AppendResult(interp, "unknown format \"", format,
+                             "\": should be 'json' or 'gml'", (char*)NULL);
+            return TCL_ERROR;
+        }
+        char *typeName = Tcl_GetString(objv[7]);
+        char *urlIn = Tcl_GetString(objv[8]);
+        std::string url;
+        if (driver[0] == 'd' && strcmp(driver, "db") == 0) {
+            url = urlIn;
+        } else {
+            url = g_renderer->getCanonicalPath(std::string(urlIn));
+            if (url.empty()) {
+                Tcl_AppendResult(interp, "file not found: \"",
+                                 urlIn, "\"", (char*)NULL);
+                return TCL_ERROR;
+            }
+        }
+        if (GetBooleanFromObj(interp, objv[9], &cache) != TCL_OK) {
+            return TCL_ERROR;
+        }
+
+        long ssSize;
+        if (Tcl_GetLongFromObj(interp, objv[10], &ssSize) != TCL_OK) {
+            return TCL_ERROR;
+        }
+
+        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
+        char *styleSheetStr = (char *)malloc((size_t)(ssSize+1L));
+        if (!SocketRead(styleSheetStr, ssSize)) {
+            free(styleSheetStr);
+            return TCL_ERROR;
+        }
+        styleSheetStr[ssSize] = '\0';
+        osgEarth::Config styleConf("style", styleSheetStr);
+        free(styleSheetStr);
+        styleConf.add("type", "text/css");
+        TRACE("style CSS: %s", styleConf.value().c_str());
+#if 1
+        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
+        // For relative paths:
+        //styleConf.setReferrer(g_renderer->getCacheDirectory());
+        std::string cssString = styleConf.value();
+        std::vector<std::string> blocks;
+        osgEarth::Symbology::CssUtils::split(cssString, blocks);
+        for (std::vector<std::string>::iterator itr = blocks.begin();
+             itr != blocks.end(); ++itr) {
+            osgEarth::Config blockConf(styleConf);
+            blockConf.value() = *itr;
+            osgEarth::Symbology::Style style(blockConf);
+
+            TRACE("Style: %s", style.getName().c_str());
+            TRACE("%s", itr->c_str());
+
+            if (style.has<osgEarth::Symbology::IconSymbol>()) {
+                TRACE("Found icon symbol");
+                osgEarth::Symbology::IconSymbol *is =
+                    style.get<osgEarth::Symbology::IconSymbol>();
+                if (is->url().isSet()) {
+                    TRACE("Icon url before: expr: '%s' eval: '%s'",
+                         is->url()->expr().c_str(), is->url()->eval().c_str());
+                    // eval() will try to evaluate the expr()
+                    std::string path = g_renderer->getCanonicalPath(is->url()->eval());
+                    //setInfix() to set the expr() string, setLiteral() will quote
+                    is->url()->setLiteral(path);
+                    TRACE("Icon url after: %s", path.c_str());
+                }
+            }
+            if (style.has<osgEarth::Symbology::ModelSymbol>()) {
+                osgEarth::Symbology::ModelSymbol *ms =
+                    style.get<osgEarth::Symbology::ModelSymbol>();
+                if (ms->url().isSet()) {
+                    // eval() will try to evaluate the expr()
+                    std::string path = g_renderer->getCanonicalPath(ms->url()->eval());
+                    //setInfix() to set the expr() string, setLiteral() will quote
+                    ms->url()->setLiteral(path);
+                }
+            }
+            // Need to create a new style otherwise the original CSS is used
+            // without the re-written URLs
+            geomOpts.styles()->addStyle(osgEarth::Symbology::Style(style.getConfig(false)));
+        }
+#else
+        osgEarth::Config stylesheetConf;
+        stylesheetConf.add(styleConf);
+        geomOpts.styles() = new osgEarth::Symbology::StyleSheet(stylesheetConf);
+#endif
+
+        if (objc > 11) {
+            long scriptSize;
+            if (Tcl_GetLongFromObj(interp, objv[11], &scriptSize) != TCL_OK) {
+                return TCL_ERROR;
+            }
+            char *scriptStr = NULL;
+            if (scriptSize > 0) {
+                scriptStr = (char *)malloc((size_t)scriptSize);
+                if (!SocketRead(scriptStr, scriptSize)) {
+                    free(scriptStr);
+                    return TCL_ERROR;
+                }
+            }
+            std::string scripts(scriptStr, scriptSize);
+            free(scriptStr);
+            if (!scripts.empty()) {
+                TRACE("script: %s", scripts.c_str());
+                osg::ref_ptr<osgEarth::Symbology::StyleSheet::ScriptDef> scriptDef = 
+                    new osgEarth::Symbology::StyleSheet::ScriptDef(scripts);
+                geomOpts.styles()->setScript(scriptDef.get());
+            } else {
+                TRACE("no script");
+            }
+        }
+        if (objc > 12) {
+            long selectorsSize;
+            if (Tcl_GetLongFromObj(interp, objv[12], &selectorsSize) != TCL_OK) {
+                return TCL_ERROR;
+            }
+            char *selectorsStr = (char *)malloc((size_t)selectorsSize);
+            if (!SocketRead(selectorsStr, selectorsSize)) {
+                free(selectorsStr);
+                return TCL_ERROR;
+            }
+            Tcl_Obj *selectorsObj = Tcl_NewStringObj(selectorsStr, selectorsSize);
+            free(selectorsStr);
+            int numSelectors;
+            Tcl_Obj **selectors;
+            if (Tcl_ListObjGetElements(interp, selectorsObj, &numSelectors, &selectors) != TCL_OK) {
+                return TCL_ERROR;
+            }
+            TRACE("Num Selectors: %d", numSelectors);
+            for (int i = 0; i < numSelectors; i++) {
+                int numFields;
+                Tcl_Obj **fields;
+                if (Tcl_ListObjGetElements(interp, selectors[i], &numFields, &fields) != TCL_OK ||
+                    numFields % 2 != 0) {
+                    return TCL_ERROR;
+                }
+                std::map<std::string, std::string> fieldMap;
+                std::map<std::string, std::string>::iterator itr;
+                for (int f = 0; f < numFields; f+=2) {
+                    char *name = Tcl_GetString(fields[f]);
+                    char *value = Tcl_GetString(fields[f+1]);
+                    fieldMap[name] = value;
+                    TRACE("selector[%s] = %s", name, value);
+                }
+                osgEarth::Symbology::StyleSelector ss;
+                itr = fieldMap.find("name");
+                if (itr != fieldMap.end()) {
+                    ss.name() = itr->second;
+                }
+                itr = fieldMap.find("style");
+                if (itr != fieldMap.end()) {
+                    ss.styleName() = itr->second;
+                }
+                itr = fieldMap.find("styleExpression");
+                if (itr != fieldMap.end()) {
+                    TRACE("selector: %s", itr->second.c_str());
+                    ss.styleExpression() = osgEarth::Symbology::StringExpression(itr->second);
+                }
+                itr = fieldMap.find("query");
+                if (itr != fieldMap.end()) {
+                    osgEarth::Symbology::Query query;
+                    query.expression() = itr->second;
+                    itr = fieldMap.find("queryOrderBy");
+                    if (itr != fieldMap.end()) {
+                        query.orderby() = itr->second;
+                    }
+                    itr = fieldMap.find("queryBounds");
+                    if (itr !=  fieldMap.end()) {
+                        double xmin, ymin, xmax, ymax;
+                        if (sscanf(itr->second.c_str(), "%lf %lf %lf %lf", &xmin, &ymin, &xmax, &ymax) != 4) {
+                            return TCL_ERROR;
+                        }
+                        TRACE("query bounds: %g %g %g %g", xmin, ymin, xmax, ymax);
+                        query.bounds() = osgEarth::Bounds(xmin, ymin, xmax, ymax);
+                    }
+                    ss.query() = query;
+                }
+                geomOpts.styles()->selectors().push_back(ss);
+            }
+            Tcl_DecrRefCount(selectorsObj);
+        }
+        bool terrainPatch = false;
+        if (objc > 13) {
+            if (GetBooleanFromObj(interp, objv[13], &terrainPatch) != TCL_OK) {
+                return TCL_ERROR;
+            }
+        }
+        bool lighting = true;
+        geomOpts.enableLighting() = lighting;
+        geomOpts.minRange() = 0.f;
+        geomOpts.maxRange() = FLT_MAX;
+        if (objc > 14) {
+            float min, max;
+            if (GetFloatFromObj(interp, objv[14], &min) != TCL_OK ||
+                GetFloatFromObj(interp, objv[15], &max) != TCL_OK) {
+                return TCL_ERROR;
+            }
+            geomOpts.minRange() = min;
+            geomOpts.maxRange() = max;
+        }
+        if (driver[0] == 'd' && strcmp(driver, "db") == 0) {
+            osgEarth::Drivers::OGRFeatureOptions opts;
+            opts.name() = name;
+            opts.connection() = url;
+            opts.layer() = typeName;
+            geomOpts.featureOptions() = opts;
+        } else if (driver[0] == 'o' && strcmp(driver, "ogr") == 0) {
+            osgEarth::Drivers::OGRFeatureOptions opts;
+            opts.name() = name;
+            if (osgDB::getLowerCaseFileExtension(url) == "csv") {
+                url = loadCSVLayer(url.c_str());
+            }
+            opts.url() = url;
+            geomOpts.featureOptions() = opts;
+        } else if (driver[0] == 't' && strcmp(driver, "tfs") == 0) {
+            osgEarth::Drivers::TFSFeatureOptions opts;
+            opts.name() = name;
+            opts.url() = url;
+            opts.format() = format;
+            geomOpts.featureOptions() = opts;
+        } else if (driver[0] == 'w' && strcmp(driver, "wfs") == 0) {
+            osgEarth::Drivers::WFSFeatureOptions opts;
+            opts.name() = name;
+            opts.url() = url;
+            opts.outputFormat() = format;
+            opts.typeName() = typeName;
+            geomOpts.featureOptions() = opts;
+        } else if (driver[0] == 'w' && strcmp(driver, "wkt_file") == 0) {
+            osgEarth::Drivers::OGRFeatureOptions opts;
+            opts.name() = name;
+            opts.geometryUrl() = url;
+            geomOpts.featureOptions() = opts;
+        } else {
+            Tcl_AppendResult(interp, "unknown feature driver \"", driver,
+                             "\": should be 'db', 'ogr', 'tfs', 'wfs', or 'wkt_file'", (char*)NULL);
+            return TCL_ERROR;
+        }
+        g_renderer->addModelLayer(name, geomOpts, pos, cache, lighting, visible,
+                                  terrainPatch);
+    } else if (type[0] == 'm' && strcmp(type, "mask") == 0) {
+        char *driver = Tcl_GetString(objv[5]);
+        char *format = Tcl_GetString(objv[6]);
+        if (format && strlen(format) > 0 &&
+            strcmp(format, "json") != 0 && strcmp(format, "gml") != 0) {
+            Tcl_AppendResult(interp, "unknown format \"", format,
+                             "\": should be 'json' or 'gml'", (char*)NULL);
+            return TCL_ERROR;
+        }
+        char *typeName = Tcl_GetString(objv[7]);
+        osgEarth::Drivers::FeatureMaskOptions maskOpts;
+        std::string url;
+        if (objc > 8) {
+            if (driver[0] == 'w' && strcmp(driver, "wkt") == 0) {
+                url = Tcl_GetString(objv[8]);
+                if (url.empty()) {
+                    Tcl_AppendResult(interp, "Empty WKT length",
+                                     (char*)NULL);
+                    return TCL_ERROR;
+                }
+            } else {
+                char *urlIn = Tcl_GetString(objv[8]);
+                url = g_renderer->getCanonicalPath(std::string(urlIn));
+                if (url.empty()) {
+                    Tcl_AppendResult(interp, "file not found: \"",
+                                     urlIn, "\"", (char*)NULL);
+                    return TCL_ERROR;
+                }
+            }
+        }
+        if (driver[0] == 'd' && strcmp(driver, "db") == 0) {
+            osgEarth::Drivers::OGRFeatureOptions opts;
+            opts.name() = name;
+            opts.connection() = url;
+            opts.layer() = typeName;
+            maskOpts.featureOptions() = opts;
+        } else if (driver[0] == 'o' && strcmp(driver, "ogr") == 0) {
+            osgEarth::Drivers::OGRFeatureOptions opts;
+            opts.name() = name;
+            opts.url() = url;
+            maskOpts.featureOptions() = opts;
+        } else if (driver[0] == 't' && strcmp(driver, "tfs") == 0) {
+            osgEarth::Drivers::TFSFeatureOptions opts;
+            opts.name() = name;
+            opts.url() = url;
+            opts.format() = format;
+            maskOpts.featureOptions() = opts;
+        } else if (driver[0] == 'w' && strcmp(driver, "wfs") == 0) {
+            osgEarth::Drivers::WFSFeatureOptions opts;
+            opts.name() = name;
+            opts.url() = url;
+            opts.outputFormat() = format;
+            opts.typeName() = typeName;
+            maskOpts.featureOptions() = opts;
+        } else if (driver[0] == 'w' && strcmp(driver, "wkt") == 0) {
+            // Inline geometry, url is length of following data
+            char *ptr;
+            long len = strtol(url.c_str(), &ptr, 10);
+            if (len <= 0 || *ptr != '\0') {
+                Tcl_AppendResult(interp, "Failed to parse WKT length: \"",
+                                 url.c_str(), "\"", (char*)NULL);
+                return TCL_ERROR;
+            }
+            char *wkt = (char *)malloc((size_t)len);
+            if (!SocketRead(wkt, len)) {
+                free(wkt);
+                Tcl_AppendResult(interp, "Failed to read WKT string",
+                                 (char*)NULL);
+                return TCL_ERROR;
+            }
+            osgEarth::Drivers::OGRFeatureOptions opts;
+            opts.name() = name;
+            TRACE("Geometry: \"%s\"", wkt);
+            osgEarth::Config conf("geometry", std::string(wkt));
+            free(wkt);
+            opts.geometryConfig() = conf;
+            maskOpts.featureOptions() = opts;
+        } else if (driver[0] == 'w' && strcmp(driver, "wkt_file") == 0) {
+            osgEarth::Drivers::OGRFeatureOptions opts;
+            opts.name() = name;
+            opts.geometryUrl() = url;
+            maskOpts.featureOptions() = opts;
+        } else {
+            Tcl_AppendResult(interp, "unknown mask driver \"", driver,
+                             "\": should be 'db', 'ogr', 'tfs', 'wfs', 'wkt', or 'wkt_file'", (char*)NULL);
+            return TCL_ERROR;
+        }
+        int minLOD = 0;
+        if (objc > 9) {
+            if (Tcl_GetIntFromObj(interp, objv[9], &minLOD) != TCL_OK) {
+                return TCL_ERROR;
+            }
+        }
+        g_renderer->addTerrainMaskLayer(name, maskOpts, (unsigned)minLOD);
+    } else if (type[0] == 'm' && strcmp(type, "model") == 0) {
+        char *driver = Tcl_GetString(objv[5]);
+        if (strcmp(driver, "simple") != 0) {
+            return TCL_ERROR;
+        }
+        osgEarth::Drivers::SimpleModelOptions modelOpts;
+        std::string url;
+        if (objc > 6) {
+            char *urlIn = Tcl_GetString(objv[6]);
+            url = g_renderer->getCanonicalPath(std::string(urlIn));
+            if (url.empty()) {
+                Tcl_AppendResult(interp, "file not found: \"",
+                                 urlIn, "\"", (char*)NULL);
+                return TCL_ERROR;
+            }
+        }
+        modelOpts.url() = url;
+        double x, y, z;
+        x = y = z = 0.0;
+        if (objc > 8) {
+            if (Tcl_GetDoubleFromObj(interp, objv[7], &x) != TCL_OK ||
+                Tcl_GetDoubleFromObj(interp, objv[8], &y) != TCL_OK) {
+                return TCL_ERROR;
+            }
+        }
+        if (objc > 9) {
+            if (Tcl_GetDoubleFromObj(interp, objv[9], &z) != TCL_OK) {
+                return TCL_ERROR;
+            }
+        }
+        modelOpts.location() = osg::Vec3d(x, y, z);
+        double rotx = 0.0, roty = 0.0, rotz = 0.0;
+        if (objc > 12) {
+            if (Tcl_GetDoubleFromObj(interp, objv[10], &rotx) != TCL_OK ||
+                Tcl_GetDoubleFromObj(interp, objv[11], &roty) != TCL_OK ||
+                Tcl_GetDoubleFromObj(interp, objv[12], &rotz) != TCL_OK) {
+                return TCL_ERROR;
+            }
+        }
+        if (rotx != 0.0 || roty != 0.0 || rotz != 0.0) {
+            modelOpts.orientation() = osg::Vec3d(rotx, roty, rotz);
+        }
+        modelOpts.paged() = false;
+        bool lighting = true;
+        bool terrainPatch = false;
+        if (objc > 13) {
+            if (GetBooleanFromObj(interp, objv[13], &terrainPatch) != TCL_OK) {
+                return TCL_ERROR;
+            }
+        }
+        g_renderer->addModelLayer(name, modelOpts, pos, cache, lighting,
+                                  visible, terrainPatch);
+    } else if (type[0] == 'i' && strcmp(type, "icon") == 0) {
+        char *driver = Tcl_GetString(objv[5]);
+        char *format = Tcl_GetString(objv[6]);
+        if (format && strlen(format) > 0 &&
+            strcmp(format, "json") != 0 && strcmp(format, "gml") != 0) {
+            Tcl_AppendResult(interp, "unknown format \"", format,
+                             "\": should be 'json' or 'gml'", (char*)NULL);
+            return TCL_ERROR;
+        }
+        char *typeName = Tcl_GetString(objv[7]);
+        char *urlIn = Tcl_GetString(objv[8]);
+        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
+        if (url.empty()) {
+            Tcl_AppendResult(interp, "file not found: \"",
+                             urlIn, "\"", (char*)NULL);
+            return TCL_ERROR;
+        }
+        if (GetBooleanFromObj(interp, objv[9], &cache) != TCL_OK) {
+            return TCL_ERROR;
+        }
+        char *icon = Tcl_GetString(objv[10]);
+        char *scaleExpr = Tcl_GetString(objv[11]);
+        char *headingExpr = Tcl_GetString(objv[12]);
+        bool declutter = false;
+        if (GetBooleanFromObj(interp, objv[13], &declutter) != TCL_OK) {
+            return TCL_ERROR;
+        }
+        char *placementStr = Tcl_GetString(objv[14]);
+        char *alignStr = Tcl_GetString(objv[15]);
+
+        bool lighting = true;
+        osgEarth::Symbology::Style style;
+        osgEarth::Symbology::IconSymbol *is = style.getOrCreateSymbol<osgEarth::Symbology::IconSymbol>();
+        if (icon != NULL && strlen(icon) > 0) {
+            std::string iconFile = g_renderer->getIconFile(icon);
+            if (!iconFile.empty()) {
+                is->url()->setLiteral(iconFile);
+            } else {
+                is->url() = osgEarth::Symbology::StringExpression(icon);
+            }
+        } else {
+            is->url()->setLiteral(g_renderer->getPinIcon());
+        }
+        if (scaleExpr != NULL && strlen(scaleExpr) > 0) {
+            is->scale() = osgEarth::Symbology::NumericExpression(scaleExpr);
+        }
+        if (headingExpr != NULL && strlen(headingExpr) > 0) {
+            is->heading() = osgEarth::Symbology::NumericExpression(headingExpr);
+        }
+        is->declutter() = declutter;
+        is->occlusionCull() = false;
+        is->occlusionCullAltitude() = 200000;
+        is->alignment() = ParseIconAlignment(alignStr, osgEarth::Symbology::IconSymbol::ALIGN_CENTER_BOTTOM);
+        is->placement() = ParseInstancePlacement(placementStr, osgEarth::Symbology::InstanceSymbol::PLACEMENT_VERTEX);
+        //is->density() = 1.0f; // For PLACEMENT_INTERVAL/RANDOM
+#if 0
+        osgEarth::Symbology::AltitudeSymbol *alt = style.getOrCreateSymbol<osgEarth::Symbology::AltitudeSymbol>();
+        alt->clamping() = osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
+        alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_DRAPE;
+        //alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
+        //alt->verticalOffset() = osgEarth::Symbology::NumericExpression();
+        //alt->verticalScale() = osgEarth::Symbology::NumericExpression();
+#endif
+#ifdef USE_DEPTH_OFFSET
+        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
+        rs->depthOffset()->enabled() = true;
+        rs->depthOffset()->minBias() = 1000;
+#endif
+        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
+        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
+        geomOpts.styles()->addStyle(style);
+        geomOpts.enableLighting() = false;
+        geomOpts.minRange() = 0.f;
+        geomOpts.maxRange() = FLT_MAX;
+        if (objc > 16) {
+            float min, max;
+            if (GetFloatFromObj(interp, objv[16], &min) != TCL_OK ||
+                GetFloatFromObj(interp, objv[17], &max) != TCL_OK) {
+                return TCL_ERROR;
+            }
+            geomOpts.minRange() = min;
+            geomOpts.maxRange() = max;
+        }
+        //geomOpts.renderOrder() = int;
+        //geomOpts.depthTestEnabled() = bool;
+        if (driver[0] == 'o' && strcmp(driver, "ogr") == 0) {
+            osgEarth::Drivers::OGRFeatureOptions opts;
+            opts.name() = name;
+            if (osgDB::getLowerCaseFileExtension(url) == "csv") {
+                url = loadCSVLayer(url.c_str());
+            }
+            opts.url() = url;
+            geomOpts.featureOptions() = opts;
+        } else if (driver[0] == 't' && strcmp(driver, "tfs") == 0) {
+            osgEarth::Drivers::TFSFeatureOptions opts;
+            opts.name() = name;
+            opts.url() = url;
+            opts.format() = format;
+            geomOpts.featureOptions() = opts;
+        } else if (driver[0] == 'w' && strcmp(driver, "wfs") == 0) {
+            osgEarth::Drivers::WFSFeatureOptions opts;
+            opts.name() = name;
+            opts.url() = url;
+            opts.outputFormat() = format;
+            opts.typeName() = typeName;
+            geomOpts.featureOptions() = opts;
+        } else {
+            Tcl_AppendResult(interp, "unknown feature driver \"", driver,
+                             "\": should be 'ogr', 'tfs', or 'wfs'", (char*)NULL);
+            return TCL_ERROR;
+        }
+        g_renderer->addModelLayer(name, geomOpts, pos, cache, lighting, visible);
+    } else if (type[0] == 'p' && strcmp(type, "point") == 0) {
+        char *driver = Tcl_GetString(objv[5]);
+        char *format = Tcl_GetString(objv[6]);
+        if (format && strlen(format) > 0 &&
+            strcmp(format, "json") != 0 && strcmp(format, "gml") != 0) {
+            Tcl_AppendResult(interp, "unknown format \"", format,
+                             "\": should be 'json' or 'gml'", (char*)NULL);
+            return TCL_ERROR;
+        }
+        char *typeName = Tcl_GetString(objv[7]);
+        char *urlIn = Tcl_GetString(objv[8]);
+        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
+        if (url.empty()) {
+            Tcl_AppendResult(interp, "file not found: \"",
+                             urlIn, "\"", (char*)NULL);
+            return TCL_ERROR;
+        }
+        if (GetBooleanFromObj(interp, objv[9], &cache) != TCL_OK) {
+            return TCL_ERROR;
+        }
+        float r, g, b;
+        if (GetFloatFromObj(interp, objv[10], &r) != TCL_OK ||
+            GetFloatFromObj(interp, objv[11], &g) != TCL_OK ||
+            GetFloatFromObj(interp, objv[12], &b) != TCL_OK) {
+            return TCL_ERROR;
+        }
+        float ptSize;
+        if (GetFloatFromObj(interp, objv[13], &ptSize) != TCL_OK) {
+            return TCL_ERROR;
+        }
+        bool lighting = true;
+        osgEarth::Symbology::Style style;
+        osgEarth::Symbology::PointSymbol *ps = style.getOrCreateSymbol<osgEarth::Symbology::PointSymbol>();
+        ps->fill()->color() = osgEarth::Symbology::Color(r, g, b);
+        ps->size() = ptSize;
+
+        osgEarth::Symbology::AltitudeSymbol::Clamping clamping =
+            osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
+        if (objc > 14) {
+            clamping = ParseClamping(Tcl_GetString(objv[14]), clamping);
+        }
+        osgEarth::Symbology::AltitudeSymbol::Technique technique =
+            osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
+        if (objc > 15) {
+            technique = ParseClampingTechnique(Tcl_GetString(objv[15]), technique);
+        }
+        osgEarth::Symbology::AltitudeSymbol *alt = style.getOrCreateSymbol<osgEarth::Symbology::AltitudeSymbol>();
+        alt->clamping() = clamping;
+        alt->technique() = technique;
+        //alt->verticalOffset() = osgEarth::Symbology::NumericExpression();
+        //alt->verticalScale() = osgEarth::Symbology::NumericExpression();
+
+#if 1 || defined(USE_DEPTH_OFFSET)
+        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
+        rs->depthOffset()->enabled() = true;
+        rs->depthOffset()->minBias() = 1000;
+#endif
+        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
+        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
+        geomOpts.styles()->addStyle(style);
+        geomOpts.enableLighting() = false;
+        geomOpts.minRange() = 0.f;
+        geomOpts.maxRange() = FLT_MAX;
+        if (objc > 16) {
+            float min, max;
+            if (GetFloatFromObj(interp, objv[16], &min) != TCL_OK ||
+                GetFloatFromObj(interp, objv[17], &max) != TCL_OK) {
+                return TCL_ERROR;
+            }
+            geomOpts.minRange() = min;
+            geomOpts.maxRange() = max;
+        }
+        //geomOpts.renderOrder() = int;
+        //geomOpts.depthTestEnabled() = bool;
+        if (driver[0] == 'o' && strcmp(driver, "ogr") == 0) {
+            osgEarth::Drivers::OGRFeatureOptions opts;
+            opts.name() = name;
+            if (osgDB::getLowerCaseFileExtension(url) == "csv") {
+                url = loadCSVLayer(url.c_str());
+            }
+            opts.url() = url;
+            geomOpts.featureOptions() = opts;
+        } else if (driver[0] == 't' && strcmp(driver, "tfs") == 0) {
+            osgEarth::Drivers::TFSFeatureOptions opts;
+            opts.name() = name;
+            opts.url() = url;
+            opts.format() = format;
+            geomOpts.featureOptions() = opts;
+        } else if (driver[0] == 'w' && strcmp(driver, "wfs") == 0) {
+            osgEarth::Drivers::WFSFeatureOptions opts;
+            opts.name() = name;
+            opts.url() = url;
+            opts.outputFormat() = format;
+            opts.typeName() = typeName;
+            geomOpts.featureOptions() = opts;
+        } else {
+            Tcl_AppendResult(interp, "unknown feature driver \"", driver,
+                             "\": should be 'ogr', 'tfs', or 'wfs'", (char*)NULL);
+            return TCL_ERROR;
+        }
+        g_renderer->addModelLayer(name, geomOpts, pos, cache, lighting, visible);
+    } else if (type[0] == 'p' && strcmp(type, "polygon") == 0) {
+        char *driver = Tcl_GetString(objv[5]);
+        char *format = Tcl_GetString(objv[6]);
+        if (format && strlen(format) > 0 &&
+            strcmp(format, "json") != 0 && strcmp(format, "gml") != 0) {
+            Tcl_AppendResult(interp, "unknown format \"", format,
+                             "\": should be 'json' or 'gml'", (char*)NULL);
+            return TCL_ERROR;
+        }
+        char *typeName = Tcl_GetString(objv[7]);
+        char *urlIn = Tcl_GetString(objv[8]);
+        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
+        if (url.empty()) {
+            Tcl_AppendResult(interp, "file not found: \"",
+                             urlIn, "\"", (char*)NULL);
+            return TCL_ERROR;
+        }
+        if (GetBooleanFromObj(interp, objv[9], &cache) != TCL_OK) {
+            return TCL_ERROR;
+        }
+        float r, g, b;
+        if (GetFloatFromObj(interp, objv[10], &r) != TCL_OK ||
+            GetFloatFromObj(interp, objv[11], &g) != TCL_OK ||
+            GetFloatFromObj(interp, objv[12], &b) != TCL_OK) {
+            return TCL_ERROR;
+        }
+        float lineWidth = 0.0f;
+        if (objc > 13) {
+            if (GetFloatFromObj(interp, objv[13], &lineWidth) != TCL_OK) {
+                return TCL_ERROR;
+            }
+        }
+        float strokeR = 0, strokeG = 0, strokeB = 0;
+        if (objc > 14) {
+            if (GetFloatFromObj(interp, objv[14], &strokeR) != TCL_OK ||
+                GetFloatFromObj(interp, objv[15], &strokeG) != TCL_OK ||
+                GetFloatFromObj(interp, objv[16], &strokeB) != TCL_OK) {
+                return TCL_ERROR;
+            }
+        }
+        osgEarth::Symbology::Style style;
+        osgEarth::Symbology::PolygonSymbol *ps = style.getOrCreateSymbol<osgEarth::Symbology::PolygonSymbol>();
+        ps->fill()->color() = osgEarth::Symbology::Color(r, g, b);
+
+        if (lineWidth > 0.0f) {
+            osgEarth::Symbology::LineSymbol *ls = style.getOrCreateSymbol<osgEarth::Symbology::LineSymbol>();
+            ls->stroke()->color() = osgEarth::Symbology::Color(strokeR, strokeG, strokeB);
+            ls->stroke()->width() = lineWidth;
+            ls->stroke()->widthUnits() = osgEarth::Units::PIXELS;
+            ls->stroke()->lineCap() = osgEarth::Symbology::Stroke::LINECAP_FLAT; // _SQUARE, _ROUND
+            //ls->stroke()->roundingRatio() = 0.4f;
+            ls->stroke()->lineJoin() = osgEarth::Symbology::Stroke::LINEJOIN_MITRE; // _ROUND
+            //ls->stroke()->stipplePattern() = 0; // Bitmask: unsigned short
+            //ls->stroke()->stippleFactor() = 0; // num reps: unsigned
+        }
+
+        osgEarth::Symbology::AltitudeSymbol::Clamping clamping =
+            osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
+        if (objc > 17) {
+            clamping = ParseClamping(Tcl_GetString(objv[17]), clamping);
+        }
+        osgEarth::Symbology::AltitudeSymbol::Technique technique =
+            osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
+        if (objc > 18) {
+            technique = ParseClampingTechnique(Tcl_GetString(objv[18]), technique);
+        }
+        osgEarth::Symbology::AltitudeSymbol *alt = style.getOrCreateSymbol<osgEarth::Symbology::AltitudeSymbol>();
+        alt->clamping() = clamping;
+        alt->technique() = technique;
+        //alt->verticalOffset() = osgEarth::Symbology::NumericExpression();
+        //alt->verticalScale() = osgEarth::Symbology::NumericExpression();
+
+#if 1 || defined(USE_DEPTH_OFFSET)
+        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
+        rs->depthOffset()->enabled() = true;
+        rs->depthOffset()->minBias() = 1000;
+#endif
+        bool terrainPatch = false;
+        if (objc > 19) {
+            if (GetBooleanFromObj(interp, objv[19], &terrainPatch) != TCL_OK) {
+                return TCL_ERROR;
+            }
+        }
+        bool lighting = true;
+        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
+        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
+        geomOpts.styles()->addStyle(style);
+        geomOpts.enableLighting() = lighting;
+        geomOpts.minRange() = 0.f;
+        geomOpts.maxRange() = FLT_MAX;
+        if (objc > 21) {
+            float min, max;
+            if (GetFloatFromObj(interp, objv[20], &min) != TCL_OK ||
+                GetFloatFromObj(interp, objv[21], &max) != TCL_OK) {
+                return TCL_ERROR;
+            }
+            geomOpts.minRange() = min;
+            geomOpts.maxRange() = max;
+        }
+        if (driver[0] == 'o' && strcmp(driver, "ogr") == 0) {
+            osgEarth::Drivers::OGRFeatureOptions opts;
+            opts.name() = name;
+            if (osgDB::getLowerCaseFileExtension(url) == "csv") {
+                url = loadCSVLayer(url.c_str());
+            }
+            opts.url() = url;
+            geomOpts.featureOptions() = opts;
+        } else if (driver[0] == 't' && strcmp(driver, "tfs") == 0) {
+            osgEarth::Drivers::TFSFeatureOptions opts;
+            opts.name() = name;
+            opts.url() = url;
+            opts.format() = format;
+            geomOpts.featureOptions() = opts;
+        } else if (driver[0] == 'w' && strcmp(driver, "wfs") == 0) {
+            osgEarth::Drivers::WFSFeatureOptions opts;
+            opts.name() = name;
+            opts.url() = url;
+            opts.outputFormat() = format;
+            opts.typeName() = typeName;
+            geomOpts.featureOptions() = opts;
+        } else {
+            Tcl_AppendResult(interp, "unknown feature driver \"", driver,
+                             "\": should be 'ogr', 'tfs', or 'wfs'", (char*)NULL);
+            return TCL_ERROR;
+        }
+        g_renderer->addModelLayer(name, geomOpts, pos, cache, lighting, visible,
+                                  terrainPatch);
+    } else if (type[0] == 'l' && strcmp(type, "line") == 0) {
+        char *driver = Tcl_GetString(objv[5]);
+        char *format = Tcl_GetString(objv[6]);
+        if (format && strlen(format) > 0 &&
+            strcmp(format, "json") != 0 && strcmp(format, "gml") != 0) {
+            Tcl_AppendResult(interp, "unknown format \"", format,
+                             "\": should be 'json' or 'gml'", (char*)NULL);
+            return TCL_ERROR;
+        }
+        char *typeName = Tcl_GetString(objv[7]);
+        char *urlIn = Tcl_GetString(objv[8]);
+        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
+        if (url.empty()) {
+            Tcl_AppendResult(interp, "file not found: \"",
+                             urlIn, "\"", (char*)NULL);
+            return TCL_ERROR;
+        }
+        if (GetBooleanFromObj(interp, objv[9], &cache) != TCL_OK) {
+            return TCL_ERROR;
+        }
+        float r, g, b;
+        if (GetFloatFromObj(interp, objv[10], &r) != TCL_OK ||
+            GetFloatFromObj(interp, objv[11], &g) != TCL_OK ||
+            GetFloatFromObj(interp, objv[12], &b) != TCL_OK) {
+            return TCL_ERROR;
+        }
+        float lineWidth;
+        osgEarth::Units units = osgEarth::Units::PIXELS;
+        if (!osgEarth::Units::parse(Tcl_GetString(objv[13]), lineWidth,
+                                    units, osgEarth::Units::PIXELS)) {
+            Tcl_AppendResult(interp, "Unkown units: \"",
+                             Tcl_GetString(objv[13]), "\"", (char*)NULL);
+            return TCL_ERROR;
+        }
+
+        osgEarth::Symbology::Stroke::LineCapStyle cap = osgEarth::Symbology::Stroke::LINECAP_FLAT;
+        osgEarth::Symbology::Stroke::LineJoinStyle join =  osgEarth::Symbology::Stroke::LINEJOIN_MITRE;
+        if (objc > 14) {
+            cap = ParseLineCapStyle(Tcl_GetString(objv[14]), cap);
+        }
+        if (objc > 15) {
+            join = ParseLineJoinStyle(Tcl_GetString(objv[15]), join);
+        }
+        bool lighting = true;
+        osgEarth::Symbology::Style style;
+        osgEarth::Symbology::LineSymbol *ls = style.getOrCreateSymbol<osgEarth::Symbology::LineSymbol>();
+        ls->stroke()->color() = osgEarth::Symbology::Color(r, g, b);
+        ls->stroke()->width() = lineWidth;
+        ls->stroke()->widthUnits() = units;
+        ls->stroke()->minPixels() = 1.0f;
+        ls->stroke()->lineCap() = cap;
+        //ls->stroke()->roundingRatio() = 0.4f;
+        ls->stroke()->lineJoin() = join;
+        if (objc > 16) {
+            unsigned short stipplePattern = 0U;
+            unsigned int stippleFactor = 1U;
+            if (GetUShortFromObj(interp, objv[16], &stipplePattern) != TCL_OK ||
+                GetUIntFromObj(interp, objv[17], &stippleFactor) != TCL_OK) {
+                return TCL_ERROR;
+            }
+            if (stipplePattern > 0U) {
+                ls->stroke()->stipplePattern() = stipplePattern; // Bitmask: unsigned short
+                ls->stroke()->stippleFactor() = stippleFactor; // num reps: unsigned
+            }
+        }
+
+        osgEarth::Symbology::AltitudeSymbol::Clamping clamping =
+            osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
+        if (objc > 18) {
+            clamping = ParseClamping(Tcl_GetString(objv[18]), clamping);
+        }
+        osgEarth::Symbology::AltitudeSymbol::Technique technique =
+            osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
+        if (objc > 18) {
+            technique = ParseClampingTechnique(Tcl_GetString(objv[19]), technique);
+        }
+        osgEarth::Symbology::AltitudeSymbol *alt = style.getOrCreateSymbol<osgEarth::Symbology::AltitudeSymbol>();
+        alt->clamping() = clamping;
+        alt->technique() = technique;
+        //alt->clampingResolution() = 1.0f;
+        //alt->binding() = osgEarth::Symbology::AltitudeSymbol::BINDING_VERTEX; //BINDING_CENTROID
+        //alt->verticalOffset() = osgEarth::Symbology::NumericExpression();
+        //alt->verticalScale() = osgEarth::Symbology::NumericExpression();
+
+#if 1 || defined(USE_DEPTH_OFFSET)
+        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
+        rs->depthOffset()->enabled() = true;
+        rs->depthOffset()->minBias() = 1000;
+#endif
+        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
+        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
+        geomOpts.styles()->addStyle(style);
+        geomOpts.enableLighting() = false;
+        geomOpts.minRange() = 0.f;
+        geomOpts.maxRange() = FLT_MAX;
+        if (objc > 20) {
+            float min, max;
+            if (GetFloatFromObj(interp, objv[20], &min) != TCL_OK ||
+                GetFloatFromObj(interp, objv[21], &max) != TCL_OK) {
+                return TCL_ERROR;
+            }
+            geomOpts.minRange() = min;
+            geomOpts.maxRange() = max;
+        }
+        if (driver[0] == 'o' && strcmp(driver, "ogr") == 0) {
+            osgEarth::Drivers::OGRFeatureOptions opts;
+            opts.name() = name;
+            if (osgDB::getLowerCaseFileExtension(url) == "csv") {
+                url = loadCSVLayer(url.c_str());
+            }
+            opts.url() = url;
+            geomOpts.featureOptions() = opts;
+        } else if (driver[0] == 't' && strcmp(driver, "tfs") == 0) {
+            osgEarth::Drivers::TFSFeatureOptions opts;
+            opts.name() = name;
+            opts.url() = url;
+            opts.format() = format;
+            geomOpts.featureOptions() = opts;
+        } else if (driver[0] == 'w' && strcmp(driver, "wfs") == 0) {
+            osgEarth::Drivers::WFSFeatureOptions opts;
+            opts.name() = name;
+            opts.url() = url;
+            opts.outputFormat() = format;
+            opts.typeName() = typeName;
+            geomOpts.featureOptions() = opts;
+        } else {
+            Tcl_AppendResult(interp, "unknown feature driver \"", driver,
+                             "\": should be 'ogr', 'tfs', or 'wfs'", (char*)NULL);
+            return TCL_ERROR;
+        }
+        g_renderer->addModelLayer(name, geomOpts, pos, cache, lighting, visible);
+    } else if (type[0] == 't' && strcmp(type, "text") == 0) {
+        char *driver = Tcl_GetString(objv[5]);
+        char *format = Tcl_GetString(objv[6]);
+        if (format && strlen(format) > 0 &&
+            strcmp(format, "json") != 0 && strcmp(format, "gml") != 0) {
+            Tcl_AppendResult(interp, "unknown format \"", format,
+                             "\": should be 'json' or 'gml'", (char*)NULL);
+            return TCL_ERROR;
+        }
+        char *typeName = Tcl_GetString(objv[7]);
+        char *urlIn = Tcl_GetString(objv[8]);
+        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
+        if (url.empty()) {
+            Tcl_AppendResult(interp, "file not found: \"",
+                             urlIn, "\"", (char*)NULL);
+            return TCL_ERROR;
+        }
+        if (GetBooleanFromObj(interp, objv[9], &cache) != TCL_OK) {
+            return TCL_ERROR;
+        }
+        char *content = Tcl_GetString(objv[10]);
+        char *priority = Tcl_GetString(objv[11]);
+        float fgR, fgG, fgB;
+        float bgR, bgG, bgB;
+        float haloWidth, ftSize;
+        osg::Vec2s pixelOffset(0, 0);
+        if (GetFloatFromObj(interp, objv[12], &fgR) != TCL_OK ||
+            GetFloatFromObj(interp, objv[13], &fgG) != TCL_OK ||
+            GetFloatFromObj(interp, objv[14], &fgB) != TCL_OK ||
+            GetFloatFromObj(interp, objv[15], &bgR) != TCL_OK ||
+            GetFloatFromObj(interp, objv[16], &bgG) != TCL_OK ||
+            GetFloatFromObj(interp, objv[17], &bgB) != TCL_OK ||
+            GetFloatFromObj(interp, objv[18], &haloWidth) != TCL_OK ||
+            GetFloatFromObj(interp, objv[19], &ftSize) != TCL_OK) {
+            return TCL_ERROR;
+        }
+        bool declutter;
+        if (GetBooleanFromObj(interp, objv[20], &declutter) != TCL_OK) {
+            return TCL_ERROR;
+        }
+        osgEarth::Symbology::TextSymbol::Alignment alignment =
+            ParseTextAlignment(Tcl_GetString(objv[21]));
+        int xoff, yoff;
+        if (Tcl_GetIntFromObj(interp, objv[22], &xoff) != TCL_OK ||
+            Tcl_GetIntFromObj(interp, objv[23], &yoff) != TCL_OK) {
+            return TCL_ERROR;
+        }
+        pixelOffset.x() = (short)xoff;
+        pixelOffset.y() = (short)yoff;
+        bool lighting = true;
+        osgEarth::Symbology::Style style;
+        osgEarth::Symbology::TextSymbol *ts = style.getOrCreateSymbol<osgEarth::Symbology::TextSymbol>();
+        ts->halo()->color() = osgEarth::Symbology::Color(bgR, bgG, bgB);
+        ts->halo()->width() = haloWidth;
+        ts->fill()->color() = osgEarth::Symbology::Color(fgR, fgG, fgB);
+        ts->content() = osgEarth::Symbology::StringExpression(content);
+        if (priority != NULL && strlen(priority) > 0) {
+            ts->priority() = osgEarth::Symbology::NumericExpression(priority);
+        }
+        //ts->font() = "Arial";
+        ts->size() = ftSize;
+        ts->alignment() = alignment;
+        ts->declutter() = declutter;
+        ts->pixelOffset() = pixelOffset;
+        ts->encoding() = osgEarth::Symbology::TextSymbol::ENCODING_UTF8;
+#ifdef USE_DEPTH_OFFSET
+        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
+        rs->depthOffset()->enabled() = true;
+        rs->depthOffset()->minBias() = 1000;
+#endif
+        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
+        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
+        geomOpts.styles()->addStyle(style);
+        geomOpts.enableLighting() = false;
+        geomOpts.minRange() = 0.f;
+        geomOpts.maxRange() = FLT_MAX;
+        if (objc > 24) {
+            float min, max;
+            if (GetFloatFromObj(interp, objv[24], &min) != TCL_OK ||
+                GetFloatFromObj(interp, objv[25], &max) != TCL_OK) {
+                return TCL_ERROR;
+            }
+            geomOpts.minRange() = min;
+            geomOpts.maxRange() = max;
+        }
+        if (driver[0] == 'o' && strcmp(driver, "ogr") == 0) {
+            osgEarth::Drivers::OGRFeatureOptions opts;
+            opts.name() = name;
+            if (osgDB::getLowerCaseFileExtension(url) == "csv") {
+                url = loadCSVLayer(url.c_str());
+            }
+            opts.url() = url;
+            geomOpts.featureOptions() = opts;
+        } else if (driver[0] == 't' && strcmp(driver, "tfs") == 0) {
+            osgEarth::Drivers::TFSFeatureOptions opts;
+            opts.name() = name;
+            opts.url() = url;
+            opts.format() = format;
+            geomOpts.featureOptions() = opts;
+        } else if (driver[0] == 'w' && strcmp(driver, "wfs") == 0) {
+            osgEarth::Drivers::WFSFeatureOptions opts;
+            opts.name() = name;
+            opts.url() = url;
+            opts.outputFormat() = format;
+            opts.typeName() = typeName;
+            geomOpts.featureOptions() = opts;
+        } else {
+            Tcl_AppendResult(interp, "unknown feature driver \"", driver,
+                             "\": should be 'ogr', 'tfs', or 'wfs'", (char*)NULL);
+            return TCL_ERROR;
+        }
+        g_renderer->addModelLayer(name, geomOpts, pos, cache, lighting, visible);
+    } else {
+        Tcl_AppendResult(interp, "unknown map layer type \"", type,
+                         "\": should be 'image', 'elevation' or 'model'", (char*)NULL);
+        return TCL_ERROR;
+    }
+    return TCL_OK;
+}
+
+static int
+MapLayerDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+                 Tcl_Obj *const *objv)
+{
+    if (objc > 3) {
+        char *name = Tcl_GetString(objv[3]);
+        g_renderer->removeImageLayer(name);
+        g_renderer->removeElevationLayer(name);
+        g_renderer->removeTerrainMaskLayer(name);
+        g_renderer->removeModelLayer(name);
+    } else {
+        g_renderer->clearMap();
+    }
+
+    return TCL_OK;
+}
+
+static int
+MapLayerMoveOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+               Tcl_Obj *const *objv)
+{
+    int pos;
+    if (Tcl_GetIntFromObj(interp, objv[3], &pos) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    char *name = Tcl_GetString(objv[4]);
+    if (pos < 0) {
+        Tcl_AppendResult(interp, "bad layer pos ", pos,
+                         ": must be positive", (char*)NULL);
+        return TCL_ERROR;
+    }
+    g_renderer->moveImageLayer(name, (unsigned int)pos);
+    g_renderer->moveElevationLayer(name, (unsigned int)pos);
+    g_renderer->moveModelLayer(name, (unsigned int)pos);
+
+    return TCL_OK;
+}
+
+static int
+MapLayerOpacityOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+                  Tcl_Obj *const *objv)
+{
+    double opacity;
+    if (Tcl_GetDoubleFromObj(interp, objv[3], &opacity) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    char *name = Tcl_GetString(objv[4]);
+    if (opacity < 0.0 || opacity > 1.0) {
+        Tcl_AppendResult(interp, "bad layer opacity ", opacity,
+                         ": must be [0,1]", (char*)NULL);
+        return TCL_ERROR;
+    }
+    g_renderer->setImageLayerOpacity(name, opacity);
+    g_renderer->setModelLayerOpacity(name, opacity);
+
+    return TCL_OK;
+}
+
+static int
+MapLayerNamesOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+                Tcl_Obj *const *objv)
+{
+    std::vector<std::string> layers;
+    if (objc < 4) {
+        g_renderer->getElevationLayerNames(layers);
+        g_renderer->getTerrainMaskLayerNames(layers);
+        g_renderer->getImageLayerNames(layers);
+        g_renderer->getModelLayerNames(layers);
+    } else {
+        char *type = Tcl_GetString(objv[3]);
+        if (type[0] == 'i' && strcmp(type, "image") == 0) {
+            g_renderer->getImageLayerNames(layers);
+        } else if (type[0] == 'e' && strcmp(type, "elevation") == 0) {
+            g_renderer->getElevationLayerNames(layers);
+        } else if (type[0] == 'm' && strcmp(type, "mask") == 0) {
+            g_renderer->getTerrainMaskLayerNames(layers);
+        } else if (type[0] == 'm' && strcmp(type, "model") == 0) {
+            g_renderer->getModelLayerNames(layers);
+        } else {
+            Tcl_AppendResult(interp, "uknown type \"", type,
+                             "\": must be image, elevation, mask or model",
+                             (char*)NULL);
+            return TCL_ERROR;
+        }
+    }
+    std::ostringstream oss;
+    size_t len = 0;
+    oss << "nv>map names {";
+    len += 14;
+    for (size_t i = 0; i < layers.size(); i++) {
+        oss << "\"" << layers[i] << "\"";
+        len += 2 + layers[i].length();
+        if (i < layers.size() - 1) {
+            oss << " ";
+            len++;
+        }
+    }
+    oss << "}\n";
+    len += 2;
+#ifdef USE_THREADS
+    queueResponse(oss.str().c_str(), len, Response::VOLATILE);
+#else 
+    ssize_t bytesWritten = SocketWrite(oss.str().c_str(), len);
+
+    if (bytesWritten < 0) {
+        return TCL_ERROR;
+    }
+#endif /*USE_THREADS*/
+    return TCL_OK;
+}
+
+static int
+MapLayerVisibleOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+                  Tcl_Obj *const *objv)
+{
+    bool visible;
+    if (GetBooleanFromObj(interp, objv[3], &visible) != TCL_OK) {
+        return TCL_ERROR;
+    }
+
+    if (objc > 4) {
+        char *name = Tcl_GetString(objv[4]);
+        g_renderer->setImageLayerVisibility(name, visible);
+        g_renderer->setElevationLayerVisibility(name, visible);
+        g_renderer->setModelLayerVisibility(name, visible);
+    } else {
+        std::vector<std::string> layers;
+        g_renderer->getImageLayerNames(layers);
+        g_renderer->getElevationLayerNames(layers);
+        g_renderer->getModelLayerNames(layers);
+        for (std::vector<std::string>::iterator itr = layers.begin();
+             itr != layers.end(); ++itr) {
+            g_renderer->setImageLayerVisibility(itr->c_str(), visible);
+            g_renderer->setElevationLayerVisibility(itr->c_str(), visible);
+            g_renderer->setModelLayerVisibility(itr->c_str(), visible);
+        }
+    }
+
+    return TCL_OK;
+}
+
+static CmdSpec mapLayerOps[] = {
+    {"add",     1, MapLayerAddOp,       6, 0, "name type driver ?url? ?args?"},
+    {"delete",  1, MapLayerDeleteOp,    3, 4, "?name?"},
+    {"move",    1, MapLayerMoveOp,      5, 5, "pos name"},
+    {"names",   1, MapLayerNamesOp,     3, 4, "?type?"},
+    {"opacity", 1, MapLayerOpacityOp,   5, 5, "opacity name"},
+    {"visible", 1, MapLayerVisibleOp,   4, 5, "bool ?name?"},
+};
+static int nMapLayerOps = NumCmdSpecs(mapLayerOps);
+
+static int
+MapLayerOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+           Tcl_Obj *const *objv)
+{
+    Tcl_ObjCmdProc *proc;
+
+    proc = GetOpFromObj(interp, nMapLayerOps, mapLayerOps,
+                        CMDSPEC_ARG2, objc, objv, 0);
+    if (proc == NULL) {
+        return TCL_ERROR;
+    }
+    return (*proc) (clientData, interp, objc, objv);
+}
+
+static int
+MapLoadOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+          Tcl_Obj *const *objv)
+{
+    char *opt = Tcl_GetString(objv[2]);
+    if (opt[0] == 'f' && strcmp(opt, "file") == 0) {
+        g_renderer->loadEarthFile(Tcl_GetString(objv[3]));
+    } else if (opt[0] == 'u' && strcmp(opt, "url") == 0) {
+        std::ostringstream path;
+        path << "server:" << Tcl_GetString(objv[3]);
+        g_renderer->loadEarthFile(path.str().c_str());
+    } else if (opt[0] == 'd' && strcmp(opt, "data") == 0) {
+        opt = Tcl_GetString(objv[3]);
+        if (opt[0] != 'f' || strcmp(opt, "follows") != 0) {
+            return TCL_ERROR;
+        }
+        int len;
+        if (Tcl_GetIntFromObj(interp, objv[4], &len) != TCL_OK) {
+            return TCL_ERROR;
+        }
+        // Read Earth file from socket
+        char *buf = (char *)malloc((size_t)len);
+        SocketRead(buf, (size_t)len);
+        std::ostringstream path;
+        path << "/tmp/tmp" << getpid() << ".earth";
+        const char *pathStr = path.str().c_str();
+        FILE *tmpFile = fopen(pathStr, "w");
+        fwrite(buf, len, 1, tmpFile);
+        fclose(tmpFile);
+        g_renderer->loadEarthFile(pathStr);
+        unlink(pathStr);
+        free(buf);
+    } else {
+        return TCL_ERROR;
+    }
+    return TCL_OK;
+}
+
+static int
+MapPinAddOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+            Tcl_Obj *const *objv)
+{
+    int x, y;
+    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
+        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    char *label = NULL;
+    if (objc > 5) {
+        label = Tcl_GetString(objv[5]);
+    }
+
+    if (g_renderer->getMapSRS() == NULL) {
+        Tcl_AppendResult(interp, "Could not get map SRS", (char*)NULL);
+        return TCL_ERROR;
+    }
+
+    // Get lat/long
+    double latitude, longitude;
+    if (!g_renderer->mouseToLatLong(x, y, &latitude, &longitude)) {
+        USER_ERROR("Can't add pin here");
+        return TCL_OK;
+    }
+
+    g_renderer->addPlaceNode(latitude, longitude, label);
+    return TCL_OK;
+}
+
+static int
+MapPinDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+               Tcl_Obj *const *objv)
+{
+    int x, y;
+    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
+        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
+        return TCL_ERROR;
+    }
+
+    g_renderer->deletePlaceNode(x, y);
+    return TCL_OK;
+}
+
+static int
+MapPinHoverOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+              Tcl_Obj *const *objv)
+{
+    int x, y;
+    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
+        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
+        return TCL_ERROR;
+    }
+
+    g_renderer->hoverPlaceNode(x, y);
+    return TCL_OK;
+}
+
+static CmdSpec mapPinOps[] = {
+    {"add",     1, MapPinAddOp,     5, 6, "x y ?label?"},
+    {"delete",  1, MapPinDeleteOp,  5, 5, "x y"},
+    {"hover",   1, MapPinHoverOp,   5, 5, "x y"},
+};
+static int nMapPinOps = NumCmdSpecs(mapPinOps);
+
+static int
+MapPinOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+           Tcl_Obj *const *objv)
+{
+    Tcl_ObjCmdProc *proc;
+
+    proc = GetOpFromObj(interp, nMapPinOps, mapPinOps,
+                        CMDSPEC_ARG2, objc, objv, 0);
+    if (proc == NULL) {
+        return TCL_ERROR;
+    }
+    return (*proc) (clientData, interp, objc, objv);
+}
+
+static int
+MapPositionDisplayOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+                     Tcl_Obj *const *objv)
+{
+    bool state;
+    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    Renderer::CoordinateDisplayType type = Renderer::COORDS_LATLONG_DECIMAL_DEGREES;
+    if (state && objc > 3) {
+        const char *str = Tcl_GetString(objv[3]);
+        if (str[0] == 'l' && strcmp(str, "latlong_decimal_degrees") == 0) {
+            type = Renderer::COORDS_LATLONG_DECIMAL_DEGREES;
+        } else if (str[0] == 'l' && strcmp(str, "latlong_degrees_decimal_minutes") == 0) {
+            type = Renderer::COORDS_LATLONG_DEGREES_DECIMAL_MINUTES;
+        } else if (str[0] == 'l' && strcmp(str, "latlong_degrees_minutes_seconds") == 0) {
+            type = Renderer::COORDS_LATLONG_DEGREES_MINUTES_SECONDS;
+        } else if (str[0] == 'm' && strcmp(str, "mgrs") == 0) {
+            type = Renderer::COORDS_MGRS;
+        } else {
+            Tcl_AppendResult(interp, "invalid type: \"", str,
+                             "\": should be 'latlong_decimal_degrees', 'latlong_degrees_decimal_minutes', 'latlong_degrees_minutes_seconds', or 'mgrs'",
+                             (char*)NULL);
+            return TCL_ERROR;
+        }
+    }
+    if (state && objc > 4) {
+        int precision;
+        if (Tcl_GetIntFromObj(interp, objv[4], &precision) != TCL_OK) {
+            return TCL_ERROR;
+        }
+        g_renderer->setCoordinateReadout(state, type, precision);
+    } else {
+        g_renderer->setCoordinateReadout(state, type);
+    }
+
+    return TCL_OK;
+}
+
+static int
+MapResetOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+           Tcl_Obj *const *objv)
+{
+    char *typeStr = Tcl_GetString(objv[2]);
+    osgEarth::MapOptions::CoordinateSystemType type;
+    if (typeStr[0] == 'g' && strcmp(typeStr, "geocentric") == 0) {
+        type = osgEarth::MapOptions::CSTYPE_GEOCENTRIC;
+    } else if (typeStr[0] == 'g' && strcmp(typeStr, "geocentric_cube") == 0) {
+        type = osgEarth::MapOptions::CSTYPE_GEOCENTRIC_CUBE;
+    } else if (typeStr[0] == 'p' && strcmp(typeStr, "projected") == 0) {
+        type = osgEarth::MapOptions::CSTYPE_PROJECTED;
+    } else {
+        Tcl_AppendResult(interp, "bad map type \"", typeStr,
+                         "\": must be geocentric or projected", (char*)NULL);
+        return TCL_ERROR;
+    }
+    float color[3];
+    if (GetFloatFromObj(interp, objv[3], &color[0]) != TCL_OK ||
+        GetFloatFromObj(interp, objv[4], &color[1]) != TCL_OK ||
+        GetFloatFromObj(interp, objv[5], &color[2]) != TCL_OK) {
+        return TCL_ERROR;
+    }
+
+    osg::Vec4f bgColor(color[0],color[1],color[2],1);
+    if (type == osgEarth::MapOptions::CSTYPE_PROJECTED) {
+        if (objc < 7) {
+            Tcl_AppendResult(interp, "wrong # arguments: profile required for projected maps", (char*)NULL);
+            return TCL_ERROR;
+        }
+        char *profile = Tcl_GetString(objv[6]);
+        if (strcmp(profile, "global-geodetic") == 0 ||
+            strcmp(profile, "geodetic") == 0 ||
+            strcmp(profile, "epsg:4326") == 0 ||
+            strcmp(profile, "wgs84") == 0 ||
+            strcmp(profile, "plate-carre") == 0 ||
+            strcmp(profile, "plate-carree") == 0) {
+            // As of osgearth 2.7, these are not permitted as map profiles.
+            // Use epsg:32663 instead
+            Tcl_AppendResult(interp, "Invalid profile: can't use geographic coordinate system as projection.  Consider using an equirectangular projection (epsg:32663) instead.", (char*)NULL);
+            return TCL_ERROR;
+        }
+        if (objc > 7) {
+            if (objc < 11) {
+                Tcl_AppendResult(interp, "wrong # arguments: 4 bounds arguments required", (char*)NULL);
+                return TCL_ERROR;
+            }
+            double bounds[4];
+            for (int i = 0; i < 4; i++) {
+                if (Tcl_GetDoubleFromObj(interp, objv[7+i], &bounds[i]) != TCL_OK) {
+                    return TCL_ERROR;
+                }
+            }
+            // Check if min > max
+            if (bounds[0] > bounds[2] ||
+                bounds[1] > bounds[3]) {
+                Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
+                return TCL_ERROR;
+            }
+            if (bounds[0] < -180. || bounds[0] > 180. ||
+                bounds[2] < -180. || bounds[2] > 180. ||
+                bounds[1] < -90. || bounds[1] > 90. ||
+                bounds[3] < -90. || bounds[3] > 90.) {
+                Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
+                return TCL_ERROR;
+            }
+            g_renderer->resetMap(type, bgColor, profile, bounds, "wgs84");
+        } else {
+            // If no bounds are given, this must be a named profile with implicit bounds
+            if (osgEarth::Registry::instance()->getNamedProfile(profile) == NULL) {
+                Tcl_AppendResult(interp, "bad named profile \"", profile,
+                                 "\": must be e.g. 'global-geodetic', 'global-mercator'...", (char*)NULL);
+                return TCL_ERROR;
+            }
+            g_renderer->resetMap(type, bgColor, profile);
+        }
+    } else {
+        // No profile required for geocentric (3D) maps
+        g_renderer->resetMap(type, bgColor);
+    }
+
+    return TCL_OK;
+}
+
+static int
+MapScaleBarOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+              Tcl_Obj *const *objv)
+{
+    bool state;
+    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    g_renderer->setScaleBar(state);
+    if (state && objc > 3) {
+        const char *unitStr = Tcl_GetString(objv[3]);
+        ScaleBarUnits units;
+        if (unitStr[0] == 'm' && strcmp(unitStr, "meters") == 0) {
+            units = UNITS_METERS;
+        } else if (unitStr[0] == 'f' && strcmp(unitStr, "feet") == 0) {
+            units = UNITS_INTL_FEET;
+        } else if (unitStr[0] == 'u' && strcmp(unitStr, "us_survey_feet") == 0) {
+            units = UNITS_US_SURVEY_FEET;
+        } else if (unitStr[0] == 'n' && strcmp(unitStr, "nautical_miles") == 0) {
+            units = UNITS_NAUTICAL_MILES;
+        } else {
+            Tcl_AppendResult(interp, "bad units \"", unitStr,
+                             "\": must be 'meters', 'feet', 'us_survey_feet' or 'nautical_miles'", (char*)NULL);
+            return TCL_ERROR;
+        }
+        g_renderer->setScaleBarUnits(units);
+    }
+    return TCL_OK;
+}
+
+static int
+MapSequencePauseOp(ClientData clientData, Tcl_Interp *interp, int objc,
+                   Tcl_Obj *const *objv)
+{
+    const char *layer = Tcl_GetString(objv[3]);
+    bool ret = g_renderer->sequencePause(layer);
+    if (!ret) {
+        Tcl_AppendResult(interp, "Layer \"", layer,
+                         "\" doesn't exist or has no sequence", (char *)NULL);
+        return TCL_ERROR;
+    }
+
+    return TCL_OK;
+}
+
+static int
+MapSequencePlayOp(ClientData clientData, Tcl_Interp *interp, int objc,
+                  Tcl_Obj *const *objv)
+{
+    const char *layer = Tcl_GetString(objv[3]);
+    bool ret = g_renderer->sequencePlay(layer);
+    if (!ret) {
+        Tcl_AppendResult(interp, "Layer \"", layer,
+                         "\" doesn't exist or has no sequence", (char *)NULL);
+        return TCL_ERROR;
+    }
+
+    return TCL_OK;
+}
+
+static int
+MapSequenceSeekOp(ClientData clientData, Tcl_Interp *interp, int objc,
+                  Tcl_Obj *const *objv)
+{
+    const char *layer = Tcl_GetString(objv[4]);
+    int frame;
+    if (Tcl_GetIntFromObj(interp, objv[3], &frame) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    bool ret = g_renderer->sequenceSeek(layer, (unsigned int)frame);
+    if (!ret) {
+        Tcl_AppendResult(interp, "Layer \"", layer,
+                         "\" doesn't exist or has no sequence", (char *)NULL);
+        return TCL_ERROR;
+    }
+
+    return TCL_OK;
+}
+
+static CmdSpec mapSequenceOps[] = {
+    {"pause", 2, MapSequencePauseOp, 4, 4, "layerName"},
+    {"play",  2, MapSequencePlayOp,  4, 4, "layerName"},
+    {"seek",  1, MapSequenceSeekOp,  5, 5, "frame layerName"},
+};
+static int nMapSequenceOps = NumCmdSpecs(mapSequenceOps);
+
+static int
+MapSequenceOp(ClientData clientData, Tcl_Interp *interp, int objc,
+           Tcl_Obj *const *objv)
+{
+    Tcl_ObjCmdProc *proc;
+
+    proc = GetOpFromObj(interp, nMapSequenceOps, mapSequenceOps,
+                        CMDSPEC_ARG2, objc, objv, 0);
+    if (proc == NULL) {
+        return TCL_ERROR;
+    }
+    return (*proc) (clientData, interp, objc, objv);
+}
+
+static int
+MapSetPositionOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+            Tcl_Obj *const *objv)
+{
+    if (objc < 3) {
+        g_renderer->clearReadout();
+    } else {
+        int x, y;
+        if (Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK ||
+            Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK) {
+            return TCL_ERROR;
+        }
+        g_renderer->setReadout(x, y);
+    }
+    return TCL_OK;
+}
+
+static int
+MapTerrainAmbientOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+                    Tcl_Obj *const *objv)
+{
+    float ambient;
+    if (GetFloatFromObj(interp, objv[3], &ambient) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    g_renderer->setSkyAmbient(ambient);
+    return TCL_OK;
+}
+
+static int
+MapTerrainColorOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+                  Tcl_Obj *const *objv)
+{
+    float color[3];
+    if (GetFloatFromObj(interp, objv[3], &color[0]) != TCL_OK ||
+        GetFloatFromObj(interp, objv[4], &color[1]) != TCL_OK ||
+        GetFloatFromObj(interp, objv[5], &color[2]) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    g_renderer->setTerrainColor(osg::Vec4f(color[0],color[1],color[2],1));
+    return TCL_OK;
+}
+
+static int
+MapTerrainEdgesOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+                  Tcl_Obj *const *objv)
+{
+    bool state;
+    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    g_renderer->setTerrainEdges(state);
+    return TCL_OK;
+}
+
+static int
+MapTerrainLightingOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+                     Tcl_Obj *const *objv)
+{
+    bool state;
+    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
+        return TCL_ERROR;
+    }
+
+    g_renderer->setTerrainLighting(state);
+    return TCL_OK;
+}
+
+static int
+MapTerrainLineColorOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+                      Tcl_Obj *const *objv)
+{
+    float color[3];
+    if (GetFloatFromObj(interp, objv[3], &color[0]) != TCL_OK ||
+        GetFloatFromObj(interp, objv[4], &color[1]) != TCL_OK ||
+        GetFloatFromObj(interp, objv[5], &color[2]) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    g_renderer->setTerrainLineColor(osg::Vec4f(color[0], color[1], color[2],1));
+    return TCL_OK;
+}
+
+static int
+MapTerrainLineWidthOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+                      Tcl_Obj *const *objv)
+{
+    float width;
+    if (GetFloatFromObj(interp, objv[3], &width) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    g_renderer->setTerrainLineWidth(width);
+    return TCL_OK;
+}
+
+static int
+MapTerrainVertScaleOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+                      Tcl_Obj *const *objv)
+{
+    double scale;
+    if (Tcl_GetDoubleFromObj(interp, objv[3], &scale) != TCL_OK) {
+        return TCL_ERROR;
+    }
+
+    g_renderer->setTerrainVerticalScale(scale);
+    return TCL_OK;
+}
+
+static int
+MapTerrainWireframeOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+                      Tcl_Obj *const *objv)
+{
+    bool state;
+    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
+        return TCL_ERROR;
+    }
+
+    g_renderer->setTerrainWireframe(state);
+    return TCL_OK;
+}
+
+static CmdSpec mapTerrainOps[] = {
+    {"ambient",   1, MapTerrainAmbientOp,   4, 4, "val"},
+    {"color",     1, MapTerrainColorOp,     6, 6, "r g b"},
+    {"edges",     1, MapTerrainEdgesOp,     4, 4, "bool"},
+    {"lighting",  2, MapTerrainLightingOp,  4, 4, "bool"},
+    {"linecolor", 5, MapTerrainLineColorOp, 6, 6, "r g b"},
+    {"linewidth", 5, MapTerrainLineWidthOp, 4, 4, "val"},
+    {"vertscale", 1, MapTerrainVertScaleOp, 4, 4, "val"},
+    {"wireframe", 1, MapTerrainWireframeOp, 4, 4, "bool"},
+};
+static int nMapTerrainOps = NumCmdSpecs(mapTerrainOps);
+
+static int
+MapTerrainOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+           Tcl_Obj *const *objv)
+{
+    Tcl_ObjCmdProc *proc;
+
+    proc = GetOpFromObj(interp, nMapTerrainOps, mapTerrainOps,
+                        CMDSPEC_ARG2, objc, objv, 0);
+    if (proc == NULL) {
+        return TCL_ERROR;
+    }
+    return (*proc) (clientData, interp, objc, objv);
+}
+
+static int
+MapTimeOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+          Tcl_Obj *const *objv)
+{
+    osgEarth::DateTime now;
+    int year, month, day;
+    double hours;
+    year = now.year();
+    month = now.month();
+    day = now.day();
+    hours = now.hours();
+    if (objc > 2) {
+        if (Tcl_GetDoubleFromObj(interp, objv[2], &hours) != TCL_OK) {
+            return TCL_ERROR;
+        }
+    }
+    if (objc > 3) {
+        if (Tcl_GetIntFromObj(interp, objv[3], &day) != TCL_OK) {
+            return TCL_ERROR;
+        }
+    }
+    if (objc > 4) {
+        if (Tcl_GetIntFromObj(interp, objv[4], &month) != TCL_OK) {
+            return TCL_ERROR;
+        }
+    }
+    if (objc > 5) {
+        if (Tcl_GetIntFromObj(interp, objv[5], &year) != TCL_OK) {
+            return TCL_ERROR;
+        }
+    }
+
+    g_renderer->setEphemerisTime(year, month, day, hours);
+    return TCL_OK;
+}
+
+static CmdSpec mapOps[] = {
+    {"attrib",   1, MapAttributionOp,     3, 3, "string"},
+    {"box",      1, MapBoxOp,             3, 0, "op ?params..."},
+    {"coords",   1, MapCoordsOp,          4, 6, "token coords ?srs? ?verticalDatum?"},
+    {"grid",     1, MapGraticuleOp,       3, 4, "bool ?type?"},
+    {"layer",    2, MapLayerOp,           3, 0, "op ?params...?"},
+    {"load",     2, MapLoadOp,            4, 5, "options"},
+    {"pin",      2, MapPinOp,             3, 0, "op ?params...?"},
+    {"posdisp",  2, MapPositionDisplayOp, 3, 5, "bool ?format? ?precision?"},
+    {"reset",    1, MapResetOp,           6, 11, "type r g b ?profile xmin ymin xmax ymax?"},
+    {"scalebar", 1, MapScaleBarOp,        3, 4, "bool ?units?"},
+    {"sequence", 3, MapSequenceOp,        3, 0, "op ?params...?"},
+    {"setpos",   3, MapSetPositionOp,     2, 4, "x y"},
+    {"terrain",  1, MapTerrainOp,         3, 0, "op ?params...?"},
+    {"time",     1, MapTimeOp,            2, 6, "?hours? ?day? ?month? ?year?"},
+};
+static int nMapOps = NumCmdSpecs(mapOps);
+
+static int
+MapCmd(ClientData clientData, Tcl_Interp *interp, int objc, 
+       Tcl_Obj *const *objv)
+{
+    Tcl_ObjCmdProc *proc;
+
+    proc = GetOpFromObj(interp, nMapOps, mapOps,
+                        CMDSPEC_ARG1, objc, objv, 0);
+    if (proc == NULL) {
+        return TCL_ERROR;
+    }
+    return (*proc) (clientData, interp, objc, objv);
+}
+
+static int
+MouseClickOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+             Tcl_Obj *const *objv)
+{
+    int button;
+    double x, y;
+
+    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
+        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
+        return TCL_ERROR;
+    }
+
+    g_renderer->mouseClick(button, x, y);
+    return TCL_OK;
+}
+
+static int
+MouseDoubleClickOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+                   Tcl_Obj *const *objv)
+{
+    int button;
+    double x, y;
+
+    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
+        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
+        return TCL_ERROR;
+    }
+
+    g_renderer->mouseDoubleClick(button, x, y);
+    return TCL_OK;
+}
+
+static int
+MouseDragOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+            Tcl_Obj *const *objv)
+{
+    int button;
+    double x, y;
+
+    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
+        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
+        return TCL_ERROR;
+    }
+
+    g_renderer->mouseDrag(button, x, y);
+    return TCL_OK;
+}
+
+static int
+MouseMotionOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+              Tcl_Obj *const *objv)
+{
+    double x, y;
+
+    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
+        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
+        return TCL_ERROR;
+    }
+
+    g_renderer->mouseMotion(x, y);
+    return TCL_OK;
+}
+
+static int
+MouseReleaseOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+               Tcl_Obj *const *objv)
+{
+    int button;
+    double x, y;
+
+    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
+        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
+        return TCL_ERROR;
+    }
+
+    g_renderer->mouseRelease(button, x, y);
+    return TCL_OK;
+}
+
+static int
+MouseScrollOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+              Tcl_Obj *const *objv)
+{
+    int direction;
+
+    if (Tcl_GetIntFromObj(interp, objv[2], &direction) != TCL_OK) {
+        return TCL_ERROR;
+    }
+
+    g_renderer->mouseScroll(direction);
+    return TCL_OK;
+}
+
+static CmdSpec mouseOps[] = {
+    {"click",    1, MouseClickOp,       5, 5, "button x y"},
+    {"dblclick", 2, MouseDoubleClickOp, 5, 5, "button x y"},
+    {"drag",     2, MouseDragOp,        5, 5, "button x y"},
+    {"motion",   1, MouseMotionOp,      4, 4, "x y"},
+    {"release",  1, MouseReleaseOp,     5, 5, "button x y"},
+    {"scroll",   1, MouseScrollOp,      3, 3, "direction"},
+};
+static int nMouseOps = NumCmdSpecs(mouseOps);
+
+static int
+MouseCmd(ClientData clientData, Tcl_Interp *interp, int objc, 
+         Tcl_Obj *const *objv)
+{
+    Tcl_ObjCmdProc *proc;
+
+    proc = GetOpFromObj(interp, nMouseOps, mouseOps,
+                        CMDSPEC_ARG1, objc, objv, 0);
+    if (proc == NULL) {
+        return TCL_ERROR;
+    }
+    return (*proc) (clientData, interp, objc, objv);
+}
+
+static int
+PlacardConfigOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+                Tcl_Obj *const *objv)
+{
+    int namec;
+    Tcl_Obj **namev = NULL;
+    const char *layerName = Tcl_GetString(objv[5]);
+
+    if (Tcl_ListObjGetElements(interp, objv[2], &namec, &namev) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    if (namec % 2 != 0) {
+        Tcl_AppendResult(interp, "invalid attribute list",
+                         (char *)NULL);
+        return TCL_ERROR;
+    }
+    Placard placardConf;
+    for (int i = 0; i < namec; i+=2) {
+        std::string name(Tcl_GetString(namev[i]));
+        std::string label(Tcl_GetString(namev[i+1]));
+        placardConf.addEntry(name, label);
+    }
+    osgEarth::Config styleConf("style", Tcl_GetString(objv[3]));
+    styleConf.add("type", "text/css");
+    placardConf.setStyle(osgEarth::Symbology::Style(styleConf));
+
+    float padding;
+    if (GetFloatFromObj(interp, objv[4], &padding) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    placardConf.setPadding(padding);
+    g_renderer->setPlacardConfig(placardConf, layerName);
+    return TCL_OK;
+}
+
+static int
+PlacardEnableOp(ClientData clientData, Tcl_Interp *interp, int objc,
+                Tcl_Obj *const *objv)
+{
+    const char *layerName = Tcl_GetString(objv[3]);
+    bool enable;
+    if (GetBooleanFromObj(interp, objv[2], &enable) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    g_renderer->enablePlacard(layerName, enable);
+    return TCL_OK;
+}
+
+static CmdSpec placardOps[] = {
+    {"config", 1, PlacardConfigOp, 6, 6, "attrlist style padding layerName"},
+    {"enable", 1, PlacardEnableOp, 4, 4, "bool layerName"},
+};
+static int nPlacardOps = NumCmdSpecs(placardOps);
+
+static int
+PlacardCmd(ClientData clientData, Tcl_Interp *interp, int objc, 
+          Tcl_Obj *const *objv)
+{
+    Tcl_ObjCmdProc *proc;
+
+    proc = GetOpFromObj(interp, nPlacardOps, placardOps,
+                        CMDSPEC_ARG1, objc, objv, 0);
+    if (proc == NULL) {
+        return TCL_ERROR;
+    }
+    return (*proc) (clientData, interp, objc, objv);
+}
+
+static int
+RendererRenderOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+                 Tcl_Obj *const *objv)
+{
+    g_renderer->eventuallyRender();
+    return TCL_OK;
+}
+
+static CmdSpec rendererOps[] = {
+    {"render",     1, RendererRenderOp, 2, 2, ""},
+};
+static int nRendererOps = NumCmdSpecs(rendererOps);
+
+static int
+RendererCmd(ClientData clientData, Tcl_Interp *interp, int objc, 
+            Tcl_Obj *const *objv)
+{
+    Tcl_ObjCmdProc *proc;
+
+    proc = GetOpFromObj(interp, nRendererOps, rendererOps,
+                        CMDSPEC_ARG1, objc, objv, 0);
+    if (proc == NULL) {
+        return TCL_ERROR;
+    }
+    return (*proc) (clientData, interp, objc, objv);
+}
+
+static int
+ScreenBgColorOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+                Tcl_Obj *const *objv)
+{
+    float color[3];
+
+    if (GetFloatFromObj(interp, objv[2], &color[0]) != TCL_OK ||
+        GetFloatFromObj(interp, objv[3], &color[1]) != TCL_OK ||
+        GetFloatFromObj(interp, objv[4], &color[2]) != TCL_OK) {
+        return TCL_ERROR;
+    }
+
+    g_renderer->setBackgroundColor(color);
+    return TCL_OK;
+}
+
+static int
+ScreenCoordsOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+               Tcl_Obj *const *objv)
+{
+    int tokenLength;
+    const char *token = Tcl_GetStringFromObj(objv[2], &tokenLength);
+    int numCoords;
+    Tcl_Obj **coords;
+    if (Tcl_ListObjGetElements(interp, objv[3], &numCoords, &coords) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    if (numCoords == 0) {
+        Tcl_AppendResult(interp, "no x,y,z coordinates in list", (char *)NULL);
+        return TCL_ERROR;
+    }
+    if (numCoords % 3 != 0) {
+        Tcl_AppendResult(interp, "invalid number of coordinates in list",
+                         (char *)NULL);
+        return TCL_ERROR;
+    }
+
+    const osgEarth::SpatialReference *srs = NULL;
+    if (objc < 5) {
+        srs = g_renderer->getMapSRS();
+        if (srs == NULL) {
+            Tcl_AppendResult(interp, "Could not determine map SRS", (char*)NULL);
+            return TCL_ERROR;
+        }
+    } else {
+        std::string srsInit(Tcl_GetString(objv[4]));
+        std::string verticalDatum;
+        if (objc > 5) {
+            verticalDatum = Tcl_GetString(objv[5]);
+        }
+        srs = osgEarth::SpatialReference::get(srsInit, verticalDatum);
+        if (srs == NULL) {
+            Tcl_AppendResult(interp, "bad SRS \"", srsInit.c_str(), "\"", (char*)NULL);
+            return TCL_ERROR;
+        }
+    }
+    Tcl_Obj *listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
+    std::vector<osg::Vec3d> coordVec;
+    for (int i = 0; i < numCoords; i += 3) {
+        double x, y, z;
+        if (Tcl_GetDoubleFromObj(interp, coords[i], &x) != TCL_OK ||
+            Tcl_GetDoubleFromObj(interp, coords[i+1], &y) != TCL_OK ||
+            Tcl_GetDoubleFromObj(interp, coords[i+2], &z) != TCL_OK) {
+            return TCL_ERROR;
+        }
+        // ALTMODE_RELATIVE is height above terrain, ALTMODE_ABSOLUTE means
+        // relative to the vertical datum
+        osgEarth::GeoPoint mapPoint(srs, x, y, z, osgEarth::ALTMODE_ABSOLUTE);
+        osg::Vec3d world;
+        if (g_renderer->getWorldCoords(mapPoint, &world)) {
+            coordVec.push_back(world);
+        } else {
+            coordVec.push_back(osg::Vec3d(std::numeric_limits<double>::quiet_NaN(),
+                                          std::numeric_limits<double>::quiet_NaN(),
+                                          std::numeric_limits<double>::quiet_NaN()));
+        }
+    }
+    g_renderer->worldToScreen(coordVec);
+    for (std::vector<osg::Vec3d>::iterator itr = coordVec.begin();
+         itr != coordVec.end(); ++itr) {
+        Tcl_Obj *objPtr = Tcl_NewDoubleObj(itr->x());
+        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
+        objPtr = Tcl_NewDoubleObj(itr->y());
+        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
+        objPtr = Tcl_NewDoubleObj(itr->z());
+        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
+    }
+    // send coords to client
+    int listLength;
+    const char *string = Tcl_GetStringFromObj(listObjPtr, &listLength);
+    size_t length = listLength + tokenLength + 22;
+    char *mesg = new char[length];
+    length = snprintf(mesg, length, "nv>screen coords %s {%s}\n", token, string);
+    Tcl_DecrRefCount(listObjPtr);
+    queueResponse(mesg, length, Response::VOLATILE);
+    delete [] mesg;
+    return TCL_OK;
+}
+
+static int
+ScreenSizeOp(ClientData clientData, Tcl_Interp *interp, int objc, 
+             Tcl_Obj *const *objv)
+{
+    int width, height;
+
+    if (Tcl_GetIntFromObj(interp, objv[2], &width) != TCL_OK ||
+        Tcl_GetIntFromObj(interp, objv[3], &height) != TCL_OK) {
+        return TCL_ERROR;
+    }
+
+    g_renderer->setWindowSize(width, height);
+    return TCL_OK;
+}
+
+static CmdSpec screenOps[] = {
+    {"bgcolor", 1, ScreenBgColorOp, 5, 5, "r g b"},
+    {"coords",  1, ScreenCoordsOp, 4, 6, "token coords ?srs? ?verticalDatum?"},
+    {"size",    1, ScreenSizeOp, 4, 4, "width height"}
+};
+static int nScreenOps = NumCmdSpecs(screenOps);
+
+static int
+ScreenCmd(ClientData clientData, Tcl_Interp *interp, int objc, 
+          Tcl_Obj *const *objv)
+{
+    Tcl_ObjCmdProc *proc;
+
+    proc = GetOpFromObj(interp, nScreenOps, screenOps,
+                        CMDSPEC_ARG1, objc, objv, 0);
+    if (proc == NULL) {
+        return TCL_ERROR;
+    }
+    return (*proc) (clientData, interp, objc, objv);
+}
+
+static int
+SelectAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
+            Tcl_Obj *const *objv)
+{
+    int numIDs;
+    Tcl_Obj **ids;
+    if (Tcl_ListObjGetElements(interp, objv[2], &numIDs, &ids) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    if (numIDs == 0) {
+        Tcl_AppendResult(interp, "no IDs in list", (char *)NULL);
+        return TCL_ERROR;
+    }
+    std::vector<unsigned long> featureIDs;
+    for (int i = 0; i < numIDs; i++) {
+        long id;
+        if (Tcl_GetLongFromObj(interp, ids[i], &id) != TCL_OK) {
+            return TCL_ERROR;
+        }
+        featureIDs.push_back((unsigned long)id);
+    }
+    const char *layerName = Tcl_GetString(objv[3]);
+
+    g_renderer->selectFeatures(featureIDs, layerName, false);
+    return TCL_OK;
+}
+
+static int
+SelectClearOp(ClientData clientData, Tcl_Interp *interp, int objc,
+              Tcl_Obj *const *objv)
+{
+    g_renderer->clearSelection();
+    return TCL_OK;
+}
+
+static int
+SelectDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
+               Tcl_Obj *const *objv)
+{
+    int numIDs;
+    Tcl_Obj **ids;
+    if (Tcl_ListObjGetElements(interp, objv[2], &numIDs, &ids) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    if (numIDs == 0) {
+        Tcl_AppendResult(interp, "no IDs in list", (char *)NULL);
+        return TCL_ERROR;
+    }
+    std::vector<unsigned long> featureIDs;
+    for (int i = 0; i < numIDs; i++) {
+        long id;
+        if (Tcl_GetLongFromObj(interp, ids[i], &id) != TCL_OK) {
+            return TCL_ERROR;
+        }
+        featureIDs.push_back((unsigned long)id);
+    }
+    const char *layerName = Tcl_GetString(objv[3]);
+
+    g_renderer->deselectFeatures(featureIDs, layerName);
+    return TCL_OK;
+}
+
+static int
+SelectFeatureOp(ClientData clientData, Tcl_Interp *interp, int objc,
+                Tcl_Obj *const *objv)
+{
+    int numIDs;
+    Tcl_Obj **ids;
+    if (Tcl_ListObjGetElements(interp, objv[2], &numIDs, &ids) != TCL_OK) {
+        return TCL_ERROR;
+    }
+    if (numIDs == 0) {
+        Tcl_AppendResult(interp, "no IDs in list", (char *)NULL);
+        return TCL_ERROR;
+    }
+    std::vector<unsigned long> featureIDs;
+    for (int i = 0; i < numIDs; i++) {
+        long id;
+        if (Tcl_GetLongFromObj(interp, ids[i], &id) != TCL_OK) {
+            return TCL_ERROR;
+        }
+        featureIDs.push_back((unsigned long)id);
+    }
+    const char *layerName = Tcl_GetString(objv[3]);
+
+    g_renderer->selectFeatures(featureIDs, layerName);
+    return TCL_OK;
+}
+
+static int
+SelectModeOp(ClientData clientData, Tcl_Interp *interp, int objc,
+             Tcl_Obj *const *objv)
+{
+    const char *modeStr = Tcl_GetString(objv[2]);
+    // Parse mode string
+    Renderer::SelectMode mode = Renderer::SELECT_OFF;
+    if (modeStr[0] == 'o' && strcmp(modeStr, "off") == 0) {
+        mode = Renderer::SELECT_OFF;
+    } else if (modeStr[0] == 'o' && strcmp(modeStr, "on") == 0) {
+        mode = Renderer::SELECT_ON;
+    } else {
+        Tcl_AppendResult(interp, "bad select mode \"", modeStr,
+                             "\": must be 'on' or 'off'", (char*)NULL);
+        return TCL_ERROR;
+    }
+    g_renderer->setSelectMode(mode);
+    return TCL_OK;
+}
+
+static CmdSpec selectOps[] = {
+    {"clear",   1, SelectClearOp,   2, 2, ""},
+    {"fadd",    2, SelectAddOp,     4, 4, "idlist layerName"},
+    {"fdelete", 2, SelectDeleteOp,  4, 4, "idlist layerName"},
+    {"feature", 2, SelectFeatureOp, 4, 4, "idlist layerName"},
+    {"mode",    1, SelectModeOp,    3, 3, "mode"},
+};
+static int nSelectOps = NumCmdSpecs(selectOps);
+
+static int
+SelectCmd(ClientData clientData, Tcl_Interp *interp, int objc, 
+          Tcl_Obj *const *objv)
+{
+    Tcl_ObjCmdProc *proc;
+
+    proc = GetOpFromObj(interp, nSelectOps, selectOps,
+                        CMDSPEC_ARG1, objc, objv, 0);
+    if (proc == NULL) {
+        return TCL_ERROR;
+    }
+    return (*proc) (clientData, interp, objc, objv);
+}
+
+#ifdef USE_READ_THREAD
+int
+GeoVis::queueCommands(Tcl_Interp *interp,
+                      ClientData clientData,
+                      ReadBuffer *inBufPtr)
+{
+    Tcl_DString commandString;
+    Tcl_DStringInit(&commandString);
+    fd_set readFds;
+
+    FD_ZERO(&readFds);
+    FD_SET(inBufPtr->file(), &readFds);
+    while (inBufPtr->isLineAvailable() || 
+           (select(inBufPtr->file()+1, &readFds, NULL, NULL, NULL) > 0)) {
+        size_t numBytes;
+        unsigned char *buffer;
+
+        /* A short read is treated as an error here because we assume that we
+         * will always get commands line by line. */
+        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
+            /* Terminate the server if we can't communicate with the client
+             * anymore. */
+            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
+                TRACE("Exiting server on EOF from client");
+                return -1;
+            } else {
+                ERROR("Exiting server, failed to read from client: %s",
+                      strerror(errno));
+                return -1;
+            }
+        }
+        Tcl_DStringAppend(&commandString, (char *)buffer, numBytes);
+        if (Tcl_CommandComplete(Tcl_DStringValue(&commandString))) {
+            // Add to queue
+            Command *command = new Command(Command::COMMAND);
+            command->setMessage((unsigned char *)Tcl_DStringValue(&commandString),
+                                Tcl_DStringLength(&commandString), Command::VOLATILE);
+            g_inQueue->enqueue(command);
+            Tcl_DStringSetLength(&commandString, 0);
+        }
+        FD_SET(inBufPtr->file(), &readFds);
+    }
+
+    return 1;
+}
+#endif
+
+/**
+ * \brief Execute commands from client in Tcl interpreter
+ * 
+ * In this threaded model, the select call is for event compression.  We
+ * want to execute render server commands as long as they keep coming.  
+ * This lets us execute a stream of many commands but render once.  This 
+ * benefits camera movements, screen resizing, and opacity changes 
+ * (using a slider on the client).  The down side is you don't render
+ * until there's a lull in the command stream.  If the client needs an
+ * image, it can issue an "imgflush" command.  That breaks us out of the
+ * read loop.
+ */
+int
+GeoVis::processCommands(Tcl_Interp *interp,
+                        ClientData clientData,
+                        ReadBuffer *inBufPtr, 
+                        int fdOut,
+                        struct timeval *timeout)
+{
+    int ret = 1;
+    int status = TCL_OK;
+
+    Tcl_DString command;
+    Tcl_DStringInit(&command);
+    fd_set readFds;
+    struct timeval tv, *tvPtr;
+
+    FD_ZERO(&readFds);
+    FD_SET(inBufPtr->file(), &readFds);
+
+    bool polling = false;
+    if (timeout->tv_sec >= 0L) {
+        tv.tv_sec = timeout->tv_sec;
+        tv.tv_usec = timeout->tv_usec;
+        polling = tv.tv_sec == 0 && tv.tv_usec == 0;
+        tvPtr = &tv;
+    } else {
+        // Block until data available
+        tvPtr = NULL;
+        TRACE("Blocking on select()");
+    }
+    while (inBufPtr->isLineAvailable() || 
+           (ret = select(inBufPtr->file()+1, &readFds, NULL, NULL, tvPtr)) > 0) {
+        size_t numBytes;
+        unsigned char *buffer;
+
+        /* A short read is treated as an error here because we assume that we
+         * will always get commands line by line. */
+        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
+            /* Terminate the server if we can't communicate with the client
+             * anymore. */
+            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
+                TRACE("Exiting server on EOF from client");
+                return -1;
+            } else {
+                ERROR("Exiting server, failed to read from client: %s",
+                      strerror(errno));
+                return -1;
+            }
+        }
+#if 0
+        Tcl_DString tmp;
+        Tcl_DStringInit(&tmp);
+        Tcl_Encoding encoding = Tcl_GetEncoding(interp, "identity");
+        TRACE("Encoding name: %s", Tcl_GetEncodingName(encoding));
+        Tcl_ExternalToUtfDString(encoding, (const char *)buffer, numBytes, &tmp);
+        Tcl_FreeEncoding(encoding);
+        Tcl_DStringAppend(&command, Tcl_DStringValue(&tmp), Tcl_DStringLength(&tmp));
+        Tcl_DStringFree(&tmp);
+#else
+        Tcl_DStringAppend(&command, (char *)buffer, numBytes);
+#endif
+        if (Tcl_CommandComplete(Tcl_DStringValue(&command))) {
+            struct timeval start, finish;
+            gettimeofday(&start, NULL);
+            g_stats.nCommands++;
+            status = ExecuteCommand(interp, &command);
+            gettimeofday(&finish, NULL);
+            g_stats.cmdTime += (MSECS_ELAPSED(start, finish) / 1.0e+3);
+            if (status == TCL_BREAK) {
+                return 2;               /* This was caused by a "imgflush"
+                                         * command. Break out of the read loop
+                                         * and allow a new image to be
+                                         * rendered. */
+            } else { //if (status != TCL_OK) {
+                ret = 0;
+                if (handleError(interp, clientData, status, fdOut) < 0) {
+                    return -1;
+                }
+            }
+            if (status == TCL_OK) {
+                ret = 3;
+            }
+        }
+
+        polling = true;
+        tv.tv_sec = tv.tv_usec = 0L;    /* On successive reads, we break out
+                                         * if no data is available. */
+        FD_SET(inBufPtr->file(), &readFds);
+        tvPtr = &tv;
+    }
+    if (!polling && ret == 0 && timeout->tv_sec > 0L) {
+        // If idle timeout expired, disconnect
+        TRACE("Exiting server after timeout waiting for client command");
+        return -1;
+    }
+
+    return ret;
+}
+
+/**
+ * \brief Send error message to client socket
+ */
+int
+GeoVis::handleError(Tcl_Interp *interp,
+                    ClientData clientData,
+                    int status, int fdOut)
+{
+    const char *string;
+    int nBytes;
+
+    if (status != TCL_OK) {
+        string = Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY);
+        nBytes = strlen(string);
+        if (nBytes > 0) {
+            TRACE("status=%d errorInfo=(%s)", status, string);
+
+            std::ostringstream oss;
+            oss << "nv>viserror -type internal_error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
+            std::string ostr = oss.str();
+            nBytes = ostr.length();
+
+            if (queueResponse(ostr.c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
+                return -1;
+            }
+        }
+    }
+
+    std::string msg = getUserMessages();
+    nBytes = msg.length();
+    if (nBytes > 0) {
+        string = msg.c_str();
+        TRACE("userError=(%s)", string);
+
+        std::ostringstream oss;
+        oss << "nv>viserror -type error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
+        std::string ostr = oss.str();
+        nBytes = ostr.length();
+
+        if (queueResponse(ostr.c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
+            return -1;
+        }
+
+        clearUserMessages();
+    }
+
+    return 0;
+}
+
+/**
+ * \brief Make Tcl interpreter safe, set encoding and add commands
+ */
+void
+GeoVis::initTcl(Tcl_Interp *interp, ClientData clientData)
+{
+    TRACE("LANG: %s", getenv("LANG"));
+    Tcl_GetEncodingNames(interp);
+    TRACE("Supported encodings: %s", Tcl_GetStringResult(interp));
+    int result = Tcl_Eval(interp, "encoding system\n");
+    if (result == TCL_OK) {
+        TRACE("Current system encoding: %s", Tcl_GetStringResult(interp));
+    } else {
+        ERROR("Couldn't determine system encoding");
+    }
+    const char *encoding = "utf-8";
+    if (Tcl_SetSystemEncoding(interp, encoding) != TCL_OK) {
+        TRACE("Failed to set Tcl encoding to %s", encoding);
+    } else {
+        TRACE("Set system encoding to %s", encoding);
+    }
+
+    Tcl_MakeSafe(interp);
+
+    Tcl_CreateObjCommand(interp, "camera",     CameraCmd,     clientData, NULL);
+    Tcl_CreateObjCommand(interp, "clientinfo", ClientInfoCmd, clientData, NULL);
+    Tcl_CreateObjCommand(interp, "colormap",   ColorMapCmd,   clientData, NULL);
+    Tcl_CreateObjCommand(interp, "file",       FileCmd,       clientData, NULL);
+    Tcl_CreateObjCommand(interp, "imgflush",   ImageFlushCmd, clientData, NULL);
+    Tcl_CreateObjCommand(interp, "key",        KeyCmd,        clientData, NULL);
+    Tcl_CreateObjCommand(interp, "legend",     LegendCmd,     clientData, NULL);
+    Tcl_CreateObjCommand(interp, "map",        MapCmd,        clientData, NULL);
+    Tcl_CreateObjCommand(interp, "mouse",      MouseCmd,      clientData, NULL);
+    Tcl_CreateObjCommand(interp, "placard",    PlacardCmd,    clientData, NULL);
+    Tcl_CreateObjCommand(interp, "renderer",   RendererCmd,   clientData, NULL);
+    Tcl_CreateObjCommand(interp, "screen",     ScreenCmd,     clientData, NULL);
+    Tcl_CreateObjCommand(interp, "select",     SelectCmd,     clientData, NULL);
+}
+
+/**
+ * \brief Delete Tcl commands and interpreter
+ */
+void GeoVis::exitTcl(Tcl_Interp *interp)
+{
+    Tcl_DeleteCommand(interp, "camera");
+    Tcl_DeleteCommand(interp, "clientinfo");
+    Tcl_DeleteCommand(interp, "colormap");
+    Tcl_DeleteCommand(interp, "file");
+    Tcl_DeleteCommand(interp, "imgflush");
+    Tcl_DeleteCommand(interp, "key");
+    Tcl_DeleteCommand(interp, "legend");
+    Tcl_DeleteCommand(interp, "map");
+    Tcl_DeleteCommand(interp, "mouse");
+    Tcl_DeleteCommand(interp, "placard");
+    Tcl_DeleteCommand(interp, "renderer");
+    Tcl_DeleteCommand(interp, "screen");
+    Tcl_DeleteCommand(interp, "select");
+
+    Tcl_DeleteInterp(interp);
+}
Index: /geovis/branches/1.0/RendererCmd.h
===================================================================
--- /geovis/branches/1.0/RendererCmd.h	(revision 6666)
+++ /geovis/branches/1.0/RendererCmd.h	(revision 6666)
@@ -0,0 +1,48 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2004-2013  HUBzero Foundation, LLC
+ *
+ * Author: Leif Delgass <ldelgass@purdue.edu>
+ */
+
+#ifndef GEOVIS_RENDERERCMD_H
+#define GEOVIS_RENDERERCMD_H
+
+#include <sys/time.h>
+#include <cstdio>
+#include <tcl.h>
+
+#include "ReadBuffer.h"
+#include "ResponseQueue.h"
+
+namespace GeoVis {
+
+extern ssize_t queueResponse(const void *bytes, size_t len, 
+                             Response::AllocationType allocType,
+                             Response::ResponseType type = Response::DATA);
+
+#ifdef USE_READ_THREAD
+extern int queueCommands(Tcl_Interp *interp,
+                         ClientData clientData,
+                         ReadBuffer *inBufPtr);
+#endif
+
+extern int processCommands(Tcl_Interp *interp,
+                           ClientData clientData,
+                           ReadBuffer *inBufPtr,
+                           int fdOut,
+                           struct timeval *timeout);
+
+extern int handleError(Tcl_Interp *interp,
+                       ClientData clientData,
+                       int status,
+                       int fdOut);
+
+extern void initTcl(Tcl_Interp *interp,
+                    ClientData clientData);
+
+extern void exitTcl(Tcl_Interp *interp);
+
+}
+
+#endif
Index: /geovis/branches/1.0/ResponseQueue.cpp
===================================================================
--- /geovis/branches/1.0/ResponseQueue.cpp	(revision 6666)
+++ /geovis/branches/1.0/ResponseQueue.cpp	(revision 6666)
@@ -0,0 +1,119 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2004-2013  HUBzero Foundation, LLC
+ *
+ * Author: George A. Howlett <gah@purdue.edu>
+ */
+
+#include <pthread.h>
+#include <semaphore.h>
+#include <cerrno>
+#include <cstring>
+#include <cstdlib>
+#include <list>
+
+#include "Trace.h"
+#include "ResponseQueue.h"
+
+using namespace GeoVis;
+
+ResponseQueue::ResponseQueue()
+{
+    pthread_mutex_init(&_idle, NULL);
+    if (sem_init(&_ready, 0, 0) < 0) {
+        ERROR("can't initialize semaphore: %s", strerror(errno));
+    }
+}
+
+ResponseQueue::~ResponseQueue()
+{
+    TRACE("Deleting ResponseQueue");
+    pthread_mutex_destroy(&_idle);
+    if (sem_destroy(&_ready) < 0) {
+        ERROR("can't destroy semaphore: %s", strerror(errno));
+    }
+    for (std::list<Response *>::iterator itr = _list.begin();
+         itr != _list.end(); ++itr) {
+        delete *itr;
+    }
+    _list.clear();
+}
+
+void
+ResponseQueue::enqueue(Response *response)
+{
+    if (pthread_mutex_lock(&_idle) != 0) {
+        ERROR("can't lock mutex: %s", strerror(errno));
+    }
+#ifdef QUEUE_ONLY_ONE_FRAME
+    /* Examine the list and remove any queued responses of the same type. */
+#ifdef TRACE_RESPONSE_QUEUE
+    TRACE("Before # of elements is %d", _list.size());
+#endif
+    for (std::list<Response *>::iterator itr = _list.begin();
+         itr != _list.end();) {
+        /* Remove any duplicate image or legend responses. There should be no
+         * more than one.  Note that if the client starts using multiple legends
+         * for different fields, this optimization should be disabled for legends.
+         * We may also need to differentiate between screen images and "hardcopy"
+         * images.
+         */
+        if ((response->type() == Response::IMAGE ||
+             response->type() == Response::LEGEND) &&
+            (*itr)->type() == response->type()) {
+            TRACE("Removing duplicate response of type: %d", (*itr)->type());
+            delete *itr;
+            itr = _list.erase(itr);
+        } else {
+#ifdef TRACE_RESPONSE_QUEUE
+            TRACE("Found queued response of type %d", (*itr)->type());
+#endif
+            ++itr;
+        }
+    }
+#endif
+    /* Add the new response to the end of the list. */
+    _list.push_back(response);
+#ifdef TRACE_RESPONSE_QUEUE
+    TRACE("After # of elements is %d", _list.size());
+#endif
+    if (sem_post(&_ready) < 0) {
+        ERROR("can't post semaphore: %s", strerror(errno));
+    }
+    if (pthread_mutex_unlock(&_idle) != 0) {
+        ERROR("can't unlock mutex: %s", strerror(errno));
+    }
+}
+
+Response *
+ResponseQueue::dequeue()
+{
+    Response *response = NULL;
+
+    int ret;
+    while ((ret = sem_wait(&_ready)) < 0 && errno == EINTR)
+        continue;
+    if (ret < 0) {
+        ERROR("can't wait on semaphore: %s", strerror(errno));
+    }
+    if (pthread_mutex_lock(&_idle) != 0) {
+        ERROR("can't lock mutex: %s", strerror(errno));
+    }
+    /* List may be empty if semaphore value was incremented beyond list
+     * size in the case of an enqueue operation deleting from the queue
+     * This is not an error, so just unlock and return to wait loop
+     */
+    if (_list.empty()) {
+        TRACE("Empty queue");
+    } else {
+        response = _list.front();
+        _list.pop_front();
+#ifdef TRACE_RESPONSE_QUEUE
+        TRACE("Dequeued response of type %d", response->type());
+#endif
+    }
+    if (pthread_mutex_unlock(&_idle) != 0) {
+        ERROR("can't unlock mutex: %s", strerror(errno));
+    }
+    return response;
+}
Index: /geovis/branches/1.0/ResponseQueue.h
===================================================================
--- /geovis/branches/1.0/ResponseQueue.h	(revision 6666)
+++ /geovis/branches/1.0/ResponseQueue.h	(revision 6666)
@@ -0,0 +1,130 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2004-2013  HUBzero Foundation, LLC
+ *
+ * Author: George A. Howlett <gah@purdue.edu>
+ */
+#ifndef GEOVIS_RESPONSE_QUEUE_H
+#define GEOVIS_RESPONSE_QUEUE_H
+
+#include <pthread.h>
+#include <semaphore.h>
+#include <cstdlib>
+#include <cstring>
+#include <list>
+
+namespace GeoVis {
+
+/**
+ * \brief Holds data for a response to be sent to client
+ */
+class Response
+{
+public:
+    enum AllocationType {
+        STATIC,
+        DYNAMIC,
+        VOLATILE
+    };
+    enum ResponseType {
+        IMAGE,          /**< Image to be displayed. */
+        LEGEND,         /**< Legend to be displayed. */
+        DATA,           /**< Any other type of non-error message. */
+        ERROR,          /**< Error message. */
+        OK              /**< Ack for client when no render required */
+    };
+
+    Response(ResponseType type) :
+        _mesg(NULL),
+        _length(0),
+        _type(type)
+    {
+    }
+
+    virtual ~Response()
+    {
+	if ((_length > 0) && (_mesg != NULL) && (_allocType == DYNAMIC)) {
+            free(_mesg);
+	}
+    }
+
+    /// Get the ResponseType
+    ResponseType type()
+    {
+	return _type;
+    }
+
+    /// Get the Response data
+    unsigned char *message()
+    {
+	return _mesg;
+    }
+
+    /// Get the number of bytes in the Response data
+    size_t length()
+    {
+	return _length;
+    }
+
+    /// Set the message/data making up the Response
+    /**
+     * If the AllocationType is DYNAMIC, the message data will be free()d
+     * by the destructor.  If the AllocationType is VOLATILE, a copy of 
+     * the message data will be made.
+     *
+     * \param[in] mesg The Response data, can be a command and/or binary data
+     * \param[in] length The number of bytes in mesg
+     * \param[in] type Specify how the memory was allocated for mesg
+     */ 
+    void setMessage(unsigned char *mesg, size_t length, AllocationType type)
+    {
+	if (type == VOLATILE) {
+	    _length = length;
+	    _mesg = (unsigned char *)malloc(length);
+	    memcpy(_mesg, mesg, length);
+	    _allocType = DYNAMIC;
+	} else {
+	    _length = length;
+	    _mesg = mesg;
+	    _allocType = type;
+	}
+    }
+
+private:
+    /** 
+     * (Malloc-ed by caller, freed by destructor)
+     * Contains the message/bytes to be sent to the client. */
+    unsigned char *_mesg;
+    size_t _length;       /**< # of bytes in the above message. */
+    ResponseType _type;
+    AllocationType _allocType;
+};
+
+/**
+ * \brief Queue to hold pending Responses to be sent to the client
+ *
+ * A semaphore and mutex are used to control access to the 
+ * queue by a reader and writer thread
+ */
+class ResponseQueue
+{
+public:
+    ResponseQueue();
+
+    virtual ~ResponseQueue();
+
+    /// Add a response to the end of the queue
+    void enqueue(Response *response);
+
+    /// Remove a response from the front of the queue
+    Response *dequeue();
+
+private:
+    pthread_mutex_t _idle;
+    sem_t _ready; /**< Semaphore indicating that a response has been queued. */
+    std::list<Response *> _list;
+};
+
+}
+
+#endif
Index: /geovis/branches/1.0/ScaleBar.cpp
===================================================================
--- /geovis/branches/1.0/ScaleBar.cpp	(revision 6666)
+++ /geovis/branches/1.0/ScaleBar.cpp	(revision 6666)
@@ -0,0 +1,148 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2014  HUBzero Foundation, LLC
+ *
+ * Author: Leif Delgass <ldelgass@purdue.edu>
+ */
+
+#include "ScaleBar.h"
+
+using namespace GeoVis;
+
+double GeoVis::normalizeScaleMeters(double meters)
+{
+    if (meters <= 3) {
+        meters = 1;
+    } else if (meters <= 7.5) {
+        meters = 5;
+    } else if (meters <= 15) {
+        meters = 10;
+    } else if (meters <= 35) {
+        meters = 20;
+    } else if (meters <= 75) {
+        meters = 50;
+    } else if (meters <= 150) {
+        meters = 100;
+    } else if (meters <= 350) {
+        meters = 200;
+    } else if (meters <= 750) {
+        meters = 500;
+    } else if (meters <= 1500) {
+        meters = 1000;
+    } else if (meters <= 3500) {
+        meters = 2000;
+    } else if (meters <= 7500) {
+        meters = 5000;
+    } else if (meters <= 15000) {
+        meters = 10000;
+    } else if (meters <= 35000) {
+        meters = 20000;
+    } else if (meters <= 55000) {
+        meters = 50000;
+    } else if (meters <= 150000) {
+        meters = 100000;
+    } else if (meters <= 350000) {
+        meters = 200000;
+    } else if (meters <= 750000) {
+        meters = 500000;
+    } else if (meters <= 1500000) {
+        meters = 1000000;
+    } else {
+        meters = 2000000;
+    }
+    return meters;
+}
+
+double GeoVis::normalizeScaleFeet(double feet)
+{
+    double feetPerMile = 5280.0;
+    if (feet <= 7.5) {
+        feet = 5;
+    } else if (feet <= 15) {
+        feet = 10;
+    } else if (feet <= 35) {
+        feet = 20;
+    } else if (feet <= 75) {
+        feet = 50;
+    } else if (feet <= 150) {
+        feet = 100;
+    } else if (feet <= 350) {
+        feet = 200;
+    } else if (feet <= 750) {
+        feet = 500;
+    } else if (feet <= 1500) {
+        feet = 1000;
+    } else if (feet <= 3640) {
+        feet = 2000;
+    } else if (feet <= 1.5 * feetPerMile) {
+        feet = 1 * feetPerMile;
+    } else if (feet <= 3.5 * feetPerMile) {
+        feet = 2 * feetPerMile;
+    } else if (feet <= 7.5 * feetPerMile) {
+        feet = 5 * feetPerMile;
+    } else if (feet <= 15 * feetPerMile) {
+        feet = 10 * feetPerMile;
+    } else if (feet <= 35 * feetPerMile) {
+        feet = 20 * feetPerMile;
+    } else if (feet <= 75 * feetPerMile) {
+        feet = 50 * feetPerMile;
+    } else if (feet <= 150 * feetPerMile) {
+        feet = 100 * feetPerMile;
+    } else if (feet <= 350 * feetPerMile) {
+        feet = 200 * feetPerMile;
+    } else if (feet <= 750 * feetPerMile) {
+        feet = 500 * feetPerMile;
+    } else if (feet <= 1500 * feetPerMile) {
+        feet = 1000 * feetPerMile;
+    } else {
+        feet = 2000 * feetPerMile;
+    }
+    return feet;
+}
+
+double GeoVis::normalizeScaleNauticalMiles(double nmi)
+{
+    //double feetPerMile = 6076.12;
+    if (nmi <= 0.0015) {
+        nmi = 0.001;
+    } else if (nmi <= 0.0035) {
+        nmi = 0.002;
+    } else if (nmi <= 0.0075) {
+        nmi = 0.005;
+    } else if (nmi <= 0.015) {
+        nmi = 0.01;
+    } else if (nmi <= 0.035) {
+        nmi = 0.02;
+    } else if (nmi <= 0.075) {
+        nmi = 0.05;
+    } else if (nmi <= 0.15) {
+        nmi = 0.1;
+    } else if (nmi <= 0.35) {
+        nmi = 0.2;
+    } else if (nmi <= 0.75) {
+        nmi = 0.5;
+    } else if (nmi <= 1.5) {
+        nmi = 1;
+    } else if (nmi <= 3.5) {
+        nmi = 2;
+    } else if (nmi <= 7.5) {
+        nmi = 5;
+    } else if (nmi <= 15) {
+        nmi = 10;
+    } else if (nmi <= 35) {
+        nmi = 20;
+    } else if (nmi <= 75) {
+        nmi = 50;
+    } else if (nmi <= 150) {
+        nmi = 100;
+    } else if (nmi <= 350) {
+        nmi = 200;
+    } else if (nmi <= 750) {
+        nmi = 500;
+    } else if (nmi <= 1500) {
+        nmi = 1000;
+    } else {
+        nmi = 2000;
+    }
+    return nmi;
+}
Index: /geovis/branches/1.0/ScaleBar.h
===================================================================
--- /geovis/branches/1.0/ScaleBar.h	(revision 6666)
+++ /geovis/branches/1.0/ScaleBar.h	(revision 6666)
@@ -0,0 +1,28 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2014  HUBzero Foundation, LLC
+ *
+ * Author: Leif Delgass <ldelgass@purdue.edu>
+ */
+
+#ifndef GEOVIS_SCALEBAR_H
+#define GEOVIS_SCALEBAR_H
+
+namespace GeoVis {
+
+enum ScaleBarUnits {
+    UNITS_METERS,
+    UNITS_INTL_FEET,
+    UNITS_US_SURVEY_FEET,
+    UNITS_NAUTICAL_MILES
+};
+
+extern double normalizeScaleMeters(double meters);
+
+extern double normalizeScaleFeet(double feet);
+
+extern double normalizeScaleNauticalMiles(double nmi);
+
+}
+
+#endif
Index: /geovis/branches/1.0/Stats.cpp
===================================================================
--- /geovis/branches/1.0/Stats.cpp	(revision 6666)
+++ /geovis/branches/1.0/Stats.cpp	(revision 6666)
@@ -0,0 +1,149 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2013-2014  HUBzero Foundation, LLC
+ *
+ * Author: Leif Delgass <ldelgass@purdue.edu>
+ */
+
+#include <cstring>
+#include <errno.h>
+#include <sys/time.h>
+#include <sys/times.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <md5.h>
+
+#include <iostream>
+#include <string>
+#include <sstream>
+
+#include "Stats.h"
+#include "RenderServer.h"
+#include "Trace.h"
+
+using namespace GeoVis;
+
+/* 
+ * Session information:
+ *   - Name of render server
+ *   - Process ID
+ *   - Hostname where server is running
+ *   - Stop date of session
+ *   - Stop date of session in seconds
+ *   - Number of data sets loaded from client
+ *   - Number of data bytes total loaded from client
+ *   - Number of frames returned
+ *   - Number of bytes total returned (in frames)
+ *   - Number of commands received
+ *   - Total elapsed time of all commands
+ *   - Total elapsed time of session
+ *   - Exit code of vizserver
+ *   - User time 
+ *   - System time
+ *   - User time of children 
+ *   - System time of children 
+ */
+int
+GeoVis::serverStats(const Stats& stats, int code) 
+{
+    struct timeval now;
+    /* Get ending time. */
+    gettimeofday(&now, NULL);
+    double session_time = CVT2SECS(now) - CVT2SECS(stats.start);
+
+    std::ostringstream oss;
+    char buf[BUFSIZ];
+    gethostname(buf, BUFSIZ-1);
+    buf[BUFSIZ-1] = '\0';
+    std::string hostname(buf);
+    strcpy(buf, ctime(&now.tv_sec));
+    buf[strlen(buf) - 1] = '\0';
+    std::string date(buf);
+
+    oss << "render_stop "
+        << "renderer geovis "
+        << "pid " << getpid() << " "
+        << "host " << hostname << " "
+        << "date {" << date << "} "
+        << "date_secs " << now.tv_sec << " "
+        << "num_data_sets " << stats.nDataSets << " "
+        << "data_set_bytes " << stats.nDataBytes << " "
+        << "num_frames " << stats.nFrames << " "
+        << "frame_bytes " << stats.nFrameBytes << " "
+        << "num_commands " << stats.nCommands << " "
+        << "cmd_time " << stats.cmdTime << " "
+        << "session_time " << session_time << " "
+        << "status " << code << " ";
+
+    {
+	long clocksPerSec = sysconf(_SC_CLK_TCK);
+	double clockRes = 1.0 / clocksPerSec;
+	struct tms tms;
+
+	memset(&tms, 0, sizeof(tms));
+	times(&tms);
+
+        oss << "utime " << (tms.tms_utime * clockRes) << " "
+            << "stime " << (tms.tms_stime * clockRes) << " "
+            << "cutime " << (tms.tms_cutime * clockRes) << " "
+            << "cstime " << (tms.tms_cstime * clockRes) << " ";
+    }
+
+    oss << std::endl;
+
+    int fd = getStatsFile();
+    std::string ostr = oss.str();
+    int result = writeToStatsFile(fd, ostr.c_str(), ostr.length());
+    close(fd);
+    return result;
+}
+
+int
+GeoVis::getStatsFile(const char *key)
+{
+    char fileName[33];
+    md5_state_t state;
+    md5_byte_t digest[16];
+
+    if (key == NULL || g_statsFile >= 0) {
+        return g_statsFile;
+    }
+
+    std::ostringstream keyStream;
+    keyStream << key << " "
+              << getpid();
+    std::string keystr = keyStream.str();
+    TRACE("Stats file key: '%s'", keystr.c_str());
+
+    md5_init(&state);
+    md5_append(&state, (const md5_byte_t *)keystr.c_str(), keystr.length());
+    md5_finish(&state, digest);
+    for (int i = 0; i < 16; i++) {
+        sprintf(fileName + i * 2, "%02x", digest[i]);
+    }
+
+    std::ostringstream oss;
+    oss << STATSDIR << "/" << fileName;
+    g_statsFile = open(oss.str().c_str(), O_EXCL | O_CREAT | O_WRONLY, 0600);
+    if (g_statsFile < 0) {
+	ERROR("can't open \"%s\": %s", fileName, strerror(errno));
+	return -1;
+    }
+    return g_statsFile;
+}
+
+int
+GeoVis::writeToStatsFile(int fd, const char *s, size_t length)
+{
+    if (fd >= 0) {
+        ssize_t numWritten;
+
+        numWritten = write(fd, s, length);
+        if (numWritten == (ssize_t)length) {
+            close(dup(fd));
+        }
+    }
+    return 0;
+}
Index: /geovis/branches/1.0/Stats.h
===================================================================
--- /geovis/branches/1.0/Stats.h	(revision 6666)
+++ /geovis/branches/1.0/Stats.h	(revision 6666)
@@ -0,0 +1,41 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2013-2014  HUBzero Foundation, LLC
+ *
+ * Author: Leif Delgass <ldelgass@purdue.edu>
+ */
+
+#ifndef GEOVIS_STATS_H
+#define GEOVIS_STATS_H
+
+#include <cstdlib>
+#include <sys/types.h>
+#include <sys/time.h>
+
+#ifndef STATSDIR
+#define STATSDIR	"/var/tmp/visservers"
+#endif
+
+namespace GeoVis {
+
+class Stats {
+public:
+    pid_t pid;
+    size_t nDataSets;       /**< # of data sets received */
+    size_t nDataBytes;      /**< # of bytes received as data sets */
+    size_t nFrames;         /**< # of frames sent to client. */
+    size_t nFrameBytes;     /**< # of bytes for all frames. */
+    size_t nCommands;       /**< # of commands executed */
+    double cmdTime;         /**< Elasped time spent executing commands. */
+    struct timeval start;   /**< Start of elapsed time. */
+};
+
+extern int serverStats(const Stats& stats, int code); 
+
+extern int getStatsFile(const char *key);
+
+extern int writeToStatsFile(int fd, const char *s, size_t length);
+
+}
+
+#endif
Index: /geovis/branches/1.0/TGAWriter.cpp
===================================================================
--- /geovis/branches/1.0/TGAWriter.cpp	(revision 6666)
+++ /geovis/branches/1.0/TGAWriter.cpp	(revision 6666)
@@ -0,0 +1,205 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2004-2013  HUBzero Foundation, LLC
+ *
+ * Author: Leif Delgass <ldelgass@purdue.edu>
+ */
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <cerrno>
+#include <sys/uio.h>
+
+#include <iostream>
+#include <fstream>
+
+#include "TGAWriter.h"
+#include "Trace.h"
+#ifdef USE_THREADS
+#include "ResponseQueue.h"
+#endif
+
+#ifdef USE_THREADS
+
+/**
+ * \brief Writes image command + data to supplied file descriptor.
+ *
+ * The image data must be supplied in BGR(A) order with bottom to 
+ * top scanline ordering.
+ *
+ * \param[in] queue Pointer to ResponseQueue to write to
+ * \param[in] cmdName Command name to send (byte length will be appended)
+ * \param[in] data Image data
+ * \param[in] width Width of image in pixels
+ * \param[in] height Height of image in pixels
+ * \param[in] bytesPerPixel Should be 3 or 4, depending on alpha
+ */
+void
+GeoVis::queueTGA(ResponseQueue *queue, const char *cmdName, 
+                 const unsigned char *data,
+                 int width, int height,
+                 int bytesPerPixel)
+{
+    TRACE("(%dx%d)\n", width, height);
+
+    size_t headerLength = 18;
+
+    char header[headerLength];
+    memset(header, 0, headerLength);
+    header[2] = (char)2;  // image type (2 = uncompressed true-color)
+    header[12] = (char)width;
+    header[13] = (char)(width >> 8);
+    header[14] = (char)height;
+    header[15] = (char)(height >> 8);
+    header[16] = (char)(bytesPerPixel*8); // bits per pixel
+
+    size_t dataLength = width * height * bytesPerPixel;
+    size_t cmdLength;
+
+    char command[200];
+    cmdLength = snprintf(command, sizeof(command), "%s %lu\n", cmdName, 
+                         (unsigned long)headerLength + dataLength);
+
+    size_t length;
+    unsigned char *mesg = NULL;
+
+    length = headerLength + dataLength + cmdLength;
+    mesg = (unsigned char *)malloc(length);
+    if (mesg == NULL) {
+        ERROR("can't allocate %ld bytes for the image message", length);
+        return;
+    }
+    memcpy(mesg, command, cmdLength);
+    memcpy(mesg + cmdLength, header, headerLength);
+    memcpy(mesg + cmdLength + headerLength, data, dataLength);
+
+    Response *response = NULL;
+    if (strncmp(cmdName, "nv>legend", 9) == 0) {
+        response = new Response(Response::LEGEND);
+    } else {
+        response = new Response(Response::IMAGE);
+    }
+    response->setMessage(mesg, length, Response::DYNAMIC);
+    queue->enqueue(response);
+    TRACE("Leaving (%dx%d)\n", width, height);
+}
+#else
+
+/**
+ * \brief Writes image command + data to supplied file descriptor.
+ *
+ * The image data must be supplied in BGR(A) order with bottom to 
+ * top scanline ordering.
+ *
+ * \param[in] fd File descriptor that will be written to
+ * \param[in] cmdName Command name to send (byte length will be appended)
+ * \param[in] data Image data
+ * \param[in] width Width of image in pixels
+ * \param[in] height Height of image in pixels
+ * \param[in] bytesPerPixel Should be 3 or 4, depending on alpha
+ */
+void
+GeoVis::writeTGA(int fd, const char *cmdName,
+                 const unsigned char *data,
+                 int width, int height,
+                 int bytesPerPixel)
+{
+    TRACE("(%dx%d)\n", width, height);
+
+    size_t headerLength = 18;
+
+    char header[headerLength];
+    memset(header, 0, headerLength);
+    header[2] = (char)2;  // image type (2 = uncompressed true-color)
+    header[12] = (char)width;
+    header[13] = (char)(width >> 8);
+    header[14] = (char)height;
+    header[15] = (char)(height >> 8);
+    header[16] = (char)(bytesPerPixel*8); // bits per pixel
+
+    size_t dataLength = width * height * bytesPerPixel;
+
+    char command[200];
+    snprintf(command, sizeof(command), "%s %lu\n", cmdName, 
+             (unsigned long)headerLength + dataLength);
+
+    size_t nRecs = 3;
+
+    struct iovec *iov;
+    iov = (struct iovec *)malloc(sizeof(struct iovec) * nRecs);
+
+    // Write the command, then the image header and data.
+    // Command
+    iov[0].iov_base = command;
+    iov[0].iov_len = strlen(command);
+    // Header of image data
+    iov[1].iov_base = header;
+    iov[1].iov_len = headerLength;
+    // Image data **must be BGR(A)!**
+    iov[2].iov_base = const_cast<unsigned char *>(data);
+    iov[2].iov_len = dataLength;
+
+    if (writev(fd, iov, nRecs) < 0) {
+	ERROR("write failed: %s\n", strerror(errno));
+    }
+    free(iov);
+
+    TRACE("Leaving (%dx%d)\n", width, height);
+}
+#endif  /*USE_THREADS*/
+
+/**
+ * \brief Writes image data to supplied file name
+ *
+ * The image data must be supplied with bottom to top 
+ * scanline ordering.  Source data should have BGR(A) 
+ * ordering, unless srcIsRGB is true, in which case 
+ * the source data will be converted from RGB(A) to 
+ * BGR(A).  Note that this is slow and it is better 
+ * to pass in BGR(A) data.
+ *
+ * \param[in] filename Path to file that will be written
+ * \param[in] imgData Image data
+ * \param[in] width Width of image in pixels
+ * \param[in] height Height of image in pixels
+ * \param[in] bytesPerPixel Should be 3 or 4, depending on alpha
+ * \param[in] srcIsRGB If true source data will be re-ordered
+ */
+void
+GeoVis::writeTGAFile(const char *filename,
+                     const unsigned char *imgData,
+                     int width, int height,
+                     int bytesPerPixel,
+                     bool srcIsRGB)
+{
+    TRACE("%s (%dx%d)\n", filename, width, height);
+
+    std::ofstream outfile(filename, std::ios::out | std::ios::binary | std::ios::trunc);
+    char header[18];
+    memset(header, 0, 18);
+    header[2] = (char)2;  // image type (2 = uncompressed true-color)
+    header[12] = (char)width;
+    header[13] = (char)(width >> 8);
+    header[14] = (char)height;
+    header[15] = (char)(height >> 8);
+    header[16] = (char)(bytesPerPixel*8); // bits per pixel
+
+    outfile.write(header, sizeof(header));
+
+    if (!srcIsRGB) {
+        outfile.write((const char *)imgData, width * height * bytesPerPixel);
+    } else {
+        // RGB(A) -> BGR(A)
+        for (int i = 0; i < width * height; i++) {
+            outfile << imgData[i*bytesPerPixel+2]
+                    << imgData[i*bytesPerPixel+1]
+                    << imgData[i*bytesPerPixel];
+            if (bytesPerPixel == 4) {
+                outfile << imgData[i*bytesPerPixel+3];
+            }
+        }
+    }
+
+    outfile.close();
+}
Index: /geovis/branches/1.0/TGAWriter.h
===================================================================
--- /geovis/branches/1.0/TGAWriter.h	(revision 6666)
+++ /geovis/branches/1.0/TGAWriter.h	(revision 6666)
@@ -0,0 +1,33 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2004-2013  HUBzero Foundation, LLC
+ *
+ * Author: Leif Delgass <ldelgass@purdue.edu>
+ */
+
+#ifndef GEOVIS_TGAWRITER_H
+#define GEOVIS_TGAWRITER_H
+
+#ifdef USE_THREADS
+#include "ResponseQueue.h"
+#endif
+
+namespace GeoVis {
+#ifdef USE_THREADS
+extern
+void queueTGA(ResponseQueue *queue, const char *cmdName, 
+              const unsigned char *data, int width, int height, 
+              int bytesPerPixel);
+#else
+extern
+void writeTGA(int fd, const char *cmdName, 
+              const unsigned char *data, int width, int height, 
+              int bytesPerPixel);
+#endif
+extern
+void writeTGAFile(const char *filename, 
+                  const unsigned char *data, int width, int height, 
+                  int bytesPerPixel, bool srcIsRGB = false);
+}
+
+#endif
Index: /geovis/branches/1.0/Trace.cpp
===================================================================
--- /geovis/branches/1.0/Trace.cpp	(revision 6666)
+++ /geovis/branches/1.0/Trace.cpp	(revision 6666)
@@ -0,0 +1,100 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2004-2013  HUBzero Foundation, LLC
+ *
+ * Author: George A. Howlett <gah@purdue.edu>
+ */
+
+#include <cstdio>
+#include <cstdarg>
+#include <cstring>
+#include <syslog.h>
+
+#include <string>
+#include <sstream>
+
+#include "Trace.h"
+
+using namespace GeoVis;
+
+static std::ostringstream g_UserErrorString;
+
+#define MSG_LEN	2047
+
+/**
+ * \brief Open syslog for writing
+ */
+void
+GeoVis::initLog()
+{
+    openlog("geovis", LOG_CONS | LOG_PERROR | LOG_PID,  LOG_USER);
+}
+
+/**
+ * \brief Close syslog
+ */
+void
+GeoVis::closeLog()
+{
+    closelog();
+}
+
+/**
+ * \brief Write a message to syslog
+ */
+void 
+GeoVis::logMessage(int priority, const char *funcname,
+                   const char *path, int lineNum, const char* fmt, ...)
+{
+    char message[MSG_LEN + 1];
+    const char *s;
+    int length;
+    va_list lst;
+
+    va_start(lst, fmt);
+    s = strrchr(path, '/');
+    if (s == NULL) {
+	s = path;
+    } else {
+	s++;
+    }
+    if (priority & LOG_DEBUG) {
+        length = snprintf(message, MSG_LEN, "%s:%d(%s): ", s, lineNum, funcname);
+    } else {
+        length = snprintf(message, MSG_LEN, "%s:%d: ", s, lineNum);
+    }
+    length += vsnprintf(message + length, MSG_LEN - length, fmt, lst);
+    message[MSG_LEN] = '\0';
+
+    syslog(priority, "%s", message);
+}
+
+/**
+ * \brief Write a user message to buffer
+ */
+void 
+GeoVis::logUserMessage(const char* fmt, ...)
+{
+    char message[MSG_LEN + 1];
+    int length = 0;
+    va_list lst;
+
+    va_start(lst, fmt);
+
+    length += vsnprintf(message, MSG_LEN, fmt, lst);
+    message[MSG_LEN] = '\0';
+
+    g_UserErrorString << message << "\n";
+}
+
+std::string
+GeoVis::getUserMessages()
+{
+    return g_UserErrorString.str();
+}
+
+void 
+GeoVis::clearUserMessages()
+{
+    g_UserErrorString.str(std::string());
+}
Index: /geovis/branches/1.0/Trace.h
===================================================================
--- /geovis/branches/1.0/Trace.h	(revision 6666)
+++ /geovis/branches/1.0/Trace.h	(revision 6666)
@@ -0,0 +1,69 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2004-2013  HUBzero Foundation, LLC
+ *
+ * Author: George A. Howlett <gah@purdue.edu>
+ */
+
+#ifndef GEOVIS_TRACE_H
+#define GEOVIS_TRACE_H
+
+#include <cstring>
+#include <string>
+
+#include <syslog.h>
+
+#include <osg/NodeVisitor>
+
+namespace GeoVis {
+
+extern void logUserMessage(const char* format, ...);
+
+extern std::string getUserMessages();
+
+extern void clearUserMessages();
+
+extern void initLog();
+
+extern void closeLog();
+
+extern void logMessage(int priority, const char *funcname, const char *fileName,
+                       int lineNum, const char* format, ...);
+
+#define ERROR(...)	GeoVis::logMessage(LOG_ERR, __FUNCTION__, __FILE__, __LINE__, __VA_ARGS__)
+#ifdef WANT_TRACE
+#define TRACE(...)	GeoVis::logMessage(LOG_DEBUG, __FUNCTION__, __FILE__, __LINE__, __VA_ARGS__)
+#else 
+#define TRACE(...) 
+#endif  /*WANT_TRACE*/
+#ifdef WANT_FRAME
+#define FRAME(...)	GeoVis::logMessage(LOG_DEBUG, __FUNCTION__, __FILE__, __LINE__, __VA_ARGS__)
+#else 
+#define FRAME(...) 
+#endif  /*WANT_FRAME*/
+#define WARN(...)	GeoVis::logMessage(LOG_WARNING, __FUNCTION__, __FILE__, __LINE__, __VA_ARGS__)
+#define INFO(...)	GeoVis::logMessage(LOG_INFO, __FUNCTION__, __FILE__, __LINE__, __VA_ARGS__)
+
+#define USER_ERROR(...) GeoVis::logUserMessage(__VA_ARGS__)
+
+class GraphPrintVisitor : public osg::NodeVisitor
+{
+public:
+    GraphPrintVisitor()
+        : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
+    {}
+
+    virtual void apply(osg::Node& node)
+    {
+        char space[256];
+        size_t len = getNodePath().size();
+        memset(space, ' ', len);
+        space[len] = '\0';
+        TRACE("%s%s (%s) %p", space, node.className(), node.getName().c_str(), &node);
+        traverse(node);
+    }
+};
+
+}
+
+#endif
Index: /geovis/branches/1.0/Types.h
===================================================================
--- /geovis/branches/1.0/Types.h	(revision 6666)
+++ /geovis/branches/1.0/Types.h	(revision 6666)
@@ -0,0 +1,27 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2004-2013  HUBzero Foundation, LLC
+ *
+ * Author: Leif Delgass <ldelgass@purdue.edu>
+ */
+
+#ifndef GEOVIS_TYPES_H
+#define GEOVIS_TYPES_H
+
+namespace GeoVis {
+
+    enum Axis {
+        X_AXIS = 0,
+        Y_AXIS,
+        Z_AXIS
+    };
+
+    enum PrincipalPlane {
+        PLANE_XY = 0,
+        PLANE_ZY,
+        PLANE_XZ
+    };
+
+}
+
+#endif
Index: /geovis/branches/1.0/Util.cpp
===================================================================
--- /geovis/branches/1.0/Util.cpp	(revision 6666)
+++ /geovis/branches/1.0/Util.cpp	(revision 6666)
@@ -0,0 +1,45 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2015  HUBzero Foundation, LLC
+ *
+ * Author: Leif Delgass <ldelgass@purdue.edu>
+ */
+
+#include <string>
+#include <fstream>
+
+#include <osgDB/FileUtils>
+#include <osgDB/FileNameUtils>
+
+#include "Util.h"
+#include "Trace.h"
+
+using namespace GeoVis;
+
+std::string GeoVis::loadCSVLayer(const char *csvFilePath, const char *longitudeColName, const char *latitudeColName)
+{
+    std::string baseName = osgDB::getStrippedName(csvFilePath);
+    std::string vrtFile = osgDB::getNameLessExtension(csvFilePath) + std::string(".vrt");
+    if (!osgDB::fileExists(vrtFile)) {
+        std::ofstream ofs(vrtFile.c_str());
+        if (ofs.is_open()) {
+            // GDAL/OGR < 1.9.2 doesn't like the <?xml?> declaration
+            // << "<?xml version=\"1.0\"?>" << std::endl
+            ofs << "<OGRVRTDataSource>" << std::endl
+                << " <OGRVRTLayer name=\"" << baseName << "\">" << std::endl
+                << "  <SrcDataSource>" << csvFilePath << "</SrcDataSource>" << std::endl
+                << "  <GeometryType>wkbPoint</GeometryType>" << std::endl
+                << "  <LayerSRS>WGS84</LayerSRS>" << std::endl
+                << "  <GeometryField encoding=\"PointFromColumns\" x=\""
+                << longitudeColName << "\" y=\""  << latitudeColName << "\"/>" << std::endl
+                << " </OGRVRTLayer>" << std::endl
+                << "</OGRVRTDataSource>" << std::endl;
+            ofs.close();
+        } else {
+            ERROR("Failed to open file '%s' for writing", vrtFile.c_str());
+        }
+    } else {
+        TRACE("Found existing file  '%s'", vrtFile.c_str());
+    }
+    return vrtFile;
+}
Index: /geovis/branches/1.0/Util.h
===================================================================
--- /geovis/branches/1.0/Util.h	(revision 6666)
+++ /geovis/branches/1.0/Util.h	(revision 6666)
@@ -0,0 +1,49 @@
+/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * Copyright (C) 2015  HUBzero Foundation, LLC
+ *
+ * Author: Leif Delgass <ldelgass@purdue.edu>
+ */
+#ifndef GEOVIS_UTIL_H
+#define GEOVIS_UTIL_H
+
+#include <cstring>
+#include <string>
+
+#include <osg/NodeVisitor>
+#include <osgEarthFeatures/FeatureIndex>
+#include <osgEarthFeatures/FeatureSourceIndexNode>
+
+namespace GeoVis {
+
+class FindFeatureSourceIndexNodeVisitor : public osg::NodeVisitor {
+public:
+    FindFeatureSourceIndexNodeVisitor() :
+        osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
+        source(NULL)
+    {
+    }
+
+    virtual ~FindFeatureSourceIndexNodeVisitor() {}
+
+    virtual void apply(osg::Node& node)
+    {
+        if (strcmp(node.className(), "FeatureSourceIndexNode") == 0) {
+            osgEarth::Features::FeatureSourceIndexNode *fsin = dynamic_cast<osgEarth::Features::FeatureSourceIndexNode *>(&node);
+            osgEarth::Features::FeatureSourceIndex *index = fsin->getIndex();
+            if (source == NULL || index->getFeatureSource() == source) {
+                nodes.push_back(fsin);
+            }
+        }
+        traverse(node);
+    }
+
+    osgEarth::Features::FeatureSource *source;
+    std::vector<osgEarth::Features::FeatureSourceIndexNode *> nodes;
+};
+
+extern std::string loadCSVLayer(const char *filename, const char *longitudeColName = "longitude", const char *latitudeColName = "latitude");
+
+}
+
+#endif
Index: /geovis/branches/1.0/cf/install-sh
===================================================================
--- /geovis/branches/1.0/cf/install-sh	(revision 6666)
+++ /geovis/branches/1.0/cf/install-sh	(revision 6666)
@@ -0,0 +1,269 @@
+#!/bin/sh
+#
+# install - install a program, script, or datafile
+#
+# This originates from X11R5 (mit/util/scripts/install.sh), which was
+# later released in X11R6 (xc/config/util/install.sh) with the
+# following copyright and license.
+#
+# Copyright (C) 1994 X Consortium
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
+# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+# Except as contained in this notice, the name of the X Consortium shall not
+# be used in advertising or otherwise to promote the sale, use or other deal-
+# ings in this Software without prior written authorization from the X Consor-
+# tium.
+#
+#
+# FSF changes to this file are in the public domain.
+#
+# Calling this script install-sh is preferred over install.sh, to prevent
+# `make' implicit rules from creating a file called install from it
+# when there is no Makefile.
+#
+# This script is compatible with the BSD install script, but was written
+# from scratch.  It can only install one file at a time, a restriction
+# shared with many OS's install programs.
+
+
+# set DOITPROG to echo to test this script
+
+# Don't use :- since 4.3BSD and earlier shells don't like it.
+doit="${DOITPROG-}"
+
+
+# put in absolute paths if you don't have them in your path; or use env. vars.
+
+mvprog="${MVPROG-mv}"
+cpprog="${CPPROG-cp}"
+chmodprog="${CHMODPROG-chmod}"
+chownprog="${CHOWNPROG-chown}"
+chgrpprog="${CHGRPPROG-chgrp}"
+stripprog="${STRIPPROG-strip}"
+rmprog="${RMPROG-rm}"
+mkdirprog="${MKDIRPROG-mkdir}"
+
+transformbasename=""
+transform_arg=""
+instcmd="$mvprog"
+chmodcmd="$chmodprog 0755"
+chowncmd=""
+chgrpcmd=""
+stripcmd=""
+rmcmd="$rmprog -f"
+mvcmd="$mvprog"
+src=""
+dst=""
+dir_arg=""
+
+while [ x"$1" != x ]; do
+    case $1 in
+	-c) instcmd="$cpprog"
+	    shift
+	    continue;;
+
+	-d) dir_arg=true
+	    shift
+	    continue;;
+
+	-m) chmodcmd="$chmodprog $2"
+	    shift
+	    shift
+	    continue;;
+
+	-o) chowncmd="$chownprog $2"
+	    shift
+	    shift
+	    continue;;
+
+	-g) chgrpcmd="$chgrpprog $2"
+	    shift
+	    shift
+	    continue;;
+
+	-s) stripcmd="$stripprog"
+	    shift
+	    continue;;
+
+	-t=*) transformarg=`echo $1 | sed 's/-t=//'`
+	    shift
+	    continue;;
+
+	-b=*) transformbasename=`echo $1 | sed 's/-b=//'`
+	    shift
+	    continue;;
+
+	*)  if [ x"$src" = x ]
+	    then
+		src=$1
+	    else
+		# this colon is to work around a 386BSD /bin/sh bug
+		:
+		dst=$1
+	    fi
+	    shift
+	    continue;;
+    esac
+done
+
+if [ x"$src" = x ]
+then
+	echo "install:	no input file specified"
+	exit 1
+else
+	true
+fi
+
+if [ x"$dir_arg" != x ]; then
+	dst=$src
+	src=""
+	
+	if [ -d $dst ]; then
+		instcmd=:
+		chmodcmd=""
+	else
+		instcmd=mkdir
+	fi
+else
+
+# Waiting for this to be detected by the "$instcmd $src $dsttmp" command
+# might cause directories to be created, which would be especially bad 
+# if $src (and thus $dsttmp) contains '*'.
+
+	if [ -f $src -o -d $src ]
+	then
+		true
+	else
+		echo "install:  $src does not exist"
+		exit 1
+	fi
+	
+	if [ x"$dst" = x ]
+	then
+		echo "install:	no destination specified"
+		exit 1
+	else
+		true
+	fi
+
+# If destination is a directory, append the input filename; if your system
+# does not like double slashes in filenames, you may need to add some logic
+
+	if [ -d $dst ]
+	then
+		dst="$dst"/`basename $src`
+	else
+		true
+	fi
+fi
+
+## this sed command emulates the dirname command
+dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'`
+
+# Make sure that the destination directory exists.
+#  this part is taken from Noah Friedman's mkinstalldirs script
+
+# Skip lots of stat calls in the usual case.
+if [ ! -d "$dstdir" ]; then
+defaultIFS=' 	
+'
+IFS="${IFS-${defaultIFS}}"
+
+oIFS="${IFS}"
+# Some sh's can't handle IFS=/ for some reason.
+IFS='%'
+set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'`
+IFS="${oIFS}"
+
+pathcomp=''
+
+while [ $# -ne 0 ] ; do
+	pathcomp="${pathcomp}${1}"
+	shift
+
+	if [ ! -d "${pathcomp}" ] ;
+        then
+		$mkdirprog "${pathcomp}"
+	else
+		true
+	fi
+
+	pathcomp="${pathcomp}/"
+done
+fi
+
+if [ x"$dir_arg" != x ]
+then
+	$doit $instcmd $dst &&
+
+	if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi &&
+	if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi &&
+	if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi &&
+	if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi
+else
+
+# If we're going to rename the final executable, determine the name now.
+
+	if [ x"$transformarg" = x ] 
+	then
+		dstfile=`basename $dst`
+	else
+		dstfile=`basename $dst $transformbasename | 
+			sed $transformarg`$transformbasename
+	fi
+
+# don't allow the sed command to completely eliminate the filename
+
+	if [ x"$dstfile" = x ] 
+	then
+		dstfile=`basename $dst`
+	else
+		true
+	fi
+
+# Make a temp file name in the proper directory.
+
+	dsttmp=$dstdir/#inst.$$#
+
+# Move or copy the file name to the temp name
+
+	$doit $instcmd $src $dsttmp &&
+
+	trap "rm -f ${dsttmp}" 0 &&
+
+# and set any options; do chmod last to preserve setuid bits
+
+# If any of these fail, we abort the whole thing.  If we want to
+# ignore errors from any of these, just make sure not to ignore
+# errors from the above "$doit $instcmd $src $dsttmp" command.
+
+	if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi &&
+	if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi &&
+	if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi &&
+	if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi &&
+
+# Now rename the file to the real destination.
+
+	$doit $rmcmd -f $dstdir/$dstfile &&
+	$doit $mvcmd $dsttmp $dstdir/$dstfile 
+
+fi &&
+
+
+exit 0
Index: /geovis/branches/1.0/cf/mkinstalldirs
===================================================================
--- /geovis/branches/1.0/cf/mkinstalldirs	(revision 6666)
+++ /geovis/branches/1.0/cf/mkinstalldirs	(revision 6666)
@@ -0,0 +1,40 @@
+#! /bin/sh
+# mkinstalldirs --- make directory hierarchy
+# Author: Noah Friedman <friedman@prep.ai.mit.edu>
+# Created: 1993-05-16
+# Public domain
+
+# $Id: mkinstalldirs,v 1.13 1999/01/05 03:18:55 bje Exp $
+
+errstatus=0
+
+for file
+do
+   set fnord `echo ":$file" | sed -ne 's/^:\//#/;s/^://;s/\// /g;s/^#/\//;p'`
+   shift
+
+   pathcomp=
+   for d
+   do
+     pathcomp="$pathcomp$d"
+     case "$pathcomp" in
+       -* ) pathcomp=./$pathcomp ;;
+     esac
+
+     if test ! -d "$pathcomp"; then
+        echo "mkdir $pathcomp"
+
+        mkdir "$pathcomp" || lasterr=$?
+
+        if test ! -d "$pathcomp"; then
+  	  errstatus=$lasterr
+        fi
+     fi
+
+     pathcomp="$pathcomp/"
+   done
+done
+
+exit $errstatus
+
+# mkinstalldirs ends here
Index: /geovis/branches/1.0/configure
===================================================================
--- /geovis/branches/1.0/configure	(revision 6666)
+++ /geovis/branches/1.0/configure	(revision 6666)
@@ -0,0 +1,5629 @@
+#! /bin/sh
+# Guess values for system-dependent variables and create Makefiles.
+# Generated by GNU Autoconf 2.69 for geovis 0.4.
+#
+# Report bugs to <rappture@nanohub.org>.
+#
+#
+# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
+#
+#
+# This configure script is free software; the Free Software Foundation
+# gives unlimited permission to copy, distribute and modify it.
+## -------------------- ##
+## M4sh Initialization. ##
+## -------------------- ##
+
+# Be more Bourne compatible
+DUALCASE=1; export DUALCASE # for MKS sh
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then :
+  emulate sh
+  NULLCMD=:
+  # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '${1+"$@"}'='"$@"'
+  setopt NO_GLOB_SUBST
+else
+  case `(set -o) 2>/dev/null` in #(
+  *posix*) :
+    set -o posix ;; #(
+  *) :
+     ;;
+esac
+fi
+
+
+as_nl='
+'
+export as_nl
+# Printing a long string crashes Solaris 7 /usr/bin/printf.
+as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo
+# Prefer a ksh shell builtin over an external printf program on Solaris,
+# but without wasting forks for bash or zsh.
+if test -z "$BASH_VERSION$ZSH_VERSION" \
+    && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='print -r --'
+  as_echo_n='print -rn --'
+elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='printf %s\n'
+  as_echo_n='printf %s'
+else
+  if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then
+    as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"'
+    as_echo_n='/usr/ucb/echo -n'
+  else
+    as_echo_body='eval expr "X$1" : "X\\(.*\\)"'
+    as_echo_n_body='eval
+      arg=$1;
+      case $arg in #(
+      *"$as_nl"*)
+	expr "X$arg" : "X\\(.*\\)$as_nl";
+	arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;;
+      esac;
+      expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl"
+    '
+    export as_echo_n_body
+    as_echo_n='sh -c $as_echo_n_body as_echo'
+  fi
+  export as_echo_body
+  as_echo='sh -c $as_echo_body as_echo'
+fi
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+  PATH_SEPARATOR=:
+  (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
+    (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
+      PATH_SEPARATOR=';'
+  }
+fi
+
+
+# IFS
+# We need space, tab and new line, in precisely that order.  Quoting is
+# there to prevent editors from complaining about space-tab.
+# (If _AS_PATH_WALK were called with IFS unset, it would disable word
+# splitting by setting IFS to empty value.)
+IFS=" ""	$as_nl"
+
+# Find who we are.  Look in the path if we contain no directory separator.
+as_myself=
+case $0 in #((
+  *[\\/]* ) as_myself=$0 ;;
+  *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+  done
+IFS=$as_save_IFS
+
+     ;;
+esac
+# We did not find ourselves, most probably we were run as `sh COMMAND'
+# in which case we are not to be found in the path.
+if test "x$as_myself" = x; then
+  as_myself=$0
+fi
+if test ! -f "$as_myself"; then
+  $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
+  exit 1
+fi
+
+# Unset variables that we do not need and which cause bugs (e.g. in
+# pre-3.0 UWIN ksh).  But do not cause bugs in bash 2.01; the "|| exit 1"
+# suppresses any "Segmentation fault" message there.  '((' could
+# trigger a bug in pdksh 5.2.14.
+for as_var in BASH_ENV ENV MAIL MAILPATH
+do eval test x\${$as_var+set} = xset \
+  && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
+done
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+LC_ALL=C
+export LC_ALL
+LANGUAGE=C
+export LANGUAGE
+
+# CDPATH.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+# Use a proper internal environment variable to ensure we don't fall
+  # into an infinite loop, continuously re-executing ourselves.
+  if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then
+    _as_can_reexec=no; export _as_can_reexec;
+    # We cannot yet assume a decent shell, so we have to provide a
+# neutralization value for shells without unset; and this also
+# works around shells that cannot unset nonexistent variables.
+# Preserve -v and -x to the replacement shell.
+BASH_ENV=/dev/null
+ENV=/dev/null
+(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV
+case $- in # ((((
+  *v*x* | *x*v* ) as_opts=-vx ;;
+  *v* ) as_opts=-v ;;
+  *x* ) as_opts=-x ;;
+  * ) as_opts= ;;
+esac
+exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"}
+# Admittedly, this is quite paranoid, since all the known shells bail
+# out after a failed `exec'.
+$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2
+as_fn_exit 255
+  fi
+  # We don't want this to propagate to other subprocesses.
+          { _as_can_reexec=; unset _as_can_reexec;}
+if test "x$CONFIG_SHELL" = x; then
+  as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then :
+  emulate sh
+  NULLCMD=:
+  # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '\${1+\"\$@\"}'='\"\$@\"'
+  setopt NO_GLOB_SUBST
+else
+  case \`(set -o) 2>/dev/null\` in #(
+  *posix*) :
+    set -o posix ;; #(
+  *) :
+     ;;
+esac
+fi
+"
+  as_required="as_fn_return () { (exit \$1); }
+as_fn_success () { as_fn_return 0; }
+as_fn_failure () { as_fn_return 1; }
+as_fn_ret_success () { return 0; }
+as_fn_ret_failure () { return 1; }
+
+exitcode=0
+as_fn_success || { exitcode=1; echo as_fn_success failed.; }
+as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; }
+as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; }
+as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; }
+if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then :
+
+else
+  exitcode=1; echo positional parameters were not saved.
+fi
+test x\$exitcode = x0 || exit 1
+test -x / || exit 1"
+  as_suggested="  as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO
+  as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO
+  eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" &&
+  test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1
+test \$(( 1 + 1 )) = 2 || exit 1"
+  if (eval "$as_required") 2>/dev/null; then :
+  as_have_required=yes
+else
+  as_have_required=no
+fi
+  if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then :
+
+else
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+as_found=false
+for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  as_found=:
+  case $as_dir in #(
+	 /*)
+	   for as_base in sh bash ksh sh5; do
+	     # Try only shells that exist, to save several forks.
+	     as_shell=$as_dir/$as_base
+	     if { test -f "$as_shell" || test -f "$as_shell.exe"; } &&
+		    { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then :
+  CONFIG_SHELL=$as_shell as_have_required=yes
+		   if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then :
+  break 2
+fi
+fi
+	   done;;
+       esac
+  as_found=false
+done
+$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } &&
+	      { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then :
+  CONFIG_SHELL=$SHELL as_have_required=yes
+fi; }
+IFS=$as_save_IFS
+
+
+      if test "x$CONFIG_SHELL" != x; then :
+  export CONFIG_SHELL
+             # We cannot yet assume a decent shell, so we have to provide a
+# neutralization value for shells without unset; and this also
+# works around shells that cannot unset nonexistent variables.
+# Preserve -v and -x to the replacement shell.
+BASH_ENV=/dev/null
+ENV=/dev/null
+(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV
+case $- in # ((((
+  *v*x* | *x*v* ) as_opts=-vx ;;
+  *v* ) as_opts=-v ;;
+  *x* ) as_opts=-x ;;
+  * ) as_opts= ;;
+esac
+exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"}
+# Admittedly, this is quite paranoid, since all the known shells bail
+# out after a failed `exec'.
+$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2
+exit 255
+fi
+
+    if test x$as_have_required = xno; then :
+  $as_echo "$0: This script requires a shell more modern than all"
+  $as_echo "$0: the shells that I found on your system."
+  if test x${ZSH_VERSION+set} = xset ; then
+    $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should"
+    $as_echo "$0: be upgraded to zsh 4.3.4 or later."
+  else
+    $as_echo "$0: Please tell bug-autoconf@gnu.org and
+$0: rappture@nanohub.org about your system, including any
+$0: error possibly output before this message. Then install
+$0: a modern shell, or manually run the script under such a
+$0: shell if you do have one."
+  fi
+  exit 1
+fi
+fi
+fi
+SHELL=${CONFIG_SHELL-/bin/sh}
+export SHELL
+# Unset more variables known to interfere with behavior of common tools.
+CLICOLOR_FORCE= GREP_OPTIONS=
+unset CLICOLOR_FORCE GREP_OPTIONS
+
+## --------------------- ##
+## M4sh Shell Functions. ##
+## --------------------- ##
+# as_fn_unset VAR
+# ---------------
+# Portably unset VAR.
+as_fn_unset ()
+{
+  { eval $1=; unset $1;}
+}
+as_unset=as_fn_unset
+
+# as_fn_set_status STATUS
+# -----------------------
+# Set $? to STATUS, without forking.
+as_fn_set_status ()
+{
+  return $1
+} # as_fn_set_status
+
+# as_fn_exit STATUS
+# -----------------
+# Exit the shell with STATUS, even in a "trap 0" or "set -e" context.
+as_fn_exit ()
+{
+  set +e
+  as_fn_set_status $1
+  exit $1
+} # as_fn_exit
+
+# as_fn_mkdir_p
+# -------------
+# Create "$as_dir" as a directory, including parents if necessary.
+as_fn_mkdir_p ()
+{
+
+  case $as_dir in #(
+  -*) as_dir=./$as_dir;;
+  esac
+  test -d "$as_dir" || eval $as_mkdir_p || {
+    as_dirs=
+    while :; do
+      case $as_dir in #(
+      *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
+      *) as_qdir=$as_dir;;
+      esac
+      as_dirs="'$as_qdir' $as_dirs"
+      as_dir=`$as_dirname -- "$as_dir" ||
+$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+	 X"$as_dir" : 'X\(//\)[^/]' \| \
+	 X"$as_dir" : 'X\(//\)$' \| \
+	 X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_dir" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)[^/].*/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`
+      test -d "$as_dir" && break
+    done
+    test -z "$as_dirs" || eval "mkdir $as_dirs"
+  } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
+
+
+} # as_fn_mkdir_p
+
+# as_fn_executable_p FILE
+# -----------------------
+# Test if FILE is an executable regular file.
+as_fn_executable_p ()
+{
+  test -f "$1" && test -x "$1"
+} # as_fn_executable_p
+# as_fn_append VAR VALUE
+# ----------------------
+# Append the text in VALUE to the end of the definition contained in VAR. Take
+# advantage of any shell optimizations that allow amortized linear growth over
+# repeated appends, instead of the typical quadratic growth present in naive
+# implementations.
+if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then :
+  eval 'as_fn_append ()
+  {
+    eval $1+=\$2
+  }'
+else
+  as_fn_append ()
+  {
+    eval $1=\$$1\$2
+  }
+fi # as_fn_append
+
+# as_fn_arith ARG...
+# ------------------
+# Perform arithmetic evaluation on the ARGs, and store the result in the
+# global $as_val. Take advantage of shells that can avoid forks. The arguments
+# must be portable across $(()) and expr.
+if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then :
+  eval 'as_fn_arith ()
+  {
+    as_val=$(( $* ))
+  }'
+else
+  as_fn_arith ()
+  {
+    as_val=`expr "$@" || test $? -eq 1`
+  }
+fi # as_fn_arith
+
+
+# as_fn_error STATUS ERROR [LINENO LOG_FD]
+# ----------------------------------------
+# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
+# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
+# script with STATUS, using 1 if that was 0.
+as_fn_error ()
+{
+  as_status=$1; test $as_status -eq 0 && as_status=1
+  if test "$4"; then
+    as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+    $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
+  fi
+  $as_echo "$as_me: error: $2" >&2
+  as_fn_exit $as_status
+} # as_fn_error
+
+if expr a : '\(a\)' >/dev/null 2>&1 &&
+   test "X`expr 00001 : '.*\(...\)'`" = X001; then
+  as_expr=expr
+else
+  as_expr=false
+fi
+
+if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
+  as_basename=basename
+else
+  as_basename=false
+fi
+
+if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
+  as_dirname=dirname
+else
+  as_dirname=false
+fi
+
+as_me=`$as_basename -- "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+	 X"$0" : 'X\(//\)$' \| \
+	 X"$0" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X/"$0" |
+    sed '/^.*\/\([^/][^/]*\)\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\/\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\/\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`
+
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+
+  as_lineno_1=$LINENO as_lineno_1a=$LINENO
+  as_lineno_2=$LINENO as_lineno_2a=$LINENO
+  eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" &&
+  test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || {
+  # Blame Lee E. McMahon (1931-1989) for sed's syntax.  :-)
+  sed -n '
+    p
+    /[$]LINENO/=
+  ' <$as_myself |
+    sed '
+      s/[$]LINENO.*/&-/
+      t lineno
+      b
+      :lineno
+      N
+      :loop
+      s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/
+      t loop
+      s/-\n.*//
+    ' >$as_me.lineno &&
+  chmod +x "$as_me.lineno" ||
+    { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; }
+
+  # If we had to re-execute with $CONFIG_SHELL, we're ensured to have
+  # already done that, so ensure we don't try to do so again and fall
+  # in an infinite loop.  This has already happened in practice.
+  _as_can_reexec=no; export _as_can_reexec
+  # Don't try to exec as it changes $[0], causing all sort of problems
+  # (the dirname of $[0] is not the place where we might find the
+  # original and so on.  Autoconf is especially sensitive to this).
+  . "./$as_me.lineno"
+  # Exit status is that of the last command.
+  exit
+}
+
+ECHO_C= ECHO_N= ECHO_T=
+case `echo -n x` in #(((((
+-n*)
+  case `echo 'xy\c'` in
+  *c*) ECHO_T='	';;	# ECHO_T is single tab character.
+  xy)  ECHO_C='\c';;
+  *)   echo `echo ksh88 bug on AIX 6.1` > /dev/null
+       ECHO_T='	';;
+  esac;;
+*)
+  ECHO_N='-n';;
+esac
+
+rm -f conf$$ conf$$.exe conf$$.file
+if test -d conf$$.dir; then
+  rm -f conf$$.dir/conf$$.file
+else
+  rm -f conf$$.dir
+  mkdir conf$$.dir 2>/dev/null
+fi
+if (echo >conf$$.file) 2>/dev/null; then
+  if ln -s conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s='ln -s'
+    # ... but there are two gotchas:
+    # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
+    # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
+    # In both cases, we have to default to `cp -pR'.
+    ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
+      as_ln_s='cp -pR'
+  elif ln conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s=ln
+  else
+    as_ln_s='cp -pR'
+  fi
+else
+  as_ln_s='cp -pR'
+fi
+rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
+rmdir conf$$.dir 2>/dev/null
+
+if mkdir -p . 2>/dev/null; then
+  as_mkdir_p='mkdir -p "$as_dir"'
+else
+  test -d ./-p && rmdir ./-p
+  as_mkdir_p=false
+fi
+
+as_test_x='test -x'
+as_executable_p=as_fn_executable_p
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+
+test -n "$DJDIR" || exec 7<&0 </dev/null
+exec 6>&1
+
+# Name of the host.
+# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status,
+# so uname gets run too.
+ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q`
+
+#
+# Initializations.
+#
+ac_default_prefix=/usr/local
+ac_clean_files=
+ac_config_libobj_dir=.
+LIBOBJS=
+cross_compiling=no
+subdirs=
+MFLAGS=
+MAKEFLAGS=
+
+# Identity of this package.
+PACKAGE_NAME='geovis'
+PACKAGE_TARNAME='geovis'
+PACKAGE_VERSION='0.4'
+PACKAGE_STRING='geovis 0.4'
+PACKAGE_BUGREPORT='rappture@nanohub.org'
+PACKAGE_URL=''
+
+# Factoring default headers for most tests.
+ac_includes_default="\
+#include <stdio.h>
+#ifdef HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+# include <sys/stat.h>
+#endif
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# ifdef HAVE_STDLIB_H
+#  include <stdlib.h>
+# endif
+#endif
+#ifdef HAVE_STRING_H
+# if !defined STDC_HEADERS && defined HAVE_MEMORY_H
+#  include <memory.h>
+# endif
+# include <string.h>
+#endif
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif
+#ifdef HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif"
+
+ac_subst_vars='LTLIBOBJS
+LIBOBJS
+OSG_LIB_SPEC
+OSG_LIB_DIR
+OSG_INC_SPEC
+TCL_VERSION
+TCL_LIB_SPEC
+TCL_INC_SPEC
+STATSDIR
+RESOURCES_DIR
+EGREP
+GREP
+CXXCPP
+ac_ct_CXX
+CXXFLAGS
+CXX
+CPP
+OBJEXT
+EXEEXT
+ac_ct_CC
+CPPFLAGS
+LDFLAGS
+CFLAGS
+CC
+MKDIR_P
+LN_S
+RANLIB
+INSTALL_DATA
+INSTALL_SCRIPT
+INSTALL_PROGRAM
+target_alias
+host_alias
+build_alias
+LIBS
+ECHO_T
+ECHO_N
+ECHO_C
+DEFS
+mandir
+localedir
+libdir
+psdir
+pdfdir
+dvidir
+htmldir
+infodir
+docdir
+oldincludedir
+includedir
+localstatedir
+sharedstatedir
+sysconfdir
+datadir
+datarootdir
+libexecdir
+sbindir
+bindir
+program_transform_name
+prefix
+exec_prefix
+PACKAGE_URL
+PACKAGE_BUGREPORT
+PACKAGE_STRING
+PACKAGE_VERSION
+PACKAGE_TARNAME
+PACKAGE_NAME
+PATH_SEPARATOR
+SHELL'
+ac_subst_files=''
+ac_user_opts='
+enable_option_checking
+with_tcllib
+with_osg_includes
+with_osg_libs
+with_statsdir
+'
+      ac_precious_vars='build_alias
+host_alias
+target_alias
+CC
+CFLAGS
+LDFLAGS
+LIBS
+CPPFLAGS
+CPP
+CXX
+CXXFLAGS
+CCC
+CXXCPP'
+
+
+# Initialize some variables set by options.
+ac_init_help=
+ac_init_version=false
+ac_unrecognized_opts=
+ac_unrecognized_sep=
+# The variables have the same names as the options, with
+# dashes changed to underlines.
+cache_file=/dev/null
+exec_prefix=NONE
+no_create=
+no_recursion=
+prefix=NONE
+program_prefix=NONE
+program_suffix=NONE
+program_transform_name=s,x,x,
+silent=
+site=
+srcdir=
+verbose=
+x_includes=NONE
+x_libraries=NONE
+
+# Installation directory options.
+# These are left unexpanded so users can "make install exec_prefix=/foo"
+# and all the variables that are supposed to be based on exec_prefix
+# by default will actually change.
+# Use braces instead of parens because sh, perl, etc. also accept them.
+# (The list follows the same order as the GNU Coding Standards.)
+bindir='${exec_prefix}/bin'
+sbindir='${exec_prefix}/sbin'
+libexecdir='${exec_prefix}/libexec'
+datarootdir='${prefix}/share'
+datadir='${datarootdir}'
+sysconfdir='${prefix}/etc'
+sharedstatedir='${prefix}/com'
+localstatedir='${prefix}/var'
+includedir='${prefix}/include'
+oldincludedir='/usr/include'
+docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
+infodir='${datarootdir}/info'
+htmldir='${docdir}'
+dvidir='${docdir}'
+pdfdir='${docdir}'
+psdir='${docdir}'
+libdir='${exec_prefix}/lib'
+localedir='${datarootdir}/locale'
+mandir='${datarootdir}/man'
+
+ac_prev=
+ac_dashdash=
+for ac_option
+do
+  # If the previous option needs an argument, assign it.
+  if test -n "$ac_prev"; then
+    eval $ac_prev=\$ac_option
+    ac_prev=
+    continue
+  fi
+
+  case $ac_option in
+  *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;;
+  *=)   ac_optarg= ;;
+  *)    ac_optarg=yes ;;
+  esac
+
+  # Accept the important Cygnus configure options, so we can diagnose typos.
+
+  case $ac_dashdash$ac_option in
+  --)
+    ac_dashdash=yes ;;
+
+  -bindir | --bindir | --bindi | --bind | --bin | --bi)
+    ac_prev=bindir ;;
+  -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*)
+    bindir=$ac_optarg ;;
+
+  -build | --build | --buil | --bui | --bu)
+    ac_prev=build_alias ;;
+  -build=* | --build=* | --buil=* | --bui=* | --bu=*)
+    build_alias=$ac_optarg ;;
+
+  -cache-file | --cache-file | --cache-fil | --cache-fi \
+  | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c)
+    ac_prev=cache_file ;;
+  -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \
+  | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*)
+    cache_file=$ac_optarg ;;
+
+  --config-cache | -C)
+    cache_file=config.cache ;;
+
+  -datadir | --datadir | --datadi | --datad)
+    ac_prev=datadir ;;
+  -datadir=* | --datadir=* | --datadi=* | --datad=*)
+    datadir=$ac_optarg ;;
+
+  -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \
+  | --dataroo | --dataro | --datar)
+    ac_prev=datarootdir ;;
+  -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \
+  | --dataroot=* | --dataroo=* | --dataro=* | --datar=*)
+    datarootdir=$ac_optarg ;;
+
+  -disable-* | --disable-*)
+    ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+      as_fn_error $? "invalid feature name: $ac_useropt"
+    ac_useropt_orig=$ac_useropt
+    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    case $ac_user_opts in
+      *"
+"enable_$ac_useropt"
+"*) ;;
+      *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig"
+	 ac_unrecognized_sep=', ';;
+    esac
+    eval enable_$ac_useropt=no ;;
+
+  -docdir | --docdir | --docdi | --doc | --do)
+    ac_prev=docdir ;;
+  -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*)
+    docdir=$ac_optarg ;;
+
+  -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv)
+    ac_prev=dvidir ;;
+  -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*)
+    dvidir=$ac_optarg ;;
+
+  -enable-* | --enable-*)
+    ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+      as_fn_error $? "invalid feature name: $ac_useropt"
+    ac_useropt_orig=$ac_useropt
+    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    case $ac_user_opts in
+      *"
+"enable_$ac_useropt"
+"*) ;;
+      *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig"
+	 ac_unrecognized_sep=', ';;
+    esac
+    eval enable_$ac_useropt=\$ac_optarg ;;
+
+  -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \
+  | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \
+  | --exec | --exe | --ex)
+    ac_prev=exec_prefix ;;
+  -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \
+  | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \
+  | --exec=* | --exe=* | --ex=*)
+    exec_prefix=$ac_optarg ;;
+
+  -gas | --gas | --ga | --g)
+    # Obsolete; use --with-gas.
+    with_gas=yes ;;
+
+  -help | --help | --hel | --he | -h)
+    ac_init_help=long ;;
+  -help=r* | --help=r* | --hel=r* | --he=r* | -hr*)
+    ac_init_help=recursive ;;
+  -help=s* | --help=s* | --hel=s* | --he=s* | -hs*)
+    ac_init_help=short ;;
+
+  -host | --host | --hos | --ho)
+    ac_prev=host_alias ;;
+  -host=* | --host=* | --hos=* | --ho=*)
+    host_alias=$ac_optarg ;;
+
+  -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht)
+    ac_prev=htmldir ;;
+  -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \
+  | --ht=*)
+    htmldir=$ac_optarg ;;
+
+  -includedir | --includedir | --includedi | --included | --include \
+  | --includ | --inclu | --incl | --inc)
+    ac_prev=includedir ;;
+  -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \
+  | --includ=* | --inclu=* | --incl=* | --inc=*)
+    includedir=$ac_optarg ;;
+
+  -infodir | --infodir | --infodi | --infod | --info | --inf)
+    ac_prev=infodir ;;
+  -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*)
+    infodir=$ac_optarg ;;
+
+  -libdir | --libdir | --libdi | --libd)
+    ac_prev=libdir ;;
+  -libdir=* | --libdir=* | --libdi=* | --libd=*)
+    libdir=$ac_optarg ;;
+
+  -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \
+  | --libexe | --libex | --libe)
+    ac_prev=libexecdir ;;
+  -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \
+  | --libexe=* | --libex=* | --libe=*)
+    libexecdir=$ac_optarg ;;
+
+  -localedir | --localedir | --localedi | --localed | --locale)
+    ac_prev=localedir ;;
+  -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*)
+    localedir=$ac_optarg ;;
+
+  -localstatedir | --localstatedir | --localstatedi | --localstated \
+  | --localstate | --localstat | --localsta | --localst | --locals)
+    ac_prev=localstatedir ;;
+  -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \
+  | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*)
+    localstatedir=$ac_optarg ;;
+
+  -mandir | --mandir | --mandi | --mand | --man | --ma | --m)
+    ac_prev=mandir ;;
+  -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*)
+    mandir=$ac_optarg ;;
+
+  -nfp | --nfp | --nf)
+    # Obsolete; use --without-fp.
+    with_fp=no ;;
+
+  -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+  | --no-cr | --no-c | -n)
+    no_create=yes ;;
+
+  -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+  | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r)
+    no_recursion=yes ;;
+
+  -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \
+  | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \
+  | --oldin | --oldi | --old | --ol | --o)
+    ac_prev=oldincludedir ;;
+  -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \
+  | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \
+  | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*)
+    oldincludedir=$ac_optarg ;;
+
+  -prefix | --prefix | --prefi | --pref | --pre | --pr | --p)
+    ac_prev=prefix ;;
+  -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*)
+    prefix=$ac_optarg ;;
+
+  -program-prefix | --program-prefix | --program-prefi | --program-pref \
+  | --program-pre | --program-pr | --program-p)
+    ac_prev=program_prefix ;;
+  -program-prefix=* | --program-prefix=* | --program-prefi=* \
+  | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*)
+    program_prefix=$ac_optarg ;;
+
+  -program-suffix | --program-suffix | --program-suffi | --program-suff \
+  | --program-suf | --program-su | --program-s)
+    ac_prev=program_suffix ;;
+  -program-suffix=* | --program-suffix=* | --program-suffi=* \
+  | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*)
+    program_suffix=$ac_optarg ;;
+
+  -program-transform-name | --program-transform-name \
+  | --program-transform-nam | --program-transform-na \
+  | --program-transform-n | --program-transform- \
+  | --program-transform | --program-transfor \
+  | --program-transfo | --program-transf \
+  | --program-trans | --program-tran \
+  | --progr-tra | --program-tr | --program-t)
+    ac_prev=program_transform_name ;;
+  -program-transform-name=* | --program-transform-name=* \
+  | --program-transform-nam=* | --program-transform-na=* \
+  | --program-transform-n=* | --program-transform-=* \
+  | --program-transform=* | --program-transfor=* \
+  | --program-transfo=* | --program-transf=* \
+  | --program-trans=* | --program-tran=* \
+  | --progr-tra=* | --program-tr=* | --program-t=*)
+    program_transform_name=$ac_optarg ;;
+
+  -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd)
+    ac_prev=pdfdir ;;
+  -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*)
+    pdfdir=$ac_optarg ;;
+
+  -psdir | --psdir | --psdi | --psd | --ps)
+    ac_prev=psdir ;;
+  -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*)
+    psdir=$ac_optarg ;;
+
+  -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+  | -silent | --silent | --silen | --sile | --sil)
+    silent=yes ;;
+
+  -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
+    ac_prev=sbindir ;;
+  -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
+  | --sbi=* | --sb=*)
+    sbindir=$ac_optarg ;;
+
+  -sharedstatedir | --sharedstatedir | --sharedstatedi \
+  | --sharedstated | --sharedstate | --sharedstat | --sharedsta \
+  | --sharedst | --shareds | --shared | --share | --shar \
+  | --sha | --sh)
+    ac_prev=sharedstatedir ;;
+  -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \
+  | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \
+  | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \
+  | --sha=* | --sh=*)
+    sharedstatedir=$ac_optarg ;;
+
+  -site | --site | --sit)
+    ac_prev=site ;;
+  -site=* | --site=* | --sit=*)
+    site=$ac_optarg ;;
+
+  -srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
+    ac_prev=srcdir ;;
+  -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
+    srcdir=$ac_optarg ;;
+
+  -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \
+  | --syscon | --sysco | --sysc | --sys | --sy)
+    ac_prev=sysconfdir ;;
+  -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \
+  | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*)
+    sysconfdir=$ac_optarg ;;
+
+  -target | --target | --targe | --targ | --tar | --ta | --t)
+    ac_prev=target_alias ;;
+  -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*)
+    target_alias=$ac_optarg ;;
+
+  -v | -verbose | --verbose | --verbos | --verbo | --verb)
+    verbose=yes ;;
+
+  -version | --version | --versio | --versi | --vers | -V)
+    ac_init_version=: ;;
+
+  -with-* | --with-*)
+    ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+      as_fn_error $? "invalid package name: $ac_useropt"
+    ac_useropt_orig=$ac_useropt
+    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    case $ac_user_opts in
+      *"
+"with_$ac_useropt"
+"*) ;;
+      *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig"
+	 ac_unrecognized_sep=', ';;
+    esac
+    eval with_$ac_useropt=\$ac_optarg ;;
+
+  -without-* | --without-*)
+    ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+      as_fn_error $? "invalid package name: $ac_useropt"
+    ac_useropt_orig=$ac_useropt
+    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    case $ac_user_opts in
+      *"
+"with_$ac_useropt"
+"*) ;;
+      *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig"
+	 ac_unrecognized_sep=', ';;
+    esac
+    eval with_$ac_useropt=no ;;
+
+  --x)
+    # Obsolete; use --with-x.
+    with_x=yes ;;
+
+  -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \
+  | --x-incl | --x-inc | --x-in | --x-i)
+    ac_prev=x_includes ;;
+  -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \
+  | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*)
+    x_includes=$ac_optarg ;;
+
+  -x-libraries | --x-libraries | --x-librarie | --x-librari \
+  | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l)
+    ac_prev=x_libraries ;;
+  -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \
+  | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*)
+    x_libraries=$ac_optarg ;;
+
+  -*) as_fn_error $? "unrecognized option: \`$ac_option'
+Try \`$0 --help' for more information"
+    ;;
+
+  *=*)
+    ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='`
+    # Reject names that are not valid shell variable names.
+    case $ac_envvar in #(
+      '' | [0-9]* | *[!_$as_cr_alnum]* )
+      as_fn_error $? "invalid variable name: \`$ac_envvar'" ;;
+    esac
+    eval $ac_envvar=\$ac_optarg
+    export $ac_envvar ;;
+
+  *)
+    # FIXME: should be removed in autoconf 3.0.
+    $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2
+    expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null &&
+      $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2
+    : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}"
+    ;;
+
+  esac
+done
+
+if test -n "$ac_prev"; then
+  ac_option=--`echo $ac_prev | sed 's/_/-/g'`
+  as_fn_error $? "missing argument to $ac_option"
+fi
+
+if test -n "$ac_unrecognized_opts"; then
+  case $enable_option_checking in
+    no) ;;
+    fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;;
+    *)     $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;;
+  esac
+fi
+
+# Check all directory arguments for consistency.
+for ac_var in	exec_prefix prefix bindir sbindir libexecdir datarootdir \
+		datadir sysconfdir sharedstatedir localstatedir includedir \
+		oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
+		libdir localedir mandir
+do
+  eval ac_val=\$$ac_var
+  # Remove trailing slashes.
+  case $ac_val in
+    */ )
+      ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'`
+      eval $ac_var=\$ac_val;;
+  esac
+  # Be sure to have absolute directory names.
+  case $ac_val in
+    [\\/$]* | ?:[\\/]* )  continue;;
+    NONE | '' ) case $ac_var in *prefix ) continue;; esac;;
+  esac
+  as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val"
+done
+
+# There might be people who depend on the old broken behavior: `$host'
+# used to hold the argument of --host etc.
+# FIXME: To remove some day.
+build=$build_alias
+host=$host_alias
+target=$target_alias
+
+# FIXME: To remove some day.
+if test "x$host_alias" != x; then
+  if test "x$build_alias" = x; then
+    cross_compiling=maybe
+  elif test "x$build_alias" != "x$host_alias"; then
+    cross_compiling=yes
+  fi
+fi
+
+ac_tool_prefix=
+test -n "$host_alias" && ac_tool_prefix=$host_alias-
+
+test "$silent" = yes && exec 6>/dev/null
+
+
+ac_pwd=`pwd` && test -n "$ac_pwd" &&
+ac_ls_di=`ls -di .` &&
+ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` ||
+  as_fn_error $? "working directory cannot be determined"
+test "X$ac_ls_di" = "X$ac_pwd_ls_di" ||
+  as_fn_error $? "pwd does not report name of working directory"
+
+
+# Find the source files, if location was not specified.
+if test -z "$srcdir"; then
+  ac_srcdir_defaulted=yes
+  # Try the directory containing this script, then the parent directory.
+  ac_confdir=`$as_dirname -- "$as_myself" ||
+$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+	 X"$as_myself" : 'X\(//\)[^/]' \| \
+	 X"$as_myself" : 'X\(//\)$' \| \
+	 X"$as_myself" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_myself" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)[^/].*/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`
+  srcdir=$ac_confdir
+  if test ! -r "$srcdir/$ac_unique_file"; then
+    srcdir=..
+  fi
+else
+  ac_srcdir_defaulted=no
+fi
+if test ! -r "$srcdir/$ac_unique_file"; then
+  test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .."
+  as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir"
+fi
+ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work"
+ac_abs_confdir=`(
+	cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg"
+	pwd)`
+# When building in place, set srcdir=.
+if test "$ac_abs_confdir" = "$ac_pwd"; then
+  srcdir=.
+fi
+# Remove unnecessary trailing slashes from srcdir.
+# Double slashes in file names in object file debugging info
+# mess up M-x gdb in Emacs.
+case $srcdir in
+*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;;
+esac
+for ac_var in $ac_precious_vars; do
+  eval ac_env_${ac_var}_set=\${${ac_var}+set}
+  eval ac_env_${ac_var}_value=\$${ac_var}
+  eval ac_cv_env_${ac_var}_set=\${${ac_var}+set}
+  eval ac_cv_env_${ac_var}_value=\$${ac_var}
+done
+
+#
+# Report the --help message.
+#
+if test "$ac_init_help" = "long"; then
+  # Omit some internal or obsolete options to make the list less imposing.
+  # This message is too long to be a string in the A/UX 3.1 sh.
+  cat <<_ACEOF
+\`configure' configures geovis 0.4 to adapt to many kinds of systems.
+
+Usage: $0 [OPTION]... [VAR=VALUE]...
+
+To assign environment variables (e.g., CC, CFLAGS...), specify them as
+VAR=VALUE.  See below for descriptions of some of the useful variables.
+
+Defaults for the options are specified in brackets.
+
+Configuration:
+  -h, --help              display this help and exit
+      --help=short        display options specific to this package
+      --help=recursive    display the short help of all the included packages
+  -V, --version           display version information and exit
+  -q, --quiet, --silent   do not print \`checking ...' messages
+      --cache-file=FILE   cache test results in FILE [disabled]
+  -C, --config-cache      alias for \`--cache-file=config.cache'
+  -n, --no-create         do not create output files
+      --srcdir=DIR        find the sources in DIR [configure dir or \`..']
+
+Installation directories:
+  --prefix=PREFIX         install architecture-independent files in PREFIX
+                          [$ac_default_prefix]
+  --exec-prefix=EPREFIX   install architecture-dependent files in EPREFIX
+                          [PREFIX]
+
+By default, \`make install' will install all the files in
+\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc.  You can specify
+an installation prefix other than \`$ac_default_prefix' using \`--prefix',
+for instance \`--prefix=\$HOME'.
+
+For better control, use the options below.
+
+Fine tuning of the installation directories:
+  --bindir=DIR            user executables [EPREFIX/bin]
+  --sbindir=DIR           system admin executables [EPREFIX/sbin]
+  --libexecdir=DIR        program executables [EPREFIX/libexec]
+  --sysconfdir=DIR        read-only single-machine data [PREFIX/etc]
+  --sharedstatedir=DIR    modifiable architecture-independent data [PREFIX/com]
+  --localstatedir=DIR     modifiable single-machine data [PREFIX/var]
+  --libdir=DIR            object code libraries [EPREFIX/lib]
+  --includedir=DIR        C header files [PREFIX/include]
+  --oldincludedir=DIR     C header files for non-gcc [/usr/include]
+  --datarootdir=DIR       read-only arch.-independent data root [PREFIX/share]
+  --datadir=DIR           read-only architecture-independent data [DATAROOTDIR]
+  --infodir=DIR           info documentation [DATAROOTDIR/info]
+  --localedir=DIR         locale-dependent data [DATAROOTDIR/locale]
+  --mandir=DIR            man documentation [DATAROOTDIR/man]
+  --docdir=DIR            documentation root [DATAROOTDIR/doc/geovis]
+  --htmldir=DIR           html documentation [DOCDIR]
+  --dvidir=DIR            dvi documentation [DOCDIR]
+  --pdfdir=DIR            pdf documentation [DOCDIR]
+  --psdir=DIR             ps documentation [DOCDIR]
+_ACEOF
+
+  cat <<\_ACEOF
+_ACEOF
+fi
+
+if test -n "$ac_init_help"; then
+  case $ac_init_help in
+     short | recursive ) echo "Configuration of geovis 0.4:";;
+   esac
+  cat <<\_ACEOF
+
+Optional Packages:
+  --with-PACKAGE[=ARG]    use PACKAGE [ARG=yes]
+  --without-PACKAGE       do not use PACKAGE (same as --with-PACKAGE=no)
+  --with-tcllib=DIR       location of Tcl binary library libtclstubs.a
+                          [default=/usr/lib]
+  --with-osg-includes=DIR OSG include files are in DIR
+  --with-osg-libs=DIR     OSG libraries are in DIR
+  --with-statsdir=DIR     Write statistics in DIR
+
+Some influential environment variables:
+  CC          C compiler command
+  CFLAGS      C compiler flags
+  LDFLAGS     linker flags, e.g. -L<lib dir> if you have libraries in a
+              nonstandard directory <lib dir>
+  LIBS        libraries to pass to the linker, e.g. -l<library>
+  CPPFLAGS    (Objective) C/C++ preprocessor flags, e.g. -I<include dir> if
+              you have headers in a nonstandard directory <include dir>
+  CPP         C preprocessor
+  CXX         C++ compiler command
+  CXXFLAGS    C++ compiler flags
+  CXXCPP      C++ preprocessor
+
+Use these variables to override the choices made by `configure' or to help
+it to find libraries and programs with nonstandard names/locations.
+
+Report bugs to <rappture@nanohub.org>.
+_ACEOF
+ac_status=$?
+fi
+
+if test "$ac_init_help" = "recursive"; then
+  # If there are subdirs, report their specific --help.
+  for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue
+    test -d "$ac_dir" ||
+      { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } ||
+      continue
+    ac_builddir=.
+
+case "$ac_dir" in
+.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
+*)
+  ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'`
+  # A ".." for each directory in $ac_dir_suffix.
+  ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
+  case $ac_top_builddir_sub in
+  "") ac_top_builddir_sub=. ac_top_build_prefix= ;;
+  *)  ac_top_build_prefix=$ac_top_builddir_sub/ ;;
+  esac ;;
+esac
+ac_abs_top_builddir=$ac_pwd
+ac_abs_builddir=$ac_pwd$ac_dir_suffix
+# for backward compatibility:
+ac_top_builddir=$ac_top_build_prefix
+
+case $srcdir in
+  .)  # We are building in place.
+    ac_srcdir=.
+    ac_top_srcdir=$ac_top_builddir_sub
+    ac_abs_top_srcdir=$ac_pwd ;;
+  [\\/]* | ?:[\\/]* )  # Absolute name.
+    ac_srcdir=$srcdir$ac_dir_suffix;
+    ac_top_srcdir=$srcdir
+    ac_abs_top_srcdir=$srcdir ;;
+  *) # Relative name.
+    ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
+    ac_top_srcdir=$ac_top_build_prefix$srcdir
+    ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
+esac
+ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
+
+    cd "$ac_dir" || { ac_status=$?; continue; }
+    # Check for guested configure.
+    if test -f "$ac_srcdir/configure.gnu"; then
+      echo &&
+      $SHELL "$ac_srcdir/configure.gnu" --help=recursive
+    elif test -f "$ac_srcdir/configure"; then
+      echo &&
+      $SHELL "$ac_srcdir/configure" --help=recursive
+    else
+      $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2
+    fi || ac_status=$?
+    cd "$ac_pwd" || { ac_status=$?; break; }
+  done
+fi
+
+test -n "$ac_init_help" && exit $ac_status
+if $ac_init_version; then
+  cat <<\_ACEOF
+geovis configure 0.4
+generated by GNU Autoconf 2.69
+
+Copyright (C) 2012 Free Software Foundation, Inc.
+This configure script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it.
+_ACEOF
+  exit
+fi
+
+## ------------------------ ##
+## Autoconf initialization. ##
+## ------------------------ ##
+
+# ac_fn_c_try_compile LINENO
+# --------------------------
+# Try to compile conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_compile ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  rm -f conftest.$ac_objext
+  if { { ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_compile") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    grep -v '^ *+' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+    mv -f conftest.er1 conftest.err
+  fi
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && {
+	 test -z "$ac_c_werror_flag" ||
+	 test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+	ac_retval=1
+fi
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_compile
+
+# ac_fn_c_try_cpp LINENO
+# ----------------------
+# Try to preprocess conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_cpp ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  if { { ac_try="$ac_cpp conftest.$ac_ext"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    grep -v '^ *+' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+    mv -f conftest.er1 conftest.err
+  fi
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } > conftest.i && {
+	 test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" ||
+	 test ! -s conftest.err
+       }; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+    ac_retval=1
+fi
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_cpp
+
+# ac_fn_cxx_try_compile LINENO
+# ----------------------------
+# Try to compile conftest.$ac_ext, and return whether this succeeded.
+ac_fn_cxx_try_compile ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  rm -f conftest.$ac_objext
+  if { { ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_compile") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    grep -v '^ *+' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+    mv -f conftest.er1 conftest.err
+  fi
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && {
+	 test -z "$ac_cxx_werror_flag" ||
+	 test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+	ac_retval=1
+fi
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_cxx_try_compile
+
+# ac_fn_cxx_try_cpp LINENO
+# ------------------------
+# Try to preprocess conftest.$ac_ext, and return whether this succeeded.
+ac_fn_cxx_try_cpp ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  if { { ac_try="$ac_cpp conftest.$ac_ext"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    grep -v '^ *+' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+    mv -f conftest.er1 conftest.err
+  fi
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } > conftest.i && {
+	 test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" ||
+	 test ! -s conftest.err
+       }; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+    ac_retval=1
+fi
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_cxx_try_cpp
+
+# ac_fn_cxx_check_header_mongrel LINENO HEADER VAR INCLUDES
+# ---------------------------------------------------------
+# Tests whether HEADER exists, giving a warning if it cannot be compiled using
+# the include files in INCLUDES and setting the cache variable VAR
+# accordingly.
+ac_fn_cxx_check_header_mongrel ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  if eval \${$3+:} false; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+  $as_echo_n "(cached) " >&6
+fi
+eval ac_res=\$$3
+	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+else
+  # Is the header compilable?
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 usability" >&5
+$as_echo_n "checking $2 usability... " >&6; }
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$4
+#include <$2>
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+  ac_header_compiler=yes
+else
+  ac_header_compiler=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_compiler" >&5
+$as_echo "$ac_header_compiler" >&6; }
+
+# Is the header present?
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 presence" >&5
+$as_echo_n "checking $2 presence... " >&6; }
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <$2>
+_ACEOF
+if ac_fn_cxx_try_cpp "$LINENO"; then :
+  ac_header_preproc=yes
+else
+  ac_header_preproc=no
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5
+$as_echo "$ac_header_preproc" >&6; }
+
+# So?  What about this header?
+case $ac_header_compiler:$ac_header_preproc:$ac_cxx_preproc_warn_flag in #((
+  yes:no: )
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&5
+$as_echo "$as_me: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&2;}
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5
+$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;}
+    ;;
+  no:yes:* )
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: present but cannot be compiled" >&5
+$as_echo "$as_me: WARNING: $2: present but cannot be compiled" >&2;}
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2:     check for missing prerequisite headers?" >&5
+$as_echo "$as_me: WARNING: $2:     check for missing prerequisite headers?" >&2;}
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: see the Autoconf documentation" >&5
+$as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;}
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2:     section \"Present But Cannot Be Compiled\"" >&5
+$as_echo "$as_me: WARNING: $2:     section \"Present But Cannot Be Compiled\"" >&2;}
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5
+$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;}
+( $as_echo "## ----------------------------------- ##
+## Report this to rappture@nanohub.org ##
+## ----------------------------------- ##"
+     ) | sed "s/^/$as_me: WARNING:     /" >&2
+    ;;
+esac
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  eval "$3=\$ac_header_compiler"
+fi
+eval ac_res=\$$3
+	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+fi
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_cxx_check_header_mongrel
+
+# ac_fn_cxx_try_run LINENO
+# ------------------------
+# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes
+# that executables *can* be run.
+ac_fn_cxx_try_run ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  if { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && { ac_try='./conftest$ac_exeext'
+  { { case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_try") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: program exited with status $ac_status" >&5
+       $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_retval=$ac_status
+fi
+  rm -rf conftest.dSYM conftest_ipa8_conftest.oo
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_cxx_try_run
+
+# ac_fn_cxx_check_header_compile LINENO HEADER VAR INCLUDES
+# ---------------------------------------------------------
+# Tests whether HEADER exists and can be compiled using the include files in
+# INCLUDES, setting the cache variable VAR accordingly.
+ac_fn_cxx_check_header_compile ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$4
+#include <$2>
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+  eval "$3=yes"
+else
+  eval "$3=no"
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+eval ac_res=\$$3
+	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_cxx_check_header_compile
+
+# ac_fn_cxx_try_link LINENO
+# -------------------------
+# Try to link conftest.$ac_ext, and return whether this succeeded.
+ac_fn_cxx_try_link ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  rm -f conftest.$ac_objext conftest$ac_exeext
+  if { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    grep -v '^ *+' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+    mv -f conftest.er1 conftest.err
+  fi
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && {
+	 test -z "$ac_cxx_werror_flag" ||
+	 test ! -s conftest.err
+       } && test -s conftest$ac_exeext && {
+	 test "$cross_compiling" = yes ||
+	 test -x conftest$ac_exeext
+       }; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+	ac_retval=1
+fi
+  # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information
+  # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would
+  # interfere with the next link command; also delete a directory that is
+  # left behind by Apple's compiler.  We do this before executing the actions.
+  rm -rf conftest.dSYM conftest_ipa8_conftest.oo
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_cxx_try_link
+
+# ac_fn_cxx_check_func LINENO FUNC VAR
+# ------------------------------------
+# Tests whether FUNC exists, setting the cache variable VAR accordingly
+ac_fn_cxx_check_func ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+/* Define $2 to an innocuous variant, in case <limits.h> declares $2.
+   For example, HP-UX 11i <limits.h> declares gettimeofday.  */
+#define $2 innocuous_$2
+
+/* System header to define __stub macros and hopefully few prototypes,
+    which can conflict with char $2 (); below.
+    Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+    <limits.h> exists even on freestanding compilers.  */
+
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+
+#undef $2
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char $2 ();
+/* The GNU C library defines this for functions which it implements
+    to always fail with ENOSYS.  Some functions are actually named
+    something starting with __ and the normal name is an alias.  */
+#if defined __stub_$2 || defined __stub___$2
+choke me
+#endif
+
+int
+main ()
+{
+return $2 ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_link "$LINENO"; then :
+  eval "$3=yes"
+else
+  eval "$3=no"
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+fi
+eval ac_res=\$$3
+	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_cxx_check_func
+cat >config.log <<_ACEOF
+This file contains any messages produced by compilers while
+running configure, to aid debugging if configure makes a mistake.
+
+It was created by geovis $as_me 0.4, which was
+generated by GNU Autoconf 2.69.  Invocation command line was
+
+  $ $0 $@
+
+_ACEOF
+exec 5>>config.log
+{
+cat <<_ASUNAME
+## --------- ##
+## Platform. ##
+## --------- ##
+
+hostname = `(hostname || uname -n) 2>/dev/null | sed 1q`
+uname -m = `(uname -m) 2>/dev/null || echo unknown`
+uname -r = `(uname -r) 2>/dev/null || echo unknown`
+uname -s = `(uname -s) 2>/dev/null || echo unknown`
+uname -v = `(uname -v) 2>/dev/null || echo unknown`
+
+/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown`
+/bin/uname -X     = `(/bin/uname -X) 2>/dev/null     || echo unknown`
+
+/bin/arch              = `(/bin/arch) 2>/dev/null              || echo unknown`
+/usr/bin/arch -k       = `(/usr/bin/arch -k) 2>/dev/null       || echo unknown`
+/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown`
+/usr/bin/hostinfo      = `(/usr/bin/hostinfo) 2>/dev/null      || echo unknown`
+/bin/machine           = `(/bin/machine) 2>/dev/null           || echo unknown`
+/usr/bin/oslevel       = `(/usr/bin/oslevel) 2>/dev/null       || echo unknown`
+/bin/universe          = `(/bin/universe) 2>/dev/null          || echo unknown`
+
+_ASUNAME
+
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    $as_echo "PATH: $as_dir"
+  done
+IFS=$as_save_IFS
+
+} >&5
+
+cat >&5 <<_ACEOF
+
+
+## ----------- ##
+## Core tests. ##
+## ----------- ##
+
+_ACEOF
+
+
+# Keep a trace of the command line.
+# Strip out --no-create and --no-recursion so they do not pile up.
+# Strip out --silent because we don't want to record it for future runs.
+# Also quote any args containing shell meta-characters.
+# Make two passes to allow for proper duplicate-argument suppression.
+ac_configure_args=
+ac_configure_args0=
+ac_configure_args1=
+ac_must_keep_next=false
+for ac_pass in 1 2
+do
+  for ac_arg
+  do
+    case $ac_arg in
+    -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;;
+    -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+    | -silent | --silent | --silen | --sile | --sil)
+      continue ;;
+    *\'*)
+      ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
+    esac
+    case $ac_pass in
+    1) as_fn_append ac_configure_args0 " '$ac_arg'" ;;
+    2)
+      as_fn_append ac_configure_args1 " '$ac_arg'"
+      if test $ac_must_keep_next = true; then
+	ac_must_keep_next=false # Got value, back to normal.
+      else
+	case $ac_arg in
+	  *=* | --config-cache | -C | -disable-* | --disable-* \
+	  | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \
+	  | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \
+	  | -with-* | --with-* | -without-* | --without-* | --x)
+	    case "$ac_configure_args0 " in
+	      "$ac_configure_args1"*" '$ac_arg' "* ) continue ;;
+	    esac
+	    ;;
+	  -* ) ac_must_keep_next=true ;;
+	esac
+      fi
+      as_fn_append ac_configure_args " '$ac_arg'"
+      ;;
+    esac
+  done
+done
+{ ac_configure_args0=; unset ac_configure_args0;}
+{ ac_configure_args1=; unset ac_configure_args1;}
+
+# When interrupted or exit'd, cleanup temporary files, and complete
+# config.log.  We remove comments because anyway the quotes in there
+# would cause problems or look ugly.
+# WARNING: Use '\'' to represent an apostrophe within the trap.
+# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug.
+trap 'exit_status=$?
+  # Save into config.log some information that might help in debugging.
+  {
+    echo
+
+    $as_echo "## ---------------- ##
+## Cache variables. ##
+## ---------------- ##"
+    echo
+    # The following way of writing the cache mishandles newlines in values,
+(
+  for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do
+    eval ac_val=\$$ac_var
+    case $ac_val in #(
+    *${as_nl}*)
+      case $ac_var in #(
+      *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
+$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
+      esac
+      case $ac_var in #(
+      _ | IFS | as_nl) ;; #(
+      BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #(
+      *) { eval $ac_var=; unset $ac_var;} ;;
+      esac ;;
+    esac
+  done
+  (set) 2>&1 |
+    case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #(
+    *${as_nl}ac_space=\ *)
+      sed -n \
+	"s/'\''/'\''\\\\'\'''\''/g;
+	  s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p"
+      ;; #(
+    *)
+      sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
+      ;;
+    esac |
+    sort
+)
+    echo
+
+    $as_echo "## ----------------- ##
+## Output variables. ##
+## ----------------- ##"
+    echo
+    for ac_var in $ac_subst_vars
+    do
+      eval ac_val=\$$ac_var
+      case $ac_val in
+      *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
+      esac
+      $as_echo "$ac_var='\''$ac_val'\''"
+    done | sort
+    echo
+
+    if test -n "$ac_subst_files"; then
+      $as_echo "## ------------------- ##
+## File substitutions. ##
+## ------------------- ##"
+      echo
+      for ac_var in $ac_subst_files
+      do
+	eval ac_val=\$$ac_var
+	case $ac_val in
+	*\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
+	esac
+	$as_echo "$ac_var='\''$ac_val'\''"
+      done | sort
+      echo
+    fi
+
+    if test -s confdefs.h; then
+      $as_echo "## ----------- ##
+## confdefs.h. ##
+## ----------- ##"
+      echo
+      cat confdefs.h
+      echo
+    fi
+    test "$ac_signal" != 0 &&
+      $as_echo "$as_me: caught signal $ac_signal"
+    $as_echo "$as_me: exit $exit_status"
+  } >&5
+  rm -f core *.core core.conftest.* &&
+    rm -f -r conftest* confdefs* conf$$* $ac_clean_files &&
+    exit $exit_status
+' 0
+for ac_signal in 1 2 13 15; do
+  trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal
+done
+ac_signal=0
+
+# confdefs.h avoids OS command line length limits that DEFS can exceed.
+rm -f -r conftest* confdefs.h
+
+$as_echo "/* confdefs.h */" > confdefs.h
+
+# Predefined preprocessor variables.
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_NAME "$PACKAGE_NAME"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_TARNAME "$PACKAGE_TARNAME"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_VERSION "$PACKAGE_VERSION"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_STRING "$PACKAGE_STRING"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_URL "$PACKAGE_URL"
+_ACEOF
+
+
+# Let the site file select an alternate cache file if it wants to.
+# Prefer an explicitly selected file to automatically selected ones.
+ac_site_file1=NONE
+ac_site_file2=NONE
+if test -n "$CONFIG_SITE"; then
+  # We do not want a PATH search for config.site.
+  case $CONFIG_SITE in #((
+    -*)  ac_site_file1=./$CONFIG_SITE;;
+    */*) ac_site_file1=$CONFIG_SITE;;
+    *)   ac_site_file1=./$CONFIG_SITE;;
+  esac
+elif test "x$prefix" != xNONE; then
+  ac_site_file1=$prefix/share/config.site
+  ac_site_file2=$prefix/etc/config.site
+else
+  ac_site_file1=$ac_default_prefix/share/config.site
+  ac_site_file2=$ac_default_prefix/etc/config.site
+fi
+for ac_site_file in "$ac_site_file1" "$ac_site_file2"
+do
+  test "x$ac_site_file" = xNONE && continue
+  if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then
+    { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5
+$as_echo "$as_me: loading site script $ac_site_file" >&6;}
+    sed 's/^/| /' "$ac_site_file" >&5
+    . "$ac_site_file" \
+      || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "failed to load site script $ac_site_file
+See \`config.log' for more details" "$LINENO" 5; }
+  fi
+done
+
+if test -r "$cache_file"; then
+  # Some versions of bash will fail to source /dev/null (special files
+  # actually), so we avoid doing that.  DJGPP emulates it as a regular file.
+  if test /dev/null != "$cache_file" && test -f "$cache_file"; then
+    { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5
+$as_echo "$as_me: loading cache $cache_file" >&6;}
+    case $cache_file in
+      [\\/]* | ?:[\\/]* ) . "$cache_file";;
+      *)                      . "./$cache_file";;
+    esac
+  fi
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5
+$as_echo "$as_me: creating cache $cache_file" >&6;}
+  >$cache_file
+fi
+
+# Check that the precious variables saved in the cache have kept the same
+# value.
+ac_cache_corrupted=false
+for ac_var in $ac_precious_vars; do
+  eval ac_old_set=\$ac_cv_env_${ac_var}_set
+  eval ac_new_set=\$ac_env_${ac_var}_set
+  eval ac_old_val=\$ac_cv_env_${ac_var}_value
+  eval ac_new_val=\$ac_env_${ac_var}_value
+  case $ac_old_set,$ac_new_set in
+    set,)
+      { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5
+$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;}
+      ac_cache_corrupted=: ;;
+    ,set)
+      { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5
+$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;}
+      ac_cache_corrupted=: ;;
+    ,);;
+    *)
+      if test "x$ac_old_val" != "x$ac_new_val"; then
+	# differences in whitespace do not lead to failure.
+	ac_old_val_w=`echo x $ac_old_val`
+	ac_new_val_w=`echo x $ac_new_val`
+	if test "$ac_old_val_w" != "$ac_new_val_w"; then
+	  { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5
+$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;}
+	  ac_cache_corrupted=:
+	else
+	  { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5
+$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;}
+	  eval $ac_var=\$ac_old_val
+	fi
+	{ $as_echo "$as_me:${as_lineno-$LINENO}:   former value:  \`$ac_old_val'" >&5
+$as_echo "$as_me:   former value:  \`$ac_old_val'" >&2;}
+	{ $as_echo "$as_me:${as_lineno-$LINENO}:   current value: \`$ac_new_val'" >&5
+$as_echo "$as_me:   current value: \`$ac_new_val'" >&2;}
+      fi;;
+  esac
+  # Pass precious variables to config.status.
+  if test "$ac_new_set" = set; then
+    case $ac_new_val in
+    *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;;
+    *) ac_arg=$ac_var=$ac_new_val ;;
+    esac
+    case " $ac_configure_args " in
+      *" '$ac_arg' "*) ;; # Avoid dups.  Use of quotes ensures accuracy.
+      *) as_fn_append ac_configure_args " '$ac_arg'" ;;
+    esac
+  fi
+done
+if $ac_cache_corrupted; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+  { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5
+$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;}
+  as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5
+fi
+## -------------------- ##
+## Main body of script. ##
+## -------------------- ##
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+ac_aux_dir=
+for ac_dir in cf "$srcdir"/cf; do
+  if test -f "$ac_dir/install-sh"; then
+    ac_aux_dir=$ac_dir
+    ac_install_sh="$ac_aux_dir/install-sh -c"
+    break
+  elif test -f "$ac_dir/install.sh"; then
+    ac_aux_dir=$ac_dir
+    ac_install_sh="$ac_aux_dir/install.sh -c"
+    break
+  elif test -f "$ac_dir/shtool"; then
+    ac_aux_dir=$ac_dir
+    ac_install_sh="$ac_aux_dir/shtool install -c"
+    break
+  fi
+done
+if test -z "$ac_aux_dir"; then
+  as_fn_error $? "cannot find install-sh, install.sh, or shtool in cf \"$srcdir\"/cf" "$LINENO" 5
+fi
+
+# These three variables are undocumented and unsupported,
+# and are intended to be withdrawn in a future Autoconf release.
+# They can cause serious problems if a builder's source tree is in a directory
+# whose full name contains unusual characters.
+ac_config_guess="$SHELL $ac_aux_dir/config.guess"  # Please don't use this var.
+ac_config_sub="$SHELL $ac_aux_dir/config.sub"  # Please don't use this var.
+ac_configure="$SHELL $ac_aux_dir/configure"  # Please don't use this var.
+
+
+
+
+# Check whether --with-tcllib was given.
+if test "${with_tcllib+set}" = set; then :
+  withval=$with_tcllib; with_tcllib=$withval
+else
+  with_tcllib=""
+fi
+
+
+
+# Check whether --with-osg-includes was given.
+if test "${with_osg_includes+set}" = set; then :
+  withval=$with_osg_includes; with_osg_includes=$withval
+else
+  with_osg_includes=""
+fi
+
+
+
+# Check whether --with-osg-libs was given.
+if test "${with_osg_libs+set}" = set; then :
+  withval=$with_osg_libs; with_osg_libs=$withval
+else
+  with_osg_libs=""
+fi
+
+
+
+# Check whether --with-statsdir was given.
+if test "${with_statsdir+set}" = set; then :
+  withval=$with_statsdir; STATSDIR=$withval
+else
+  STATSDIR="/var/tmp/visservers"
+fi
+
+
+#------------------------------------------------------------------------
+# Handle the --prefix=... option
+#------------------------------------------------------------------------
+
+if test "${prefix}" = "NONE"; then
+    prefix=/usr/local
+fi
+if test "${exec_prefix}" = "NONE"; then
+    exec_prefix=$prefix
+fi
+
+if test "x${incdir}" == "x"; then
+  incdir=${exec_prefix}/include
+fi
+libdir=${exec_prefix}/lib
+
+if test "${libdir}" != "${prefix}/lib"; then
+    LIB_SEARCH_DIRS="-L ${prefix}/lib -L ${libdir}"
+else
+    LIB_SEARCH_DIRS="-L ${libdir}"
+fi
+
+# Find a good install program.  We prefer a C program (faster),
+# so one script is as good as another.  But avoid the broken or
+# incompatible versions:
+# SysV /etc/install, /usr/sbin/install
+# SunOS /usr/etc/install
+# IRIX /sbin/install
+# AIX /bin/install
+# AmigaOS /C/install, which installs bootblocks on floppy discs
+# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag
+# AFS /usr/afsws/bin/install, which mishandles nonexistent args
+# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff"
+# OS/2's system install, which has a completely different semantic
+# ./install, which can be erroneously created by make from ./install.sh.
+# Reject install programs that cannot install multiple files.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5
+$as_echo_n "checking for a BSD-compatible install... " >&6; }
+if test -z "$INSTALL"; then
+if ${ac_cv_path_install+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    # Account for people who put trailing slashes in PATH elements.
+case $as_dir/ in #((
+  ./ | .// | /[cC]/* | \
+  /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \
+  ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \
+  /usr/ucb/* ) ;;
+  *)
+    # OSF1 and SCO ODT 3.0 have their own names for install.
+    # Don't use installbsd from OSF since it installs stuff as root
+    # by default.
+    for ac_prog in ginstall scoinst install; do
+      for ac_exec_ext in '' $ac_executable_extensions; do
+	if as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext"; then
+	  if test $ac_prog = install &&
+	    grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then
+	    # AIX install.  It has an incompatible calling convention.
+	    :
+	  elif test $ac_prog = install &&
+	    grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then
+	    # program-specific install script used by HP pwplus--don't use.
+	    :
+	  else
+	    rm -rf conftest.one conftest.two conftest.dir
+	    echo one > conftest.one
+	    echo two > conftest.two
+	    mkdir conftest.dir
+	    if "$as_dir/$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir" &&
+	      test -s conftest.one && test -s conftest.two &&
+	      test -s conftest.dir/conftest.one &&
+	      test -s conftest.dir/conftest.two
+	    then
+	      ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c"
+	      break 3
+	    fi
+	  fi
+	fi
+      done
+    done
+    ;;
+esac
+
+  done
+IFS=$as_save_IFS
+
+rm -rf conftest.one conftest.two conftest.dir
+
+fi
+  if test "${ac_cv_path_install+set}" = set; then
+    INSTALL=$ac_cv_path_install
+  else
+    # As a last resort, use the slow shell script.  Don't cache a
+    # value for INSTALL within a source directory, because that will
+    # break other packages using the cache if that directory is
+    # removed, or if the value is a relative name.
+    INSTALL=$ac_install_sh
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5
+$as_echo "$INSTALL" >&6; }
+
+# Use test -z because SunOS4 sh mishandles braces in ${var-val}.
+# It thinks the first close brace ends the variable substitution.
+test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}'
+
+test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}'
+
+test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644'
+
+if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args.
+set dummy ${ac_tool_prefix}ranlib; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_RANLIB+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$RANLIB"; then
+  ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+RANLIB=$ac_cv_prog_RANLIB
+if test -n "$RANLIB"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5
+$as_echo "$RANLIB" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_RANLIB"; then
+  ac_ct_RANLIB=$RANLIB
+  # Extract the first word of "ranlib", so it can be a program name with args.
+set dummy ranlib; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_RANLIB+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_RANLIB"; then
+  ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_RANLIB="ranlib"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB
+if test -n "$ac_ct_RANLIB"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RANLIB" >&5
+$as_echo "$ac_ct_RANLIB" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_RANLIB" = x; then
+    RANLIB=":"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    RANLIB=$ac_ct_RANLIB
+  fi
+else
+  RANLIB="$ac_cv_prog_RANLIB"
+fi
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ln -s works" >&5
+$as_echo_n "checking whether ln -s works... " >&6; }
+LN_S=$as_ln_s
+if test "$LN_S" = "ln -s"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no, using $LN_S" >&5
+$as_echo "no, using $LN_S" >&6; }
+fi
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a thread-safe mkdir -p" >&5
+$as_echo_n "checking for a thread-safe mkdir -p... " >&6; }
+if test -z "$MKDIR_P"; then
+  if ${ac_cv_path_mkdir+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/opt/sfw/bin
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_prog in mkdir gmkdir; do
+	 for ac_exec_ext in '' $ac_executable_extensions; do
+	   as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext" || continue
+	   case `"$as_dir/$ac_prog$ac_exec_ext" --version 2>&1` in #(
+	     'mkdir (GNU coreutils) '* | \
+	     'mkdir (coreutils) '* | \
+	     'mkdir (fileutils) '4.1*)
+	       ac_cv_path_mkdir=$as_dir/$ac_prog$ac_exec_ext
+	       break 3;;
+	   esac
+	 done
+       done
+  done
+IFS=$as_save_IFS
+
+fi
+
+  test -d ./--version && rmdir ./--version
+  if test "${ac_cv_path_mkdir+set}" = set; then
+    MKDIR_P="$ac_cv_path_mkdir -p"
+  else
+    # As a last resort, use the slow shell script.  Don't cache a
+    # value for MKDIR_P within a source directory, because that will
+    # break other packages using the cache if that directory is
+    # removed, or if the value is a relative name.
+    MKDIR_P="$ac_install_sh -d"
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $MKDIR_P" >&5
+$as_echo "$MKDIR_P" >&6; }
+
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}gcc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_CC="${ac_tool_prefix}gcc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_CC"; then
+  ac_ct_CC=$CC
+  # Extract the first word of "gcc", so it can be a program name with args.
+set dummy gcc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_CC"; then
+  ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_CC="gcc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+$as_echo "$ac_ct_CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_CC" = x; then
+    CC=""
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    CC=$ac_ct_CC
+  fi
+else
+  CC="$ac_cv_prog_CC"
+fi
+
+if test -z "$CC"; then
+          if test -n "$ac_tool_prefix"; then
+    # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}cc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_CC="${ac_tool_prefix}cc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  fi
+fi
+if test -z "$CC"; then
+  # Extract the first word of "cc", so it can be a program name with args.
+set dummy cc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+  ac_prog_rejected=no
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then
+       ac_prog_rejected=yes
+       continue
+     fi
+    ac_cv_prog_CC="cc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+if test $ac_prog_rejected = yes; then
+  # We found a bogon in the path, so make sure we never use it.
+  set dummy $ac_cv_prog_CC
+  shift
+  if test $# != 0; then
+    # We chose a different compiler from the bogus one.
+    # However, it has the same basename, so the bogon will be chosen
+    # first if we set CC to just the basename; use the full file name.
+    shift
+    ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@"
+  fi
+fi
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$CC"; then
+  if test -n "$ac_tool_prefix"; then
+  for ac_prog in cl.exe
+  do
+    # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_CC="$ac_tool_prefix$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+    test -n "$CC" && break
+  done
+fi
+if test -z "$CC"; then
+  ac_ct_CC=$CC
+  for ac_prog in cl.exe
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_CC"; then
+  ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_CC="$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+$as_echo "$ac_ct_CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  test -n "$ac_ct_CC" && break
+done
+
+  if test "x$ac_ct_CC" = x; then
+    CC=""
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    CC=$ac_ct_CC
+  fi
+fi
+
+fi
+
+
+test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "no acceptable C compiler found in \$PATH
+See \`config.log' for more details" "$LINENO" 5; }
+
+# Provide some information about the compiler.
+$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5
+set X $ac_compile
+ac_compiler=$2
+for ac_option in --version -v -V -qversion; do
+  { { ac_try="$ac_compiler $ac_option >&5"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_compiler $ac_option >&5") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    sed '10a\
+... rest of stderr output deleted ...
+         10q' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+  fi
+  rm -f conftest.er1 conftest.err
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }
+done
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out"
+# Try to create an executable without -o first, disregard a.out.
+# It will help us diagnose broken compilers, and finding out an intuition
+# of exeext.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5
+$as_echo_n "checking whether the C compiler works... " >&6; }
+ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'`
+
+# The possible output files:
+ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*"
+
+ac_rmfiles=
+for ac_file in $ac_files
+do
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;;
+    * ) ac_rmfiles="$ac_rmfiles $ac_file";;
+  esac
+done
+rm -f $ac_rmfiles
+
+if { { ac_try="$ac_link_default"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link_default") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then :
+  # Autoconf-2.13 could set the ac_cv_exeext variable to `no'.
+# So ignore a value of `no', otherwise this would lead to `EXEEXT = no'
+# in a Makefile.  We should not override ac_cv_exeext if it was cached,
+# so that the user can short-circuit this test for compilers unknown to
+# Autoconf.
+for ac_file in $ac_files ''
+do
+  test -f "$ac_file" || continue
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj )
+	;;
+    [ab].out )
+	# We found the default executable, but exeext='' is most
+	# certainly right.
+	break;;
+    *.* )
+	if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no;
+	then :; else
+	   ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+	fi
+	# We set ac_cv_exeext here because the later test for it is not
+	# safe: cross compilers may not add the suffix if given an `-o'
+	# argument, so we may need to know it at that point already.
+	# Even if this section looks crufty: it has the advantage of
+	# actually working.
+	break;;
+    * )
+	break;;
+  esac
+done
+test "$ac_cv_exeext" = no && ac_cv_exeext=
+
+else
+  ac_file=''
+fi
+if test -z "$ac_file"; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+$as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error 77 "C compiler cannot create executables
+See \`config.log' for more details" "$LINENO" 5; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5
+$as_echo_n "checking for C compiler default output file name... " >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5
+$as_echo "$ac_file" >&6; }
+ac_exeext=$ac_cv_exeext
+
+rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out
+ac_clean_files=$ac_clean_files_save
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5
+$as_echo_n "checking for suffix of executables... " >&6; }
+if { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then :
+  # If both `conftest.exe' and `conftest' are `present' (well, observable)
+# catch `conftest.exe'.  For instance with Cygwin, `ls conftest' will
+# work properly (i.e., refer to `conftest.exe'), while it won't with
+# `rm'.
+for ac_file in conftest.exe conftest conftest.*; do
+  test -f "$ac_file" || continue
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;;
+    *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+	  break;;
+    * ) break;;
+  esac
+done
+else
+  { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot compute suffix of executables: cannot compile and link
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+rm -f conftest conftest$ac_cv_exeext
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5
+$as_echo "$ac_cv_exeext" >&6; }
+
+rm -f conftest.$ac_ext
+EXEEXT=$ac_cv_exeext
+ac_exeext=$EXEEXT
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <stdio.h>
+int
+main ()
+{
+FILE *f = fopen ("conftest.out", "w");
+ return ferror (f) || fclose (f) != 0;
+
+  ;
+  return 0;
+}
+_ACEOF
+ac_clean_files="$ac_clean_files conftest.out"
+# Check that the compiler produces executables we can run.  If not, either
+# the compiler is broken, or we cross compile.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5
+$as_echo_n "checking whether we are cross compiling... " >&6; }
+if test "$cross_compiling" != yes; then
+  { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }
+  if { ac_try='./conftest$ac_cv_exeext'
+  { { case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_try") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }; then
+    cross_compiling=no
+  else
+    if test "$cross_compiling" = maybe; then
+	cross_compiling=yes
+    else
+	{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot run C compiled programs.
+If you meant to cross compile, use \`--host'.
+See \`config.log' for more details" "$LINENO" 5; }
+    fi
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5
+$as_echo "$cross_compiling" >&6; }
+
+rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out
+ac_clean_files=$ac_clean_files_save
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5
+$as_echo_n "checking for suffix of object files... " >&6; }
+if ${ac_cv_objext+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.o conftest.obj
+if { { ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_compile") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then :
+  for ac_file in conftest.o conftest.obj conftest.*; do
+  test -f "$ac_file" || continue;
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;;
+    *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'`
+       break;;
+  esac
+done
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot compute suffix of object files: cannot compile
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+rm -f conftest.$ac_cv_objext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5
+$as_echo "$ac_cv_objext" >&6; }
+OBJEXT=$ac_cv_objext
+ac_objext=$OBJEXT
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5
+$as_echo_n "checking whether we are using the GNU C compiler... " >&6; }
+if ${ac_cv_c_compiler_gnu+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+#ifndef __GNUC__
+       choke me
+#endif
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_compiler_gnu=yes
+else
+  ac_compiler_gnu=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_cv_c_compiler_gnu=$ac_compiler_gnu
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5
+$as_echo "$ac_cv_c_compiler_gnu" >&6; }
+if test $ac_compiler_gnu = yes; then
+  GCC=yes
+else
+  GCC=
+fi
+ac_test_CFLAGS=${CFLAGS+set}
+ac_save_CFLAGS=$CFLAGS
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5
+$as_echo_n "checking whether $CC accepts -g... " >&6; }
+if ${ac_cv_prog_cc_g+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_save_c_werror_flag=$ac_c_werror_flag
+   ac_c_werror_flag=yes
+   ac_cv_prog_cc_g=no
+   CFLAGS="-g"
+   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_prog_cc_g=yes
+else
+  CFLAGS=""
+      cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+  ac_c_werror_flag=$ac_save_c_werror_flag
+	 CFLAGS="-g"
+	 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_prog_cc_g=yes
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+   ac_c_werror_flag=$ac_save_c_werror_flag
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5
+$as_echo "$ac_cv_prog_cc_g" >&6; }
+if test "$ac_test_CFLAGS" = set; then
+  CFLAGS=$ac_save_CFLAGS
+elif test $ac_cv_prog_cc_g = yes; then
+  if test "$GCC" = yes; then
+    CFLAGS="-g -O2"
+  else
+    CFLAGS="-g"
+  fi
+else
+  if test "$GCC" = yes; then
+    CFLAGS="-O2"
+  else
+    CFLAGS=
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5
+$as_echo_n "checking for $CC option to accept ISO C89... " >&6; }
+if ${ac_cv_prog_cc_c89+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_cv_prog_cc_c89=no
+ac_save_CC=$CC
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <stdarg.h>
+#include <stdio.h>
+struct stat;
+/* Most of the following tests are stolen from RCS 5.7's src/conf.sh.  */
+struct buf { int x; };
+FILE * (*rcsopen) (struct buf *, struct stat *, int);
+static char *e (p, i)
+     char **p;
+     int i;
+{
+  return p[i];
+}
+static char *f (char * (*g) (char **, int), char **p, ...)
+{
+  char *s;
+  va_list v;
+  va_start (v,p);
+  s = g (p, va_arg (v,int));
+  va_end (v);
+  return s;
+}
+
+/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default.  It has
+   function prototypes and stuff, but not '\xHH' hex character constants.
+   These don't provoke an error unfortunately, instead are silently treated
+   as 'x'.  The following induces an error, until -std is added to get
+   proper ANSI mode.  Curiously '\x00'!='x' always comes out true, for an
+   array size at least.  It's necessary to write '\x00'==0 to get something
+   that's true only with -std.  */
+int osf4_cc_array ['\x00' == 0 ? 1 : -1];
+
+/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters
+   inside strings and character constants.  */
+#define FOO(x) 'x'
+int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1];
+
+int test (int i, double x);
+struct s1 {int (*f) (int a);};
+struct s2 {int (*f) (double a);};
+int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int);
+int argc;
+char **argv;
+int
+main ()
+{
+return f (e, argv, 0) != argv[0]  ||  f (e, argv, 1) != argv[1];
+  ;
+  return 0;
+}
+_ACEOF
+for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \
+	-Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__"
+do
+  CC="$ac_save_CC $ac_arg"
+  if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_prog_cc_c89=$ac_arg
+fi
+rm -f core conftest.err conftest.$ac_objext
+  test "x$ac_cv_prog_cc_c89" != "xno" && break
+done
+rm -f conftest.$ac_ext
+CC=$ac_save_CC
+
+fi
+# AC_CACHE_VAL
+case "x$ac_cv_prog_cc_c89" in
+  x)
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
+$as_echo "none needed" >&6; } ;;
+  xno)
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
+$as_echo "unsupported" >&6; } ;;
+  *)
+    CC="$CC $ac_cv_prog_cc_c89"
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5
+$as_echo "$ac_cv_prog_cc_c89" >&6; } ;;
+esac
+if test "x$ac_cv_prog_cc_c89" != xno; then :
+
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5
+$as_echo_n "checking how to run the C preprocessor... " >&6; }
+# On Suns, sometimes $CPP names a directory.
+if test -n "$CPP" && test -d "$CPP"; then
+  CPP=
+fi
+if test -z "$CPP"; then
+  if ${ac_cv_prog_CPP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+      # Double quotes because CPP needs to be expanded
+    for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp"
+    do
+      ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+  # Use a header file that comes with gcc, so configuring glibc
+  # with a fresh cross-compiler works.
+  # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+  # <limits.h> exists even on freestanding compilers.
+  # On the NeXT, cc -E runs the code through the compiler's parser,
+  # not just through cpp. "Syntax error" is here to catch this case.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+		     Syntax error
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+
+else
+  # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+  # OK, works on sane cases.  Now check whether nonexistent headers
+  # can be detected and how.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <ac_nonexistent.h>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+  # Broken: success on invalid input.
+continue
+else
+  # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then :
+  break
+fi
+
+    done
+    ac_cv_prog_CPP=$CPP
+
+fi
+  CPP=$ac_cv_prog_CPP
+else
+  ac_cv_prog_CPP=$CPP
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5
+$as_echo "$CPP" >&6; }
+ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+  # Use a header file that comes with gcc, so configuring glibc
+  # with a fresh cross-compiler works.
+  # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+  # <limits.h> exists even on freestanding compilers.
+  # On the NeXT, cc -E runs the code through the compiler's parser,
+  # not just through cpp. "Syntax error" is here to catch this case.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+		     Syntax error
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+
+else
+  # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+  # OK, works on sane cases.  Now check whether nonexistent headers
+  # can be detected and how.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <ac_nonexistent.h>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+  # Broken: success on invalid input.
+continue
+else
+  # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then :
+
+else
+  { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "C preprocessor \"$CPP\" fails sanity check
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+if test -z "$CXX"; then
+  if test -n "$CCC"; then
+    CXX=$CCC
+  else
+    if test -n "$ac_tool_prefix"; then
+  for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC
+  do
+    # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CXX+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CXX"; then
+  ac_cv_prog_CXX="$CXX" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_CXX="$ac_tool_prefix$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+CXX=$ac_cv_prog_CXX
+if test -n "$CXX"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CXX" >&5
+$as_echo "$CXX" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+    test -n "$CXX" && break
+  done
+fi
+if test -z "$CXX"; then
+  ac_ct_CXX=$CXX
+  for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_CXX+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_CXX"; then
+  ac_cv_prog_ac_ct_CXX="$ac_ct_CXX" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_CXX="$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CXX=$ac_cv_prog_ac_ct_CXX
+if test -n "$ac_ct_CXX"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CXX" >&5
+$as_echo "$ac_ct_CXX" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  test -n "$ac_ct_CXX" && break
+done
+
+  if test "x$ac_ct_CXX" = x; then
+    CXX="g++"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    CXX=$ac_ct_CXX
+  fi
+fi
+
+  fi
+fi
+# Provide some information about the compiler.
+$as_echo "$as_me:${as_lineno-$LINENO}: checking for C++ compiler version" >&5
+set X $ac_compile
+ac_compiler=$2
+for ac_option in --version -v -V -qversion; do
+  { { ac_try="$ac_compiler $ac_option >&5"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_compiler $ac_option >&5") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    sed '10a\
+... rest of stderr output deleted ...
+         10q' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+  fi
+  rm -f conftest.er1 conftest.err
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }
+done
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C++ compiler" >&5
+$as_echo_n "checking whether we are using the GNU C++ compiler... " >&6; }
+if ${ac_cv_cxx_compiler_gnu+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+#ifndef __GNUC__
+       choke me
+#endif
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+  ac_compiler_gnu=yes
+else
+  ac_compiler_gnu=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_cv_cxx_compiler_gnu=$ac_compiler_gnu
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_cxx_compiler_gnu" >&5
+$as_echo "$ac_cv_cxx_compiler_gnu" >&6; }
+if test $ac_compiler_gnu = yes; then
+  GXX=yes
+else
+  GXX=
+fi
+ac_test_CXXFLAGS=${CXXFLAGS+set}
+ac_save_CXXFLAGS=$CXXFLAGS
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX accepts -g" >&5
+$as_echo_n "checking whether $CXX accepts -g... " >&6; }
+if ${ac_cv_prog_cxx_g+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_save_cxx_werror_flag=$ac_cxx_werror_flag
+   ac_cxx_werror_flag=yes
+   ac_cv_prog_cxx_g=no
+   CXXFLAGS="-g"
+   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+  ac_cv_prog_cxx_g=yes
+else
+  CXXFLAGS=""
+      cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+
+else
+  ac_cxx_werror_flag=$ac_save_cxx_werror_flag
+	 CXXFLAGS="-g"
+	 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+  ac_cv_prog_cxx_g=yes
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+   ac_cxx_werror_flag=$ac_save_cxx_werror_flag
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cxx_g" >&5
+$as_echo "$ac_cv_prog_cxx_g" >&6; }
+if test "$ac_test_CXXFLAGS" = set; then
+  CXXFLAGS=$ac_save_CXXFLAGS
+elif test $ac_cv_prog_cxx_g = yes; then
+  if test "$GXX" = yes; then
+    CXXFLAGS="-g -O2"
+  else
+    CXXFLAGS="-g"
+  fi
+else
+  if test "$GXX" = yes; then
+    CXXFLAGS="-O2"
+  else
+    CXXFLAGS=
+  fi
+fi
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C++ preprocessor" >&5
+$as_echo_n "checking how to run the C++ preprocessor... " >&6; }
+if test -z "$CXXCPP"; then
+  if ${ac_cv_prog_CXXCPP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+      # Double quotes because CXXCPP needs to be expanded
+    for CXXCPP in "$CXX -E" "/lib/cpp"
+    do
+      ac_preproc_ok=false
+for ac_cxx_preproc_warn_flag in '' yes
+do
+  # Use a header file that comes with gcc, so configuring glibc
+  # with a fresh cross-compiler works.
+  # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+  # <limits.h> exists even on freestanding compilers.
+  # On the NeXT, cc -E runs the code through the compiler's parser,
+  # not just through cpp. "Syntax error" is here to catch this case.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+		     Syntax error
+_ACEOF
+if ac_fn_cxx_try_cpp "$LINENO"; then :
+
+else
+  # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+  # OK, works on sane cases.  Now check whether nonexistent headers
+  # can be detected and how.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <ac_nonexistent.h>
+_ACEOF
+if ac_fn_cxx_try_cpp "$LINENO"; then :
+  # Broken: success on invalid input.
+continue
+else
+  # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then :
+  break
+fi
+
+    done
+    ac_cv_prog_CXXCPP=$CXXCPP
+
+fi
+  CXXCPP=$ac_cv_prog_CXXCPP
+else
+  ac_cv_prog_CXXCPP=$CXXCPP
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CXXCPP" >&5
+$as_echo "$CXXCPP" >&6; }
+ac_preproc_ok=false
+for ac_cxx_preproc_warn_flag in '' yes
+do
+  # Use a header file that comes with gcc, so configuring glibc
+  # with a fresh cross-compiler works.
+  # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+  # <limits.h> exists even on freestanding compilers.
+  # On the NeXT, cc -E runs the code through the compiler's parser,
+  # not just through cpp. "Syntax error" is here to catch this case.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+		     Syntax error
+_ACEOF
+if ac_fn_cxx_try_cpp "$LINENO"; then :
+
+else
+  # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+  # OK, works on sane cases.  Now check whether nonexistent headers
+  # can be detected and how.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <ac_nonexistent.h>
+_ACEOF
+if ac_fn_cxx_try_cpp "$LINENO"; then :
+  # Broken: success on invalid input.
+continue
+else
+  # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then :
+
+else
+  { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "C++ preprocessor \"$CXXCPP\" fails sanity check
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+if test -z "$CXX"; then
+  if test -n "$CCC"; then
+    CXX=$CCC
+  else
+    if test -n "$ac_tool_prefix"; then
+  for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC
+  do
+    # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CXX+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CXX"; then
+  ac_cv_prog_CXX="$CXX" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_CXX="$ac_tool_prefix$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+CXX=$ac_cv_prog_CXX
+if test -n "$CXX"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CXX" >&5
+$as_echo "$CXX" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+    test -n "$CXX" && break
+  done
+fi
+if test -z "$CXX"; then
+  ac_ct_CXX=$CXX
+  for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_CXX+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_CXX"; then
+  ac_cv_prog_ac_ct_CXX="$ac_ct_CXX" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_CXX="$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CXX=$ac_cv_prog_ac_ct_CXX
+if test -n "$ac_ct_CXX"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CXX" >&5
+$as_echo "$ac_ct_CXX" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  test -n "$ac_ct_CXX" && break
+done
+
+  if test "x$ac_ct_CXX" = x; then
+    CXX="g++"
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    CXX=$ac_ct_CXX
+  fi
+fi
+
+  fi
+fi
+# Provide some information about the compiler.
+$as_echo "$as_me:${as_lineno-$LINENO}: checking for C++ compiler version" >&5
+set X $ac_compile
+ac_compiler=$2
+for ac_option in --version -v -V -qversion; do
+  { { ac_try="$ac_compiler $ac_option >&5"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_compiler $ac_option >&5") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    sed '10a\
+... rest of stderr output deleted ...
+         10q' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+  fi
+  rm -f conftest.er1 conftest.err
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }
+done
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C++ compiler" >&5
+$as_echo_n "checking whether we are using the GNU C++ compiler... " >&6; }
+if ${ac_cv_cxx_compiler_gnu+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+#ifndef __GNUC__
+       choke me
+#endif
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+  ac_compiler_gnu=yes
+else
+  ac_compiler_gnu=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_cv_cxx_compiler_gnu=$ac_compiler_gnu
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_cxx_compiler_gnu" >&5
+$as_echo "$ac_cv_cxx_compiler_gnu" >&6; }
+if test $ac_compiler_gnu = yes; then
+  GXX=yes
+else
+  GXX=
+fi
+ac_test_CXXFLAGS=${CXXFLAGS+set}
+ac_save_CXXFLAGS=$CXXFLAGS
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX accepts -g" >&5
+$as_echo_n "checking whether $CXX accepts -g... " >&6; }
+if ${ac_cv_prog_cxx_g+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_save_cxx_werror_flag=$ac_cxx_werror_flag
+   ac_cxx_werror_flag=yes
+   ac_cv_prog_cxx_g=no
+   CXXFLAGS="-g"
+   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+  ac_cv_prog_cxx_g=yes
+else
+  CXXFLAGS=""
+      cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+
+else
+  ac_cxx_werror_flag=$ac_save_cxx_werror_flag
+	 CXXFLAGS="-g"
+	 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+  ac_cv_prog_cxx_g=yes
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+   ac_cxx_werror_flag=$ac_save_cxx_werror_flag
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cxx_g" >&5
+$as_echo "$ac_cv_prog_cxx_g" >&6; }
+if test "$ac_test_CXXFLAGS" = set; then
+  CXXFLAGS=$ac_save_CXXFLAGS
+elif test $ac_cv_prog_cxx_g = yes; then
+  if test "$GXX" = yes; then
+    CXXFLAGS="-g -O2"
+  else
+    CXXFLAGS="-g"
+  fi
+else
+  if test "$GXX" = yes; then
+    CXXFLAGS="-O2"
+  else
+    CXXFLAGS=
+  fi
+fi
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+
+#
+# Need to check for the following dependencies:
+#       Tcl headers and library
+#       GL headers and library
+#       pthread library
+#       OpenSceneGraph libraries
+#       osgEarth libraries
+#
+
+TCL_VERSION="8.4"
+for dir in \
+ ${exec_prefix} \
+ ${exec_prefix}/lib ; do
+  tclconfig="${dir}/tclConfig.sh"
+  if test -f "$tclconfig" ; then
+    . $tclconfig
+    break
+  fi
+done
+TCL_INC_SPEC="$TCL_INCLUDE_SPEC"
+
+if test "x$with_tcllib" != "x" ; then
+   tclconfig="${with_tcllib}/tclConfig.sh"
+   if test -f "$tclconfig" ; then
+    . $tclconfig
+   fi
+   TCL_LIB_SPEC="-L${with_tcllib} -ltcl${TCL_VERSION}"
+fi
+if test "x$with_tclinclude" != "x" ; then
+   TCL_INC_SPEC="-I${with_tclinclude}"
+fi
+
+OSG_INC_SPEC=-I${incdir}
+OSG_LIB_DIR=${libdir}
+OSG_LIB_SPEC="-L${libdir}"
+
+if test "x$with_osg_includes" != "x"; then
+   OSG_INC_SPEC=-I$with_osg_includes
+fi
+
+if test "x$with_osg_libs" != "x"; then
+   OSG_LIB_DIR=$with_osg_libs
+   OSG_LIB_SPEC="-L$with_osg_libs"
+fi
+
+RESOURCES_DIR=${libdir}/resources
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5
+$as_echo_n "checking for grep that handles long lines and -e... " >&6; }
+if ${ac_cv_path_GREP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -z "$GREP"; then
+  ac_path_GREP_found=false
+  # Loop through the user's path and test for each of PROGNAME-LIST
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_prog in grep ggrep; do
+    for ac_exec_ext in '' $ac_executable_extensions; do
+      ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext"
+      as_fn_executable_p "$ac_path_GREP" || continue
+# Check for GNU ac_path_GREP and select it if it is found.
+  # Check for GNU $ac_path_GREP
+case `"$ac_path_GREP" --version 2>&1` in
+*GNU*)
+  ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;;
+*)
+  ac_count=0
+  $as_echo_n 0123456789 >"conftest.in"
+  while :
+  do
+    cat "conftest.in" "conftest.in" >"conftest.tmp"
+    mv "conftest.tmp" "conftest.in"
+    cp "conftest.in" "conftest.nl"
+    $as_echo 'GREP' >> "conftest.nl"
+    "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break
+    diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+    as_fn_arith $ac_count + 1 && ac_count=$as_val
+    if test $ac_count -gt ${ac_path_GREP_max-0}; then
+      # Best one so far, save it but keep looking for a better one
+      ac_cv_path_GREP="$ac_path_GREP"
+      ac_path_GREP_max=$ac_count
+    fi
+    # 10*(2^10) chars as input seems more than enough
+    test $ac_count -gt 10 && break
+  done
+  rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+      $ac_path_GREP_found && break 3
+    done
+  done
+  done
+IFS=$as_save_IFS
+  if test -z "$ac_cv_path_GREP"; then
+    as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
+  fi
+else
+  ac_cv_path_GREP=$GREP
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5
+$as_echo "$ac_cv_path_GREP" >&6; }
+ GREP="$ac_cv_path_GREP"
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5
+$as_echo_n "checking for egrep... " >&6; }
+if ${ac_cv_path_EGREP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if echo a | $GREP -E '(a|b)' >/dev/null 2>&1
+   then ac_cv_path_EGREP="$GREP -E"
+   else
+     if test -z "$EGREP"; then
+  ac_path_EGREP_found=false
+  # Loop through the user's path and test for each of PROGNAME-LIST
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_prog in egrep; do
+    for ac_exec_ext in '' $ac_executable_extensions; do
+      ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext"
+      as_fn_executable_p "$ac_path_EGREP" || continue
+# Check for GNU ac_path_EGREP and select it if it is found.
+  # Check for GNU $ac_path_EGREP
+case `"$ac_path_EGREP" --version 2>&1` in
+*GNU*)
+  ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;;
+*)
+  ac_count=0
+  $as_echo_n 0123456789 >"conftest.in"
+  while :
+  do
+    cat "conftest.in" "conftest.in" >"conftest.tmp"
+    mv "conftest.tmp" "conftest.in"
+    cp "conftest.in" "conftest.nl"
+    $as_echo 'EGREP' >> "conftest.nl"
+    "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break
+    diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+    as_fn_arith $ac_count + 1 && ac_count=$as_val
+    if test $ac_count -gt ${ac_path_EGREP_max-0}; then
+      # Best one so far, save it but keep looking for a better one
+      ac_cv_path_EGREP="$ac_path_EGREP"
+      ac_path_EGREP_max=$ac_count
+    fi
+    # 10*(2^10) chars as input seems more than enough
+    test $ac_count -gt 10 && break
+  done
+  rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+      $ac_path_EGREP_found && break 3
+    done
+  done
+  done
+IFS=$as_save_IFS
+  if test -z "$ac_cv_path_EGREP"; then
+    as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
+  fi
+else
+  ac_cv_path_EGREP=$EGREP
+fi
+
+   fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5
+$as_echo "$ac_cv_path_EGREP" >&6; }
+ EGREP="$ac_cv_path_EGREP"
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5
+$as_echo_n "checking for ANSI C header files... " >&6; }
+if ${ac_cv_header_stdc+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <float.h>
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+  ac_cv_header_stdc=yes
+else
+  ac_cv_header_stdc=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+if test $ac_cv_header_stdc = yes; then
+  # SunOS 4.x string.h does not declare mem*, contrary to ANSI.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <string.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  $EGREP "memchr" >/dev/null 2>&1; then :
+
+else
+  ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+  # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <stdlib.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  $EGREP "free" >/dev/null 2>&1; then :
+
+else
+  ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+  # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi.
+  if test "$cross_compiling" = yes; then :
+  :
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <ctype.h>
+#include <stdlib.h>
+#if ((' ' & 0x0FF) == 0x020)
+# define ISLOWER(c) ('a' <= (c) && (c) <= 'z')
+# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c))
+#else
+# define ISLOWER(c) \
+		   (('a' <= (c) && (c) <= 'i') \
+		     || ('j' <= (c) && (c) <= 'r') \
+		     || ('s' <= (c) && (c) <= 'z'))
+# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c))
+#endif
+
+#define XOR(e, f) (((e) && !(f)) || (!(e) && (f)))
+int
+main ()
+{
+  int i;
+  for (i = 0; i < 256; i++)
+    if (XOR (islower (i), ISLOWER (i))
+	|| toupper (i) != TOUPPER (i))
+      return 2;
+  return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_run "$LINENO"; then :
+
+else
+  ac_cv_header_stdc=no
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+  conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5
+$as_echo "$ac_cv_header_stdc" >&6; }
+if test $ac_cv_header_stdc = yes; then
+
+$as_echo "#define STDC_HEADERS 1" >>confdefs.h
+
+fi
+
+# On IRIX 5.3, sys/types and inttypes.h are conflicting.
+for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \
+		  inttypes.h stdint.h unistd.h
+do :
+  as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
+ac_fn_cxx_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default
+"
+if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
+  cat >>confdefs.h <<_ACEOF
+#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+
+done
+
+
+for ac_header in stdio.h unistd.h stdlib.h string.h sys/types.h
+do :
+  as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
+ac_fn_cxx_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default"
+if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
+  cat >>confdefs.h <<_ACEOF
+#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+
+done
+
+for ac_header in sys/socket.h sys/time.h netinet/in.h arpa/inet.h netdb.h
+do :
+  as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
+ac_fn_cxx_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default"
+if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
+  cat >>confdefs.h <<_ACEOF
+#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+
+done
+
+
+for ac_func in accept4
+do :
+  ac_fn_cxx_check_func "$LINENO" "accept4" "ac_cv_func_accept4"
+if test "x$ac_cv_func_accept4" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_ACCEPT4 1
+_ACEOF
+
+fi
+done
+
+
+
+
+
+
+
+
+
+
+ac_config_files="$ac_config_files Makefile"
+
+cat >confcache <<\_ACEOF
+# This file is a shell script that caches the results of configure
+# tests run on this system so they can be shared between configure
+# scripts and configure runs, see configure's option --config-cache.
+# It is not useful on other systems.  If it contains results you don't
+# want to keep, you may remove or edit it.
+#
+# config.status only pays attention to the cache file if you give it
+# the --recheck option to rerun configure.
+#
+# `ac_cv_env_foo' variables (set or unset) will be overridden when
+# loading this file, other *unset* `ac_cv_foo' will be assigned the
+# following values.
+
+_ACEOF
+
+# The following way of writing the cache mishandles newlines in values,
+# but we know of no workaround that is simple, portable, and efficient.
+# So, we kill variables containing newlines.
+# Ultrix sh set writes to stderr and can't be redirected directly,
+# and sets the high bit in the cache file unless we assign to the vars.
+(
+  for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do
+    eval ac_val=\$$ac_var
+    case $ac_val in #(
+    *${as_nl}*)
+      case $ac_var in #(
+      *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
+$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
+      esac
+      case $ac_var in #(
+      _ | IFS | as_nl) ;; #(
+      BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #(
+      *) { eval $ac_var=; unset $ac_var;} ;;
+      esac ;;
+    esac
+  done
+
+  (set) 2>&1 |
+    case $as_nl`(ac_space=' '; set) 2>&1` in #(
+    *${as_nl}ac_space=\ *)
+      # `set' does not quote correctly, so add quotes: double-quote
+      # substitution turns \\\\ into \\, and sed turns \\ into \.
+      sed -n \
+	"s/'/'\\\\''/g;
+	  s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p"
+      ;; #(
+    *)
+      # `set' quotes correctly as required by POSIX, so do not add quotes.
+      sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
+      ;;
+    esac |
+    sort
+) |
+  sed '
+     /^ac_cv_env_/b end
+     t clear
+     :clear
+     s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/
+     t end
+     s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/
+     :end' >>confcache
+if diff "$cache_file" confcache >/dev/null 2>&1; then :; else
+  if test -w "$cache_file"; then
+    if test "x$cache_file" != "x/dev/null"; then
+      { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5
+$as_echo "$as_me: updating cache $cache_file" >&6;}
+      if test ! -f "$cache_file" || test -h "$cache_file"; then
+	cat confcache >"$cache_file"
+      else
+        case $cache_file in #(
+        */* | ?:*)
+	  mv -f confcache "$cache_file"$$ &&
+	  mv -f "$cache_file"$$ "$cache_file" ;; #(
+        *)
+	  mv -f confcache "$cache_file" ;;
+	esac
+      fi
+    fi
+  else
+    { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5
+$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;}
+  fi
+fi
+rm -f confcache
+
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+# Let make expand exec_prefix.
+test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
+
+# Transform confdefs.h into DEFS.
+# Protect against shell expansion while executing Makefile rules.
+# Protect against Makefile macro expansion.
+#
+# If the first sed substitution is executed (which looks for macros that
+# take arguments), then branch to the quote section.  Otherwise,
+# look for a macro that doesn't take arguments.
+ac_script='
+:mline
+/\\$/{
+ N
+ s,\\\n,,
+ b mline
+}
+t clear
+:clear
+s/^[	 ]*#[	 ]*define[	 ][	 ]*\([^	 (][^	 (]*([^)]*)\)[	 ]*\(.*\)/-D\1=\2/g
+t quote
+s/^[	 ]*#[	 ]*define[	 ][	 ]*\([^	 ][^	 ]*\)[	 ]*\(.*\)/-D\1=\2/g
+t quote
+b any
+:quote
+s/[	 `~#$^&*(){}\\|;'\''"<>?]/\\&/g
+s/\[/\\&/g
+s/\]/\\&/g
+s/\$/$$/g
+H
+:any
+${
+	g
+	s/^\n//
+	s/\n/ /g
+	p
+}
+'
+DEFS=`sed -n "$ac_script" confdefs.h`
+
+
+ac_libobjs=
+ac_ltlibobjs=
+U=
+for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue
+  # 1. Remove the extension, and $U if already installed.
+  ac_script='s/\$U\././;s/\.o$//;s/\.obj$//'
+  ac_i=`$as_echo "$ac_i" | sed "$ac_script"`
+  # 2. Prepend LIBOBJDIR.  When used with automake>=1.10 LIBOBJDIR
+  #    will be set to the directory where LIBOBJS objects are built.
+  as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext"
+  as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo'
+done
+LIBOBJS=$ac_libobjs
+
+LTLIBOBJS=$ac_ltlibobjs
+
+
+
+: "${CONFIG_STATUS=./config.status}"
+ac_write_fail=0
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files $CONFIG_STATUS"
+{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5
+$as_echo "$as_me: creating $CONFIG_STATUS" >&6;}
+as_write_fail=0
+cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1
+#! $SHELL
+# Generated by $as_me.
+# Run this file to recreate the current configuration.
+# Compiler output produced by configure, useful for debugging
+# configure, is in config.log if it exists.
+
+debug=false
+ac_cs_recheck=false
+ac_cs_silent=false
+
+SHELL=\${CONFIG_SHELL-$SHELL}
+export SHELL
+_ASEOF
+cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1
+## -------------------- ##
+## M4sh Initialization. ##
+## -------------------- ##
+
+# Be more Bourne compatible
+DUALCASE=1; export DUALCASE # for MKS sh
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then :
+  emulate sh
+  NULLCMD=:
+  # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '${1+"$@"}'='"$@"'
+  setopt NO_GLOB_SUBST
+else
+  case `(set -o) 2>/dev/null` in #(
+  *posix*) :
+    set -o posix ;; #(
+  *) :
+     ;;
+esac
+fi
+
+
+as_nl='
+'
+export as_nl
+# Printing a long string crashes Solaris 7 /usr/bin/printf.
+as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo
+# Prefer a ksh shell builtin over an external printf program on Solaris,
+# but without wasting forks for bash or zsh.
+if test -z "$BASH_VERSION$ZSH_VERSION" \
+    && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='print -r --'
+  as_echo_n='print -rn --'
+elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='printf %s\n'
+  as_echo_n='printf %s'
+else
+  if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then
+    as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"'
+    as_echo_n='/usr/ucb/echo -n'
+  else
+    as_echo_body='eval expr "X$1" : "X\\(.*\\)"'
+    as_echo_n_body='eval
+      arg=$1;
+      case $arg in #(
+      *"$as_nl"*)
+	expr "X$arg" : "X\\(.*\\)$as_nl";
+	arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;;
+      esac;
+      expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl"
+    '
+    export as_echo_n_body
+    as_echo_n='sh -c $as_echo_n_body as_echo'
+  fi
+  export as_echo_body
+  as_echo='sh -c $as_echo_body as_echo'
+fi
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+  PATH_SEPARATOR=:
+  (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
+    (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
+      PATH_SEPARATOR=';'
+  }
+fi
+
+
+# IFS
+# We need space, tab and new line, in precisely that order.  Quoting is
+# there to prevent editors from complaining about space-tab.
+# (If _AS_PATH_WALK were called with IFS unset, it would disable word
+# splitting by setting IFS to empty value.)
+IFS=" ""	$as_nl"
+
+# Find who we are.  Look in the path if we contain no directory separator.
+as_myself=
+case $0 in #((
+  *[\\/]* ) as_myself=$0 ;;
+  *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+  done
+IFS=$as_save_IFS
+
+     ;;
+esac
+# We did not find ourselves, most probably we were run as `sh COMMAND'
+# in which case we are not to be found in the path.
+if test "x$as_myself" = x; then
+  as_myself=$0
+fi
+if test ! -f "$as_myself"; then
+  $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
+  exit 1
+fi
+
+# Unset variables that we do not need and which cause bugs (e.g. in
+# pre-3.0 UWIN ksh).  But do not cause bugs in bash 2.01; the "|| exit 1"
+# suppresses any "Segmentation fault" message there.  '((' could
+# trigger a bug in pdksh 5.2.14.
+for as_var in BASH_ENV ENV MAIL MAILPATH
+do eval test x\${$as_var+set} = xset \
+  && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
+done
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+LC_ALL=C
+export LC_ALL
+LANGUAGE=C
+export LANGUAGE
+
+# CDPATH.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+
+# as_fn_error STATUS ERROR [LINENO LOG_FD]
+# ----------------------------------------
+# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
+# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
+# script with STATUS, using 1 if that was 0.
+as_fn_error ()
+{
+  as_status=$1; test $as_status -eq 0 && as_status=1
+  if test "$4"; then
+    as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+    $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
+  fi
+  $as_echo "$as_me: error: $2" >&2
+  as_fn_exit $as_status
+} # as_fn_error
+
+
+# as_fn_set_status STATUS
+# -----------------------
+# Set $? to STATUS, without forking.
+as_fn_set_status ()
+{
+  return $1
+} # as_fn_set_status
+
+# as_fn_exit STATUS
+# -----------------
+# Exit the shell with STATUS, even in a "trap 0" or "set -e" context.
+as_fn_exit ()
+{
+  set +e
+  as_fn_set_status $1
+  exit $1
+} # as_fn_exit
+
+# as_fn_unset VAR
+# ---------------
+# Portably unset VAR.
+as_fn_unset ()
+{
+  { eval $1=; unset $1;}
+}
+as_unset=as_fn_unset
+# as_fn_append VAR VALUE
+# ----------------------
+# Append the text in VALUE to the end of the definition contained in VAR. Take
+# advantage of any shell optimizations that allow amortized linear growth over
+# repeated appends, instead of the typical quadratic growth present in naive
+# implementations.
+if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then :
+  eval 'as_fn_append ()
+  {
+    eval $1+=\$2
+  }'
+else
+  as_fn_append ()
+  {
+    eval $1=\$$1\$2
+  }
+fi # as_fn_append
+
+# as_fn_arith ARG...
+# ------------------
+# Perform arithmetic evaluation on the ARGs, and store the result in the
+# global $as_val. Take advantage of shells that can avoid forks. The arguments
+# must be portable across $(()) and expr.
+if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then :
+  eval 'as_fn_arith ()
+  {
+    as_val=$(( $* ))
+  }'
+else
+  as_fn_arith ()
+  {
+    as_val=`expr "$@" || test $? -eq 1`
+  }
+fi # as_fn_arith
+
+
+if expr a : '\(a\)' >/dev/null 2>&1 &&
+   test "X`expr 00001 : '.*\(...\)'`" = X001; then
+  as_expr=expr
+else
+  as_expr=false
+fi
+
+if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
+  as_basename=basename
+else
+  as_basename=false
+fi
+
+if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
+  as_dirname=dirname
+else
+  as_dirname=false
+fi
+
+as_me=`$as_basename -- "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+	 X"$0" : 'X\(//\)$' \| \
+	 X"$0" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X/"$0" |
+    sed '/^.*\/\([^/][^/]*\)\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\/\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\/\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`
+
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+ECHO_C= ECHO_N= ECHO_T=
+case `echo -n x` in #(((((
+-n*)
+  case `echo 'xy\c'` in
+  *c*) ECHO_T='	';;	# ECHO_T is single tab character.
+  xy)  ECHO_C='\c';;
+  *)   echo `echo ksh88 bug on AIX 6.1` > /dev/null
+       ECHO_T='	';;
+  esac;;
+*)
+  ECHO_N='-n';;
+esac
+
+rm -f conf$$ conf$$.exe conf$$.file
+if test -d conf$$.dir; then
+  rm -f conf$$.dir/conf$$.file
+else
+  rm -f conf$$.dir
+  mkdir conf$$.dir 2>/dev/null
+fi
+if (echo >conf$$.file) 2>/dev/null; then
+  if ln -s conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s='ln -s'
+    # ... but there are two gotchas:
+    # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
+    # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
+    # In both cases, we have to default to `cp -pR'.
+    ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
+      as_ln_s='cp -pR'
+  elif ln conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s=ln
+  else
+    as_ln_s='cp -pR'
+  fi
+else
+  as_ln_s='cp -pR'
+fi
+rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
+rmdir conf$$.dir 2>/dev/null
+
+
+# as_fn_mkdir_p
+# -------------
+# Create "$as_dir" as a directory, including parents if necessary.
+as_fn_mkdir_p ()
+{
+
+  case $as_dir in #(
+  -*) as_dir=./$as_dir;;
+  esac
+  test -d "$as_dir" || eval $as_mkdir_p || {
+    as_dirs=
+    while :; do
+      case $as_dir in #(
+      *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
+      *) as_qdir=$as_dir;;
+      esac
+      as_dirs="'$as_qdir' $as_dirs"
+      as_dir=`$as_dirname -- "$as_dir" ||
+$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+	 X"$as_dir" : 'X\(//\)[^/]' \| \
+	 X"$as_dir" : 'X\(//\)$' \| \
+	 X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_dir" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)[^/].*/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`
+      test -d "$as_dir" && break
+    done
+    test -z "$as_dirs" || eval "mkdir $as_dirs"
+  } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
+
+
+} # as_fn_mkdir_p
+if mkdir -p . 2>/dev/null; then
+  as_mkdir_p='mkdir -p "$as_dir"'
+else
+  test -d ./-p && rmdir ./-p
+  as_mkdir_p=false
+fi
+
+
+# as_fn_executable_p FILE
+# -----------------------
+# Test if FILE is an executable regular file.
+as_fn_executable_p ()
+{
+  test -f "$1" && test -x "$1"
+} # as_fn_executable_p
+as_test_x='test -x'
+as_executable_p=as_fn_executable_p
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+
+exec 6>&1
+## ----------------------------------- ##
+## Main body of $CONFIG_STATUS script. ##
+## ----------------------------------- ##
+_ASEOF
+test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# Save the log message, to keep $0 and so on meaningful, and to
+# report actual input values of CONFIG_FILES etc. instead of their
+# values after options handling.
+ac_log="
+This file was extended by geovis $as_me 0.4, which was
+generated by GNU Autoconf 2.69.  Invocation command line was
+
+  CONFIG_FILES    = $CONFIG_FILES
+  CONFIG_HEADERS  = $CONFIG_HEADERS
+  CONFIG_LINKS    = $CONFIG_LINKS
+  CONFIG_COMMANDS = $CONFIG_COMMANDS
+  $ $0 $@
+
+on `(hostname || uname -n) 2>/dev/null | sed 1q`
+"
+
+_ACEOF
+
+case $ac_config_files in *"
+"*) set x $ac_config_files; shift; ac_config_files=$*;;
+esac
+
+
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+# Files that config.status was made for.
+config_files="$ac_config_files"
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+ac_cs_usage="\
+\`$as_me' instantiates files and other configuration actions
+from templates according to the current configuration.  Unless the files
+and actions are specified as TAGs, all are instantiated by default.
+
+Usage: $0 [OPTION]... [TAG]...
+
+  -h, --help       print this help, then exit
+  -V, --version    print version number and configuration settings, then exit
+      --config     print configuration, then exit
+  -q, --quiet, --silent
+                   do not print progress messages
+  -d, --debug      don't remove temporary files
+      --recheck    update $as_me by reconfiguring in the same conditions
+      --file=FILE[:TEMPLATE]
+                   instantiate the configuration file FILE
+
+Configuration files:
+$config_files
+
+Report bugs to <rappture@nanohub.org>."
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
+ac_cs_version="\\
+geovis config.status 0.4
+configured by $0, generated by GNU Autoconf 2.69,
+  with options \\"\$ac_cs_config\\"
+
+Copyright (C) 2012 Free Software Foundation, Inc.
+This config.status script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it."
+
+ac_pwd='$ac_pwd'
+srcdir='$srcdir'
+INSTALL='$INSTALL'
+MKDIR_P='$MKDIR_P'
+test -n "\$AWK" || AWK=awk
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# The default lists apply if the user does not specify any file.
+ac_need_defaults=:
+while test $# != 0
+do
+  case $1 in
+  --*=?*)
+    ac_option=`expr "X$1" : 'X\([^=]*\)='`
+    ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'`
+    ac_shift=:
+    ;;
+  --*=)
+    ac_option=`expr "X$1" : 'X\([^=]*\)='`
+    ac_optarg=
+    ac_shift=:
+    ;;
+  *)
+    ac_option=$1
+    ac_optarg=$2
+    ac_shift=shift
+    ;;
+  esac
+
+  case $ac_option in
+  # Handling of the options.
+  -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
+    ac_cs_recheck=: ;;
+  --version | --versio | --versi | --vers | --ver | --ve | --v | -V )
+    $as_echo "$ac_cs_version"; exit ;;
+  --config | --confi | --conf | --con | --co | --c )
+    $as_echo "$ac_cs_config"; exit ;;
+  --debug | --debu | --deb | --de | --d | -d )
+    debug=: ;;
+  --file | --fil | --fi | --f )
+    $ac_shift
+    case $ac_optarg in
+    *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
+    '') as_fn_error $? "missing file argument" ;;
+    esac
+    as_fn_append CONFIG_FILES " '$ac_optarg'"
+    ac_need_defaults=false;;
+  --he | --h |  --help | --hel | -h )
+    $as_echo "$ac_cs_usage"; exit ;;
+  -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+  | -silent | --silent | --silen | --sile | --sil | --si | --s)
+    ac_cs_silent=: ;;
+
+  # This is an error.
+  -*) as_fn_error $? "unrecognized option: \`$1'
+Try \`$0 --help' for more information." ;;
+
+  *) as_fn_append ac_config_targets " $1"
+     ac_need_defaults=false ;;
+
+  esac
+  shift
+done
+
+ac_configure_extra_args=
+
+if $ac_cs_silent; then
+  exec 6>/dev/null
+  ac_configure_extra_args="$ac_configure_extra_args --silent"
+fi
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+if \$ac_cs_recheck; then
+  set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion
+  shift
+  \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6
+  CONFIG_SHELL='$SHELL'
+  export CONFIG_SHELL
+  exec "\$@"
+fi
+
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+exec 5>>config.log
+{
+  echo
+  sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX
+## Running $as_me. ##
+_ASBOX
+  $as_echo "$ac_log"
+} >&5
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+
+# Handling of arguments.
+for ac_config_target in $ac_config_targets
+do
+  case $ac_config_target in
+    "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;;
+
+  *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;;
+  esac
+done
+
+
+# If the user did not use the arguments to specify the items to instantiate,
+# then the envvar interface is used.  Set only those that are not.
+# We use the long form for the default assignment because of an extremely
+# bizarre bug on SunOS 4.1.3.
+if $ac_need_defaults; then
+  test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files
+fi
+
+# Have a temporary directory for convenience.  Make it in the build tree
+# simply because there is no reason against having it here, and in addition,
+# creating and moving files from /tmp can sometimes cause problems.
+# Hook for its removal unless debugging.
+# Note that there is a small window in which the directory will not be cleaned:
+# after its creation but before its name has been assigned to `$tmp'.
+$debug ||
+{
+  tmp= ac_tmp=
+  trap 'exit_status=$?
+  : "${ac_tmp:=$tmp}"
+  { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status
+' 0
+  trap 'as_fn_exit 1' 1 2 13 15
+}
+# Create a (secure) tmp directory for tmp files.
+
+{
+  tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` &&
+  test -d "$tmp"
+}  ||
+{
+  tmp=./conf$$-$RANDOM
+  (umask 077 && mkdir "$tmp")
+} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5
+ac_tmp=$tmp
+
+# Set up the scripts for CONFIG_FILES section.
+# No need to generate them if there are no CONFIG_FILES.
+# This happens for instance with `./config.status config.h'.
+if test -n "$CONFIG_FILES"; then
+
+
+ac_cr=`echo X | tr X '\015'`
+# On cygwin, bash can eat \r inside `` if the user requested igncr.
+# But we know of no other shell where ac_cr would be empty at this
+# point, so we can use a bashism as a fallback.
+if test "x$ac_cr" = x; then
+  eval ac_cr=\$\'\\r\'
+fi
+ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' </dev/null 2>/dev/null`
+if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then
+  ac_cs_awk_cr='\\r'
+else
+  ac_cs_awk_cr=$ac_cr
+fi
+
+echo 'BEGIN {' >"$ac_tmp/subs1.awk" &&
+_ACEOF
+
+
+{
+  echo "cat >conf$$subs.awk <<_ACEOF" &&
+  echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' &&
+  echo "_ACEOF"
+} >conf$$subs.sh ||
+  as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'`
+ac_delim='%!_!# '
+for ac_last_try in false false false false false :; do
+  . ./conf$$subs.sh ||
+    as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+
+  ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X`
+  if test $ac_delim_n = $ac_delim_num; then
+    break
+  elif $ac_last_try; then
+    as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+  else
+    ac_delim="$ac_delim!$ac_delim _$ac_delim!! "
+  fi
+done
+rm -f conf$$subs.sh
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK &&
+_ACEOF
+sed -n '
+h
+s/^/S["/; s/!.*/"]=/
+p
+g
+s/^[^!]*!//
+:repl
+t repl
+s/'"$ac_delim"'$//
+t delim
+:nl
+h
+s/\(.\{148\}\)..*/\1/
+t more1
+s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/
+p
+n
+b repl
+:more1
+s/["\\]/\\&/g; s/^/"/; s/$/"\\/
+p
+g
+s/.\{148\}//
+t nl
+:delim
+h
+s/\(.\{148\}\)..*/\1/
+t more2
+s/["\\]/\\&/g; s/^/"/; s/$/"/
+p
+b
+:more2
+s/["\\]/\\&/g; s/^/"/; s/$/"\\/
+p
+g
+s/.\{148\}//
+t delim
+' <conf$$subs.awk | sed '
+/^[^""]/{
+  N
+  s/\n//
+}
+' >>$CONFIG_STATUS || ac_write_fail=1
+rm -f conf$$subs.awk
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+_ACAWK
+cat >>"\$ac_tmp/subs1.awk" <<_ACAWK &&
+  for (key in S) S_is_set[key] = 1
+  FS = ""
+
+}
+{
+  line = $ 0
+  nfields = split(line, field, "@")
+  substed = 0
+  len = length(field[1])
+  for (i = 2; i < nfields; i++) {
+    key = field[i]
+    keylen = length(key)
+    if (S_is_set[key]) {
+      value = S[key]
+      line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3)
+      len += length(value) + length(field[++i])
+      substed = 1
+    } else
+      len += 1 + keylen
+  }
+
+  print line
+}
+
+_ACAWK
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then
+  sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g"
+else
+  cat
+fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \
+  || as_fn_error $? "could not setup config files machinery" "$LINENO" 5
+_ACEOF
+
+# VPATH may cause trouble with some makes, so we remove sole $(srcdir),
+# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and
+# trailing colons and then remove the whole line if VPATH becomes empty
+# (actually we leave an empty line to preserve line numbers).
+if test "x$srcdir" = x.; then
+  ac_vpsub='/^[	 ]*VPATH[	 ]*=[	 ]*/{
+h
+s///
+s/^/:/
+s/[	 ]*$/:/
+s/:\$(srcdir):/:/g
+s/:\${srcdir}:/:/g
+s/:@srcdir@:/:/g
+s/^:*//
+s/:*$//
+x
+s/\(=[	 ]*\).*/\1/
+G
+s/\n//
+s/^[^=]*=[	 ]*$//
+}'
+fi
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+fi # test -n "$CONFIG_FILES"
+
+
+eval set X "  :F $CONFIG_FILES      "
+shift
+for ac_tag
+do
+  case $ac_tag in
+  :[FHLC]) ac_mode=$ac_tag; continue;;
+  esac
+  case $ac_mode$ac_tag in
+  :[FHL]*:*);;
+  :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;;
+  :[FH]-) ac_tag=-:-;;
+  :[FH]*) ac_tag=$ac_tag:$ac_tag.in;;
+  esac
+  ac_save_IFS=$IFS
+  IFS=:
+  set x $ac_tag
+  IFS=$ac_save_IFS
+  shift
+  ac_file=$1
+  shift
+
+  case $ac_mode in
+  :L) ac_source=$1;;
+  :[FH])
+    ac_file_inputs=
+    for ac_f
+    do
+      case $ac_f in
+      -) ac_f="$ac_tmp/stdin";;
+      *) # Look for the file first in the build tree, then in the source tree
+	 # (if the path is not absolute).  The absolute path cannot be DOS-style,
+	 # because $ac_f cannot contain `:'.
+	 test -f "$ac_f" ||
+	   case $ac_f in
+	   [\\/$]*) false;;
+	   *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";;
+	   esac ||
+	   as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;;
+      esac
+      case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac
+      as_fn_append ac_file_inputs " '$ac_f'"
+    done
+
+    # Let's still pretend it is `configure' which instantiates (i.e., don't
+    # use $as_me), people would be surprised to read:
+    #    /* config.h.  Generated by config.status.  */
+    configure_input='Generated from '`
+	  $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g'
+	`' by configure.'
+    if test x"$ac_file" != x-; then
+      configure_input="$ac_file.  $configure_input"
+      { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5
+$as_echo "$as_me: creating $ac_file" >&6;}
+    fi
+    # Neutralize special characters interpreted by sed in replacement strings.
+    case $configure_input in #(
+    *\&* | *\|* | *\\* )
+       ac_sed_conf_input=`$as_echo "$configure_input" |
+       sed 's/[\\\\&|]/\\\\&/g'`;; #(
+    *) ac_sed_conf_input=$configure_input;;
+    esac
+
+    case $ac_tag in
+    *:-:* | *:-) cat >"$ac_tmp/stdin" \
+      || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;;
+    esac
+    ;;
+  esac
+
+  ac_dir=`$as_dirname -- "$ac_file" ||
+$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+	 X"$ac_file" : 'X\(//\)[^/]' \| \
+	 X"$ac_file" : 'X\(//\)$' \| \
+	 X"$ac_file" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$ac_file" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)[^/].*/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\/\)$/{
+	    s//\1/
+	    q
+	  }
+	  /^X\(\/\).*/{
+	    s//\1/
+	    q
+	  }
+	  s/.*/./; q'`
+  as_dir="$ac_dir"; as_fn_mkdir_p
+  ac_builddir=.
+
+case "$ac_dir" in
+.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
+*)
+  ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'`
+  # A ".." for each directory in $ac_dir_suffix.
+  ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
+  case $ac_top_builddir_sub in
+  "") ac_top_builddir_sub=. ac_top_build_prefix= ;;
+  *)  ac_top_build_prefix=$ac_top_builddir_sub/ ;;
+  esac ;;
+esac
+ac_abs_top_builddir=$ac_pwd
+ac_abs_builddir=$ac_pwd$ac_dir_suffix
+# for backward compatibility:
+ac_top_builddir=$ac_top_build_prefix
+
+case $srcdir in
+  .)  # We are building in place.
+    ac_srcdir=.
+    ac_top_srcdir=$ac_top_builddir_sub
+    ac_abs_top_srcdir=$ac_pwd ;;
+  [\\/]* | ?:[\\/]* )  # Absolute name.
+    ac_srcdir=$srcdir$ac_dir_suffix;
+    ac_top_srcdir=$srcdir
+    ac_abs_top_srcdir=$srcdir ;;
+  *) # Relative name.
+    ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
+    ac_top_srcdir=$ac_top_build_prefix$srcdir
+    ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
+esac
+ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
+
+
+  case $ac_mode in
+  :F)
+  #
+  # CONFIG_FILE
+  #
+
+  case $INSTALL in
+  [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;;
+  *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;;
+  esac
+  ac_MKDIR_P=$MKDIR_P
+  case $MKDIR_P in
+  [\\/$]* | ?:[\\/]* ) ;;
+  */*) ac_MKDIR_P=$ac_top_build_prefix$MKDIR_P ;;
+  esac
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# If the template does not know about datarootdir, expand it.
+# FIXME: This hack should be removed a few years after 2.60.
+ac_datarootdir_hack=; ac_datarootdir_seen=
+ac_sed_dataroot='
+/datarootdir/ {
+  p
+  q
+}
+/@datadir@/p
+/@docdir@/p
+/@infodir@/p
+/@localedir@/p
+/@mandir@/p'
+case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in
+*datarootdir*) ac_datarootdir_seen=yes;;
+*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*)
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5
+$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;}
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+  ac_datarootdir_hack='
+  s&@datadir@&$datadir&g
+  s&@docdir@&$docdir&g
+  s&@infodir@&$infodir&g
+  s&@localedir@&$localedir&g
+  s&@mandir@&$mandir&g
+  s&\\\${datarootdir}&$datarootdir&g' ;;
+esac
+_ACEOF
+
+# Neutralize VPATH when `$srcdir' = `.'.
+# Shell code in configure.ac might set extrasub.
+# FIXME: do we really want to maintain this feature?
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ac_sed_extra="$ac_vpsub
+$extrasub
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+:t
+/@[a-zA-Z_][a-zA-Z_0-9]*@/!b
+s|@configure_input@|$ac_sed_conf_input|;t t
+s&@top_builddir@&$ac_top_builddir_sub&;t t
+s&@top_build_prefix@&$ac_top_build_prefix&;t t
+s&@srcdir@&$ac_srcdir&;t t
+s&@abs_srcdir@&$ac_abs_srcdir&;t t
+s&@top_srcdir@&$ac_top_srcdir&;t t
+s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t
+s&@builddir@&$ac_builddir&;t t
+s&@abs_builddir@&$ac_abs_builddir&;t t
+s&@abs_top_builddir@&$ac_abs_top_builddir&;t t
+s&@INSTALL@&$ac_INSTALL&;t t
+s&@MKDIR_P@&$ac_MKDIR_P&;t t
+$ac_datarootdir_hack
+"
+eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \
+  >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+
+test -z "$ac_datarootdir_hack$ac_datarootdir_seen" &&
+  { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } &&
+  { ac_out=`sed -n '/^[	 ]*datarootdir[	 ]*:*=/p' \
+      "$ac_tmp/out"`; test -z "$ac_out"; } &&
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir'
+which seems to be undefined.  Please make sure it is defined" >&5
+$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir'
+which seems to be undefined.  Please make sure it is defined" >&2;}
+
+  rm -f "$ac_tmp/stdin"
+  case $ac_file in
+  -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";;
+  *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";;
+  esac \
+  || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+ ;;
+
+
+
+  esac
+
+done # for ac_tag
+
+
+as_fn_exit 0
+_ACEOF
+ac_clean_files=$ac_clean_files_save
+
+test $ac_write_fail = 0 ||
+  as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5
+
+
+# configure is writing to config.log, and then calls config.status.
+# config.status does its own redirection, appending to config.log.
+# Unfortunately, on DOS this fails, as config.log is still kept open
+# by configure, so config.status won't be able to write to it; its
+# output is simply discarded.  So we exec the FD to /dev/null,
+# effectively closing config.log, so it can be properly (re)opened and
+# appended to by config.status.  When coming back to configure, we
+# need to make the FD available again.
+if test "$no_create" != yes; then
+  ac_cs_success=:
+  ac_config_status_args=
+  test "$silent" = yes &&
+    ac_config_status_args="$ac_config_status_args --quiet"
+  exec 5>/dev/null
+  $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false
+  exec 5>>config.log
+  # Use ||, not &&, to avoid exiting from the if with $? = 1, which
+  # would make configure fail if this is the last instruction.
+  $ac_cs_success || as_fn_exit 1
+fi
+if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5
+$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;}
+fi
+
Index: /geovis/branches/1.0/configure.ac
===================================================================
--- /geovis/branches/1.0/configure.ac	(revision 6666)
+++ /geovis/branches/1.0/configure.ac	(revision 6666)
@@ -0,0 +1,128 @@
+AC_PREREQ([2.60])
+AC_INIT(geovis, 0.4, rappture@nanohub.org)
+AC_CONFIG_AUX_DIR(cf)
+
+AC_ARG_WITH(
+    [tcllib],
+    [AS_HELP_STRING([--with-tcllib[=DIR]],
+        [location of Tcl binary library libtclstubs.a @<:@default=/usr/lib@:>@])],
+    [with_tcllib=$withval],
+    [with_tcllib=""])
+
+AC_ARG_WITH(
+    [osg-includes],
+    [AS_HELP_STRING([--with-osg-includes=DIR],
+        [OSG include files are in DIR])],
+    [with_osg_includes=$withval],
+    [with_osg_includes=""])
+
+AC_ARG_WITH(
+    [osg-libs],
+    [AS_HELP_STRING([--with-osg-libs=DIR],
+        [OSG libraries are in DIR])],
+    [with_osg_libs=$withval],
+    [with_osg_libs=""])
+
+AC_ARG_WITH(
+    [statsdir],
+    [AS_HELP_STRING([--with-statsdir=DIR],
+        [Write statistics in DIR])],
+    [STATSDIR=$withval],
+    [STATSDIR="/var/tmp/visservers"])
+
+#------------------------------------------------------------------------
+# Handle the --prefix=... option
+#------------------------------------------------------------------------
+
+if test "${prefix}" = "NONE"; then
+    prefix=/usr/local
+fi
+if test "${exec_prefix}" = "NONE"; then
+    exec_prefix=$prefix
+fi
+
+if test "x${incdir}" == "x"; then
+  incdir=${exec_prefix}/include
+fi
+libdir=${exec_prefix}/lib
+
+if test "${libdir}" != "${prefix}/lib"; then
+    LIB_SEARCH_DIRS="-L ${prefix}/lib -L ${libdir}"
+else
+    LIB_SEARCH_DIRS="-L ${libdir}"
+fi
+
+AC_PROG_INSTALL
+AC_PROG_RANLIB
+AC_PROG_LN_S
+AC_PROG_MKDIR_P
+
+dnl find and test the C compiler
+AC_PROG_CC
+AC_PROG_CPP
+AC_PROG_CXXCPP
+AC_PROG_CXX
+AC_LANG_CPLUSPLUS
+
+#
+# Need to check for the following dependencies:
+#       Tcl headers and library
+#       GL headers and library
+#       pthread library
+#       OpenSceneGraph libraries
+#       osgEarth libraries
+#
+
+TCL_VERSION="8.4"
+for dir in \
+ ${exec_prefix} \
+ ${exec_prefix}/lib ; do
+  tclconfig="${dir}/tclConfig.sh"
+  if test -f "$tclconfig" ; then
+    . $tclconfig
+    break
+  fi
+done
+TCL_INC_SPEC="$TCL_INCLUDE_SPEC"
+
+if test "x$with_tcllib" != "x" ; then
+   tclconfig="${with_tcllib}/tclConfig.sh"
+   if test -f "$tclconfig" ; then
+    . $tclconfig
+   fi
+   TCL_LIB_SPEC="-L${with_tcllib} -ltcl${TCL_VERSION}"
+fi
+if test "x$with_tclinclude" != "x" ; then
+   TCL_INC_SPEC="-I${with_tclinclude}"
+fi
+
+OSG_INC_SPEC=-I${incdir}
+OSG_LIB_DIR=${libdir}
+OSG_LIB_SPEC="-L${libdir}"
+
+if test "x$with_osg_includes" != "x"; then
+   OSG_INC_SPEC=-I$with_osg_includes
+fi
+
+if test "x$with_osg_libs" != "x"; then
+   OSG_LIB_DIR=$with_osg_libs
+   OSG_LIB_SPEC="-L$with_osg_libs"
+fi
+
+RESOURCES_DIR=${libdir}/resources
+
+AC_CHECK_HEADERS([stdio.h unistd.h stdlib.h string.h sys/types.h])
+AC_CHECK_HEADERS([sys/socket.h sys/time.h netinet/in.h arpa/inet.h netdb.h])
+
+AC_CHECK_FUNCS([accept4])
+
+AC_SUBST(RESOURCES_DIR)
+AC_SUBST(STATSDIR)
+AC_SUBST(TCL_INC_SPEC)
+AC_SUBST(TCL_LIB_SPEC)
+AC_SUBST(TCL_VERSION)
+AC_SUBST(OSG_INC_SPEC)
+AC_SUBST(OSG_LIB_DIR)
+AC_SUBST(OSG_LIB_SPEC)
+
+AC_OUTPUT([Makefile])
Index: /geovis/branches/1.0/geovis_protocol.txt
===================================================================
--- /geovis/branches/1.0/geovis_protocol.txt	(revision 6666)
+++ /geovis/branches/1.0/geovis_protocol.txt	(revision 6666)
@@ -0,0 +1,403 @@
+================================================================================
+================================================================================
+Protocol for geovis render server.  
+
+Commands take the general form of command and arguments using Tcl syntax and are
+terminated by a newline.  Some command arguments expect a Tcl list which can be
+sent by enclosing the list in curly braces to prevent the interpreter from 
+splitting the list into multiple arguments.  Likewise, strings with spaces 
+should be quoted or enclosed in curly braces.  In the documentation below,
+arguments are indicated by angle brackets and optional arguments are surrounded
+by question marks -- the brackets and question marks are not part of the
+protocol and should not be included in argument strings.  The arguments shown 
+in angle brackets should be replaced with a string (where there are fixed 
+choices this document indicates valid values by separating them with pipe 
+symbols), a list in Tcl syntax, or a numeric value.  Boolean arguments can take
+the form of 0|1, true|false, or yes|no.  Since the protocol commands are 
+executed in a Tcl interpreter, Tcl code such as math expressions in expr 
+commands enclosed in square brackets are also allowed.  Since the interpreter
+is a "safe" interpreter, some Tcl commands that could cause security issues may 
+not be available for use.
+
+When binary data is to be sent, it should follow the newline after a command 
+that indicates incoming data including a byte count argument.
+================================================================================
+================================================================================
+Requests:
+================================================================================
+
+== General scene and renderer commands ==
+
+camera delete <name>
+       Delete named viewpoint
+camera dist <distance>
+       Set camera distance from focal point
+camera extent <xmin> <ymin> <xmax> <ymax> <?duration?> <?srs?>
+       Zoom to extent.  Will fit the extent to the vertical FOV and center on
+       the extent centroid.
+       <xmin>,<ymin>,<xmax>,<ymax> = extent bounds in the specified SRS,
+       or in the map's SRS if none is specified.
+       <duration> = Duration of viewpoint move animation in seconds
+       <srs> = The SRS of the extent bounds
+camera get
+       Request camera parameters
+camera go <x> <y> <?zoomFactor?> <?duration?>
+       Move camera to new focal point.
+       <x>,<y> = Screen/mouse coordinates
+       <zoomFactor> = Zoom distance multiplier, 1.0=no zoom
+       <duration> = Duration of viewpoint move animation in seconds
+camera lextent <layerName> <?duration?>
+       Zoom to layer extent.  Will fit the extent to the vertical FOV and center
+       on the extent centroid.
+       <layerName> = Name of layer to use for extent.
+       <duration> = Duration of viewpoint move animation in seconds
+camera orient <quatW> <quatX> <quatY> <quatZ>
+       Set scene orientation using a quaternion
+camera pan <x> <y>
+       <x,y> viewport coordinates (window center at 0,0).  Positive x pan
+       means pan object to right (camera to left).  Positive y pan means
+       pan object down (camera up).  For example a pan of 0.5, 0.5 would
+       move the object center to the lower right corner of the window
+camera reset <?all?>
+       Option all resets orientation/rotation as well as pan/zoom/clip range
+camera restore <name> <?duration?>
+       Set viewpoint to a named viewpoint, with optional animation duration in 
+       seconds
+camera rotate <x> <y>
+       Specify relative rotation in viewport coordinates
+camera save <name>
+       Save current viewpoint to a named viewpoint
+camera set <x> <y> <z> <heading> <pitch> <distance> <?duration?> <?srs?> <?vertDatum?>
+       Explicitly set camera parameters, with optional animation duration in 
+       seconds
+       <x>, <y> = Map coordinates of focal point
+       <z> = Altitude of focal point (see vertDatum)
+       <heading> = Azimuth in degrees
+       <pitch> = Elevation in degrees
+       <distance> = Distance of camera from focal point in map units(?)
+       <srs> = coordinate system, defaults to map coordinate system
+       <vertDatum> = vertical datum for interpreting z coordinate 
+camera throw <bool>
+       Enable/disable throw inertia for panning/rotating when using mouse events
+       through protocol
+camera zoom <y>
+       Specify zoom by viewport coordinates (i.e. normalized y mouse coords)
+
+clientinfo <list>
+
+colormap add <colorMapName> <colorMap>
+         (Re-)define a colormap.  If colorMapName doesn't exist, it is created,
+         otherwise the colormap is redefined and all users will be updated.
+	 colorMap = Tcl list of {value r g b a} control points
+colormap define <colorMapName> <colorMap>
+         (Re-)define a colormap (same as 'add')
+colormap delete <?colorMapName?>
+colormap res <numberOfTableEntries>
+         Set the "resolution" or number of lookup table entries in the colormap
+         <numberOfTableEntries> = integer or "default"
+
+legend <colormapName> <w> <h> <?opaque?> <?bgR?> <?bgG?> <?bgB?>
+       Render a legend colorbar.
+       <colormapName> - Name of the colormap defined with the colormap command
+       <w>,<h> - width and height of colorbar
+       <opaque> - If false, blend with background color
+       <bgR>,<bgG>,<bgB> - Background color (defaults to black)
+
+renderer render
+         Force a new image to be rendered
+
+screen bgcolor <r> <g> <b>
+screen coords <token> <coordList> <?srs?> <?verticalDatum?>
+       Transate map coordinates to screen/mouse coordinates.
+       <token> - String token, included in response
+       <coordList> - A list of x,y,z map coordinates.  If <srs> is not given,
+       these are in the map's coordinate system.
+       <srs> - Optional horizontal srs init string for coordinate system of 
+       x,y,z
+       <verticalDatum> - Optional vertical srs init string for coordinate system
+       of x,y,z
+screen size <width> <height>
+
+== Mouse and Keyboard Events ==
+
+key press <key>
+key release <key>
+
+mouse click <button> <x> <y>
+mouse dblclick <button> <x> <y>
+mouse drag <button> <x> <y>
+mouse motion <x> <y>
+mouse release <button> <x> <y>
+mouse scroll <direction>
+
+== Interaction Configuration ==
+
+placard config <attrlist> <style> <padding> <layerName>
+    <attrlist> - List of attributes.  The list should consist of alternating
+    attribute names and attribute labels.  The names are the column names of the
+    attributes in the feature data and the corresponding labels will be 
+    displayed on the info placard
+    <style> - CSS style attributes for TextSymbol and PolygonSymbol (fill for
+    backing quad)
+    <padding> - Pixels of padding between text and edge of backdrop quad.
+    <layerName> - The name of the layer containing the feature data.
+
+placard enable <bool> <layerName>
+    Enable/disable display of feature attribute placard on select.
+    <layerName> - The name of the layer containing the feature data.
+
+select clear
+    Clear any annotation or feature selection
+
+select fadd <featureIDList> <layerName>
+    Add features to the selected set
+    <featureIDList> - List of feature IDs to be selected.
+    <layerName> - The name of the layer containing the feature data.
+
+select fdelete <featureIDList> <layerName>
+    Remove features from the selected set
+    <featureIDList> - List of feature IDs to be selected.
+    <layerName> - The name of the layer containing the feature data.
+
+select feature <featureIDList> <layerName>
+    Set/replace the set of currently selected features
+    <featureIDList> - List of feature IDs to be selected.
+    <layerName> - The name of the layer containing the feature data.
+
+select mode <mode>
+    <mode> - boolean: Enable/disable click to select mode.
+
+== Map Commands ==
+
+map attrib <string>
+    <string> - Attribution/copyright to display in corner of map
+
+map coords <token> <coordList> <?srs?> <?verticalDatum?>
+    Translate screen/mouse coordinates into map coordinates.
+    <token> - String token, included in response
+    <coordList> - A list of x,y screen/mouse coordinates
+    <srs> - Optional horizontal srs init string for coordinate system to return
+    <verticalDatum> - Optional vertical srs init string for coordinate system to
+    return
+
+map grid <bool> <?type?>
+    Toggle graticule and set type
+    <type> = geodetic|utm|mgrs
+
+General form:
+map layer add <layerName> <type> <driver> <?url?> <?cache?> ...
+    <type> = image|elevation|feature|icon|line|mask|model|point|polygon|text
+    <driver> = arcgis|colorramp|db|debug|gdal|ogr|tfs|tms|wcs|wfs|wms|xyz
+    <cache> = bool indicating if tiles should be cached on disk
+
+Specific image layer types:
+map layer add <layerName> image agglite <url> <cache> <coverage> <featuredriver> <format> <typeName> <styleLen> <?scriptLen?> <?selectorsLen?>
+    Rasterize a feature source to an image layer using the AGGLite renderer
+    <url> = URL of source
+    <cache> = bool indicating if tiles should be cached on disk
+    <coverage> = bool indicating if data should not be interpolated (true means
+    no interpolation)
+    <featuredriver> = Feature driver to read source (db, ogr, tfs, wfs)
+    <format> = See Feature/model layers below
+    <typeName> = See Feature/model layers below
+    <styleLen> = See Feature/model layers below
+    <scriptLen> = See Feature/model layers below
+    <selectorsLen> = See Feature/model layers below
+map layer add <layerName> image colorramp <url> <cache> <coverage> <elevdriver> <profile> <colormapName>
+    Add a colormapped image layer from a file or URL
+    <url> = URL of source
+    <cache> = bool indicating if tiles should be cached on disk
+    <coverage> = bool indicating if data should not be interpolated (true means
+    no interpolation)
+    <elevdriver> = Elevation driver to read source
+    <profile> = profile of source
+    <colormapName> = Name of colormap
+map layer add <layerName> image arcgis <url> <?cache?> <?coverage?> <?token?> <?layers?>
+    Add an ArcGIS MapServer image layer from a URL
+    <url> = URL of source
+    <cache> = bool indicating if tiles should be cached on disk
+    <coverage> = bool indicating if data should not be interpolated (true means
+    no interpolation)
+    <token> = login credential
+map layer add <layerName> image gdal <url> <?cache?> <?coverage?>
+    Add a GDAL image layer from a file or URL
+    <url> = URL of source
+    <cache> = bool indicating if tiles should be cached on disk
+    <coverage> = bool indicating if data should not be interpolated (true means
+    no interpolation)
+map layer add <layerName> image wms <url> <cache> <coverage> <layers> <format> <transparent> <?times?> <?frameSeconds?>
+    Add a WMS image layer from a URL + layer string
+    <url> = URL of WMS service
+    <cache> = bool indicating if tiles may be cached on disk
+    <coverage> = bool indicating if data should not be interpolated (true means
+    no interpolation)
+    <layers> = layers string for WMS server
+    <format> = Image format to return (e.g. 'png')
+    <transparent> = bool
+    <times> = optional comma separated list of timestamps for WMS-T
+    <frameSeconds> = time in seconds to display each animated frame for WMS-T
+map layer add <layerName> image tms <url> <cache> <coverage>
+    Add a TMS image layer from a URL
+    <url> = URL of TMS service
+    <cache> = bool indicating if tiles should be cached on disk
+    <coverage> = bool indicating if data should not be interpolated (true means
+    no interpolation)
+map layer add <layerName> image xyz <urlPattern> <cache> <coverage>
+    Add an image layer from a web map using a template URL
+    <urlPattern> = URL template of source.  '{x}', '{y}', '{z}' will be replaced
+    with tile row (x), column (y) and zoom level (z).  A block of characters in
+    square braces can be used to specify round-robin server numbers, e.g.
+    'host[012]' will exand to 'host0', host1', 'host2'.
+    <cache> = bool indicating if tiles should be cached on disk
+    <coverage> = bool indicating if data should not be interpolated (true means
+    no interpolation)
+
+Elevation layers:
+map layer add <layerName> elevation <driver> <url> <?cache?> <?verticalDatum?>
+    Add an elevation image layer from a file or URL
+    <driver> = gdal|tms|wcs
+    <verticalDatum> = override vertical datum in file.  This is required for
+    the gdal driver if the vertical datum is a geoid (i.e. not geodetic HAE).
+
+Feature/model and mask layers:
+In the following:
+<driver> = db|ogr|tfs|wfs
+<url> = file/web service address, or for driver 'db' the postgis connection
+string
+<format> = json|gml (for tfs or wfs driver)
+<typeName> = layer name for db or wfs driver
+<clamping> = none|terrain|relative|absolute
+<clamptechnique> = drape|gpu|scene|map
+
+map layer add <layerName> feature <driver> <format> <typeName> <url> <cache> <styleLen> <?scriptLen?> <?selectorsLen?> <?terrainPatch?> <?visibilityRangeMin?> <?visibilityRangeMax?>
+    Data payload follows command: style, then script, then selectors
+    <styleLen> = Number of bytes in style sheet. Stylesheet payload consists of 
+    CSS-formatted styles (see:
+    http://docs.osgearth.org/en/latest/user/features.html and
+    http://docs.osgearth.org/en/latest/references/symbology.html)
+    <scriptLen> = Number of bytes in scripts.  Scripts payload consists of
+    JavaScript code (see references above)
+    <selectorsLen> = Number of bytes in selectors.  Selectors payload consists
+    of a Tcl list of name/value pairs
+    <terrainPatch> = Boolean indicating if layer should be used for elevation
+    queries.
+map layer add <layerName> icon <driver> <format> <typeName> <url> <cache> <icon> <scale> <heading> <declutter> <placement> <alignment> <?visibilityRangeMin?> <?visibilityRangeMax?>
+    Add an icon feature layer from a file or URL
+    <placement> = vertex|centroid|interval|random
+    <alignment> = left_top|left_center|left_bottom|center_top|center_center|center_bottom|right_top|right_center|right_bottom
+map layer add <layerName> line <driver> <format> <typeName> <url> <cache> <r> <g> <b> <width> <?cap?> <?join?> <?stipplePattern?> <?stippleFactor?> <?clamping?> <?clamptechnique?> <?visibilityRangeMin?> <?visibilityRangeMax?>
+    Add a line feature layer from a file or URL
+    cap/join only used if width > 1
+    <cap> = flat|square|round
+    <join> = mitre|round
+map layer add <layerName> point <driver> <format> <typeName> <url> <cache> <r> <g> <b> <size> <?clamping?> <?clamptechnique?> <?visibilityRangeMin?> <?visibilityRangeMax?>
+    Add a point feature layer from a file or URL
+map layer add <layerName> polygon <driver> <format> <typeName> <url> <cache> <r> <g> <b> <?lineWidth?> <?strokeR?> <?strokeG?> <?strokeB?> <?clamping?> <?clamptechnique?> <?terrainPatch?> <?visibilityRangeMin?> <?visibilityRangeMax?>
+    Add a polygon feature layer from a file or URL
+map layer add <layerName> text <driver> <format> <typeName> <url> <cache> <content> <priority> <fgR> <fgG> <fgB> <bgR> <bgG> <bgB> <haloWidth> <fontSize> <declutter> <alignment> <xoffset> <yoffset> <?visibilityRangeMin?> <?visibilityRangeMax?>
+    Add a text symbol layer from a file or URL
+    <alignment> = left_top|left_center|left_bottom|center_top|center_center|center_bottom|right_top|right_center|right_bottom|left_baseline|center|baseline|right_baseline|left_bottom_baseline|center_bottom_baseline|right_bottom_baseline
+
+map layer add <layerName> mask <driver> <format> <typeName> <url> <?minLOD?>
+    Create a terrain mask layer to cut a hole in the terrain geometry.  The
+    first feature found is assumed to be a boundary polygon.
+    <driver> = db|ogr|tfs|wfs|wkt|wkt_file
+    <minLOD> minimum LOD to at which to apply the mask, defaults to 0.
+map layer add <layerName> mask wkt <format> <typeName> <length> <?minLOD?>
+    Like generic mask layer command, but instead of a <url>, included data in
+    the payload which is a WKT literal string.
+    <format> and <typeName> are unused
+    <length> = length of following data for inline WKT string
+map layer add <layerName> mask wkt_file <format> <typeName> <url> <?minLOD?>
+    <url> = URL for a text file containing a WKT string
+    <format> and <typeName> are unused
+
+map layer add <layerName> model simple <url> <x> <y> <?z?> <?rotx?> <?roty?> <?rotz?> <?terrainPatch?>
+    <url> = Path to a 3D model file that OSG can load (e.g. .osg file)
+    <x>,<y>,<z> = Map coordinates for placing the model
+    <rotx>,<roty>,<rotz> = Rotations (in degrees) to apply to orient model
+    <terrainPatch> = boolean indicating if model should be used for elevation
+    queries.
+
+map layer delete <layerName>
+map layer opacity <opacity> <layerName>
+map layer move <pos> <layerName>
+map layer names <?type?>
+    Generates a response listing the names of the given type (or all types if
+    not specified)
+    <type> = elevation|image|mask|model
+map layer visible <bool> <layerName>
+
+map load data follows <nbytes>
+    Send an .earth file over the wire
+map load file <path>
+    Load an .earth file from a filesystem mounted on the server
+map load url <url>
+    Load an .earth file from a network address
+
+map posdisp <bool> <?format?> <?precision?>
+    Toggle coordinate/position display, set format and precision
+    <format> = latlong_decimal_degrees|latlong_degrees_decimal_minutes|
+               latlong_degrees_minutes_seconds|mgrs
+    <precision> = integer number of digits to display
+
+map reset <type> <r> <g> <b> <?profile?> <?xmin?> <?ymin?> <?xmax?> <?ymax?>
+    Clear the scene and rebuild a new map from scratch.  When resetting the 
+    map to geocentric, a profile and extents should be omitted.  When the map
+    type is projected, the profile is required and extents are optional.
+    <type> = geocentric|projected
+    <r>,<g>,<b> - Background color red, green and blue components [0,1]
+    <profile> = Well known profile string, e.g. 'global-geodetic',
+    'global-mercator'.  If specifying bounds (i.e. not global), use 'geodetic'
+    or 'spherical-mercator' or an SRS init string (such as an EPSG code)
+    xmin,ymin,xmax,ymax = map bounds (in profile projection/units)
+
+map scalebar <bool> <?units?>
+    Toggle scale label/bar
+    <units> = meters|feet|us_survey_feet|nautical_miles
+map setpos <x> <y>
+    Set the coordinate/position display based on mouse coordinates
+map terrain edges <bool>
+    *Not currently implemented*
+map terrain lighting <bool>
+map terrain linecolor <r> <g> <b>
+    *Not currently implemented*
+map terrain vertscale <val>
+    Set scale factor for terrain height
+map terrain wireframe <bool>
+map time <?hours?> <?day?> <?month?> <?year?>
+    Set ephemeris time for sky.  When args are omitted, set to current time.
+    Time is interpreted as UTC.
+
+================================================================================
+Replies:
+================================================================================
+nv>image -type image -bytes <nbytes>
+  <binary RGB data>
+nv>legend <colormapName> <rmin> <rmax> <nbytes>
+  <binary RGB data>
+nv>map coords <token> <mapCoordList> <?srs?> <?verticalDatum?>
+   Reply to query of map coordinates from screen coordinates.  The mapCoordList
+   is a list of x,y,z map coordinates.  The srs of the map coordinates may be 
+   included if not in map's native coordinate system.  NaN in the results 
+   indicates off-map input coordinates.
+nv> map names <list>
+   Reply to query of layer names.  <list> is a Tcl formatted list.
+nv>screen coords <token> <screenCoordList>
+   Reply to query of screen coordinates from map coordinates.  The
+   screenCoordList is a list of x,y,z screen coordinates (z is the depth).  NaN
+   in the results indicates a point outside the view frustum or invalid input
+   map coordinate
+nv>select annotation <guid> <annotationName>
+nv>select clear
+nv>select feature <guid> <featureIDList> <numFeatures> <layerName>
+nv>select region <xmin> <ymin> <xmax> <ymax>
+nv>ok -token <seqnum>
+   Reply indicated commands through <seqnum> (numbered beginning at 0) have been
+   processed, but no new image was rendered
+================================================================================
+Error Replies:
+================================================================================
+nv>viserror -bytes <nbytes>
+   <multi-line error string of nbytes>
+================================================================================
Index: /geovis/branches/1.0/idatatest.cpp
===================================================================
--- /geovis/branches/1.0/idatatest.cpp	(revision 6666)
+++ /geovis/branches/1.0/idatatest.cpp	(revision 6666)
@@ -0,0 +1,67 @@
+#include <vector>
+#include <cstdlib>
+#include <iostream>
+#include <sstream>
+#include <fstream>
+
+#include <osgDB/FileNameUtils>
+
+#include "IData.h"
+
+using namespace IData;
+
+int main(int argc, char *argv[])
+{
+    iDataInit(getenv("USER"));
+
+    std::vector<Collection> collections;
+    getCollectionList(collections);
+    std::cout << std::endl << std::endl;
+    for (std::vector<Collection>::iterator itr = collections.begin();
+         itr != collections.end(); ++itr) {
+        std::cout << "id: " << itr->id << " name: " << itr->name << std::endl;
+    }
+
+    int collection = 3;
+    if (argc > 1) {
+        collection = atoi(argv[1]); 
+    }
+
+    std::cout << std::endl << std::endl;
+    std::vector<DirectoryItem> dir;
+    getContents(collection, "", dir);
+    std::cout << std::endl << std::endl;
+    for (std::vector<DirectoryItem>::iterator itr = dir.begin();
+         itr != dir.end(); ++itr) {
+        std::cout << " name: " << itr->name;
+        if (!itr->isDir) {
+            std::cout << " size: " << itr->size << " ctime: " << itr->ctime
+                      << " type: " << itr->type << " doi: " << itr->doi;
+        }
+        std::cout << std::endl;
+    }
+
+    std::cout << std::endl << std::endl;
+    const char *filename = NULL;
+    if (argc > 2) {
+        filename = argv[2];
+    } else if (collection == 3) {
+        filename = "station.csv";
+    } else {
+        iDataCleanup();
+        return 0;
+    }
+
+    std::ostringstream oss;
+    oss << "//" << filename;
+    Buffer buff;
+    getFile(collection, oss.str().c_str(), &buff);
+    std::cout << std::endl << std::endl;
+    std::cout << filename << " size: " << buff.size << std::endl;
+    std::ofstream file(filename);
+    file.write((char *)buff.data, buff.size);
+    file.close();
+
+    iDataCleanup();
+    return 0;
+}
Index: /geovis/branches/1.0/md5.c
===================================================================
--- /geovis/branches/1.0/md5.c	(revision 6666)
+++ /geovis/branches/1.0/md5.c	(revision 6666)
@@ -0,0 +1,381 @@
+/*
+  Copyright (C) 1999, 2000, 2002 Aladdin Enterprises.  All rights reserved.
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  L. Peter Deutsch
+  ghost@aladdin.com
+
+ */
+/* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */
+/*
+  Independent implementation of MD5 (RFC 1321).
+
+  This code implements the MD5 Algorithm defined in RFC 1321, whose
+  text is available at
+	http://www.ietf.org/rfc/rfc1321.txt
+  The code is derived from the text of the RFC, including the test suite
+  (section A.5) but excluding the rest of Appendix A.  It does not include
+  any code or documentation that is identified in the RFC as being
+  copyrighted.
+
+  The original and principal author of md5.c is L. Peter Deutsch
+  <ghost@aladdin.com>.  Other authors are noted in the change history
+  that follows (in reverse chronological order):
+
+  2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order
+	either statically or dynamically; added missing #include <string.h>
+	in library.
+  2002-03-11 lpd Corrected argument list for main(), and added int return
+	type, in test program and T value program.
+  2002-02-21 lpd Added missing #include <stdio.h> in test program.
+  2000-07-03 lpd Patched to eliminate warnings about "constant is
+	unsigned in ANSI C, signed in traditional"; made test program
+	self-checking.
+  1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
+  1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5).
+  1999-05-03 lpd Original version.
+ */
+
+#include "md5.h"
+#include <string.h>
+
+#undef BYTE_ORDER	/* 1 = big-endian, -1 = little-endian, 0 = unknown */
+#ifdef ARCH_IS_BIG_ENDIAN
+#  define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1)
+#else
+#  define BYTE_ORDER 0
+#endif
+
+#define T_MASK ((md5_word_t)~0)
+#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87)
+#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9)
+#define T3    0x242070db
+#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111)
+#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050)
+#define T6    0x4787c62a
+#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec)
+#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe)
+#define T9    0x698098d8
+#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850)
+#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e)
+#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841)
+#define T13    0x6b901122
+#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c)
+#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71)
+#define T16    0x49b40821
+#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d)
+#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf)
+#define T19    0x265e5a51
+#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855)
+#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2)
+#define T22    0x02441453
+#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e)
+#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437)
+#define T25    0x21e1cde6
+#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829)
+#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278)
+#define T28    0x455a14ed
+#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa)
+#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07)
+#define T31    0x676f02d9
+#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375)
+#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd)
+#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e)
+#define T35    0x6d9d6122
+#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3)
+#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb)
+#define T38    0x4bdecfa9
+#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f)
+#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f)
+#define T41    0x289b7ec6
+#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805)
+#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a)
+#define T44    0x04881d05
+#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6)
+#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a)
+#define T47    0x1fa27cf8
+#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a)
+#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb)
+#define T50    0x432aff97
+#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58)
+#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6)
+#define T53    0x655b59c3
+#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d)
+#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82)
+#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e)
+#define T57    0x6fa87e4f
+#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f)
+#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb)
+#define T60    0x4e0811a1
+#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d)
+#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca)
+#define T63    0x2ad7d2bb
+#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e)
+
+
+static void
+md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/)
+{
+    md5_word_t
+	a = pms->abcd[0], b = pms->abcd[1],
+	c = pms->abcd[2], d = pms->abcd[3];
+    md5_word_t t;
+#if BYTE_ORDER > 0
+    /* Define storage only for big-endian CPUs. */
+    md5_word_t X[16];
+#else
+    /* Define storage for little-endian or both types of CPUs. */
+    md5_word_t xbuf[16];
+    const md5_word_t *X;
+#endif
+
+    {
+#if BYTE_ORDER == 0
+	/*
+	 * Determine dynamically whether this is a big-endian or
+	 * little-endian machine, since we can use a more efficient
+	 * algorithm on the latter.
+	 */
+	static const int w = 1;
+
+	if (*((const md5_byte_t *)&w)) /* dynamic little-endian */
+#endif
+#if BYTE_ORDER <= 0		/* little-endian */
+	{
+	    /*
+	     * On little-endian machines, we can process properly aligned
+	     * data without copying it.
+	     */
+	    if (!((data - (const md5_byte_t *)0) & 3)) {
+		/* data are properly aligned */
+		X = (const md5_word_t *)data;
+	    } else {
+		/* not aligned */
+		memcpy(xbuf, data, 64);
+		X = xbuf;
+	    }
+	}
+#endif
+#if BYTE_ORDER == 0
+	else			/* dynamic big-endian */
+#endif
+#if BYTE_ORDER >= 0		/* big-endian */
+	{
+	    /*
+	     * On big-endian machines, we must arrange the bytes in the
+	     * right order.
+	     */
+	    const md5_byte_t *xp = data;
+	    int i;
+
+#  if BYTE_ORDER == 0
+	    X = xbuf;		/* (dynamic only) */
+#  else
+#    define xbuf X		/* (static only) */
+#  endif
+	    for (i = 0; i < 16; ++i, xp += 4)
+		xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24);
+	}
+#endif
+    }
+
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
+
+    /* Round 1. */
+    /* Let [abcd k s i] denote the operation
+       a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
+#define F(x, y, z) (((x) & (y)) | (~(x) & (z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + F(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+    /* Do the following 16 operations. */
+    SET(a, b, c, d,  0,  7,  T1);
+    SET(d, a, b, c,  1, 12,  T2);
+    SET(c, d, a, b,  2, 17,  T3);
+    SET(b, c, d, a,  3, 22,  T4);
+    SET(a, b, c, d,  4,  7,  T5);
+    SET(d, a, b, c,  5, 12,  T6);
+    SET(c, d, a, b,  6, 17,  T7);
+    SET(b, c, d, a,  7, 22,  T8);
+    SET(a, b, c, d,  8,  7,  T9);
+    SET(d, a, b, c,  9, 12, T10);
+    SET(c, d, a, b, 10, 17, T11);
+    SET(b, c, d, a, 11, 22, T12);
+    SET(a, b, c, d, 12,  7, T13);
+    SET(d, a, b, c, 13, 12, T14);
+    SET(c, d, a, b, 14, 17, T15);
+    SET(b, c, d, a, 15, 22, T16);
+#undef SET
+
+     /* Round 2. */
+     /* Let [abcd k s i] denote the operation
+          a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
+#define G(x, y, z) (((x) & (z)) | ((y) & ~(z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + G(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  1,  5, T17);
+    SET(d, a, b, c,  6,  9, T18);
+    SET(c, d, a, b, 11, 14, T19);
+    SET(b, c, d, a,  0, 20, T20);
+    SET(a, b, c, d,  5,  5, T21);
+    SET(d, a, b, c, 10,  9, T22);
+    SET(c, d, a, b, 15, 14, T23);
+    SET(b, c, d, a,  4, 20, T24);
+    SET(a, b, c, d,  9,  5, T25);
+    SET(d, a, b, c, 14,  9, T26);
+    SET(c, d, a, b,  3, 14, T27);
+    SET(b, c, d, a,  8, 20, T28);
+    SET(a, b, c, d, 13,  5, T29);
+    SET(d, a, b, c,  2,  9, T30);
+    SET(c, d, a, b,  7, 14, T31);
+    SET(b, c, d, a, 12, 20, T32);
+#undef SET
+
+     /* Round 3. */
+     /* Let [abcd k s t] denote the operation
+          a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + H(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  5,  4, T33);
+    SET(d, a, b, c,  8, 11, T34);
+    SET(c, d, a, b, 11, 16, T35);
+    SET(b, c, d, a, 14, 23, T36);
+    SET(a, b, c, d,  1,  4, T37);
+    SET(d, a, b, c,  4, 11, T38);
+    SET(c, d, a, b,  7, 16, T39);
+    SET(b, c, d, a, 10, 23, T40);
+    SET(a, b, c, d, 13,  4, T41);
+    SET(d, a, b, c,  0, 11, T42);
+    SET(c, d, a, b,  3, 16, T43);
+    SET(b, c, d, a,  6, 23, T44);
+    SET(a, b, c, d,  9,  4, T45);
+    SET(d, a, b, c, 12, 11, T46);
+    SET(c, d, a, b, 15, 16, T47);
+    SET(b, c, d, a,  2, 23, T48);
+#undef SET
+
+     /* Round 4. */
+     /* Let [abcd k s t] denote the operation
+          a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
+#define I(x, y, z) ((y) ^ ((x) | ~(z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + I(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  0,  6, T49);
+    SET(d, a, b, c,  7, 10, T50);
+    SET(c, d, a, b, 14, 15, T51);
+    SET(b, c, d, a,  5, 21, T52);
+    SET(a, b, c, d, 12,  6, T53);
+    SET(d, a, b, c,  3, 10, T54);
+    SET(c, d, a, b, 10, 15, T55);
+    SET(b, c, d, a,  1, 21, T56);
+    SET(a, b, c, d,  8,  6, T57);
+    SET(d, a, b, c, 15, 10, T58);
+    SET(c, d, a, b,  6, 15, T59);
+    SET(b, c, d, a, 13, 21, T60);
+    SET(a, b, c, d,  4,  6, T61);
+    SET(d, a, b, c, 11, 10, T62);
+    SET(c, d, a, b,  2, 15, T63);
+    SET(b, c, d, a,  9, 21, T64);
+#undef SET
+
+     /* Then perform the following additions. (That is increment each
+        of the four registers by the value it had before this block
+        was started.) */
+    pms->abcd[0] += a;
+    pms->abcd[1] += b;
+    pms->abcd[2] += c;
+    pms->abcd[3] += d;
+}
+
+void
+md5_init(md5_state_t *pms)
+{
+    pms->count[0] = pms->count[1] = 0;
+    pms->abcd[0] = 0x67452301;
+    pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476;
+    pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301;
+    pms->abcd[3] = 0x10325476;
+}
+
+void
+md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes)
+{
+    const md5_byte_t *p = data;
+    int left = nbytes;
+    int offset = (pms->count[0] >> 3) & 63;
+    md5_word_t nbits = (md5_word_t)(nbytes << 3);
+
+    if (nbytes <= 0)
+	return;
+
+    /* Update the message length. */
+    pms->count[1] += nbytes >> 29;
+    pms->count[0] += nbits;
+    if (pms->count[0] < nbits)
+	pms->count[1]++;
+
+    /* Process an initial partial block. */
+    if (offset) {
+	int copy = (offset + nbytes > 64 ? 64 - offset : nbytes);
+
+	memcpy(pms->buf + offset, p, copy);
+	if (offset + copy < 64)
+	    return;
+	p += copy;
+	left -= copy;
+	md5_process(pms, pms->buf);
+    }
+
+    /* Process full blocks. */
+    for (; left >= 64; p += 64, left -= 64)
+	md5_process(pms, p);
+
+    /* Process a final partial block. */
+    if (left)
+	memcpy(pms->buf, p, left);
+}
+
+void
+md5_finish(md5_state_t *pms, md5_byte_t digest[16])
+{
+    static const md5_byte_t pad[64] = {
+	0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+    };
+    md5_byte_t data[8];
+    int i;
+
+    /* Save the length before padding. */
+    for (i = 0; i < 8; ++i)
+	data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3));
+    /* Pad to 56 bytes mod 64. */
+    md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1);
+    /* Append the length. */
+    md5_append(pms, data, 8);
+    for (i = 0; i < 16; ++i)
+	digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3));
+}
Index: /geovis/branches/1.0/md5.h
===================================================================
--- /geovis/branches/1.0/md5.h	(revision 6666)
+++ /geovis/branches/1.0/md5.h	(revision 6666)
@@ -0,0 +1,91 @@
+/*
+  Copyright (C) 1999, 2002 Aladdin Enterprises.  All rights reserved.
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  L. Peter Deutsch
+  ghost@aladdin.com
+
+ */
+/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */
+/*
+  Independent implementation of MD5 (RFC 1321).
+
+  This code implements the MD5 Algorithm defined in RFC 1321, whose
+  text is available at
+	http://www.ietf.org/rfc/rfc1321.txt
+  The code is derived from the text of the RFC, including the test suite
+  (section A.5) but excluding the rest of Appendix A.  It does not include
+  any code or documentation that is identified in the RFC as being
+  copyrighted.
+
+  The original and principal author of md5.h is L. Peter Deutsch
+  <ghost@aladdin.com>.  Other authors are noted in the change history
+  that follows (in reverse chronological order):
+
+  2002-04-13 lpd Removed support for non-ANSI compilers; removed
+	references to Ghostscript; clarified derivation from RFC 1321;
+	now handles byte order either statically or dynamically.
+  1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
+  1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5);
+	added conditionalization for C++ compilation from Martin
+	Purschke <purschke@bnl.gov>.
+  1999-05-03 lpd Original version.
+ */
+
+#ifndef md5_INCLUDED
+#  define md5_INCLUDED
+
+/*
+ * This package supports both compile-time and run-time determination of CPU
+ * byte order.  If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be
+ * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is
+ * defined as non-zero, the code will be compiled to run only on big-endian
+ * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to
+ * run on either big- or little-endian CPUs, but will run slightly less
+ * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined.
+ */
+
+typedef unsigned char md5_byte_t; /* 8-bit byte */
+typedef unsigned int md5_word_t; /* 32-bit word */
+
+/* Define the state of the MD5 Algorithm. */
+typedef struct md5_state_s {
+    md5_word_t count[2];	/* message length in bits, lsw first */
+    md5_word_t abcd[4];		/* digest buffer */
+    md5_byte_t buf[64];		/* accumulate block */
+} md5_state_t;
+
+#ifdef __cplusplus
+extern "C" 
+{
+#endif
+
+/* Initialize the algorithm. */
+void md5_init(md5_state_t *pms);
+
+/* Append a string to the message. */
+void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes);
+
+/* Finish the message and return the digest. */
+void md5_finish(md5_state_t *pms, md5_byte_t digest[16]);
+
+#ifdef __cplusplus
+}  /* end extern "C" */
+#endif
+
+#endif /* md5_INCLUDED */
