source: geovis/trunk/Renderer.cpp @ 6654

Last change on this file since 6654 was 6654, checked in by ldelgass, 8 years ago

fix for building with osgearth 2.8.0

File size: 112.6 KB
Line 
1/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 * Copyright (C) 2004-2015  HUBzero Foundation, LLC
4 *
5 * Author: Leif Delgass <ldelgass@purdue.edu>
6 */
7
8#include <cfloat>
9#include <cstring>
10#include <cassert>
11#include <cmath>
12#include <cstdlib>
13#include <cerrno>
14
15#include <limits>
16#include <set>
17#include <fstream>
18#include <sstream>
19
20#include <sys/types.h>
21#include <unistd.h> // For getpid()
22
23#include <GL/gl.h>
24
25#ifdef WANT_TRACE
26#include <sys/time.h>
27#endif
28
29#include <osgDB/WriteFile>
30#include <osgDB/FileUtils>
31#include <osgDB/FileNameUtils>
32#include <osgGA/StateSetManipulator>
33#include <osgGA/GUIEventAdapter>
34#include <osgViewer/ViewerEventHandlers>
35
36#include <osgEarth/Version>
37#include <osgEarth/Registry>
38#include <osgEarth/FileUtils>
39#include <osgEarth/Cache>
40#include <osgEarth/CachePolicy>
41#include <osgEarthDrivers/cache_filesystem/FileSystemCache>
42#include <osgEarth/MapNode>
43#include <osgEarth/TerrainEngineNode>
44#include <osgEarth/Bounds>
45#include <osgEarth/Profile>
46#include <osgEarth/Viewpoint>
47#include <osgEarth/GeoMath>
48#include <osgEarth/TerrainLayer>
49#include <osgEarth/ImageLayer>
50#include <osgEarth/ElevationLayer>
51#include <osgEarth/ModelLayer>
52#include <osgEarth/DateTime>
53#include <osgEarth/TimeControl>
54#ifdef USE_RTT_PICKER
55#include <osgEarthUtil/RTTPicker>
56#include <osgEarthFeatures/FeatureIndex>
57#else
58#include <osgEarth/IntersectionPicker>
59#endif
60#include <osgEarthUtil/GraticuleNode>
61#include <osgEarthFeatures/FeatureModelSource>
62#include <osgEarthFeatures/Feature>
63#include <osgEarthSymbology/Color>
64#include <osgEarthSymbology/Geometry>
65#include <osgEarthSymbology/Style>
66#include <osgEarthSymbology/StyleSheet>
67#include <osgEarthSymbology/IconSymbol>
68#include <osgEarthSymbology/LineSymbol>
69
70#include <osgEarthAnnotation/AnnotationNode>
71#include <osgEarthAnnotation/FeatureNode>
72#include <osgEarthAnnotation/PlaceNode>
73#ifdef NEW_ANNOTATION_API
74#include <osgEarth/ScreenSpaceLayout>
75#else
76#include <osgEarth/Decluttering>
77#include <osgEarthAnnotation/HighlightDecoration>
78#include <osgEarthAnnotation/ScaleDecoration>
79#endif
80#include <osgEarthUtil/EarthManipulator>
81#include <osgEarthUtil/Sky>
82#include <osgEarthDrivers/sky_simple/SimpleSkyOptions>
83#include <osgEarthUtil/AutoClipPlaneHandler>
84#include <osgEarthUtil/MouseCoordsTool>
85#include <osgEarthUtil/UTMGraticule>
86#include <osgEarthUtil/MGRSGraticule>
87#include <osgEarthUtil/GeodeticGraticule>
88#include <osgEarthUtil/LatLongFormatter>
89#include <osgEarthUtil/MGRSFormatter>
90#include <osgEarthUtil/GLSLColorFilter>
91#include <osgEarthUtil/VerticalScale>
92#include <osgEarthDrivers/gdal/GDALOptions>
93#include <osgEarthDrivers/engine_mp/MPTerrainEngineOptions>
94#ifdef USE_REX
95#include <osgEarthDrivers/engine_rex/RexTerrainEngineOptions>
96#endif
97
98#include "Renderer.h"
99#ifdef USE_RTT_PICKER
100#include "Picker.h"
101#endif
102#include "IData.h"
103#include "ColorMap.h"
104#if 0
105#include "SingleWindow.h"
106#endif
107#include "ScaleBar.h"
108#include "FileUtil.h"
109#include "Util.h"
110#include "Icons.h"
111#include "Trace.h"
112
113#define MSECS_ELAPSED(t1, t2) \
114    ((t1).tv_sec == (t2).tv_sec ? (((t2).tv_usec - (t1).tv_usec)/1.0e+3) : \
115     (((t2).tv_sec - (t1).tv_sec))*1.0e+3 + (double)((t2).tv_usec - (t1).tv_usec)/1.0e+3)
116
117#ifndef CACHE_DIR
118#define CACHE_DIR "/var/cache/geovis"
119#endif
120#define BASE_IMAGE "world.tif"
121#define PIN_ICON "placemark32.png"
122
123using namespace GeoVis;
124using namespace IData;
125
126Renderer::Renderer() :
127    _needsRedraw(false),
128    _windowWidth(500),
129    _windowHeight(500),
130    _idleTimeout(-1L),
131    _pickPending(false),
132    _scaleBarUnits(UNITS_METERS)
133{
134    TRACE("Enter");
135
136    initIconMap();
137    _bgColor[0] = 0;
138    _bgColor[1] = 0;
139    _bgColor[2] = 0;
140    // 100 Mbps
141    setMaximumBitrate(1.0e8);
142    _lastFrameTime = _minFrameTime;
143
144    iDataInit(getenv("USER"));
145    setCacheBaseDirectory(CACHE_DIR);
146    char *base = getenv("MAP_BASE_URI");
147    if (base != NULL) {
148        _baseURI = base;
149        TRACE("Setting base URI: %s", _baseURI.c_str());
150    }
151}
152
153osgGA::EventQueue *Renderer::getEventQueue()
154{
155    if (_viewer.valid()) {
156        osgViewer::ViewerBase::Windows windows;
157        _viewer->getWindows(windows);
158        if (windows.size() > 0) {
159            return windows[0]->getEventQueue();
160        }
161    }
162    return NULL;
163}
164
165Renderer::~Renderer()
166{
167    TRACE("Enter");
168
169    // Removes temp files
170    deleteColorMap("all");
171
172    iDataCleanup();
173
174    // Cache is deleted in main() to allow ref-counted objects to
175    // be deleted and threads to exit first
176
177    TRACE("Leave");
178}
179
180std::string Renderer::getIconFile(const char *name) const
181{
182    std::ostringstream oss;
183    oss << _resourcePath << "/" << GeoVis::getIconFile(name);
184    return oss.str();
185}
186
187std::string Renderer::getBaseImage() const
188{
189    std::ostringstream oss;
190    oss << _resourcePath << "/" << BASE_IMAGE;
191    return oss.str();
192}
193
194std::string Renderer::getPinIcon() const
195{
196    std::ostringstream oss;
197    oss << _resourcePath << "/" << PIN_ICON;
198    return oss.str();
199}
200
201void Renderer::setupCache()
202{
203    std::ostringstream dir;
204    dir << _cacheBaseDir << "/geovis_cache" << getpid();
205    _cacheDir = dir.str();
206    const char *path = _cacheDir.c_str();
207    TRACE("Cache dir: %s", path);
208    removeDirectory(path);
209    if (!osgDB::makeDirectory(_cacheDir)) {
210        ERROR("Failed to create directory '%s'", path);
211    }
212}
213
214void Renderer::initColorMaps()
215{
216    if (!_colorMaps.empty())
217        return;
218#if 0
219    osg::TransferFunction1D *defaultColorMap = new osg::TransferFunction1D;
220    defaultColorMap->allocate(256);
221    defaultColorMap->setColor(0.00, osg::Vec4f(0,0,1,1), false);
222    defaultColorMap->setColor(0.25, osg::Vec4f(0,1,1,1), false);
223    defaultColorMap->setColor(0.50, osg::Vec4f(0,1,0,1), false);
224    defaultColorMap->setColor(0.75, osg::Vec4f(1,1,0,1), false);
225    defaultColorMap->setColor(1.00, osg::Vec4f(1,0,0,1), false);
226    defaultColorMap->updateImage();
227    addColorMap("default", defaultColorMap);
228    osg::TransferFunction1D *defaultGrayColorMap = new osg::TransferFunction1D;
229    defaultGrayColorMap->allocate(256);
230    defaultGrayColorMap->setColor(0, osg::Vec4f(0,0,0,1), false);
231    defaultGrayColorMap->setColor(1, osg::Vec4f(1,1,1,1), false);
232    defaultGrayColorMap->updateImage();
233    addColorMap("grayDefault", defaultGrayColorMap);
234#endif
235}
236
237void Renderer::saveCLRFile(const std::string& path, osg::TransferFunction1D *xfer)
238{
239    std::ofstream out(path.c_str());
240    float value;
241    unsigned int r, g, b, a;
242    for (osg::TransferFunction1D::ColorMap::const_iterator itr = xfer->getColorMap().begin();
243         itr != xfer->getColorMap().end(); ++itr) {
244        value = itr->first;
245        r = (unsigned int)(255.0 * itr->second.r());
246        g = (unsigned int)(255.0 * itr->second.g());
247        b = (unsigned int)(255.0 * itr->second.b());
248        a = (unsigned int)(255.0 * itr->second.a());
249        out << value << " " << r << " " << g << " " << b << " " << a << std::endl;
250    }
251}
252
253void Renderer::getColorMapRange(const ColorMapId& id, float *min, float *max) const
254{
255    ColorMapHashmap::const_iterator itr = _colorMaps.find(id);
256    if (itr == _colorMaps.end()) {
257        ERROR("Unknown ColorMap %s", id.c_str());
258        return;
259    }
260
261    *min = itr->second->getMinimum();
262    *max = itr->second->getMaximum();
263}
264
265std::string Renderer::getColorMapFilePath(const ColorMapId& id) const
266{
267    std::ostringstream path;
268    path << "/tmp/" << id << "_" << getpid() << ".clr";
269    return path.str();
270}
271
272void Renderer::addColorMap(const ColorMapId& id, osg::TransferFunction1D *xfer)
273{
274    _colorMaps[id] = xfer;
275    saveCLRFile(getColorMapFilePath(id), xfer);
276}
277
278void Renderer::deleteColorMap(const ColorMapId& id)
279{
280    ColorMapHashmap::iterator itr;
281    bool doAll = false;
282
283    if (id.compare("all") == 0) {
284        itr = _colorMaps.begin();
285        doAll = true;
286    } else {
287        itr = _colorMaps.find(id);
288    }
289
290    if (itr == _colorMaps.end()) {
291        if (!doAll) {
292            ERROR("Unknown ColorMap %s", id.c_str());
293        }
294        return;
295    }
296
297    do {
298        TRACE("Deleting ColorMap %s", itr->first.c_str());
299        std::string path = getColorMapFilePath(itr->first);
300        if (remove(path.c_str()) < 0) {
301            ERROR("Failed to delete colormap file '%s': %s",
302                  path.c_str(), strerror(errno));
303        }
304        itr = _colorMaps.erase(itr);
305    } while (doAll && itr != _colorMaps.end());
306}
307
308void Renderer::setColorMapNumberOfTableEntries(const ColorMapId& id,
309                                               int numEntries)
310{
311    ColorMapHashmap::iterator itr;
312    bool doAll = false;
313
314    if (id.compare("all") == 0) {
315        itr = _colorMaps.begin();
316        doAll = true;
317    } else {
318        itr = _colorMaps.find(id);
319    }
320
321    if (itr == _colorMaps.end()) {
322        ERROR("Unknown ColorMap %s", id.c_str());
323        return;
324    }
325
326    do {
327        itr->second->allocate(numEntries);
328    } while (doAll && ++itr != _colorMaps.end());
329}
330
331bool
332Renderer::renderColorMap(const ColorMapId& id, int width, int height,
333                         osg::Image *imgData,
334                         bool opaque, float bgColor[3],
335                         bool bgr, int bytesPerPixel) const
336{
337    ColorMapHashmap::const_iterator itr = _colorMaps.find(id);
338    if (itr == _colorMaps.end()) {
339        ERROR("Unknown ColorMap %s", id.c_str());
340        return false;
341    }
342
343    ColorMap::renderColorMap(itr->second, width, height, imgData, opaque, bgColor, bgr, bytesPerPixel);
344    return true;
345}
346
347void Renderer::initViewer() {
348    if (_viewer.valid())
349        return;
350    _viewer = new osgViewer::Viewer();
351#if 1
352    osg::DisplaySettings *ds = _viewer->getDisplaySettings();
353    if (ds == NULL) {
354        ds = osg::DisplaySettings::instance().get();
355    }
356    ds->setDoubleBuffer(false);
357    ds->setMinimumNumAlphaBits(8);
358    ds->setMinimumNumStencilBits(8);
359    ds->setNumMultiSamples(0);
360#endif
361    _viewer->setThreadingModel(osgViewer::ViewerBase::SingleThreaded);
362    _viewer->getDatabasePager()->setUnrefImageDataAfterApplyPolicy(false, false);
363    _viewer->setReleaseContextAtEndOfFrameHint(false);
364    //_viewer->setLightingMode(osg::View::SKY_LIGHT);
365    _viewer->getCamera()->setClearColor(osg::Vec4(_bgColor[0], _bgColor[1], _bgColor[2], 1));
366    _viewer->getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
367    _viewer->getCamera()->setNearFarRatio(0.00002);
368    _viewer->getCamera()->setSmallFeatureCullingPixelSize(-1.0f);
369    _stateManip = new osgGA::StateSetManipulator(_viewer->getCamera()->getOrCreateStateSet());
370    _viewer->addEventHandler(_stateManip);
371    //_viewer->addEventHandler(new osgViewer::StatsHandler());
372
373#ifdef DEBUG
374    if (_viewer->getViewerStats() != NULL) {
375        TRACE("Enabling stats");
376        _viewer->getViewerStats()->collectStats("scene", true);
377    }
378#endif
379#if 0
380    osgViewer::ViewerBase::Windows windows;
381    _viewer->getWindows(windows);
382    if (windows.size() == 1) {
383        windows[0]->setSyncToVBlank(false);
384    } else {
385        ERROR("Num windows: %lu", windows.size());
386    }
387#endif
388}
389
390void Renderer::finalizeViewer() {
391    initViewer();
392    TRACE("Before _viewer->isRealized()");
393    if (!_viewer->isRealized()) {
394        int screen = 0;
395        const char *displayEnv = getenv("DISPLAY");
396        if (displayEnv != NULL) {
397            TRACE("DISPLAY: %s", displayEnv);
398            // 3 parts: host, display, screen
399            int part = 0;
400            for (size_t c = 0; c < strlen(displayEnv); c++) {
401                if (displayEnv[c] == ':') {
402                    part = 1;
403                } else if (part == 1 && displayEnv[c] == '.') {
404                    part = 2;
405                } else if (part == 2) {
406                    screen = atoi(&displayEnv[c]);
407                    break;
408                }
409            }
410        }
411        TRACE("Using screen: %d", screen);
412#ifdef USE_OFFSCREEN_RENDERING
413#ifdef USE_PBUFFER
414        osg::ref_ptr<osg::GraphicsContext> pbuffer;
415        osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
416        traits->x = 0;
417        traits->y = 0;
418        traits->width = _windowWidth;
419        traits->height = _windowHeight;
420        traits->red = 8;
421        traits->green = 8;
422        traits->blue = 8;
423        traits->alpha = 8;
424        traits->windowDecoration = false;
425        traits->pbuffer = true;
426        traits->doubleBuffer = true;
427        traits->sharedContext = 0;
428
429        pbuffer = osg::GraphicsContext::createGraphicsContext(traits.get());
430        if (pbuffer.valid()) {
431            TRACE("Pixel buffer has been created successfully.");
432        } else {
433            ERROR("Pixel buffer has not been created successfully.");
434        }
435        osg::Camera *camera = new osg::Camera;
436        camera->setGraphicsContext(pbuffer.get());
437        //camera->getOrCreateStateSet()->setGlobalDefaults();
438        camera->setViewport(new osg::Viewport(0, 0, _windowWidth, _windowHeight));
439        GLenum buffer = pbuffer->getTraits()->doubleBuffer ? GL_BACK : GL_FRONT;
440        camera->setDrawBuffer(buffer);
441        camera->setReadBuffer(buffer);
442        _captureCallback = new ScreenCaptureCallback();
443        camera->setFinalDrawCallback(_captureCallback.get());
444        _viewer->addSlave(camera, osg::Matrixd(), osg::Matrixd());
445#else
446        _viewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
447        osg::Texture2D* texture2D = new osg::Texture2D;
448        texture2D->setTextureSize(_windowWidth, _windowHeight);
449        texture2D->setInternalFormat(GL_RGBA);
450        texture2D->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST);
451        texture2D->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST);
452
453        _viewer->getCamera()->setImplicitBufferAttachmentMask(0, 0);
454        _viewer->getCamera()->attach(osg::Camera::COLOR_BUFFER0, texture2D);
455        //_viewer->getCamera()->attach(osg::Camera::DEPTH_BUFFER, GL_DEPTH_COMPONENT24);
456        _viewer->getCamera()->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, GL_DEPTH24_STENCIL8_EXT);
457        _captureCallback = new ScreenCaptureCallback(texture2D);
458        _viewer->getCamera()->setFinalDrawCallback(_captureCallback.get());
459        _viewer->setUpViewInWindow(0, 0, _windowWidth, _windowHeight, screen);
460#endif
461#else
462        _captureCallback = new ScreenCaptureCallback();
463        _viewer->getCamera()->setFinalDrawCallback(_captureCallback.get());
464#if 1
465        _viewer->setUpViewInWindow(0, 0, _windowWidth, _windowHeight, screen);
466#else
467        SingleWindow *windowConfig = new SingleWindow(0, 0, _windowWidth, _windowHeight, screen);
468        osg::DisplaySettings *ds = windowConfig->getActiveDisplaySetting(*_viewer.get());
469        ds->setDoubleBuffer(false);
470        ds->setMinimumNumAlphaBits(8);
471        ds->setMinimumNumStencilBits(8);
472        ds->setNumMultiSamples(0);
473        windowConfig->setWindowDecoration(false);
474        windowConfig->setOverrideRedirect(false);
475        _viewer->apply(windowConfig);
476#endif
477#endif
478        _viewer->realize();
479        initColorMaps();
480        // HACK: This seems to initialize something required for properly
481        // mapping mouse coords
482        assert(getEventQueue() != NULL);
483        getEventQueue()->mouseMotion(_windowWidth/2, _windowHeight/2);
484    }
485}
486
487void Renderer::setAttribution(const std::string& attrib)
488{
489    TRACE("Setting map attribution: '%s'", attrib.c_str());
490    _attribution = attrib;
491    if (_copyrightLabel.valid()) {
492        _copyrightLabel->setText(_attribution);
493        _needsRedraw = true;
494    }
495}
496
497void Renderer::initControls()
498{
499    if (!_debugBox.valid()) {
500        _debugBox =
501            new osgEarth::Util::Controls::HBox(osgEarth::Util::Controls::Control::ALIGN_RIGHT,
502                                               osgEarth::Util::Controls::Control::ALIGN_TOP,
503                                               osgEarth::Util::Controls::Gutter(2, 2, 2, 2), 2.0f);
504        _debugLabel =
505            new osgEarth::Util::Controls::LabelControl("Selection: None", 12.0f);
506        _debugLabel->setForeColor(osg::Vec4f(0, 0, 0, 1));
507        _debugBox->addControl(_debugLabel.get());
508        _debugBox->setVertFill(true);
509        _debugBox->setForeColor(osg::Vec4f(0, 0, 0, 1));
510        _debugBox->setBackColor(osg::Vec4f(1, 1, 1, 0.6));
511        osgEarth::Util::Controls::ControlCanvas::getOrCreate(_viewer.get())->addControl(_debugBox.get());
512    }
513    if (_copyrightScaleBox.valid())
514        return;
515    _copyrightScaleBox =
516        new osgEarth::Util::Controls::HBox(osgEarth::Util::Controls::Control::ALIGN_RIGHT,
517                                           osgEarth::Util::Controls::Control::ALIGN_BOTTOM,
518                                           osgEarth::Util::Controls::Gutter(2, 2, 2, 2), 2.0f);
519    _attribution = "Map data © OpenStreetMap";
520    _copyrightLabel =
521        new osgEarth::Util::Controls::LabelControl(_attribution, 12.0f);
522    _copyrightLabel->setForeColor(osg::Vec4f(0, 0, 0, 1));
523    _copyrightLabel->setEncoding(osgText::String::ENCODING_UTF8);
524    _scaleLabel =
525        new osgEarth::Util::Controls::LabelControl("- km", 12.0f);
526    _scaleLabel->setForeColor(osg::Vec4f(0, 0, 0, 1));
527    _scaleBar =
528        new osgEarth::Util::Controls::Frame();
529    _scaleBar->setVertFill(true);
530    _scaleBar->setForeColor(osg::Vec4f(0, 0, 0, 1));
531    _scaleBar->setBackColor(osg::Vec4f(1, 1, 1, 0.6));
532    _scaleBar->setBorderColor(osg::Vec4f(0, 0, 0 ,1));
533    _scaleBar->setBorderWidth(1.0);
534    _copyrightScaleBox->addControl(_copyrightLabel.get());
535    _copyrightScaleBox->addControl(_scaleLabel.get());
536    _copyrightScaleBox->addControl(_scaleBar.get());
537    _copyrightScaleBox->setVertFill(true);
538    _copyrightScaleBox->setForeColor(osg::Vec4f(0, 0, 0, 1));
539    _copyrightScaleBox->setBackColor(osg::Vec4f(1, 1, 1, 0.6));
540    osgEarth::Util::Controls::ControlCanvas::getOrCreate(_viewer.get())->addControl(_copyrightScaleBox.get());
541    // Install an event callback to handle scale bar updates
542    // Can't use an update callback since that will trigger
543    // constant rendering
544    _mapNode->setEventCallback(new MapNodeCallback(this));
545}
546
547void Renderer::setGraticule(bool enable, GraticuleType type)
548{
549    if (!_mapNode.valid() || !_sceneRoot.valid())
550        return;
551    if (enable) {
552        if (_graticule.valid()) {
553            _sceneRoot->removeChild(_graticule.get());
554            _graticule = NULL;
555        }
556        switch (type) {
557        case GRATICULE_UTM: {
558            osgEarth::Util::UTMGraticule *gr = new osgEarth::Util::UTMGraticule(_mapNode.get());
559            _sceneRoot->addChild(gr);
560            _graticule = gr;
561        }
562            break;
563        case GRATICULE_MGRS: {
564            osgEarth::Util::MGRSGraticule *gr = new osgEarth::Util::MGRSGraticule(_mapNode.get());
565            _sceneRoot->addChild(gr);
566            _graticule = gr;
567        }
568            break;
569        case GRATICULE_SHADER: {
570            osgEarth::Util::GraticuleOptions opt;
571            opt.color() = osgEarth::Symbology::Color(1, 1, 0);
572            opt.labelColor() = osgEarth::Symbology::Color(1, 1, 0);
573            opt.lineWidth() = 1.0;
574            osgEarth::Util::GraticuleNode *gr = new osgEarth::Util::GraticuleNode(_mapNode.get(), opt);
575            _sceneRoot->addChild(gr);
576            _graticule = gr;
577        }
578            break;
579        case GRATICULE_GEODETIC:
580        default:
581            osgEarth::Util::GeodeticGraticule *gr = new osgEarth::Util::GeodeticGraticule(_mapNode.get());
582            osgEarth::Util::GeodeticGraticuleOptions opt = gr->getOptions();
583            opt.lineStyle()->getOrCreate<osgEarth::Symbology::LineSymbol>()->stroke()->color().set(1,0,0,1);
584            gr->setOptions(opt);
585            _sceneRoot->addChild(gr);
586            _graticule = gr;
587        }
588    } else if (_graticule.valid()) {
589        _sceneRoot->removeChild(_graticule.get());
590        _graticule = NULL;
591    }
592    _needsRedraw = true;
593}
594
595void Renderer::setReadout(int x, int y)
596{
597    if (!_coordsCallback.valid() || !_mapNode.valid() || !_viewer.valid())
598        return;
599
600    osgEarth::GeoPoint mapCoord;
601    if (mapMouseCoords(x, y, mapCoord)) {
602        _coordsCallback->set(mapCoord, _viewer->asView(), _mapNode);
603    } else {
604        _coordsCallback->reset(_viewer->asView(), _mapNode);
605    }
606    _needsRedraw = true;
607}
608
609void Renderer::clearReadout()
610{
611    if (_coordsCallback.valid()) {
612        _coordsCallback->reset(_viewer->asView(), _mapNode);
613    }
614    _needsRedraw = true;
615}
616
617void Renderer::setCoordinateReadout(bool state, CoordinateDisplayType type,
618                                    int precision)
619{
620    if (!state) {
621        if (_mouseCoordsTool.valid() && _viewer.valid()) {
622            _viewer->removeEventHandler(_mouseCoordsTool.get());
623            _mouseCoordsTool = NULL;
624        }
625        if (_coordsCallback.valid()) {
626            _coordsCallback = NULL;
627        }
628        if (_coordsBox.valid() && _viewer.valid()) {
629            _coordsBox->clearControls();
630            osgEarth::Util::Controls::ControlCanvas::getOrCreate(_viewer.get())->removeControl(_coordsBox.get());
631        }
632        _coordsBox = NULL;
633    } else {
634        initMouseCoordsTool(type, precision);
635    }
636    _needsRedraw = true;
637}
638
639osgEarth::Util::MGRSFormatter::Precision
640Renderer::getMGRSPrecision(int precisionInMeters)
641{
642    switch (precisionInMeters) {
643    case 1:
644        return osgEarth::Util::MGRSFormatter::PRECISION_1M;
645    case 10:
646        return osgEarth::Util::MGRSFormatter::PRECISION_10M;
647    case 100:
648        return osgEarth::Util::MGRSFormatter::PRECISION_100M;
649    case 1000:
650        return osgEarth::Util::MGRSFormatter::PRECISION_1000M;
651    case 10000:
652        return osgEarth::Util::MGRSFormatter::PRECISION_10000M;
653    case 100000:
654        return osgEarth::Util::MGRSFormatter::PRECISION_100000M;
655    default:
656        ERROR("Invalid precision: %d", precisionInMeters);
657        return osgEarth::Util::MGRSFormatter::PRECISION_1M;
658    }
659}
660
661void Renderer::initMouseCoordsTool(CoordinateDisplayType type, int precision)
662{
663    if (!_viewer.valid())
664        return;
665    if (_mouseCoordsTool.valid()) {
666        _viewer->removeEventHandler(_mouseCoordsTool.get());
667    }
668    _mouseCoordsTool = new MouseCoordsTool(_mapNode.get());
669    osgEarth::Util::Controls::LabelControl *readout;
670    if (_coordsCallback.valid()) {
671        readout = _coordsCallback->getLabel();
672        _coordsCallback = NULL;
673    } else {
674        readout = new osgEarth::Util::Controls::LabelControl("Lat/Long: N/A", 12.0f);
675        readout->setForeColor(osg::Vec4f(0, 0, 0, 1));
676        _coordsBox =
677            new osgEarth::Util::Controls::HBox(osgEarth::Util::Controls::Control::ALIGN_LEFT,
678                                               osgEarth::Util::Controls::Control::ALIGN_TOP,
679                                               osgEarth::Util::Controls::Gutter(2, 2, 2, 2), 2.0f);
680        _coordsBox->setVertFill(true);
681        _coordsBox->setForeColor(osg::Vec4f(0, 0, 0, 1));
682        _coordsBox->setBackColor(osg::Vec4f(1, 1, 1, 0.6));
683        _coordsBox->addControl(readout);
684        osgEarth::Util::Controls::ControlCanvas::getOrCreate(_viewer.get())->addControl(_coordsBox.get());
685    }
686
687    osgEarth::Util::Formatter *formatter = NULL;
688    if (type == COORDS_MGRS) {
689        osgEarth::Util::MGRSFormatter::Precision prec =
690            osgEarth::Util::MGRSFormatter::PRECISION_1M;
691        if (precision > 0) {
692            prec = getMGRSPrecision(precision);
693        }
694        unsigned int opts = 0u;
695        formatter = new osgEarth::Util::MGRSFormatter(prec, NULL, opts);
696    } else {
697        osgEarth::Util::LatLongFormatter::AngularFormat af;
698        unsigned int opts =  osgEarth::Util::LatLongFormatter::USE_SYMBOLS;
699        switch (type) {
700        case COORDS_LATLONG_DEGREES_DECIMAL_MINUTES:
701            af = osgEarth::Util::LatLongFormatter::FORMAT_DEGREES_DECIMAL_MINUTES;
702            break;
703        case COORDS_LATLONG_DEGREES_MINUTES_SECONDS:
704            af = osgEarth::Util::LatLongFormatter::FORMAT_DEGREES_MINUTES_SECONDS;
705            break;
706        default:
707            af = osgEarth::Util::LatLongFormatter::FORMAT_DECIMAL_DEGREES;
708        }
709        osgEarth::Util::LatLongFormatter *latlong = new osgEarth::Util::LatLongFormatter(af, opts);
710        if (precision > 0) {
711            latlong->setPrecision(precision);
712        }
713        formatter = latlong;
714    }
715    _coordsCallback = new MouseCoordsCallback(readout, formatter);
716
717    _mouseCoordsTool->addCallback(_coordsCallback.get());
718    _viewer->addEventHandler(_mouseCoordsTool.get());
719}
720
721void Renderer::initEarthManipulator()
722{
723    _manipulator = new osgEarth::Util::EarthManipulator;
724#if 1
725    osgEarth::Util::EarthManipulator::Settings *settings = _manipulator->getSettings();
726
727    settings->bindMouse(osgEarth::Util::EarthManipulator::ACTION_ROTATE,
728                        osgGA::GUIEventAdapter::MIDDLE_MOUSE_BUTTON,
729                        osgGA::GUIEventAdapter::MODKEY_ALT);
730    osgEarth::Util::EarthManipulator::ActionOptions options;
731    options.clear();
732    options.add(osgEarth::Util::EarthManipulator::OPTION_CONTINUOUS, true);
733    settings->bindMouse(osgEarth::Util::EarthManipulator::ACTION_ZOOM,
734                        osgGA::GUIEventAdapter::RIGHT_MOUSE_BUTTON,
735                        osgGA::GUIEventAdapter::MODKEY_ALT, options);
736
737    if (_map->isGeocentric()) {
738        settings->setMinMaxDistance(1.0, 2.0e7);
739    } else {
740        osgEarth::GeoExtent extent = _map->getProfile()->getExtent();
741        settings->setMinMaxDistance(1.0, getMaxDistanceFromExtent(extent));
742    }
743
744    _manipulator->applySettings(settings);
745#endif
746    _viewer->setCameraManipulator(_manipulator.get());
747    _manipulator->setNode(NULL);
748    _manipulator->setNode(_sceneRoot.get());
749    //_manipulator->computeHomePosition();
750}
751
752void Renderer::loadEarthFile(const char *path)
753{
754    TRACE("Loading %s", path);
755    osg::Node *node = osgDB::readNodeFile(path);
756    if (node == NULL) {
757        ERROR("Couldn't load %s", path);
758        return;
759    }
760    osgEarth::MapNode *mapNode = osgEarth::MapNode::findMapNode(node);
761    if (mapNode == NULL) {
762        ERROR("Couldn't find MapNode");
763        return;
764    } else {
765        initViewer();
766        _sceneRoot = new osg::Group;
767        _sceneRoot->addChild(node);
768        _map = mapNode->getMap();
769    }
770    _mapNode = mapNode;
771
772    if (_clipPlaneCullCallback.valid()) {
773        _viewer->getCamera()->removeCullCallback(_clipPlaneCullCallback.get());
774        _clipPlaneCullCallback = NULL;
775    }
776    if (_map->isGeocentric()) {
777        _clipPlaneCullCallback = new osgEarth::Util::AutoClipPlaneCullCallback(mapNode);
778        _viewer->getCamera()->addCullCallback(_clipPlaneCullCallback.get());
779    }
780    _viewer->setSceneData(_sceneRoot.get());
781
782    if (_mouseCoordsTool.valid()) {
783        initMouseCoordsTool();
784    }
785    initControls();
786    initEarthManipulator();
787   
788    _viewer->home();
789    finalizeViewer();
790    _needsRedraw = true;
791}
792
793void Renderer::resetMap(osgEarth::MapOptions::CoordinateSystemType type,
794                        const osg::Vec4f& bgColor,
795                        const char *profile,
796                        double bounds[4],
797                        const char *boundsSRS)
798{
799    TRACE("Restting map with type %d, profile %s", type, profile);
800
801    osgEarth::MapOptions mapOpts;
802    mapOpts.coordSysType() = type;
803    if (profile != NULL) {
804        if (bounds != NULL) {
805            mapOpts.profile() = osgEarth::ProfileOptions();
806            if (strcmp(profile, "geodetic") == 0) {
807                mapOpts.profile()->srsString() = "epsg:4326"; // WGS84
808            } else if (strcmp(profile, "spherical-mercator") == 0) {
809                // Projection used by Google/Bing/OSM
810                // aka epsg:900913 meters in x/y
811                // aka WGS84 Web Mercator (Auxiliary Sphere)
812                // X/Y: -20037508.34m to 20037508.34m
813                mapOpts.profile()->srsString() = "epsg:3857";
814            } else {
815                mapOpts.profile()->srsString() = profile;
816            }
817            TRACE("Input bounds: %g %g %g %g srs: \"%s\"",
818                  bounds[0], bounds[1], bounds[2], bounds[3], boundsSRS);
819            if (boundsSRS != NULL) {
820                osg::ref_ptr<osgEarth::SpatialReference> fromSRS = osgEarth::SpatialReference::create(boundsSRS);
821                osg::ref_ptr<osgEarth::SpatialReference> toSRS = osgEarth::SpatialReference::create(mapOpts.profile()->srsString().get());
822                osgEarth::Bounds extents(bounds[0], bounds[1], bounds[2], bounds[3]);
823                extents.transform(fromSRS, toSRS);
824                mapOpts.profile()->bounds() = extents;
825                //mapOpts.profile()->numTilesWideAtLod0() = 1;
826                //mapOpts.profile()->numTilesHighAtLod0() = 2;
827            } else {
828                mapOpts.profile()->bounds() =
829                    osgEarth::Bounds(bounds[0], bounds[1], bounds[2], bounds[3]);
830            }
831            TRACE("Setting profile bounds: %g %g %g %g",
832                  mapOpts.profile()->bounds()->xMin(),
833                  mapOpts.profile()->bounds()->yMin(),
834                  mapOpts.profile()->bounds()->xMax(),
835                  mapOpts.profile()->bounds()->yMax());
836        } else {
837            mapOpts.profile() = osgEarth::ProfileOptions(profile);
838        }
839    } else if (type == osgEarth::MapOptions::CSTYPE_PROJECTED) {
840        mapOpts.profile() = osgEarth::ProfileOptions("global-mercator");
841    }
842
843#ifdef USE_CACHE
844    setupCache();
845    osgEarth::Drivers::FileSystemCacheOptions cacheOpts;
846    cacheOpts.rootPath() = _cacheDir;
847    mapOpts.cache() = cacheOpts;
848#endif
849
850    initViewer();
851
852    //mapOpts.referenceURI() = _baseURI;
853    osgEarth::Map *map = new osgEarth::Map(mapOpts);
854    _map = map;
855    osgEarth::Drivers::GDALOptions bopts;
856    bopts.url() = getBaseImage();
857    addImageLayer("base", bopts);
858#ifdef USE_REX
859    osgEarth::Drivers::RexTerrainEngine::RexTerrainEngineOptions engineOpt;
860#else
861    osgEarth::Drivers::MPTerrainEngine::MPTerrainEngineOptions engineOpt;
862#endif
863    // Set background layer color
864    engineOpt.color() = bgColor;
865    //engineOpt.minLOD() = 1;
866    // Sets shader uniform for terrain renderer (config var defaults to false)
867    if (!_map->isGeocentric()) {
868        engineOpt.enableLighting() = false;
869    }
870    osgEarth::MapNodeOptions mapNodeOpts(engineOpt);
871    // Sets GL_LIGHTING state in MapNode StateSet (config var defaults to true)
872    //mapNodeOpts.enableLighting() = true;
873#if 0
874    osgEarth::TerrainOptions terrOpts;
875    //terrOpts.loadingPolicy().mapLoadingThreadsPerCore() = 1;
876    //terrOpts.loadingPolicy().numLoadingThreads() = 1;
877    //terrOpts.loadingPolicy().numCompileThreadsPerCore() = 1;
878    //terrOpts.loadingPolicy().numCompileThreads() = 1;
879    if (_map->isGeocentric()) {
880        // The default is 17 posts
881        terrOpts.tileSize() = 17;
882    } else {
883        // Can reduce if we don't have elevation data
884        terrOpts.tileSize() = 2;
885    }
886    mapNodeOpts.setTerrainOptions(terrOpts);
887#endif
888    osgEarth::MapNode *mapNode = new osgEarth::MapNode(map, mapNodeOpts);
889    _mapNode = mapNode;
890    if (_map->isGeocentric()) {
891        osgEarth::DateTime now;
892        TRACE("Creating SkyNode");
893        osgEarth::Drivers::SimpleSky::SimpleSkyOptions skyOpts;
894        skyOpts.hours() = now.hours();
895        skyOpts.ambient() = 0.2f;
896        skyOpts.atmosphericLighting() = true;
897        skyOpts.exposure() = 3.0;
898        _skyNode = osgEarth::Util::SkyNode::create(skyOpts, mapNode);
899        _skyNode->addChild(mapNode);
900        _skyNode->attach(_viewer.get(), 0);
901        _sceneRoot = new osg::Group();
902        _sceneRoot->addChild(_skyNode.get());
903        //_sceneRoot = _skyNode;
904    } else {
905        _sceneRoot = new osg::Group();
906        _sceneRoot->addChild(_mapNode.get());
907    }
908
909    if (_clipPlaneCullCallback.valid()) {
910        _viewer->getCamera()->removeCullCallback(_clipPlaneCullCallback.get());
911        _clipPlaneCullCallback = NULL;
912    }
913    if (_map->isGeocentric()) {
914        _clipPlaneCullCallback = new osgEarth::Util::AutoClipPlaneCullCallback(mapNode);
915        _viewer->getCamera()->addCullCallback(_clipPlaneCullCallback.get());
916    }
917    _viewer->setSceneData(_sceneRoot.get());
918    if (_mouseCoordsTool.valid()) {
919        initMouseCoordsTool();
920    }
921#ifdef USE_RTT_PICKER
922    if (_picker.valid()) {
923        _viewer->removeEventHandler(_picker.get());
924        _picker = NULL;
925    }
926    if (!_picker.valid()) {
927        _picker = new osgEarth::Util::RTTPicker;
928        _picker->addChild(mapNode);
929        //osg::ref_ptr<HoverCallback> callback = new HoverCallback(this);
930        osg::ref_ptr<SelectCallback> callback = new SelectCallback(this);
931        _picker->setDefaultCallback(callback.get());
932        _viewer->addEventHandler(_picker.get());
933    }
934#endif
935    initControls();
936    //_viewer->setSceneData(_sceneRoot.get());
937    initEarthManipulator();
938    _viewer->home();
939
940    finalizeViewer();
941    _needsRedraw = true;
942}
943
944void Renderer::clearMap()
945{
946    if (_map.valid()) {
947        _map->clear();
948        _needsRedraw = true;
949    }
950}
951
952void Renderer::setSkyAmbient(float value)
953{
954#if OSG_MIN_VERSION_REQUIRED(2, 7, 0)
955    if (_skyNode.valid()) {
956        _skyNode->setMinimumAmbient(osg::Vec4f(value, value, value, 1.0f));
957        _needsRedraw = true;
958    }
959#endif
960}
961
962void Renderer::setLighting(bool state)
963{
964    if (_mapNode.valid()) {
965        TRACE("Setting lighting: %d", state ? 1 : 0);
966        _mapNode->getOrCreateStateSet()
967            ->setMode(GL_LIGHTING, state ? 1 : 0);
968        _needsRedraw = true;
969    }
970}
971
972void Renderer::setViewerLightType(osg::View::LightingMode mode)
973{
974    if (_viewer.valid()) {
975        _viewer->setLightingMode(mode);
976        _needsRedraw = true;
977    }
978}
979
980void Renderer::setTerrainVerticalScale(double scale)
981{
982    if (_mapNode.valid()) {
983        if (!_verticalScale.valid()) {
984            _verticalScale = new osgEarth::Util::VerticalScale;
985            _mapNode->getTerrainEngine()->addEffect(_verticalScale);
986        }
987        _verticalScale->setScale(scale);
988        _needsRedraw = true;
989    }
990}
991
992void Renderer::setEphemerisTime(time_t utcTime)
993{
994    if (_skyNode.valid()) {
995        osgEarth::DateTime time(utcTime);
996        _skyNode->setDateTime(time);
997        _needsRedraw = true;
998    }
999}
1000
1001void Renderer::setEphemerisTime(int year, int month, int day, double hours)
1002{
1003    if (_skyNode.valid()) {
1004        osgEarth::DateTime time(year, month, day, hours);
1005        _skyNode->setDateTime(time);
1006        _needsRedraw = true;
1007    }
1008}
1009
1010void Renderer::setTerrainColor(const osg::Vec4f& color)
1011{
1012    // XXX: Don't know how to change this at runtime without a map reset
1013    _needsRedraw = true;
1014}
1015
1016void Renderer::setTerrainLighting(bool state)
1017{
1018    if (_skyNode.valid()) {
1019        _skyNode->setLighting((state ? osg::StateAttribute::ON : osg::StateAttribute::OFF));
1020    } else {
1021#if 1
1022        if (!_mapNode.valid()) {
1023            ERROR("No map node");
1024            return;
1025        }
1026        // XXX: HACK alert
1027        // Find the terrain engine container (might be above one or more decorators)
1028        osg::Group *group = _mapNode->getTerrainEngine();
1029        while (group->getParent(0) != NULL && group->getParent(0) != _mapNode.get()) {
1030            group = group->getParent(0);
1031        }
1032        if (group != NULL && group->getParent(0) == _mapNode.get()) {
1033            TRACE("Setting terrain lighting: %d", state ? 1 : 0);
1034            if (group->getOrCreateStateSet()->getUniform("oe_mode_GL_LIGHTING") != NULL) {
1035                group->getStateSet()->getUniform("oe_mode_GL_LIGHTING")->set(state);
1036            } else {
1037                ERROR("Can't get terrain lighting uniform");
1038            }
1039        } else {
1040            ERROR("Can't find terrain engine container");
1041        }
1042#else
1043        if (_stateManip.valid()) {
1044            _stateManip->setLightingEnabled(state);
1045        }
1046#endif
1047    }
1048    _needsRedraw = true;
1049}
1050
1051void Renderer::setTerrainWireframe(bool state)
1052{
1053    if (!_map.valid()) {
1054        ERROR("No map");
1055        return;
1056    }
1057#if 0
1058    if (!_mapNode.valid()) {
1059        ERROR("No map node");
1060        return;
1061    }
1062    TRACE("Setting terrain wireframe: %d", state ? 1 : 0);
1063    osg::StateSet *state = _mapNode->getOrCreateStateSet();
1064    osg::PolygonMode *pmode = dynamic_cast< osg::PolygonMode* >(state->getAttribute(osg::StateAttribute::POLYGONMODE));
1065    if (pmode == NULL) {
1066        pmode = new osg::PolygonMode;
1067        state->setAttribute(pmode);
1068    }
1069    if (state) {
1070        pmode->setMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE);
1071    } else {
1072        pmode->setMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL);
1073    }
1074    _needsRedraw = true;
1075#else
1076    if (_stateManip.valid()) {
1077        _stateManip->setPolygonMode(state ? osg::PolygonMode::LINE : osg::PolygonMode::FILL);
1078        _needsRedraw = true;
1079    }
1080#endif
1081}
1082
1083void Renderer::saveNamedViewpoint(const char *name)
1084{
1085    _viewpoints[name] = getViewpoint();
1086}
1087
1088bool Renderer::removeNamedViewpoint(const char *name)
1089{
1090    ViewpointHashmap::iterator itr = _viewpoints.find(name);
1091    if (itr != _viewpoints.end()) {
1092        _viewpoints.erase(name);
1093        return true;
1094    } else {
1095        ERROR("Unknown viewpoint: '%s'", name);
1096        return false;
1097    }
1098}
1099
1100bool Renderer::restoreNamedViewpoint(const char *name, double durationSecs)
1101{
1102    ViewpointHashmap::iterator itr = _viewpoints.find(name);
1103    if (itr != _viewpoints.end()) {
1104        setViewpoint(itr->second, durationSecs);
1105        return true;
1106    } else {
1107        ERROR("Unknown viewpoint: '%s'", name);
1108        return false;
1109    }
1110}
1111
1112void Renderer::setViewpoint(const osgEarth::Viewpoint& v, double durationSecs)
1113{
1114    if (_manipulator.valid()) {
1115        TRACE("Setting viewpoint: %g %g %g %g %g %g",
1116              v.focalPoint()->x(), v.focalPoint()->y(), v.focalPoint()->z(),
1117              v.heading()->as(osgEarth::Units::DEGREES),
1118              v.pitch()->as(osgEarth::Units::DEGREES),
1119              v.range()->as(osgEarth::Units::METERS));
1120        _manipulator->setViewpoint(v, durationSecs);
1121        _needsRedraw = true;
1122    } else {
1123        ERROR("No manipulator");
1124    }
1125}
1126
1127osgEarth::Viewpoint Renderer::getViewpoint()
1128{
1129    if (_manipulator.valid()) {
1130        return _manipulator->getViewpoint();
1131    } else {
1132        // Uninitialized, invalid viewpoint
1133        return osgEarth::Viewpoint();
1134    }
1135}
1136
1137static void srsInfo(const osgEarth::SpatialReference *srs)
1138{
1139    TRACE("SRS: %s", srs->getName().c_str());
1140    TRACE("horiz: \"%s\" vert: \"%s\"", srs->getHorizInitString().c_str(), srs->getVertInitString().c_str());
1141    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",
1142          srs->isGeographic() ? 1 : 0,
1143          srs->isGeodetic() ? 1 : 0,
1144          srs->isProjected() ? 1 : 0,
1145          srs->isECEF() ? 1 : 0,
1146          srs->isMercator() ? 1 : 0,
1147          srs->isSphericalMercator() ? 1 : 0,
1148          srs->isNorthPolar() ? 1 : 0,
1149          srs->isSouthPolar() ? 1 : 0,
1150          srs->isUserDefined() ? 1 : 0,
1151          srs->isContiguous() ? 1 : 0,
1152          srs->isCube() ? 1 : 0,
1153          srs->isLTP() ? 1 : 0,
1154          srs->isPlateCarre() ? 1 : 0);
1155}
1156
1157bool Renderer::getWorldCoords(const osgEarth::GeoPoint& mapPt, osg::Vec3d *world)
1158{
1159    if (!_mapNode.valid() || _mapNode->getTerrain() == NULL) {
1160        ERROR("No map");
1161        return false;
1162    }
1163    TRACE("Input SRS:");
1164    srsInfo(mapPt.getSRS());
1165    TRACE("Map SRS:");
1166    srsInfo(_mapNode->getMapSRS());
1167    bool ret = mapPt.toWorld(*world, _mapNode->getTerrain());
1168    TRACE("In: %g,%g,%g Out: %g,%g,%g",
1169          mapPt.x(), mapPt.y(), mapPt.z(),
1170          world->x(), world->y(), world->z());
1171    return ret;
1172}
1173
1174bool Renderer::worldToScreen(const osg::Vec3d& world, osg::Vec3d *screen, bool invertY)
1175{
1176    if (!_viewer.valid()) {
1177        ERROR("No viewer");
1178        return false;
1179    }
1180    osg::Camera *cam = _viewer->getCamera();
1181    osg::Matrixd MVP = cam->getViewMatrix() * cam->getProjectionMatrix();
1182    // Get clip coords
1183    osg::Vec4d pt;
1184    pt = osg::Vec4d(world, 1.0) * MVP;
1185    // Clip
1186    if (pt.x() < -pt.w() ||
1187        pt.x() > pt.w() ||
1188        pt.y() < -pt.w() ||
1189        pt.y() > pt.w() ||
1190        pt.z() < -pt.w() ||
1191        pt.z() > pt.w()) {
1192        // Outside frustum
1193        TRACE("invalid pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w());
1194        return false;
1195    }
1196    TRACE("clip pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w());
1197    // Perspective divide: now NDC
1198    pt /= pt.w();
1199    const osg::Viewport *viewport = cam->getViewport();
1200#if 1
1201    screen->x() = viewport->x() + viewport->width() * 0.5 + pt.x() * viewport->width() * 0.5;
1202    screen->y() = viewport->y() + viewport->height() * 0.5 + pt.y() * viewport->height() * 0.5;
1203    //double near = 0;
1204    //double far = 1;
1205    //screen->z() = (far + near) * 0.5 + (far - near) * 0.5 * pt.z();
1206    screen->z() = 0.5 + 0.5 * pt.z();
1207#else
1208    *screen = osg::Vec3d(pt.x(), pt.y(), pt.z()) * cam->getViewport()->computeWindowMatrix();
1209#endif
1210    if (invertY) {
1211        screen->y() = viewport->height() - screen->y();
1212    }
1213    TRACE("screen: %g,%g,%g", screen->x(), screen->y(), screen->z());
1214    return true;
1215}
1216
1217bool Renderer::worldToScreen(std::vector<osg::Vec3d>& coords, bool invertY)
1218{
1219    if (!_viewer.valid()) {
1220        ERROR("No viewer");
1221        return false;
1222    }
1223    bool ret = true;
1224    osg::Camera *cam = _viewer->getCamera();
1225    osg::Matrixd MVP = cam->getViewMatrix() * cam->getProjectionMatrix();
1226    const osg::Viewport *viewport = cam->getViewport();
1227    for (std::vector<osg::Vec3d>::iterator itr = coords.begin();
1228         itr != coords.end(); ++itr) {
1229        // Get clip coords
1230        osg::Vec4d pt;
1231        pt = osg::Vec4d(*itr, 1.0) * MVP;
1232        // Clip
1233        if (pt.x() < -pt.w() ||
1234            pt.x() > pt.w() ||
1235            pt.y() < -pt.w() ||
1236            pt.y() > pt.w() ||
1237            pt.z() < -pt.w() ||
1238            pt.z() > pt.w()) {
1239            // Outside frustum
1240            TRACE("invalid pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w());
1241            itr->set(std::numeric_limits<double>::quiet_NaN(),
1242                     std::numeric_limits<double>::quiet_NaN(),
1243                     std::numeric_limits<double>::quiet_NaN());
1244            ret = false;
1245            continue;
1246        }
1247        TRACE("clip pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w());
1248        // Perspective divide: now NDC
1249        pt /= pt.w();
1250#if 1
1251        itr->x() = viewport->x() + viewport->width() * 0.5 + pt.x() * viewport->width() * 0.5;
1252        itr->y() = viewport->y() + viewport->height() * 0.5 + pt.y() * viewport->height() * 0.5;
1253        //double near = 0;
1254        //double far = 1;
1255        //itr->z() = (far + near) * 0.5 + (far - near) * 0.5 * pt.z();
1256        itr->z() = 0.5 + 0.5 * pt.z();
1257#else
1258        *itr = osg::Vec3d(pt.x(), pt.y(), pt.z()) * viewport->computeWindowMatrix();
1259#endif
1260        if (invertY) {
1261            itr->y() = viewport->height() - itr->y();
1262        }
1263        TRACE("screen: %g,%g,%g", itr->x(), itr->y(), itr->z());
1264    }
1265    return ret;
1266}
1267
1268bool Renderer::mouseToLatLong(int mouseX, int mouseY,
1269                              double *latitude, double *longitude)
1270{
1271    if (getMapSRS() == NULL)
1272        return false;
1273    const osgEarth::SpatialReference *outSRS =
1274        getMapSRS()->getGeographicSRS();
1275    if (outSRS == NULL)
1276        return false;
1277    osgEarth::GeoPoint mapPoint;
1278    if (mapMouseCoords(mouseX, mouseY, mapPoint)) {
1279        mapPoint = mapPoint.transform(outSRS);
1280        *longitude = mapPoint.x();
1281        *latitude = mapPoint.y();
1282        return true;
1283    } else {
1284        return false;
1285    }
1286}
1287#if 0
1288bool Renderer::mapToLatLong(osgEarth::GeoPoint& mapPoint)
1289{
1290    if (getMapSRS() == NULL)
1291        return false;
1292    const osgEarth::SpatialReference *outSRS =
1293        getMapSRS()->getGeographicSRS();
1294    if (outSRS == NULL)
1295        return false;
1296    mapPoint = mapPoint.transform(outSRS);
1297    return true;
1298}
1299#endif
1300/**
1301 * \brief Map screen mouse coordinates to map coordinates
1302 *
1303 * This method assumes that mouse Y coordinates are 0 at the top
1304 * of the screen and increase going down if invertY is set, and
1305 * 0 at the bottom and increase going up otherwise.
1306 */
1307bool Renderer::mapMouseCoords(float mouseX, float mouseY,
1308                              osgEarth::GeoPoint& map, bool invertY)
1309{
1310    if (!_mapNode.valid() || _mapNode->getTerrain() == NULL) {
1311        ERROR("No map");
1312        return false;
1313    }
1314    if (!_viewer.valid()) {
1315        ERROR("No viewer");
1316        return false;
1317    }
1318    if (invertY) {
1319        mouseY = ((float)_windowHeight - mouseY);
1320    }
1321    osg::Vec3d world;
1322    if (_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), mouseX, mouseY, world)) {
1323        map.fromWorld(getMapSRS(), world);
1324        return true;
1325    }
1326    return false;
1327}
1328
1329bool Renderer::addImageLayer(const char *name,
1330                             osgEarth::TileSourceOptions& opts,
1331                             unsigned int pos,
1332                             bool enableCache,
1333                             bool coverage,
1334                             bool makeShared,
1335                             bool visible,
1336                             unsigned int minLOD, unsigned int maxLOD)
1337{
1338    if (!_map.valid()) {
1339        ERROR("No map");
1340        return false;
1341    }
1342    TRACE("layer: %s", name);
1343    if (!opts.tileSize().isSet()) {
1344        opts.tileSize() = 256;
1345    }
1346    osgEarth::ImageLayerOptions layerOpts(name, opts);
1347    layerOpts.textureCompression() = osg::Texture::USE_IMAGE_DATA_FORMAT;
1348    if (coverage) {
1349        layerOpts.coverage() = true;
1350    }
1351    if (makeShared) {
1352        layerOpts.shared() = true;
1353    }
1354    if (!enableCache) {
1355        TRACE("Disabling cache for layer %s", name);
1356        layerOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
1357    }
1358    if (!visible) {
1359        layerOpts.visible() = false;
1360    }
1361    layerOpts.minLevel() = minLOD;
1362    layerOpts.maxLevel() = maxLOD;
1363    osg::ref_ptr<osgEarth::ImageLayer> layer = new osgEarth::ImageLayer(layerOpts);
1364    if (pos < (unsigned int)_map->getNumImageLayers()) {
1365        _map->insertImageLayer(layer.get(), pos);
1366    } else {
1367        _map->addImageLayer(layer.get());
1368    }
1369    if (layer->getTileSource() == NULL || !layer->getTileSource()->isOK()) {
1370        ERROR("Failed to add image layer: %s", name);
1371        _map->removeImageLayer(layer.get());
1372        return false;
1373    }
1374    _needsRedraw = true;
1375    return true;
1376}
1377
1378void Renderer::addColorFilter(const char *name,
1379                              const char *shader)
1380{
1381    if (!_map.valid()) {
1382        ERROR("No map");
1383        return;
1384    }
1385    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1386    if (layer == NULL) {
1387        TRACE("Image layer not found: %s", name);
1388        return;
1389    }
1390    osgEarth::Util::GLSLColorFilter *filter = new osgEarth::Util::GLSLColorFilter;
1391    filter->setCode(shader);
1392    //filter->setCode("color.rgb = color.r > 0.5 ? vec3(1.0) : vec3(0.0);");
1393    layer->addColorFilter(filter);
1394    _needsRedraw = true;
1395}
1396
1397void Renderer::removeColorFilter(const char *name, int idx)
1398{
1399    if (!_map.valid()) {
1400        ERROR("No map");
1401        return;
1402    }
1403    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1404    if (layer == NULL) {
1405        TRACE("Image layer not found: %s", name);
1406        return;
1407    }
1408    if (idx < 0) {
1409        while (!layer->getColorFilters().empty()) {
1410            layer->removeColorFilter(layer->getColorFilters()[0]);
1411        }
1412    } else {
1413        layer->removeColorFilter(layer->getColorFilters().at(idx));
1414    }
1415    _needsRedraw = true;
1416}
1417
1418void Renderer::removeImageLayer(const char *name)
1419{
1420    if (!_map.valid()) {
1421        ERROR("No map");
1422        return;
1423    }
1424    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1425    if (layer != NULL) {
1426        _map->removeImageLayer(layer);
1427        _needsRedraw = true;
1428    } else {
1429        TRACE("Image layer not found: %s", name);
1430    }
1431}
1432
1433void Renderer::moveImageLayer(const char *name, unsigned int pos)
1434{
1435    if (!_map.valid()) {
1436        ERROR("No map");
1437        return;
1438    }
1439    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1440    if (layer != NULL) {
1441        _map->moveImageLayer(layer, pos);
1442        _needsRedraw = true;
1443    } else {
1444        TRACE("Image layer not found: %s", name);
1445    }
1446}
1447
1448void Renderer::setImageLayerOpacity(const char *name, double opacity)
1449{
1450    if (!_map.valid()) {
1451        ERROR("No map");
1452        return;
1453    }
1454    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1455    if (layer != NULL) {
1456        layer->setOpacity(opacity);
1457        _needsRedraw = true;
1458    } else {
1459        TRACE("Image layer not found: %s", name);
1460    }
1461}
1462
1463void Renderer::setImageLayerVisibleRange(const char *name, float min, float max)
1464{
1465    if (!_map.valid()) {
1466        ERROR("No map");
1467        return;
1468    }
1469    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1470    if (layer != NULL) {
1471        layer->setMinVisibleRange(min);
1472        layer->setMaxVisibleRange(max);
1473        _needsRedraw = true;
1474    } else {
1475        TRACE("Image layer not found: %s", name);
1476    }
1477}
1478
1479void Renderer::setImageLayerVisibility(const char *name, bool state)
1480{
1481    if (!_map.valid()) {
1482        ERROR("No map");
1483        return;
1484    }
1485    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1486    if (layer != NULL) {
1487        layer->setVisible(state);
1488        _needsRedraw = true;
1489    } else {
1490        TRACE("Image layer not found: %s", name);
1491    }
1492}
1493
1494bool Renderer::layerHasSequence(const char *name)
1495{
1496    if (!_map.valid()) {
1497        ERROR("No map");
1498        return false;
1499    }
1500    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1501    if (layer != NULL) {
1502        osgEarth::SequenceControl *seq = layer->getSequenceControl();
1503        if (seq == NULL) {
1504            TRACE("Image layer has no SequenceControl: %s", name);
1505            return false;
1506        }
1507        return seq->supportsSequenceControl();
1508    }
1509    TRACE("Image layer not found: %s", name);
1510    return false;
1511}
1512
1513bool Renderer::sequencePause(const char *name)
1514{
1515    if (!_map.valid()) {
1516        ERROR("No map");
1517        return false;
1518    }
1519    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1520    if (layer != NULL) {
1521        osgEarth::SequenceControl *seq = layer->getSequenceControl();
1522        if (seq == NULL || !seq->supportsSequenceControl()) {
1523            TRACE("Image layer has no SequenceControl: %s", name);
1524            return false;
1525        }
1526        seq->pauseSequence();
1527        _needsRedraw = true;
1528        return true;
1529    }
1530    TRACE("Image layer not found: %s", name);
1531    return false;
1532}
1533
1534bool Renderer::sequencePlay(const char *name)
1535{
1536    if (!_map.valid()) {
1537        ERROR("No map");
1538        return false;
1539    }
1540    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1541    if (layer != NULL) {
1542        osgEarth::SequenceControl *seq = layer->getSequenceControl();
1543        if (seq == NULL || !seq->supportsSequenceControl()) {
1544            TRACE("Image layer has no SequenceControl: %s", name);
1545            return false;
1546        }
1547        seq->playSequence();
1548        _needsRedraw = true;
1549        return true;
1550    }
1551    TRACE("Image layer not found: %s", name);
1552    return false;
1553}
1554
1555bool Renderer::sequenceSeek(const char *name, unsigned int frame)
1556{
1557    if (!_map.valid()) {
1558        ERROR("No map");
1559        return false;
1560    }
1561    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1562    if (layer != NULL) {
1563        osgEarth::SequenceControl *seq = layer->getSequenceControl();
1564        if (seq == NULL || !seq->supportsSequenceControl()) {
1565            TRACE("Image layer has no SequenceControl: %s", name);
1566            return false;
1567        }
1568        seq->seekToSequenceFrame(frame);
1569        _needsRedraw = true;
1570        return true;
1571    }
1572    TRACE("Image layer not found: %s", name);
1573    return false;
1574}
1575
1576void Renderer::addElevationLayer(const char *name,
1577                                 osgEarth::TileSourceOptions& opts,
1578                                 unsigned int pos,
1579                                 bool enableCache,
1580                                 bool visible,
1581                                 const char *verticalDatum,
1582                                 unsigned int minLOD, unsigned int maxLOD)
1583{
1584    if (!_map.valid()) {
1585        ERROR("No map");
1586        return;
1587    }
1588    TRACE("layer: %s", name);
1589    if (!opts.tileSize().isSet()) {
1590        opts.tileSize() = 17;
1591    }
1592    osgEarth::ElevationLayerOptions layerOpts(name, opts);
1593    if (!enableCache) {
1594        layerOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
1595    }
1596    if (!visible) {
1597        layerOpts.visible() = false;
1598    }
1599    // Check for vertical datum override (required for GDAL driver if not using
1600    // geodetic HAE).
1601    if (verticalDatum != NULL && strlen(verticalDatum) > 0) {
1602        layerOpts.verticalDatum() = verticalDatum;
1603    }
1604    layerOpts.minLevel() = minLOD;
1605    layerOpts.maxLevel() = maxLOD;
1606    osgEarth::ElevationLayer *layer = new osgEarth::ElevationLayer(layerOpts);
1607    _map->addElevationLayer(layer);
1608    // Map API lacks an insertElevationLayer method, so need to move it
1609    if (pos < (unsigned int)_map->getNumElevationLayers()) {
1610        _map->moveElevationLayer(layer, pos);
1611    }
1612    _needsRedraw = true;
1613}
1614
1615void Renderer::removeElevationLayer(const char *name)
1616{
1617    if (!_map.valid()) {
1618        ERROR("No map");
1619        return;
1620    }
1621    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
1622    if (layer != NULL) {
1623        _map->removeElevationLayer(layer);
1624        _needsRedraw = true;
1625    } else {
1626        TRACE("Elevation layer not found: %s", name);
1627    }
1628}
1629
1630void Renderer::moveElevationLayer(const char *name, unsigned int pos)
1631{
1632    if (!_map.valid()) {
1633        ERROR("No map");
1634        return;
1635    }
1636    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
1637    if (layer != NULL) {
1638        _map->moveElevationLayer(layer, pos);
1639        _needsRedraw = true;
1640    } else {
1641        TRACE("Elevation layer not found: %s", name);
1642    }
1643}
1644
1645void Renderer::setElevationLayerVisibility(const char *name, bool state)
1646{
1647    if (!_map.valid()) {
1648        ERROR("No map");
1649        return;
1650    }
1651    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
1652    if (layer != NULL) {
1653        layer->setVisible(state);
1654        _needsRedraw = true;
1655    } else {
1656        TRACE("Elevation layer not found: %s", name);
1657    }
1658}
1659
1660void Renderer::addTerrainMaskLayer(const char *name,
1661                                   osgEarth::MaskSourceOptions& opts,
1662                                   unsigned int minLOD)
1663{
1664    if (!_map.valid()) {
1665        ERROR("No map");
1666        return;
1667    }
1668    TRACE("layer: %s", name);
1669    osgEarth::MaskLayerOptions layerOpts(name, opts);
1670    layerOpts.minLevel() = minLOD;
1671    osgEarth::MaskLayer *layer = new osgEarth::MaskLayer(layerOpts);
1672    _map->addTerrainMaskLayer(layer);
1673    _needsRedraw = true;
1674}
1675
1676void Renderer::removeTerrainMaskLayer(const char *name)
1677{
1678    if (!_map.valid()) {
1679        ERROR("No map");
1680        return;
1681    }
1682    osgEarth::MaskLayer *layer = getTerrainMaskLayerByName(name);
1683    if (layer != NULL) {
1684        _map->removeTerrainMaskLayer(layer);
1685        _needsRedraw = true;
1686    } else {
1687        TRACE("Terrain mask layer not found: %s", name);
1688    }
1689}
1690
1691void Renderer::initAnnotations()
1692{
1693    if (!_annotations.valid()) {
1694        _annotations = new osg::Group();
1695        _annotations->setName("Annotations");
1696        _sceneRoot->addChild(_annotations.get());
1697        _placeNodes = new osg::Group();
1698        _placeNodes->setName("Place Nodes");
1699#ifdef NEW_ANNOTATION_API
1700        osgEarth::ScreenSpaceLayout::activate(_placeNodes->getOrCreateStateSet());
1701#else
1702        osgEarth::Decluttering::setEnabled(_placeNodes->getOrCreateStateSet(), true);
1703#endif
1704        _annotations->addChild(_placeNodes.get());
1705        if (_picker.valid()) {
1706            _picker->addChild(_placeNodes.get());
1707        }
1708    }
1709}
1710
1711void Renderer::setSelectMode(SelectMode mode)
1712{
1713    _selectMode = mode;
1714    switch (_selectMode) {
1715    case SELECT_OFF: {
1716#ifdef USE_RTT_PICKER
1717        if (_picker.valid()) {
1718            _viewer->removeEventHandler(_picker.get());
1719            _picker = NULL;
1720        }
1721#endif
1722    }
1723        break;
1724    case SELECT_ON: {
1725#ifdef USE_RTT_PICKER
1726        if (!_picker.valid()) {
1727            _picker = new osgEarth::Util::RTTPicker;
1728            _picker->addChild(_mapNode);
1729            osg::ref_ptr<SelectCallback> callback = new SelectCallback(this);
1730            _picker->setDefaultCallback(callback.get());
1731            _viewer->addEventHandler(_picker.get());
1732        }
1733#endif
1734    }
1735        break;
1736    default:
1737        ERROR("Unknown select mode");
1738    }
1739}
1740
1741void Renderer::enablePlacard(const char *layerName, bool state)
1742{
1743    osgEarth::ModelLayer *layer = _map->getModelLayerByName(layerName);
1744    if (layer == NULL) {
1745        ERROR("Unknown layer '%s'", layerName);
1746        return;
1747    }
1748    _placardConfigs[layerName].setEnabled(state);
1749}
1750
1751void Renderer::setPlacardConfig(const Placard& placardConf, const char *layerName)
1752{
1753    osgEarth::ModelLayer *layer = _map->getModelLayerByName(layerName);
1754    if (layer == NULL) {
1755        ERROR("Unknown layer '%s'", layerName);
1756        return;
1757    }
1758    //PlacardNode *node;
1759    //node->setConfig(placardConf);
1760    _placardConfigs[layerName] = placardConf;
1761}
1762
1763void Renderer::deselectFeatures(std::vector<unsigned long>& featureIDs, const char *layerName)
1764{
1765    // TODO: Take down placard?
1766    TRACE("Deselect features layer '%s', num features: %u", layerName, featureIDs.size());
1767    for (unsigned int i = 0; i < featureIDs.size(); i++) {
1768        _selectedFeatures[layerName].erase(featureIDs.at(i));
1769    }
1770    FeatureSelectionHashmap::iterator itr = _selectedFeatures.find(layerName);
1771    if (itr != _selectedFeatures.end()) {
1772        for (FeatureSelectionHashmap::mapped_type::iterator fitr = itr->second.begin();
1773             fitr != itr->second.end(); ++fitr) {
1774            TRACE("Selection: %lu", *fitr);
1775        }
1776    } else {
1777        TRACE("No selection");
1778    }
1779    updateDebugLabel();
1780}
1781
1782void Renderer::selectFeatures(std::vector<unsigned long>& featureIDs, const char *layerName, bool clear)
1783{
1784    bool doPlacard = false;
1785    TRACE("Select layer '%s', num features: %u", layerName, featureIDs.size());
1786    if (clear) {
1787        clearSelection();
1788    }
1789    if (featureIDs.size() == 0) {
1790        // clear selection
1791        return;
1792    }
1793    osgEarth::ModelLayer *layer = _map->getModelLayerByName(layerName);
1794    if (layer == NULL) {
1795        ERROR("Unknown layer '%s'", layerName);
1796        return;
1797    }
1798    for (unsigned int i = 0; i < featureIDs.size(); i++) {
1799        TRACE("feature ID: %u", featureIDs.at(i));
1800        _selectedFeatures[layerName].insert(featureIDs.at(i));
1801    }
1802    FeatureSelectionHashmap::iterator itr = _selectedFeatures.find(layerName);
1803    if (itr != _selectedFeatures.end()) {
1804        for (FeatureSelectionHashmap::mapped_type::iterator fitr = itr->second.begin();
1805             fitr != itr->second.end(); ++fitr) {
1806            TRACE("Selection: %lu", *fitr);
1807        }
1808    } else {
1809        TRACE("No selection");
1810    }
1811    unsigned long fid = featureIDs.at(0);
1812
1813    osgEarth::ModelSource *modelSource = layer->getModelSource();
1814    osgEarth::Features::FeatureModelSource *fms = dynamic_cast<osgEarth::Features::FeatureModelSource *>(modelSource);
1815    if (fms != NULL) {
1816        osgEarth::Features::FeatureSource *fs = fms->getFeatureSource();
1817        FindFeatureSourceIndexNodeVisitor visitor;
1818        visitor.source = fs;
1819        _sceneRoot->accept(visitor);
1820        TRACE("Num FeatureSourceIndexNodes found: %lu", visitor.nodes.size());
1821        for (size_t i = 0; i < visitor.nodes.size(); i++) {
1822            osgEarth::Features::FeatureIndex *index = visitor.nodes[i]->getIndex();
1823#if OSGEARTH_MIN_VERSION_REQUIRED(2, 8, 0)
1824            osgEarth::ObjectID id = index->getObjectID(fid);
1825            if (doPlacard) {
1826                osgEarth::Features::Feature *feature = index->getFeature(id);
1827                if (feature) {
1828                    // Find feature centroid
1829                    osgEarth::GeoPoint location;
1830                    if (feature->getGeometry()) {
1831                        osg::BoundingSphered bound;
1832                        feature->getWorldBound(getMapSRS(), bound);
1833                        location.set(getMapSRS(), bound.center(),
1834                                     osgEarth::ALTMODE_ABSOLUTE);
1835                    }
1836                    addPlacard(location, feature, layerName);
1837                }
1838            }
1839            setHighlightByObjectID(id);
1840            TRACE("FID %lu = OID %d", fid, id);
1841#endif
1842        }
1843        _needsRedraw = true;
1844    }
1845    updateDebugLabel();
1846}
1847
1848void Renderer::updateDebugLabel()
1849{
1850    if (!_debugLabel.valid()) return;
1851    std::ostringstream oss;
1852    oss << "Selection:";
1853    for (FeatureSelectionHashmap::iterator itr = _selectedFeatures.begin();
1854         itr != _selectedFeatures.end(); ++itr) {
1855        bool found = false;
1856        oss << std::endl << itr->first << ":";
1857        for (FeatureSelectionHashmap::mapped_type::iterator fitr = itr->second.begin();
1858             fitr != itr->second.end(); ++fitr) {
1859            oss << " " << *fitr;
1860            found = true;
1861        }
1862        if (!found) {
1863            oss << " None";
1864        }
1865    }
1866    _debugLabel->setText(oss.str());
1867}
1868
1869void Renderer::addPlacard(const osgEarth::GeoPoint& location,
1870                          osgEarth::Features::Feature *feature,
1871                          const char *layerName)
1872{
1873    if (feature == NULL) return;
1874    Placard placard = getPlacardConfig(layerName);
1875    if (!placard.enabled())
1876        return;
1877
1878    const osgEarth::Features::AttributeTable &attrs = feature->getAttrs();
1879    if (placard.getNumEntries() == 0) {
1880        placard.addAllAttributes(attrs);
1881    }
1882    PlacardNode *label =
1883        new PlacardNode(_mapNode.get(), location, placard, attrs);
1884#ifndef NEW_ANNOTATION_API
1885    label->getOrCreateStateSet()->setRenderBinDetails(INT_MAX, "RenderBin");
1886#endif
1887    getAnnotations()->addChild(label);
1888}
1889
1890void Renderer::clearSelection()
1891{
1892    _selectedFeatures.clear();
1893    clearHighlight();
1894    clearSelectionAnnotationNodes();
1895    clearBoxSelection();
1896    updateDebugLabel();
1897    _needsRedraw = true;
1898}
1899
1900/**
1901 * \brief Remove annotation nodes created during a pick/selection
1902 *
1903 * The primary purpose of this method is to take down the feature placard
1904 */
1905void Renderer::clearSelectionAnnotationNodes()
1906{
1907    osg::Group *nodes = getAnnotations();
1908    std::vector<osg::Node *> toRemove;
1909    for (unsigned int i = 0; i < nodes->getNumChildren(); i++) {
1910        osg::Node *node = nodes->getChild(i);
1911        // This can be Placard, PlacardLabel, Label, Place or Track Node
1912#ifdef NEW_ANNOTATION_API
1913        if (dynamic_cast<osgEarth::Annotation::GeoPositionNode *>(node) != NULL) {
1914#else
1915        if (dynamic_cast<osgEarth::Annotation::OrthoNode *>(node) != NULL) {
1916#endif
1917            toRemove.push_back(node);
1918        }
1919    }
1920    for (std::vector<osg::Node *>::iterator itr = toRemove.begin();
1921         itr != toRemove.end(); ++itr) {
1922        osgEarth::Annotation::AnnotationNode *anno = dynamic_cast<osgEarth::Annotation::AnnotationNode *>(*itr);
1923        if (anno != NULL) {
1924            if (_hovered.find(anno) != _hovered.end()) {
1925                _hovered.erase(anno);
1926            }
1927            if (_selected.find(anno) != _selected.end()) {
1928                _selected.erase(anno);
1929            }
1930        }
1931        nodes->removeChild(*itr);
1932    }
1933    _needsRedraw = true;
1934}
1935
1936void Renderer::initBoxSelection(int x, int y)
1937{
1938    double latitude, longitude;
1939    if (!mouseToLatLong(x, y, &latitude, &longitude)) {
1940        return;
1941    }
1942    _anchorLat = latitude;
1943    _anchorLong = longitude;
1944    addRhumbBox(latitude, latitude, longitude, longitude);
1945}
1946
1947void Renderer::updateBoxSelection(int x, int y)
1948{
1949    double nlat, nlong;
1950    if (!mouseToLatLong(x, y, &nlat, &nlong)) {
1951        return;
1952    }
1953    double latMin, latMax, longMin, longMax;
1954    if (nlong >= _anchorLong && nlat >= _anchorLat) {
1955        // +x +y
1956        longMin = _anchorLong;
1957        latMin = _anchorLat;
1958        longMax = nlong;
1959        latMax = nlat;
1960    } else if (nlong < _anchorLong && nlat >= _anchorLat) {
1961        // -x +y
1962        longMin = nlong;
1963        latMin = _anchorLat;
1964        longMax = _anchorLong;
1965        latMax = nlat;
1966    } else if (nlong < _anchorLong && nlat < _anchorLat) {
1967        // -x -y
1968        longMin = nlong;
1969        latMin = nlat;
1970        longMax = _anchorLong;
1971        latMax = _anchorLat;
1972    } else {
1973        // +x -y
1974        longMin = _anchorLong;
1975        latMin = nlat;
1976        longMax = nlong;
1977        latMax = _anchorLat;
1978    }
1979    osgEarth::Annotation::FeatureNode *node = _selectionBox.get();
1980    osgEarth::Symbology::Geometry *geom = node->getFeature()->getGeometry();
1981    (*geom)[0] = osg::Vec3d(longMin, latMin, 0);
1982    (*geom)[1] = osg::Vec3d(longMax, latMin, 0);
1983    (*geom)[2] = osg::Vec3d(longMax, latMax, 0);
1984    (*geom)[3] = osg::Vec3d(longMin, latMax, 0);
1985    node->init();
1986#ifndef NEW_ANNOTATION_API
1987    for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = _selected.begin();
1988         itr != _selected.end(); ++itr) {
1989        (*itr)->clearDecoration();
1990    }
1991#endif
1992    _selected.clear();
1993    SelectPlaceNodesVisitor spnv(this, latMin, latMax, longMin, longMax);
1994    _placeNodes->accept(spnv);
1995    _needsRedraw = true;
1996}
1997
1998void Renderer::getBoxSelection(double *latMin, double *latMax,
1999                               double *longMin, double *longMax,
2000                               const osgEarth::SpatialReference *outSRS)
2001{
2002    osgEarth::Annotation::FeatureNode *node = _selectionBox.get();
2003    if (node == NULL)
2004        return;
2005    osgEarth::Symbology::Geometry *geom = node->getFeature()->getGeometry();
2006    if (geom == NULL)
2007        return;
2008    *latMin = (*geom)[0].y();
2009    *latMax = (*geom)[2].y();
2010    *longMin = (*geom)[0].x();
2011    *longMax = (*geom)[2].x();
2012    if (outSRS == NULL)
2013        return;
2014
2015    const osgEarth::SpatialReference* fromSRS = _mapNode->getMapSRS()->getGeographicSRS();
2016    osgEarth::GeoPoint pt1(fromSRS, *longMin, *latMin);
2017    osgEarth::GeoPoint pt2(fromSRS, *longMax, *latMax);
2018    pt1.transform(outSRS);
2019    pt2.transform(outSRS);
2020    *latMin = pt1.y();
2021    *latMax = pt2.y();
2022    *longMin = pt1.x();
2023    *longMax = pt2.x();
2024}
2025
2026void SelectPlaceNodesVisitor::apply(osg::Node& node)
2027{
2028    osgEarth::Annotation::PlaceNode *placeNode =
2029        dynamic_cast<osgEarth::Annotation::PlaceNode*>(&node);
2030    if (placeNode != NULL) {
2031        const osgEarth::SpatialReference *outSRS =
2032            _renderer->getMapSRS()->getGeographicSRS();
2033        osgEarth::GeoPoint pt = placeNode->getPosition();
2034        pt = pt.transform(outSRS);
2035        if (pt.x() >= _longMin && pt.x() <= _longMax &&
2036            pt.y() >= _latMin && pt.y() <= _latMax) {
2037            if (_renderer->select(placeNode)) {
2038                TRACE("Select PlaceNode: %g %g n:'%s' t:'%s'",
2039                     pt.x(), pt.y(), placeNode->getName().c_str(),
2040                     placeNode->getText().c_str());
2041#ifndef NEW_ANNOTATION_API
2042                placeNode->setDecoration("select");
2043#endif
2044            }
2045        }
2046    }
2047    traverse(node);
2048}
2049
2050void Renderer::clearBoxSelection()
2051{
2052    if (_annotations.valid() && _selectionBox.valid()) {
2053        _annotations->removeChild(_selectionBox.get());
2054        _selectionBox = NULL;
2055    }
2056#ifndef NEW_ANNOTATION_API
2057    for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = _selected.begin();
2058         itr != _selected.end(); ++itr) {
2059        (*itr)->clearDecoration();
2060    }
2061#endif
2062    _selected.clear();
2063    _needsRedraw = true;
2064}
2065
2066void Renderer::addRhumbBox(double latMin, double latMax,
2067                           double longMin, double longMax)
2068{
2069    if (!_mapNode.valid()) {
2070        ERROR("No map node");
2071        return;
2072    }
2073    initAnnotations();
2074
2075    if (_selectionBox.valid()) {
2076        osgEarth::Symbology::Geometry *geom = _selectionBox->getFeature()->getGeometry();
2077        (*geom)[0] = osg::Vec3d(longMin, latMin, 0);
2078        (*geom)[1] = osg::Vec3d(longMax, latMin, 0);
2079        (*geom)[2] = osg::Vec3d(longMax, latMax, 0);
2080        (*geom)[3] = osg::Vec3d(longMin, latMax, 0);
2081        _selectionBox->init();
2082    } else {
2083        const osgEarth::SpatialReference* geoSRS = _mapNode->getMapSRS()->getGeographicSRS();
2084        osgEarth::Symbology::Geometry *geom = new osgEarth::Symbology::Polygon();
2085        geom->push_back(osg::Vec3d(longMin, latMin, 0));
2086        geom->push_back(osg::Vec3d(longMax, latMin, 0));
2087        geom->push_back(osg::Vec3d(longMax, latMax, 0));
2088        geom->push_back(osg::Vec3d(longMin, latMax, 0));
2089        osgEarth::Symbology::Style boxStyle;
2090#if 1
2091        osgEarth::Symbology::PolygonSymbol *poly = boxStyle.getOrCreate<osgEarth::Symbology::PolygonSymbol>();
2092        poly->fill()->color() = osgEarth::Symbology::Color::Cyan;
2093        poly->fill()->color().a() = 0.5;
2094#else
2095        osgEarth::Symbology::LineSymbol *line = boxStyle.getOrCreate<osgEarth::Symbology::LineSymbol>();
2096        line->stroke()->color() = osgEarth::Symbology::Color::Yellow;
2097        line->stroke()->width() = 2.0f;
2098        //line->creaseAngle() = 45.0f;
2099        line->tessellation() = 10;
2100#endif
2101        osgEarth::Symbology::AltitudeSymbol *alt = boxStyle.getOrCreate<osgEarth::Symbology::AltitudeSymbol>();
2102        alt->clamping() = osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
2103        //alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
2104        alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_DRAPE;
2105        //alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_SCENE;
2106#if 0
2107        osgEarth::Symbology::RenderSymbol* rs = boxStyle.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
2108        rs->depthOffset()->enabled() = true;
2109        rs->depthOffset()->minBias() = 1000;
2110#endif
2111        osgEarth::Features::Feature *feature = new osgEarth::Features::Feature(geom, geoSRS, boxStyle);
2112        //feature->geoInterp() = osgEarth::GEOINTERP_GREAT_CIRCLE;
2113        feature->geoInterp() = osgEarth::GEOINTERP_RHUMB_LINE;
2114        _selectionBox =
2115            new osgEarth::Annotation::FeatureNode(_mapNode, feature);
2116        _annotations->addChild(_selectionBox.get());
2117    }
2118
2119    _needsRedraw = true;
2120}
2121
2122void Renderer::addPlaceNode(double latitude, double longitude, char *labelText)
2123{
2124    if (!_mapNode.valid()) {
2125        ERROR("No map node");
2126        return;
2127    }
2128    initAnnotations();
2129
2130    const osgEarth::SpatialReference *geoSRS = _mapNode->getMapSRS()->getGeographicSRS();
2131
2132    osgEarth::Symbology::Style pin;
2133    pin.getOrCreate<osgEarth::Symbology::IconSymbol>()->url()->setLiteral(getPinIcon());
2134    osgEarth::Annotation::AnnotationNode *anno =
2135        new osgEarth::Annotation::PlaceNode(_mapNode, osgEarth::GeoPoint(geoSRS, longitude, latitude), labelText, pin);
2136    anno->setName(labelText);
2137    _placeNodes->addChild(anno);
2138#ifdef USE_RTT_PICKER
2139    osgEarth::Registry::objectIndex()->tagNode(anno, anno);
2140#else
2141    osgEarth::Annotation::DecorationInstaller
2142        highlightInstaller("select", new osgEarth::Annotation::HighlightDecoration(osg::Vec4f(1,1,0,0.5)));
2143    _placeNodes->accept(highlightInstaller);
2144
2145    // scale labels when hovering:
2146    osgEarth::Annotation::DecorationInstaller
2147        scaleInstaller("hover", new osgEarth::Annotation::ScaleDecoration(1.1f));
2148    _placeNodes->accept(scaleInstaller);
2149#endif
2150#if 0
2151    writeScene("/tmp/test.osg");
2152#endif
2153    _needsRedraw = true;
2154}
2155#if 0
2156void Renderer::hoverFeatureNodes(int x, int y, bool invertY)
2157{
2158    if (!_mapNode.valid()) {
2159        ERROR("No map node");
2160        return;
2161    }
2162    osgEarth::IntersectionPicker picker(_viewer.get(), _mapNode.get());
2163    osgEarth::IntersectionPicker::Hits hits;
2164    float mouseX = (float)x;
2165    float mouseY = (float)y;
2166    if (invertY) {
2167        mouseY = ((float)_windowHeight - mouseY);
2168    }
2169    std::set<osgEarth::Annotation::FeatureNode*> toUnHover;
2170    for (std::set<osgEarth::Annotation::FeatureNode*>::iterator itr = _hovered.begin();
2171         itr != _hovered.end(); ++itr) {
2172        toUnHover.insert(*itr);
2173    }
2174    if (picker.pick(mouseX, mouseY, hits)) {
2175        TRACE("Picker hits: %d", hits.size());
2176        for (osgEarth::IntersectionPicker::Hits::const_iterator hitr = hits.begin();
2177             hitr != hits.end(); ++hitr) {
2178            osgEarth::Annotation::FeatureNode *anno =
2179                picker.getNode<osgEarth::Annotation::FeatureNode>(*hitr);
2180            if (anno != NULL) {
2181                TRACE("Hit FeatureNode: %p", anno);
2182                if (_hovered.find(anno) == _hovered.end()) {
2183                    _hovered.insert(anno);
2184                    anno->setDecoration("hover");
2185                    _needsRedraw = true;
2186                }
2187                toUnHover.erase(anno);
2188            }
2189        }
2190#if 0
2191        writeScene("/tmp/test.osgt");
2192#endif
2193    }
2194    for (std::set<osgEarth::Annotation::FeatureNode *>::iterator itr = toUnHover.begin();
2195         itr != toUnHover.end(); ++itr) {
2196        _hovered.erase(*itr);
2197        (*itr)->clearDecoration();
2198        _needsRedraw = true;
2199    }
2200}
2201#endif
2202
2203#ifdef USE_RTT_PICKER
2204void Renderer::hoverPlaceNode(int x, int y, bool invertY)
2205{
2206    if (!_placeNodes.valid()) {
2207        TRACE("No place nodes");
2208        return;
2209    }
2210    return;
2211
2212    float mouseX = (float)x;
2213    float mouseY = (float)y;
2214    if (invertY) {
2215        mouseY = ((float)_windowHeight - mouseY);
2216    }
2217    if (_picker->pick(_viewer.get(), mouseX, mouseY)) {
2218        TRACE("Hover pick queued: %g %g", mouseX, mouseY);
2219    } else {
2220        TRACE("Failed to queue pick: %g %g", mouseX, mouseY);
2221    }
2222}
2223
2224void Renderer::deletePlaceNode(int x, int y, bool invertY)
2225{
2226    if (!_placeNodes.valid()) {
2227        TRACE("No place nodes");
2228        return;
2229    }
2230    return;
2231
2232    osg::ref_ptr<osgEarth::Util::RTTPicker> picker = new osgEarth::Util::RTTPicker;
2233    picker->addChild(_placeNodes.get());
2234    osg::ref_ptr<DeleteCallback> callback = new DeleteCallback(this);
2235    float mouseX = (float)x;
2236    float mouseY = (float)y;
2237    if (invertY) {
2238        mouseY = ((float)_windowHeight - mouseY);
2239    }
2240    if (picker->pick(_viewer.get(), mouseX, mouseY, callback)) {
2241        TRACE("Delete pick queued: %g %g", mouseX, mouseY);
2242    }
2243}
2244#else
2245void Renderer::hoverPlaceNode(int x, int y, bool invertY)
2246{
2247    if (!_placeNodes.valid()) {
2248        TRACE("No place nodes");
2249        return;
2250    }
2251
2252    osgEarth::IntersectionPicker picker(_viewer.get(), _placeNodes.get());
2253    osgEarth::IntersectionPicker::Hits hits;
2254    float mouseX = (float)x;
2255    float mouseY = (float)y;
2256    if (invertY) {
2257        mouseY = ((float)_windowHeight - mouseY);
2258    }
2259    std::set<osgEarth::Annotation::AnnotationNode*> toUnHover;
2260    for (std::set<osgEarth::Annotation::AnnotationNode*>::iterator itr = _hovered.begin();
2261         itr != _hovered.end(); ++itr) {
2262        toUnHover.insert(*itr);
2263    }
2264    if (picker.pick(mouseX, mouseY, hits)) {
2265        TRACE("Picker hits: %d", hits.size());
2266        for (osgEarth::IntersectionPicker::Hits::const_iterator hitr = hits.begin();
2267             hitr != hits.end(); ++hitr) {
2268            TRACE("Hit: node %p drawable %p idx %d", picker.getNode<osg::Node>(*hitr), hitr->drawable.get(), hitr->primitiveIndex);
2269            osgEarth::Annotation::AnnotationNode *anno =
2270                picker.getNode<osgEarth::Annotation::AnnotationNode>(*hitr);
2271            if (anno != NULL && anno->getDecoration().empty()) {
2272                TRACE("Hit AnnotationNode: %p", anno);
2273                if (_hovered.find(anno) == _hovered.end()) {
2274                    _hovered.insert(anno);
2275                    anno->setDecoration("hover");
2276                    _needsRedraw = true;
2277                }
2278                toUnHover.erase(anno);
2279            }
2280        }
2281#if 0
2282        writeScene("/tmp/test.osgt");
2283#endif
2284    }
2285    for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = toUnHover.begin();
2286         itr != toUnHover.end(); ++itr) {
2287        _hovered.erase(*itr);
2288        (*itr)->clearDecoration();
2289        _needsRedraw = true;
2290    }
2291}
2292
2293void Renderer::deletePlaceNode(int x, int y, bool invertY)
2294{
2295    if (!_placeNodes.valid()) {
2296        TRACE("No place nodes");
2297        return;
2298    }
2299    osgEarth::IntersectionPicker picker(_viewer.get(), _placeNodes.get());
2300    osgEarth::IntersectionPicker::Hits hits;
2301    float mouseX = (float)x;
2302    float mouseY = (float)y;
2303    if (invertY) {
2304        mouseY = ((float)_windowHeight - mouseY);
2305    }
2306    if (picker.pick(mouseX, mouseY, hits)) {
2307        TRACE("Picker hit!");
2308        // prevent multiple hits on the same instance
2309        std::set<osgEarth::Annotation::AnnotationNode *> fired;
2310        for (osgEarth::IntersectionPicker::Hits::const_iterator hitr = hits.begin();
2311             hitr != hits.end(); ++hitr) {
2312            osgEarth::Annotation::AnnotationNode *anno =
2313                picker.getNode<osgEarth::Annotation::AnnotationNode>(*hitr);
2314            if (anno != NULL && fired.find(anno) == fired.end()) {
2315                fired.insert(anno);
2316                _needsRedraw = true;
2317            }
2318        }
2319        for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = fired.begin();
2320             itr != fired.end(); ++itr) {
2321            (*itr)->clearDecoration();
2322            if (_hovered.find(*itr) != _hovered.end()) {
2323                _hovered.erase(*itr);
2324            }
2325            if (_selected.find(*itr) != _selected.end()) {
2326                _selected.erase(*itr);
2327            }
2328           _placeNodes->removeChild(*itr);
2329        }
2330    } else {
2331        TRACE("NO Picker hits");
2332    }
2333#if 0
2334    writeScene("/tmp/test.osg");
2335#endif
2336}
2337#endif
2338
2339void Renderer::addModelLayer(const char *name,
2340                             osgEarth::ModelSourceOptions& opts,
2341                             unsigned int pos,
2342                             bool enableCache,
2343                             bool lighting,
2344                             bool visible,
2345                             bool terrainPatch)
2346{
2347    if (!_map.valid()) {
2348        ERROR("No map");
2349        return;
2350    }
2351    TRACE("layer: %s", name);
2352    osgEarth::ModelLayerOptions layerOpts(name, opts);
2353    if (!enableCache) {
2354        TRACE("Disabling cache for layer %s", name);
2355        layerOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
2356    }
2357    if (!visible) {
2358        layerOpts.visible() = false;
2359    }
2360    layerOpts.lightingEnabled() = lighting;
2361    if (terrainPatch) {
2362        layerOpts.terrainPatch() = true;
2363    }
2364    osgEarth::ModelLayer *layer = new osgEarth::ModelLayer(layerOpts);
2365    if (pos < (unsigned int)_map->getNumModelLayers()) {
2366        _map->insertModelLayer(layer, pos);
2367    } else {
2368        _map->addModelLayer(layer);
2369    }
2370    _needsRedraw = true;
2371}
2372
2373void Renderer::removeModelLayer(const char *name)
2374{
2375    if (!_map.valid()) {
2376        ERROR("No map");
2377        return;
2378    }
2379    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
2380    if (layer != NULL) {
2381        _map->removeModelLayer(layer);
2382        _needsRedraw = true;
2383    } else {
2384        TRACE("Model layer not found: %s", name);
2385    }
2386}
2387
2388void Renderer::moveModelLayer(const char *name, unsigned int pos)
2389{
2390    if (!_map.valid()) {
2391        ERROR("No map");
2392        return;
2393    }
2394    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
2395    if (layer != NULL) {
2396        _map->moveModelLayer(layer, pos);
2397        _needsRedraw = true;
2398    } else {
2399        TRACE("Model layer not found: %s", name);
2400    }
2401}
2402
2403void Renderer::setModelLayerOpacity(const char *name, double opacity)
2404{
2405    if (!_map.valid()) {
2406        ERROR("No map");
2407        return;
2408    }
2409    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
2410    if (layer != NULL) {
2411        layer->setOpacity(opacity);
2412        _needsRedraw = true;
2413    } else {
2414        TRACE("Model layer not found: %s", name);
2415    }
2416}
2417#if 0
2418void Renderer::setModelLayerVisibleRange(const char *name, float min, float max)
2419{
2420    if (!_map.valid()) {
2421        ERROR("No map");
2422        return;
2423    }
2424    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
2425    if (layer != NULL) {
2426        layer->minVisibleRange(min);
2427        layer->maxVisibleRange(max);
2428        _needsRedraw = true;
2429    } else {
2430        TRACE("Model layer not found: %s", name);
2431    }
2432}
2433#endif
2434void Renderer::setModelLayerVisibility(const char *name, bool state)
2435{
2436    if (!_map.valid()) {
2437        ERROR("No map");
2438        return;
2439    }
2440    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
2441    if (layer != NULL) {
2442        layer->setVisible(state);
2443        _needsRedraw = true;
2444    } else {
2445        TRACE("Model layer not found: %s", name);
2446    }
2447}
2448
2449void Renderer::setLODScale(float scale)
2450{
2451    if (!_viewer.valid())
2452        return;
2453
2454    TRACE("Setting LOD scale to %g", scale);
2455    _viewer->getCamera()->setLODScale(scale);
2456    _needsRedraw = true;
2457}
2458
2459/**
2460 * \brief Resize the render window (image size for renderings)
2461 */
2462void Renderer::setWindowSize(int width, int height)
2463{
2464    if (_windowWidth == width &&
2465        _windowHeight == height) {
2466        TRACE("No change");
2467        return;
2468    }
2469
2470    TRACE("Setting window size to %dx%d", width, height);
2471
2472    double origBitrate = getMaximumBitrate();
2473
2474    _windowWidth = width;
2475    _windowHeight = height;
2476
2477    setMaximumBitrate(origBitrate);
2478
2479    if (_viewer.valid()) {
2480#ifdef USE_OFFSCREEN_RENDERING
2481#ifdef USE_PBUFFER
2482        osg::ref_ptr<osg::GraphicsContext> pbuffer;
2483        osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
2484        traits->x = 0;
2485        traits->y = 0;
2486        traits->width = _windowWidth;
2487        traits->height = _windowHeight;
2488        traits->red = 8;
2489        traits->green = 8;
2490        traits->blue = 8;
2491        traits->alpha = 8;
2492        traits->windowDecoration = false;
2493        traits->pbuffer = true;
2494        traits->doubleBuffer = true;
2495        traits->sharedContext = 0;
2496
2497        pbuffer = osg::GraphicsContext::createGraphicsContext(traits.get());
2498        if (pbuffer.valid()) {
2499            TRACE("Pixel buffer has been created successfully.");
2500        } else {
2501            ERROR("Pixel buffer has not been created successfully.");
2502        }
2503        osg::Camera *camera = new osg::Camera;
2504        camera->setGraphicsContext(pbuffer.get());
2505        //camera->getOrCreateStateSet()->setGlobalDefaults();
2506        camera->setViewport(new osg::Viewport(0, 0, _windowWidth, _windowHeight));
2507        GLenum buffer = pbuffer->getTraits()->doubleBuffer ? GL_BACK : GL_FRONT;
2508        camera->setDrawBuffer(buffer);
2509        camera->setReadBuffer(buffer);
2510        camera->setFinalDrawCallback(_captureCallback.get());
2511        _viewer->addSlave(camera, osg::Matrixd(), osg::Matrixd());
2512        _viewer->realize();
2513#else
2514        if (_captureCallback.valid()) {
2515            _captureCallback->getTexture()->setTextureSize(_windowWidth, _windowHeight);
2516        }
2517        osgViewer::ViewerBase::Windows windows;
2518        _viewer->getWindows(windows);
2519        if (windows.size() == 1) {
2520            windows[0]->setWindowRectangle(0, 0, _windowWidth, _windowHeight);
2521        } else {
2522            ERROR("Num windows: %lu", windows.size());
2523        }
2524#endif
2525#else
2526        osgViewer::ViewerBase::Windows windows;
2527        _viewer->getWindows(windows);
2528#if 1
2529        for (osgViewer::Viewer::Windows::iterator itr = windows.begin();
2530             itr != windows.end(); ++itr) {
2531            osgViewer::GraphicsWindow *window = *itr;
2532            window->setWindowRectangle(0, 0, _windowWidth, _windowHeight);
2533            //window->grabFocusIfPointerInWindow();
2534        }
2535        //_viewer->getCamera()->setViewport(new osg::Viewport(0, 0, _windowWidth, _windowHeight));
2536#else
2537        if (windows.size() == 1) {
2538            windows[0]->setWindowRectangle(0, 0, _windowWidth, _windowHeight);
2539        } else {
2540            ERROR("Num windows: %lu", windows.size());
2541        }
2542#endif
2543#endif
2544        // HACK: Without this, the mouse coordinate mapping uses the old size
2545        // for 1 frame.
2546        assert(_viewer->getEventQueue() != NULL);
2547        //TRACE("Window EventQueue: %p", getEventQueue());
2548        //TRACE("Viewer EventQueue: %p", _viewer->getEventQueue());
2549        _viewer->getEventQueue()->windowResize(0, 0, _windowWidth, _windowHeight);
2550        _needsRedraw = true;
2551    }
2552}
2553
2554/**
2555 * \brief Set the orientation of the camera from a quaternion
2556 * rotation
2557 *
2558 * \param[in] quat A quaternion with scalar part first: w,x,y,z
2559 * \param[in] absolute Is rotation absolute or relative?
2560 */
2561void Renderer::setCameraOrientation(const double quat[4], bool absolute)
2562{
2563    if (_manipulator.valid()) {
2564        _manipulator->setRotation(osg::Quat(quat[1], quat[2], quat[3], quat[0]));
2565        _needsRedraw = true;
2566    }
2567}
2568
2569/**
2570 * \brief Reset pan, zoom, clipping planes and optionally rotation
2571 *
2572 * \param[in] resetOrientation Reset the camera rotation/orientation also
2573 */
2574void Renderer::resetCamera(bool resetOrientation)
2575{
2576    TRACE("Enter: resetOrientation=%d", resetOrientation ? 1 : 0);
2577    if (_viewer.valid()) {
2578        _viewer->home();
2579        _needsRedraw = true;
2580    }
2581}
2582
2583/**
2584 * \brief Perform a 2D translation of the camera
2585 *
2586 * x,y pan amount are specified as signed absolute pan amount in viewport
2587 * units -- i.e. 0 is no pan, .5 is half the viewport, 2 is twice the viewport,
2588 * etc.
2589 *
2590 * \param[in] x Viewport coordinate horizontal panning (positive number pans
2591 * camera left, object right)
2592 * \param[in] y Viewport coordinate vertical panning (positive number pans
2593 * camera up, object down)
2594 */
2595void Renderer::panCamera(double x, double y)
2596{
2597    TRACE("Enter: %g %g", x, y);
2598
2599    if (_manipulator.valid()) {
2600        // Wants mouse delta x,y in normalized screen coords
2601        _manipulator->pan(x, y);
2602        _needsRedraw = true;
2603    }
2604}
2605
2606void Renderer::rotateCamera(double x, double y)
2607{
2608    TRACE("Enter: %g %g", x, y);
2609
2610    if (_manipulator.valid()) {
2611        _manipulator->rotate(x, y);
2612        _needsRedraw = true;
2613    }
2614}
2615
2616/**
2617 * \brief Dolly camera or set orthographic scaling based on camera type
2618 *
2619 * \param[in] y Mouse y coordinate in normalized screen coords
2620 */
2621void Renderer::zoomCamera(double y)
2622{
2623    TRACE("Enter: y: %g", y);
2624
2625    if (_manipulator.valid()) {
2626        // +y = zoom out, -y = zoom in
2627        TRACE("camDist: %g", _manipulator->getDistance());
2628#ifdef LIMIT_ZOOM_BY_MAP_SCALE
2629        if ((_mapScale < 0.0 && y > 0.0) ||
2630            (_mapScale < 0.1 && y < 0.0) ||
2631            (_mapScale > 40000.0 && y > 0.0))
2632            return;
2633#endif
2634#if 1
2635       _manipulator->zoom(0, y);
2636#else
2637        double dist = _manipulator->getDistance();
2638        dist *= (1.0 + y);
2639        _manipulator->setDistance(dist);
2640#endif
2641        _needsRedraw = true;
2642    }
2643}
2644
2645bool Renderer::getImageLayerExtent(const char *name, osgEarth::GeoExtent &ext)
2646{
2647    if (!_map.valid()) {
2648        ERROR("No map");
2649        return false;
2650    }
2651    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
2652    if (layer != NULL) {
2653        osgEarth::TileSource *ts = layer->getTileSource();
2654        if (ts != NULL) {
2655            ext = ts->getDataExtentsUnion();
2656            if (!ext.isValid() && ts->getProfile() != NULL) {
2657                ext = ts->getProfile()->getExtent();
2658            }
2659            TRACE("Image layer %s srs: %s extent: %g %g %g %g",
2660                  name, ext.getSRS() ? ext.getSRS()->getName().c_str() : "nil",
2661                  ext.xMin(), ext.yMin(), ext.xMax(), ext.yMax());
2662            return true;
2663        }
2664    } else {
2665        TRACE("Image layer not found: %s", name);
2666    }
2667    return false;
2668}
2669
2670bool Renderer::getElevationLayerExtent(const char *name, osgEarth::GeoExtent &ext)
2671{
2672    if (!_map.valid()) {
2673        ERROR("No map");
2674        return false;
2675    }
2676    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
2677    if (layer != NULL) {
2678        osgEarth::TileSource *ts = layer->getTileSource();
2679        if (ts != NULL) {
2680            ext = ts->getDataExtentsUnion();
2681            if (!ext.isValid() && ts->getProfile() != NULL) {
2682                ext = ts->getProfile()->getExtent();
2683            }
2684            TRACE("Elevation Layer %s srs: %s extent: %g %g %g %g",
2685                  name, ext.getSRS() ? ext.getSRS()->getName().c_str() : "nil",
2686                  ext.xMin(), ext.yMin(), ext.xMax(), ext.yMax());
2687            return true;
2688        }
2689    } else {
2690        TRACE("Elevation layer not found: %s", name);
2691    }
2692    return false;
2693}
2694
2695bool Renderer::getTerrainMaskLayerExtent(const char *name, osgEarth::GeoExtent &ext)
2696{
2697    if (!_map.valid()) {
2698        ERROR("No map");
2699        return false;
2700    }
2701    osgEarth::MaskLayer *layer = getTerrainMaskLayerByName(name);
2702    if (layer != NULL) {
2703        const osgEarth::SpatialReference *srs = getMapSRS();
2704        osgEarth::GeoExtent newExt(srs);
2705        osg::Vec3dArray *polypts = layer->getOrCreateMaskBoundary(1.0f, srs, NULL);
2706        if (polypts) {
2707            for (size_t i = 0; i < polypts->size(); i++) {
2708                osg::Vec3d pt = (*polypts)[i];
2709                newExt.expandToInclude(pt);
2710            }
2711        }
2712        if (!newExt.isValid()) {
2713            ERROR("Couldn't find extent of layer %s", name);
2714            return false;
2715        }
2716        ext = newExt;
2717        TRACE("Mask Layer %s srs: %s extent: %g %g %g %g",
2718              name, ext.getSRS() ? ext.getSRS()->getName().c_str() : "nil",
2719              ext.xMin(), ext.yMin(), ext.xMax(), ext.yMax());
2720        return true;
2721    } else {
2722        TRACE("Terrain mask layer not found: %s", name);
2723    }
2724    return false;
2725}
2726
2727bool Renderer::getModelLayerExtent(const char *name, osgEarth::GeoExtent &ext)
2728{
2729    if (!_map.valid()) {
2730        ERROR("No map");
2731        return false;
2732    }
2733    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
2734    if (layer != NULL) {
2735        osgEarth::ModelSource *ms = layer->getModelSource();
2736        if (ms != NULL) {
2737            osgEarth::DataExtentList& dataExtents = ms->getDataExtents();
2738            if (dataExtents.size() > 0) {
2739                ext = dataExtents[0];
2740                for (unsigned int i = 1; i < dataExtents.size(); i++) {
2741                    ext.expandToInclude(dataExtents[i]);
2742                }
2743            }
2744            if (!ext.isValid()) {
2745                ERROR("Couldn't find extent of layer %s", name);
2746                return false;
2747            }
2748            TRACE("Model Layer %s srs: %s extent: %g %g %g %g",
2749                  name, ext.getSRS() ? ext.getSRS()->getName().c_str() : "nil",
2750                  ext.xMin(), ext.yMin(), ext.xMax(), ext.yMax());
2751            return true;
2752        }
2753    } else {
2754        TRACE("Model layer not found: %s", name);
2755    }
2756    return false;
2757}
2758
2759/**
2760 * \brief Set view to fit a bounding rectangle
2761 *
2762 * A camera distance is chosen such that the extent rectangle
2763 * will fit in the vertical field of view.
2764 *
2765 * \param ext input GeoExtent
2766 * \param durationSecs Animation duration for the view change.
2767 */
2768void Renderer::setViewpointFromExtent(const osgEarth::GeoExtent& ext,
2769                                      double durationSecs)
2770{
2771    setViewpointFromRect(ext.xMin(), ext.yMin(), ext.xMax(), ext.yMax(),
2772                         ext.getSRS(), durationSecs);
2773}
2774
2775/**
2776 * \brief Set view to fit a bounding rectangle
2777 *
2778 * A camera distance is chosen such that the extent rectangle
2779 * will fit in the vertical field of view.
2780 *
2781 * \param xmin Minimum X coord in the given SRS
2782 * \param ymin Minimum Y coord in the given SRS
2783 * \param xmax Maximum X coord in the given SRS
2784 * \param ymax Maximum Y coord in the given SRS
2785 * \param srs Optional SRS of bounding points.  If omitted, wgs84 is assumed.
2786 * \param durationSecs Animation duration for the view change.
2787 */
2788void Renderer::setViewpointFromRect(double xmin, double ymin,
2789                                    double xmax, double ymax,
2790                                    const osgEarth::SpatialReference *srs,
2791                                    double durationSecs)
2792{
2793    if (!_viewer.valid() || !_manipulator.valid() || getMapSRS() == NULL)
2794        return;
2795
2796    double x = 0.0, y = 0.0, distance = 0.0;
2797    double x1 = xmin, y1 = ymin, x2 = xmax, y2 = ymax;
2798    osgEarth::Units distanceUnits = getMapSRS()->getUnits();
2799    if (getMapSRS()->isProjected() && !getMapSRS()->isPlateCarre()) {
2800        TRACE("Projected");
2801        osg::ref_ptr<const osgEarth::SpatialReference> fromSRS =
2802            srs ? srs : osgEarth::SpatialReference::create("wgs84");
2803        fromSRS->transformExtentToMBR(getMapSRS(), x1, y1, x2, y2);
2804        double height = y2 - y1;
2805        double fovy, aspect, near, far;
2806        _viewer->getCamera()->getProjectionMatrixAsPerspective(fovy, aspect, near, far);
2807        distance = height / (2. * tan(osg::DegreesToRadians(fovy)/2.));
2808        TRACE("fov: %g a: %g n: %g f: %g (%g, %g, %g, %g)", fovy, aspect, near, far, x1, y1, x2, y2);
2809        x = x1 + (x2 - x1)/2.0;
2810        y = y1 + (y2 - y1)/2.0;
2811        TRACE("x: %g y: %g, dist: %g", x, y, distance);
2812    } else if (getMapSRS()->isGeographic() && !getMapSRS()->isPlateCarre()) {
2813        // World coords are ECEF
2814        TRACE("Geocentric");
2815        osg::ref_ptr<const osgEarth::SpatialReference> fromSRS =
2816            srs ? srs : osgEarth::SpatialReference::create("wgs84");
2817        fromSRS->transformExtentToMBR(getMapSRS(), x1, y1, x2, y2);
2818        TRACE("(%g, %g, %g, %g)", x1, y1, x2, y2);
2819        // These are angular units
2820        x = x1 + (x2 - x1)/2.0;
2821        y = y1 + (y2 - y1)/2.0;
2822        // These will be ECEF
2823        osg::Vec3d world1, world2, world3, world4;
2824        // bottom
2825        getMapSRS()->transformToWorld(osg::Vec3d(x, y1, 0), world1);
2826        // top
2827        getMapSRS()->transformToWorld(osg::Vec3d(x, y2, 0), world2);
2828        // Focal point on surface
2829        getMapSRS()->transformToWorld(osg::Vec3d(x, y, 0), world3);
2830        // point on line between top and bottom points
2831        world4 = world1 + (world2 - world1)/2.0;
2832        TRACE("world1: %g,%g,%g world2: %g,%g,%g",
2833             world1.x(), world1.y(), world1.z(),
2834             world2.x(), world2.y(), world2.z());
2835        TRACE("world3: %g,%g,%g world4: %g,%g,%g",
2836             world3.x(), world3.y(), world3.z(),
2837             world4.x(), world4.y(), world4.z());
2838        double height = (world2 - world1).length();
2839        double fovy, aspect, near, far;
2840        _viewer->getCamera()->getProjectionMatrixAsPerspective(fovy, aspect, near, far);
2841        distance = height / (2. * tan(osg::DegreesToRadians(fovy)/2.));
2842        distance -= (world4 - world3).length();
2843        if (distance < 0.0) distance = 0.0;
2844        distanceUnits = osgEarth::Units::METERS;
2845        TRACE("fov: %g a: %g n: %g f: %g (%g, %g, %g, %g)", fovy, aspect, near, far, x1, y1, x2, y2);
2846        TRACE("x: %g y: %g, dist: %g", x, y, distance);
2847    } else {
2848        assert(getMapSRS()->isPlateCarre());
2849        ERROR("Plate Carree not supported");
2850        return;
2851    }
2852    TRACE("Map units: %d", getMapSRS()->getUnits().getType());
2853    osgEarth::Viewpoint vpt;
2854    vpt.focalPoint()->set(getMapSRS(), x, y, 0.0, osgEarth::ALTMODE_ABSOLUTE);
2855    vpt.range()->set(distance, distanceUnits);
2856    vpt.heading()->set(0.0, osgEarth::Units::DEGREES);
2857    vpt.pitch()->set(-90.0, osgEarth::Units::DEGREES);
2858
2859    _manipulator->setViewpoint(vpt, durationSecs);
2860    _needsRedraw = true;
2861}
2862
2863double Renderer::getMaxDistanceFromExtent(const osgEarth::GeoExtent& extent)
2864{
2865    if (!_viewer.valid() || extent.getSRS() == NULL)
2866        return 2.0e7;
2867
2868    double x = 0.0, y = 0.0, distance = 0.0;
2869    double x1 = extent.xMin(), y1 = extent.yMin(), x2 = extent.xMax(), y2 = extent.yMax();
2870    osgEarth::Units distanceUnits = extent.getSRS()->getUnits();
2871    if (extent.getSRS()->isProjected() && !extent.getSRS()->isPlateCarre()) {
2872        TRACE("Projected");
2873        double height = y2 - y1;
2874        double fovy, aspect, near, far;
2875        _viewer->getCamera()->getProjectionMatrixAsPerspective(fovy, aspect, near, far);
2876        distance = height / (2. * tan(osg::DegreesToRadians(fovy)/2.));
2877        TRACE("fov: %g a: %g n: %g f: %g (%g, %g, %g, %g)", fovy, aspect, near, far, x1, y1, x2, y2);
2878        x = x1 + (x2 - x1)/2.0;
2879        y = y1 + (y2 - y1)/2.0;
2880        TRACE("x: %g y: %g, dist: %g", x, y, distance);
2881    } else if (extent.getSRS()->isGeographic() && !extent.getSRS()->isPlateCarre()) {
2882        // World coords are ECEF
2883        TRACE("Geocentric");
2884        TRACE("(%g, %g, %g, %g)", x1, y1, x2, y2);
2885        // These are angular units
2886        x = x1 + (x2 - x1)/2.0;
2887        y = y1 + (y2 - y1)/2.0;
2888        // These will be ECEF
2889        osg::Vec3d world1, world2, world3, world4;
2890        // bottom
2891        getMapSRS()->transformToWorld(osg::Vec3d(x, y1, 0), world1);
2892        // top
2893        getMapSRS()->transformToWorld(osg::Vec3d(x, y2, 0), world2);
2894        // Focal point on surface
2895        getMapSRS()->transformToWorld(osg::Vec3d(x, y, 0), world3);
2896        // point on line between top and bottom points
2897        world4 = world1 + (world2 - world1)/2.0;
2898        double height = (world2 - world1).length();
2899        double fovy, aspect, near, far;
2900        _viewer->getCamera()->getProjectionMatrixAsPerspective(fovy, aspect, near, far);
2901        distance = height / (2. * tan(osg::DegreesToRadians(fovy)/2.));
2902        distance -= (world4 - world3).length();
2903        if (distance < 0.0) distance = 0.0;
2904        distanceUnits = osgEarth::Units::METERS;
2905        TRACE("fov: %g a: %g n: %g f: %g (%g, %g, %g, %g)", fovy, aspect, near, far, x1, y1, x2, y2);
2906        TRACE("x: %g y: %g, dist: %g", x, y, distance);
2907    } else {
2908        assert(extent.getSRS()->isPlateCarre());
2909        ERROR("Plate Carree not supported");
2910    }
2911
2912    return distance;
2913}
2914
2915/**
2916 * \brief Dolly camera to set distance from focal point
2917 *
2918 * \param[in] dist distance in world coordinate units (typically meters)
2919 */
2920void Renderer::setCameraDistance(double dist)
2921{
2922    TRACE("Enter: dist: %g", dist);
2923
2924    if (_manipulator.valid()) {
2925        TRACE("camDist: %g", _manipulator->getDistance());
2926
2927        _manipulator->setDistance(dist);
2928
2929        _needsRedraw = true;
2930    }
2931}
2932
2933void Renderer::keyPress(int key)
2934{
2935    osgGA::EventQueue *queue = getEventQueue();
2936    if (queue != NULL) {
2937        queue->keyPress(key);
2938        _needsRedraw = true;
2939    }
2940}
2941
2942void Renderer::keyRelease(int key)
2943{
2944    osgGA::EventQueue *queue = getEventQueue();
2945    if (queue != NULL) {
2946        queue->keyRelease(key);
2947        _needsRedraw = true;
2948    }
2949}
2950
2951void Renderer::setThrowingEnabled(bool state)
2952{
2953    if (_manipulator.valid()) {
2954        _manipulator->getSettings()->setThrowingEnabled(state);
2955    }
2956}
2957
2958void Renderer::mouseDoubleClick(int button, double x, double y)
2959{
2960    osgGA::EventQueue *queue = getEventQueue();
2961    if (queue != NULL) {
2962        queue->mouseDoubleButtonPress((float)x, (float)y, button);
2963        _needsRedraw = true;
2964    }
2965}
2966
2967void Renderer::mouseClick(int button, double x, double y)
2968{
2969    osgGA::EventQueue *queue = getEventQueue();
2970    if (queue != NULL) {
2971        queue->mouseButtonPress((float)x, (float)y, button);
2972        _needsRedraw = true;
2973    }
2974}
2975
2976void Renderer::mouseDrag(int button, double x, double y)
2977{
2978    osgGA::EventQueue *queue = getEventQueue();
2979    if (queue != NULL) {
2980        queue->mouseMotion((float)x, (float)y);
2981        _needsRedraw = true;
2982    }
2983}
2984
2985void Renderer::mouseRelease(int button, double x, double y)
2986{
2987    osgGA::EventQueue *queue = getEventQueue();
2988    if (queue != NULL) {
2989        queue->mouseButtonRelease((float)x, (float)y, button);
2990        _needsRedraw = true;
2991    }
2992}
2993
2994void Renderer::mouseMotion(double x, double y)
2995{
2996#if 1
2997    osgGA::EventQueue *queue = getEventQueue();
2998    if (queue != NULL) {
2999        queue->mouseMotion((float)x, (float)y);
3000        _needsRedraw = true;
3001    }
3002#else
3003    if (_mouseCoordsTool.valid()) {
3004        if (_viewer.valid() && _coordsCallback.valid()) {
3005            osgEarth::GeoPoint mapPt;
3006            if (mapMouseCoords((float)x, (float)y, mapPt)) {
3007                _coordsCallback->set(mapPt, _viewer->asView(), _mapNode);
3008            } else {
3009                _coordsCallback->reset(_viewer->asView(), _mapNode);
3010            }
3011            _needsRedraw = true;
3012        }
3013    }
3014#endif
3015}
3016
3017void Renderer::mouseScroll(int direction)
3018{
3019    osgGA::EventQueue *queue = getEventQueue();
3020    if (queue != NULL) {
3021        queue->mouseScroll((direction > 0 ? osgGA::GUIEventAdapter::SCROLL_UP : osgGA::GUIEventAdapter::SCROLL_DOWN));
3022        _needsRedraw = true;
3023    }
3024}
3025
3026/**
3027 * \brief Set the RGB background color to render into the image
3028 */
3029void Renderer::setBackgroundColor(float color[3])
3030{
3031    _bgColor[0] = color[0];
3032    _bgColor[1] = color[1];
3033    _bgColor[2] = color[2];
3034
3035    if (_viewer.valid()) {
3036        _viewer->getCamera()->setClearColor(osg::Vec4(color[0], color[1], color[2], 1));
3037
3038        _needsRedraw = true;
3039    }
3040}
3041
3042/**
3043 * \brief Sets flag to trigger rendering next time render() is called
3044 */
3045void Renderer::eventuallyRender()
3046{
3047    _needsRedraw = true;
3048}
3049
3050/**
3051 * \brief Get a timeout for select()
3052 *
3053 * If the paging thread is idle, returns <0 indicating that the
3054 * select call can block until data is available.  Otherwise,
3055 * if the frame render time was faster than the target frame
3056 * rate, return the remaining frame time.
3057 */
3058void Renderer::getTimeout(struct timeval *tv)
3059{
3060    if (!checkNeedToDoFrame()) {
3061        // <0 means no timeout, block until socket has data
3062        // If _idleTimeout is positive, server will disconnect after timeout
3063        tv->tv_sec = _idleTimeout;
3064        tv->tv_usec = 0L;
3065    } else if (_lastFrameTime < _minFrameTime) {
3066        tv->tv_sec = 0L;
3067        tv->tv_usec = (long)1.0e6*(_minFrameTime - _lastFrameTime);
3068    } else {
3069        // No timeout (poll)
3070        tv->tv_sec = tv->tv_usec = 0L;
3071    }
3072}
3073
3074/**
3075 * \brief Check if paging thread is quiescent
3076 */
3077bool Renderer::isPagerIdle()
3078{
3079    if (!_viewer.valid())
3080        return true;
3081    else
3082        return (!_viewer->getDatabasePager()->requiresUpdateSceneGraph() &&
3083                !_viewer->getDatabasePager()->getRequestsInProgress());
3084}
3085
3086/**
3087 * \brief Check is frame call is necessary to render and/or update
3088 * in response to events or timed actions
3089 */
3090bool Renderer::checkNeedToDoFrame()
3091{
3092    return (_needsRedraw || _pickPending ||
3093            (_viewer.valid() && _viewer->checkNeedToDoFrame()));
3094}
3095
3096/**
3097 * \brief MapNode event phase
3098 *
3099 * This is called by the MapNode's event callback during the event
3100 * traversal of the viewer
3101 */
3102void Renderer::mapNodeUpdate()
3103{
3104    computeMapScale();
3105}
3106
3107void Renderer::markFrameStart()
3108{
3109    _startFrameTime = osg::Timer::instance()->tick();
3110}
3111
3112void Renderer::markFrameEnd()
3113{
3114    osg::Timer_t endFrameTick = osg::Timer::instance()->tick();
3115    _lastFrameTime = osg::Timer::instance()->delta_s(_startFrameTime, endFrameTick);
3116    if (_lastFrameTime > _minFrameTime) {
3117        FRAME("BROKE FRAME by %.2f msec", (_lastFrameTime - _minFrameTime)*1000.0f);
3118    } else {
3119        FRAME("Frame time: %.2f msec", _lastFrameTime*1000.0f);
3120    }
3121#ifdef USE_THROTTLING_SLEEP
3122    if (_lastFrameTime < _minFrameTime) {
3123        FRAME("Sleeping for %.2f msec", (_minFrameTime - _lastFrameTime)*1000.0f);
3124        OpenThreads::Thread::microSleep(static_cast<unsigned int>(1000000.0*(_minFrameTime - _lastFrameTime)));
3125    }
3126#endif
3127}
3128
3129/**
3130 * \brief Cause the rendering to render a new image if needed
3131 *
3132 * The _needsRedraw flag indicates if a state change has occured since
3133 * the last rendered frame
3134 */
3135bool Renderer::render()
3136{
3137    if (_viewer.valid() && checkNeedToDoFrame()) {
3138        FRAME("Enter needsRedraw=%d",  _needsRedraw ? 1 : 0);
3139        _renderStartTime = osg::Timer::instance()->tick();
3140        FRAME("Before frame()");
3141        _viewer->frame();
3142        FRAME("After frame()");
3143        _renderStopTime = osg::Timer::instance()->tick();
3144        _renderTime = osg::Timer::instance()->delta_s(_renderStartTime, _renderStopTime);
3145        FRAME("Render time: %g msec", _renderTime * 1000.0);
3146#ifndef SLEEP_AFTER_QUEUE_FRAME
3147        _lastFrameTime = _renderTime;
3148#ifdef USE_THROTTLING_SLEEP
3149        if (_lastFrameTime < _minFrameTime) {
3150            FRAME("Sleeping for %.2f msec", (_minFrameTime - _lastFrameTime)*1000.0f);
3151            OpenThreads::Thread::microSleep(static_cast<unsigned int>(1.0e6*(_minFrameTime - _lastFrameTime)));
3152        }
3153#endif
3154#endif
3155#ifdef WANT_FRAME
3156        if (_viewer->getViewerStats() != NULL) {
3157            _viewer->getViewerStats()->report(std::cerr, _viewer->getViewerStats()->getLatestFrameNumber());
3158        }
3159#endif
3160        _needsRedraw = false;
3161        return true;
3162    } else {
3163        _renderStartTime = _renderStopTime = osg::Timer::instance()->tick();
3164        _renderTime = 0;
3165        return false;
3166    }
3167}
3168
3169/**
3170 * \brief Read back the rendered framebuffer image
3171 */
3172osg::Image *Renderer::getRenderedFrame()
3173{
3174    if (_captureCallback.valid())
3175        return _captureCallback->getImage();
3176    else
3177        return NULL;
3178}
3179
3180void Renderer::setScaleBar(bool state)
3181{
3182    if (_scaleLabel.valid()) {
3183        _scaleLabel->setVisible(state);
3184    }
3185    if (_scaleBar.valid()) {
3186        _scaleBar->setVisible(state);
3187    }
3188    _needsRedraw = true;
3189}
3190
3191void Renderer::setScaleBarUnits(ScaleBarUnits units)
3192{
3193    _scaleBarUnits = units;
3194    _needsRedraw = true;
3195}
3196
3197/**
3198 * \brief Compute the scale ratio of the map based on a horizontal center line
3199 *
3200 * The idea here is to take 2 screen points on a horizontal line in the center
3201 * of the screen and convert to lat/long.  The lat/long coordinates are then
3202 * used to compute the great circle distance (assuming spherical earth) between
3203 * the points.
3204 *
3205 * We could use local projected map coordinates for the distance computation,
3206 * which would be faster; however, this would not show e.g. the change in
3207 * scale at different latitudes
3208 */
3209double Renderer::computeMapScale()
3210{
3211    if (!_scaleLabel.valid() || !_scaleLabel->visible()) {
3212        return -1.0;
3213    }
3214    if (!_mapNode.valid() || _mapNode->getTerrain() == NULL) {
3215        ERROR("No map");
3216        return -1.0;
3217    }
3218    if (!_viewer.valid()) {
3219        ERROR("No viewer");
3220        return -1.0;
3221    }
3222
3223    double x, y;
3224    double pixelWidth = _windowWidth * 0.1 * 2.0;
3225    if (pixelWidth < 10)
3226        pixelWidth = 10;
3227    if (pixelWidth > 150)
3228        pixelWidth = 150;
3229    x = (double)(_windowWidth -1)/2.0 - pixelWidth / 2.0;
3230    y = (double)(_windowHeight-1)/2.0;
3231
3232    osg::Vec3d world1, world2;
3233    if (!_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), x, y, world1)) {
3234        // off map
3235        TRACE("Off map coords: %g %g", x, y);
3236        _scaleLabel->setText("");
3237        _scaleBar->setWidth(0);
3238        return -1.0;
3239    }
3240    x += pixelWidth;
3241    if (!_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), x, y, world2)) {
3242        // off map
3243        TRACE("Off map coords: %g %g", x, y);
3244        _scaleLabel->setText("");
3245        _scaleBar->setWidth(0);
3246        return -1.0;
3247    }
3248
3249#if 0
3250    TRACE("w1: %g %g %g w2: %g %g %g",
3251          world1.x(), world1.y(), world1.z(),
3252          world2.x(), world2.y(), world2.z());
3253#endif
3254
3255    double meters;
3256    double radius = 6378137.0;
3257    if (_mapNode->getMapSRS() &&
3258        _mapNode->getMapSRS()->getEllipsoid()) {
3259        radius = _mapNode->getMapSRS()->getEllipsoid()->getRadiusEquator();
3260    }
3261    if (!_map->isGeocentric() &&
3262        _mapNode->getMapSRS() &&
3263        _mapNode->getMapSRS()->isGeographic()) {
3264        TRACE("Map is geographic");
3265        // World cords are already lat/long
3266        // Compute great circle distance
3267        meters =
3268            osgEarth::GeoMath::distance(world1, world2, _mapNode->getMapSRS());
3269    } else if (_mapNode->getMapSRS()) {
3270        // Get map coords in lat/long
3271        osgEarth::GeoPoint mapPoint1, mapPoint2;
3272        mapPoint1.fromWorld(_mapNode->getMapSRS(), world1);
3273        mapPoint1.makeGeographic();
3274        mapPoint2.fromWorld(_mapNode->getMapSRS(), world2);
3275        mapPoint2.makeGeographic();
3276        // Compute great circle distance
3277        meters =
3278            osgEarth::GeoMath::distance(osg::DegreesToRadians(mapPoint1.y()),
3279                                        osg::DegreesToRadians(mapPoint1.x()),
3280                                        osg::DegreesToRadians(mapPoint2.y()),
3281                                        osg::DegreesToRadians(mapPoint2.x()),
3282                                        radius);
3283    } else {
3284        // Assume geocentric?
3285        ERROR("No map SRS");
3286        _scaleLabel->setText("");
3287        _scaleBar->setWidth(0);
3288        return -1.0;
3289    }
3290
3291    double scale = meters / pixelWidth;
3292    // 1mi = 5280 feet
3293    //double scaleMiles = scale / 1609.344; // International mile = 1609.344m
3294    //double scaleNauticalMiles = scale / 1852.0; // nautical mile = 1852m
3295    //double scaleUSSurveyMiles = scale / 1609.347218694; // US survey mile = 5280 US survey feet
3296    //double scaleUSSurveyFeet = scale * 3937.0/1200.0; // US survey foot = 1200/3937 m
3297#if 0
3298    TRACE("m: %g px: %g m/px: %g", meters, pixelWidth, scale);
3299#endif
3300    _mapScale = scale;
3301    switch (_scaleBarUnits) {
3302    case UNITS_NAUTICAL_MILES: {
3303        double nmi = meters / 1852.0;
3304        scale = nmi / pixelWidth;
3305        nmi = normalizeScaleNauticalMiles(nmi);
3306        pixelWidth = nmi / scale;
3307        if (_scaleLabel.valid()) {
3308            _scaleLabel->setText(osgEarth::Stringify()
3309                                 << nmi
3310                                 << " nmi");
3311        }
3312    }
3313        break;
3314    case UNITS_US_SURVEY_FEET: {
3315        double feet = meters * 3937.0/1200.0;
3316        scale = feet / pixelWidth;
3317        feet = normalizeScaleFeet(feet);
3318        pixelWidth = feet / scale;
3319        if (_scaleLabel.valid()) {
3320            if (feet >= 5280) {
3321                _scaleLabel->setText(osgEarth::Stringify()
3322                                     << feet / 5280.0
3323                                     << " miUS");
3324             } else {
3325                _scaleLabel->setText(osgEarth::Stringify()
3326                                     << feet
3327                                     << " ftUS");
3328            }
3329        }
3330    }
3331        break;
3332    case UNITS_INTL_FEET: {
3333        double feet = 5280.0 * meters / 1609.344;
3334        scale = feet / pixelWidth;
3335        feet = normalizeScaleFeet(feet);
3336        pixelWidth = feet / scale;
3337        if (_scaleLabel.valid()) {
3338            if (feet >= 5280) {
3339                _scaleLabel->setText(osgEarth::Stringify()
3340                                     << feet / 5280.0
3341                                     << " mi");
3342            } else {
3343                _scaleLabel->setText(osgEarth::Stringify()
3344                                     << feet
3345                                     << " ft");
3346            }
3347        }
3348    }
3349        break;
3350    case UNITS_METERS:
3351    default: {
3352        meters = normalizeScaleMeters(meters);
3353        pixelWidth = meters / scale;
3354        if (_scaleLabel.valid()) {
3355            if (meters >= 1000) {
3356                _scaleLabel->setText(osgEarth::Stringify()
3357                                     << meters / 1000.0
3358                                     << " km");
3359            } else {
3360                _scaleLabel->setText(osgEarth::Stringify()
3361                                     << meters
3362                                     << " m");
3363            }
3364        }
3365    }
3366        break;
3367    }
3368    if (_scaleBar.valid()) {
3369        _scaleBar->setWidth(pixelWidth);
3370    }
3371    return scale;
3372}
3373
3374std::string Renderer::getCanonicalPath(const std::string& url) const
3375{
3376    std::string retStr;
3377    std::string proto = osgDB::getServerProtocol(url);
3378    if (proto.empty()) {
3379        retStr = osgDB::getRealPath(url);
3380        if (!osgDB::fileExists(retStr)) {
3381            if (!url.empty() && url[0] != '/') {
3382                // Relative URL, assume local://
3383                std::ostringstream oss;
3384                oss << getCacheDirectory() << "/" << url;
3385                retStr = oss.str();
3386            } else {
3387                retStr = "";
3388            }
3389        }
3390    } else if (proto == "local") {
3391        TRACE("Local protocol: '%s'", getLocalFilePath(url).c_str());
3392        std::ostringstream oss;
3393        oss << getCacheDirectory() << "/" << getLocalFilePath(url);
3394        retStr = oss.str();
3395    } else if (proto == "file") {
3396        TRACE("File: '/%s'", osgDB::getServerFileName(url).c_str());
3397        std::ostringstream oss;
3398        oss << "/" <<  osgDB::getServerFileName(url);
3399        retStr = oss.str();
3400    } else if (proto == "idata") {
3401        std::string fileName = osgDB::getServerFileName(url);
3402        TRACE("IData protocol: coll: '%s', '%s'", osgDB::getServerAddress(url).c_str(), fileName.c_str());
3403        int collection = atoi(osgDB::getServerAddress(url).c_str());
3404        {
3405            std::ostringstream oss;
3406            oss << getCacheDirectory() << "/" << fileName;
3407            retStr = oss.str();
3408            std::string dirPath = osgDB::getFilePath(retStr);
3409            osgDB::makeDirectory(dirPath);
3410        }
3411        // If this is a shape file, get auxiliary files (e.g. .dbf, .shx, etc)
3412        if (osgDB::getFileExtension(fileName) == "shp") {
3413            // Check for files with same basename
3414            std::vector<DirectoryItem> items;
3415            std::string path = osgDB::getFilePath(fileName);
3416            getContents(collection, path.c_str(), items);
3417            // Get name without extension
3418            std::string basename = osgDB::getStrippedName(fileName);
3419            for (std::vector<DirectoryItem>::const_iterator itr = items.begin();
3420                 itr != items.end(); ++itr) {
3421                if (!itr->isDir &&
3422                    osgDB::getStrippedName(itr->name) == basename) {
3423                    std::string localName;
3424                    {
3425                        std::ostringstream oss;
3426                        oss << getCacheDirectory() << "/" << itr->name;
3427                        localName = oss.str();
3428                    }
3429                    if (!localName.empty() && !osgDB::fileExists(localName)) {
3430                        IData::Buffer buf;
3431                        {
3432                            std::ostringstream oss;
3433                            oss << "//" << itr->name;
3434                            IData::getFile(collection, oss.str().c_str(), &buf);
3435                        }
3436                        std::ofstream file(localName.c_str());
3437                        file.write((char *)buf.data, buf.size);
3438                        file.close();
3439                    }
3440                }
3441            }
3442        }
3443        if (!osgDB::fileExists(retStr)) {
3444            std::ostringstream oss;
3445            oss << "//" << fileName;
3446            IData::Buffer buf;
3447            IData::getFile(collection, oss.str().c_str(), &buf);
3448            std::ofstream file(retStr.c_str());
3449            file.write((char *)buf.data, buf.size);
3450            file.close();
3451        }
3452    } else {
3453        TRACE("Protocol: '%s' url: '%s'", proto.c_str(), url.c_str());
3454        retStr = url;
3455    }
3456    return retStr;
3457}
3458
3459void Renderer::writeScene(const std::string& file)
3460{
3461    if (_sceneRoot.valid()) {
3462        GraphPrintVisitor gpv;
3463        _sceneRoot->accept(gpv);
3464        //osgDB::writeNodeFile(*_sceneRoot.get(), file);
3465    }
3466}
Note: See TracBrowser for help on using the repository browser.