Index: /geovis/trunk/RenderServer.cpp
===================================================================
--- /geovis/trunk/RenderServer.cpp	(revision 4631)
+++ /geovis/trunk/RenderServer.cpp	(revision 4632)
@@ -118,5 +118,7 @@
              TARGA_BYTES_PER_PIXEL);
 #else
-    queuePPM(queue, "nv>image -type image -bytes",
+    char cmd[256];
+    sprintf(cmd, "nv>image -type image -token %lu -bytes", g_stats.nCommands);
+    queuePPM(queue, cmd,
              imgData,
              g_renderer->getWindowWidth(),
@@ -401,4 +403,6 @@
             if (cmdStatus > 1) {
                 sendAck();
+            } else {
+                TRACE("No render required and status = %d", cmdStatus);
             }
         }
Index: /geovis/trunk/Renderer.cpp
===================================================================
--- /geovis/trunk/Renderer.cpp	(revision 4631)
+++ /geovis/trunk/Renderer.cpp	(revision 4632)
@@ -11,4 +11,6 @@
 #include <cmath>
 #include <cstdlib>
+
+#include <set>
 
 #include <sys/types.h>
@@ -41,4 +43,9 @@
 #include <osgEarth/ModelLayer>
 #include <osgEarth/DateTime>
+#include <osgEarth/Pickers>
+#include <osgEarthAnnotation/AnnotationNode>
+#include <osgEarthAnnotation/PlaceNode>
+#include <osgEarthAnnotation/HighlightDecoration>
+#include <osgEarthAnnotation/ScaleDecoration>
 #include <osgEarthUtil/EarthManipulator>
 #if defined(USE_OSGEARTH_TRUNK) || OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 1)
@@ -72,4 +79,5 @@
 
 #define BASE_IMAGE "/usr/share/osgearth/data/world.tif"
+#define PIN_ICON "/usr/share/osgearth/data/placemark32.png"
 
 using namespace GeoVis;
@@ -87,9 +95,10 @@
     _bgColor[2] = 0;
     //setMaximumFrameRateInHertz(15.0);
-    // 100Mbit
+    // 100 Mbps
     setMaximumBitrate(1.0e8);
     _lastFrameTime = _minFrameTime;
     TRACE("Bandwidth target: %.2f Mbps", (float)(getMaximumBitrate()/1.0e6));
     TRACE("Frame rate target: %.2f Hz", (float)getMaximumFrameRateInHertz());
+    TRACE("Frame time target: %.2f msec", _minFrameTime * 1000.0f);
 
     char *base = getenv("MAP_BASE_URI");
@@ -1229,4 +1238,107 @@
 }
 
+void Renderer::addPlaceNode(double latitude, double longitude, char *labelText)
+{
+    if (!_mapNode.valid()) {
+        ERROR("No map node");
+        return;
+    }
+    if (!_annotations.valid()) {
+        _annotations = new osg::Group();
+        _sceneRoot->addChild(_annotations.get());
+        _placeNodes = new osg::Group();
+        osgEarth::Decluttering::setEnabled(_placeNodes->getOrCreateStateSet(), true);
+        _annotations->addChild(_placeNodes.get());
+    }
+
+    const osgEarth::SpatialReference* geoSRS = _mapNode->getMapSRS()->getGeographicSRS();
+
+    osgEarth::Symbology::Style pin;
+    pin.getOrCreate<osgEarth::Symbology::IconSymbol>()->url()->setLiteral(PIN_ICON);
+    osgEarth::Annotation::AnnotationNode *anno = new osgEarth::Annotation::PlaceNode(_mapNode, osgEarth::GeoPoint(geoSRS, longitude, latitude), labelText, pin);
+    _placeNodes->addChild(anno);
+    osgEarth::Annotation::DecorationInstaller
+        highlightInstaller("hover", new osgEarth::Annotation::HighlightDecoration());
+    _annotations->accept(highlightInstaller);
+    // scale labels when hovering:
+    osgEarth::Annotation::DecorationInstaller
+        scaleInstaller("hover", new osgEarth::Annotation::ScaleDecoration(1.1f));
+    _placeNodes->accept(scaleInstaller);
+    _needsRedraw = true;
+}
+
+void Renderer::hoverPlaceNode(int x, int y, bool invertY)
+{
+    osgEarth::Picker picker(_viewer.get(), _placeNodes.get());
+    osgEarth::Picker::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 hit!");
+        for (osgEarth::Picker::Hits::const_iterator hitr = hits.begin();
+             hitr != hits.end(); ++hitr) {
+            osgEarth::Annotation::AnnotationNode *anno =
+                picker.getNode<osgEarth::Annotation::AnnotationNode>(*hitr);
+            if (anno != NULL) {
+                if (_hovered.find(anno) == _hovered.end()) {
+                    _hovered.insert(anno);
+                    anno->setDecoration("hover");
+                    _needsRedraw = true;
+                }
+                toUnHover.erase(anno);
+            }
+        }
+    }
+    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)
+{
+    osgEarth::Picker picker(_viewer.get(), _placeNodes.get());
+    osgEarth::Picker::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::Picker::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();
+            _placeNodes->removeChild(*itr);
+            if (_hovered.find(*itr) != _hovered.end()) {
+                _hovered.erase(*itr);
+            }
+        }
+    } else {
+        TRACE("NO Picker hits");
+    }
+}
+
 void Renderer::addModelLayer(const char *name, osgEarth::ModelSourceOptions& opts)
 {
@@ -1625,5 +1737,5 @@
         return -1L;
     if (_lastFrameTime < _minFrameTime) {
-        return (long)1000000.0*(_minFrameTime - _lastFrameTime);
+        return (long)1.0e6*(_minFrameTime - _lastFrameTime);
     } else {
         // No timeout (poll)
@@ -1674,8 +1786,12 @@
     osg::Timer_t endFrameTick = osg::Timer::instance()->tick();
     _lastFrameTime = osg::Timer::instance()->delta_s(_startFrameTime, endFrameTick);
-    TRACE("Frame time: %g sec", _lastFrameTime);
+    if (_lastFrameTime > _minFrameTime) {
+        ERROR("BROKE FRAME by %.2f msec", (_lastFrameTime - _minFrameTime)*1000.0f);
+    } else {
+        TRACE("Frame time: %.2f msec", _lastFrameTime*1000.0f);
+    }
 #ifdef USE_THROTTLING_SLEEP
     if (_lastFrameTime < _minFrameTime) {
-        TRACE("Sleeping for %g secs", _minFrameTime - _lastFrameTime);
+        TRACE("Sleeping for %.2f msec", (_minFrameTime - _lastFrameTime)*1000.0f);
         OpenThreads::Thread::microSleep(static_cast<unsigned int>(1000000.0*(_minFrameTime - _lastFrameTime)));
     }
@@ -1693,18 +1809,17 @@
     if (_viewer.valid() && checkNeedToDoFrame()) {
         TRACE("Enter needsRedraw=%d",  _needsRedraw ? 1 : 0);
-#ifndef SLEEP_AFTER_QUEUE_FRAME
-        osg::Timer_t startFrameTick = osg::Timer::instance()->tick();
-#endif
+        _renderStartTime = osg::Timer::instance()->tick();
         TRACE("Before frame()");
         _viewer->frame();
         TRACE("After frame()");
+        _renderStopTime = osg::Timer::instance()->tick();
+        _renderTime = osg::Timer::instance()->delta_s(_renderStartTime, _renderStopTime);
+        TRACE("Render time: %g msec", _renderTime * 1000.0);
 #ifndef SLEEP_AFTER_QUEUE_FRAME
-        osg::Timer_t endFrameTick = osg::Timer::instance()->tick();
-        _lastFrameTime = osg::Timer::instance()->delta_s(startFrameTick, endFrameTick);
-        TRACE("Frame time: %g sec", _lastFrameTime);
+        _lastFrameTime = _renderTime;
 #ifdef USE_THROTTLING_SLEEP
         if (_lastFrameTime < _minFrameTime) {
-            TRACE("Sleeping for %g secs", _minFrameTime - _lastFrameTime);
-            OpenThreads::Thread::microSleep(static_cast<unsigned int>(1000000.0*(_minFrameTime - _lastFrameTime)));
+            TRACE("Sleeping for %.2f msec", (_minFrameTime - _lastFrameTime)*1000.0f);
+            OpenThreads::Thread::microSleep(static_cast<unsigned int>(1.0e6*(_minFrameTime - _lastFrameTime)));
         }
 #endif
@@ -1717,6 +1832,9 @@
         _needsRedraw = false;
         return true;
-    } else
+    } else {
+        _renderStartTime = _renderStopTime = osg::Timer::instance()->tick();
+        _renderTime = 0;
         return false;
+    }
 }
 
Index: /geovis/trunk/Renderer.h
===================================================================
--- /geovis/trunk/Renderer.h	(revision 4631)
+++ /geovis/trunk/Renderer.h	(revision 4632)
@@ -31,4 +31,5 @@
 #include <osgEarth/ModelSource>
 #include <osgEarth/GeoData>
+#include <osgEarthAnnotation/AnnotationNode>
 #include <osgEarthUtil/EarthManipulator>
 #include <osgEarthUtil/MouseCoordsTool>
@@ -349,4 +350,10 @@
     }
 
+    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)
     {
@@ -439,5 +446,8 @@
     double _minFrameTime;
     double _lastFrameTime;
+    double _renderTime;
     osg::Timer_t _startFrameTime;
+    osg::Timer_t _renderStartTime;
+    osg::Timer_t _renderStopTime;
 
     ColorMapHashmap _colorMaps;
@@ -448,4 +458,7 @@
     osg::ref_ptr<osg::Group> _sceneRoot;
     osg::ref_ptr<osg::Group> _graticule;
+    osg::ref_ptr<osg::Group> _annotations;
+    osg::ref_ptr<osg::Group> _placeNodes;
+    std::set<osgEarth::Annotation::AnnotationNode *> _hovered;
     osg::ref_ptr<osgEarth::MapNode> _mapNode;
     osg::ref_ptr<osgEarth::Map> _map;
Index: /geovis/trunk/RendererCmd.cpp
===================================================================
--- /geovis/trunk/RendererCmd.cpp	(revision 4631)
+++ /geovis/trunk/RendererCmd.cpp	(revision 4632)
@@ -1277,4 +1277,90 @@
     }
     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;
+    const osgEarth::SpatialReference *outSRS = 
+        g_renderer->getMapSRS()->getGeographicSRS();
+    osgEarth::GeoPoint mapPoint;
+    if (g_renderer->mapMouseCoords(x, y, mapPoint)) {
+        mapPoint = mapPoint.transform(outSRS);
+        longitude = mapPoint.x();
+        latitude = mapPoint.y();
+    } else {
+        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);
 }
 
@@ -1554,5 +1640,6 @@
     {"layer",    2, MapLayerOp,           3, 0, "op ?params...?"},
     {"load",     2, MapLoadOp,            4, 5, "options"},
-    {"posdisp",  1, MapPositionDisplayOp, 3, 5, "bool ?format? ?precision?"},
+    {"pin",      2, MapPinOp,             3, 0, "op ?params...?"},
+    {"posdisp",  2, MapPositionDisplayOp, 3, 5, "bool ?format? ?precision?"},
     {"reset",    1, MapResetOp,           3, 8, "type ?profile xmin ymin xmax ymax?"},
     {"scalebar", 1, MapScaleBarOp,        3, 4, "bool ?units?"},
@@ -1951,8 +2038,8 @@
             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);
-            g_stats.nCommands++;
             if (status == TCL_BREAK) {
                 return 2;               /* This was caused by a "imgflush"
