source: geovis/trunk/Renderer.cpp @ 6549

Last change on this file since 6549 was 6549, checked in by ldelgass, 6 years ago

Add geovis support for WMS-T animated sequence

File size: 112.1 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(0, 2, 2, 0), 2.0f);
504        _debugLabel =
505            new osgEarth::Util::Controls::LabelControl("Selection: None", 12.0f);
506        _debugLabel->setForeColor(osg::Vec4f(0, 0, 0, 1));
507        //_debugLabel->setHaloColor(osg::Vec4f(1, 1, 1, 1));
508        _debugBox->addControl(_debugLabel.get());
509        _debugBox->setVertFill(true);
510        _debugBox->setForeColor(osg::Vec4f(0, 0, 0, 1));
511        _debugBox->setBackColor(osg::Vec4f(1, 1, 1, 0.6));
512        osgEarth::Util::Controls::ControlCanvas::getOrCreate(_viewer.get())->addControl(_debugBox.get());
513    }
514    if (_copyrightScaleBox.valid())
515        return;
516    _copyrightScaleBox =
517        new osgEarth::Util::Controls::HBox(osgEarth::Util::Controls::Control::ALIGN_RIGHT,
518                                           osgEarth::Util::Controls::Control::ALIGN_BOTTOM,
519                                           osgEarth::Util::Controls::Gutter(0, 2, 2, 0), 2.0f);
520    _attribution = "Map data © OpenStreetMap";
521    _copyrightLabel =
522        new osgEarth::Util::Controls::LabelControl(_attribution, 12.0f);
523    _copyrightLabel->setForeColor(osg::Vec4f(1, 1, 1, 1));
524    _copyrightLabel->setHaloColor(osg::Vec4f(0, 0, 0, 1));
525    _copyrightLabel->setEncoding(osgText::String::ENCODING_UTF8);
526    _scaleLabel =
527        new osgEarth::Util::Controls::LabelControl("- km", 12.0f);
528    _scaleLabel->setForeColor(osg::Vec4f(1, 1, 1, 1));
529    _scaleLabel->setHaloColor(osg::Vec4f(0, 0, 0, 1));
530    _scaleBar =
531        new osgEarth::Util::Controls::Frame();
532    _scaleBar->setVertFill(true);
533    _scaleBar->setForeColor(osg::Vec4f(0, 0, 0, 1));
534    _scaleBar->setBackColor(osg::Vec4f(1, 1, 1, 0.6));
535    _scaleBar->setBorderColor(osg::Vec4f(0, 0, 0 ,1));
536    _scaleBar->setBorderWidth(1.0);
537    _copyrightScaleBox->addControl(_copyrightLabel.get());
538    _copyrightScaleBox->addControl(_scaleLabel.get());
539    _copyrightScaleBox->addControl(_scaleBar.get());
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(0, 2, 2, 0), 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{
798    TRACE("Restting map with type %d, profile %s", type, profile);
799
800    osgEarth::MapOptions mapOpts;
801    mapOpts.coordSysType() = type;
802    if (profile != NULL) {
803        if (bounds != NULL) {
804            mapOpts.profile() = osgEarth::ProfileOptions();
805            if (strcmp(profile, "geodetic") == 0) {
806                mapOpts.profile()->srsString() = "epsg:4326"; // WGS84
807            } else if (strcmp(profile, "spherical-mercator") == 0) {
808                // Projection used by Google/Bing/OSM
809                // aka epsg:900913 meters in x/y
810                // aka WGS84 Web Mercator (Auxiliary Sphere)
811                // X/Y: -20037508.34m to 20037508.34m
812                mapOpts.profile()->srsString() = "epsg:3857";
813            } else {
814                mapOpts.profile()->srsString() = profile;
815            }
816            TRACE("Setting profile bounds: %g %g %g %g",
817                  bounds[0], bounds[1], bounds[2], bounds[3]);
818            if (1) {
819                osg::ref_ptr<osgEarth::SpatialReference> fromSRS = osgEarth::SpatialReference::create("wgs84");
820                osg::ref_ptr<osgEarth::SpatialReference> toSRS = osgEarth::SpatialReference::create(mapOpts.profile()->srsString().get());
821                osgEarth::Bounds extents(bounds[0], bounds[1], bounds[2], bounds[3]);
822                extents.transform(fromSRS, toSRS);
823                mapOpts.profile()->bounds() = extents;
824                //mapOpts.profile()->numTilesWideAtLod0() = 1;
825                //mapOpts.profile()->numTilesHighAtLod0() = 2;
826            } else {
827                mapOpts.profile()->bounds() =
828                    osgEarth::Bounds(bounds[0], bounds[1], bounds[2], bounds[3]);
829            }
830        } else {
831            mapOpts.profile() = osgEarth::ProfileOptions(profile);
832        }
833    } else if (type == osgEarth::MapOptions::CSTYPE_PROJECTED) {
834        mapOpts.profile() = osgEarth::ProfileOptions("global-mercator");
835    }
836
837#ifdef USE_CACHE
838    setupCache();
839    osgEarth::Drivers::FileSystemCacheOptions cacheOpts;
840    cacheOpts.rootPath() = _cacheDir;
841    mapOpts.cache() = cacheOpts;
842#endif
843
844    initViewer();
845
846    //mapOpts.referenceURI() = _baseURI;
847    osgEarth::Map *map = new osgEarth::Map(mapOpts);
848    _map = map;
849    osgEarth::Drivers::GDALOptions bopts;
850    bopts.url() = getBaseImage();
851    addImageLayer("base", bopts);
852#ifdef USE_REX
853    osgEarth::Drivers::RexTerrainEngine::RexTerrainEngineOptions engineOpt;
854#else
855    osgEarth::Drivers::MPTerrainEngine::MPTerrainEngineOptions engineOpt;
856#endif
857    // Set background layer color
858    engineOpt.color() = bgColor;
859    //engineOpt.minLOD() = 1;
860    // Sets shader uniform for terrain renderer (config var defaults to false)
861    if (!_map->isGeocentric()) {
862        engineOpt.enableLighting() = false;
863    }
864    osgEarth::MapNodeOptions mapNodeOpts(engineOpt);
865    // Sets GL_LIGHTING state in MapNode StateSet (config var defaults to true)
866    //mapNodeOpts.enableLighting() = true;
867#if 0
868    osgEarth::TerrainOptions terrOpts;
869    //terrOpts.loadingPolicy().mapLoadingThreadsPerCore() = 1;
870    //terrOpts.loadingPolicy().numLoadingThreads() = 1;
871    //terrOpts.loadingPolicy().numCompileThreadsPerCore() = 1;
872    //terrOpts.loadingPolicy().numCompileThreads() = 1;
873    if (_map->isGeocentric()) {
874        // The default is 17 posts
875        terrOpts.tileSize() = 17;
876    } else {
877        // Can reduce if we don't have elevation data
878        terrOpts.tileSize() = 2;
879    }
880    mapNodeOpts.setTerrainOptions(terrOpts);
881#endif
882    osgEarth::MapNode *mapNode = new osgEarth::MapNode(map, mapNodeOpts);
883    _mapNode = mapNode;
884    if (_map->isGeocentric()) {
885        osgEarth::DateTime now;
886        TRACE("Creating SkyNode");
887        osgEarth::Drivers::SimpleSky::SimpleSkyOptions skyOpts;
888        skyOpts.hours() = now.hours();
889        skyOpts.ambient() = 0.2f;
890        skyOpts.atmosphericLighting() = true;
891        skyOpts.exposure() = 3.0;
892        _skyNode = osgEarth::Util::SkyNode::create(skyOpts, mapNode);
893        _skyNode->addChild(mapNode);
894        _skyNode->attach(_viewer.get(), 0);
895        _sceneRoot = new osg::Group();
896        _sceneRoot->addChild(_skyNode.get());
897        //_sceneRoot = _skyNode;
898    } else {
899        _sceneRoot = new osg::Group();
900        _sceneRoot->addChild(_mapNode.get());
901    }
902
903    if (_clipPlaneCullCallback.valid()) {
904        _viewer->getCamera()->removeCullCallback(_clipPlaneCullCallback.get());
905        _clipPlaneCullCallback = NULL;
906    }
907    if (_map->isGeocentric()) {
908        _clipPlaneCullCallback = new osgEarth::Util::AutoClipPlaneCullCallback(mapNode);
909        _viewer->getCamera()->addCullCallback(_clipPlaneCullCallback.get());
910    }
911    _viewer->setSceneData(_sceneRoot.get());
912    if (_mouseCoordsTool.valid()) {
913        initMouseCoordsTool();
914    }
915#ifdef USE_RTT_PICKER
916    if (_picker.valid()) {
917        _viewer->removeEventHandler(_picker.get());
918        _picker = NULL;
919    }
920    if (!_picker.valid()) {
921        _picker = new osgEarth::Util::RTTPicker;
922        _picker->addChild(mapNode);
923        //osg::ref_ptr<HoverCallback> callback = new HoverCallback(this);
924        osg::ref_ptr<SelectCallback> callback = new SelectCallback(this);
925        _picker->setDefaultCallback(callback.get());
926        _viewer->addEventHandler(_picker.get());
927    }
928#endif
929    initControls();
930    //_viewer->setSceneData(_sceneRoot.get());
931    initEarthManipulator();
932    _viewer->home();
933
934    finalizeViewer();
935    _needsRedraw = true;
936}
937
938void Renderer::clearMap()
939{
940    if (_map.valid()) {
941        _map->clear();
942        _needsRedraw = true;
943    }
944}
945
946void Renderer::setSkyAmbient(float value)
947{
948#if OSG_MIN_VERSION_REQUIRED(2, 7, 0)
949    if (_skyNode.valid()) {
950        _skyNode->setMinimumAmbient(osg::Vec4f(value, value, value, 1.0f));
951        _needsRedraw = true;
952    }
953#endif
954}
955
956void Renderer::setLighting(bool state)
957{
958    if (_mapNode.valid()) {
959        TRACE("Setting lighting: %d", state ? 1 : 0);
960        _mapNode->getOrCreateStateSet()
961            ->setMode(GL_LIGHTING, state ? 1 : 0);
962        _needsRedraw = true;
963    }
964}
965
966void Renderer::setViewerLightType(osg::View::LightingMode mode)
967{
968    if (_viewer.valid()) {
969        _viewer->setLightingMode(mode);
970        _needsRedraw = true;
971    }
972}
973
974void Renderer::setTerrainVerticalScale(double scale)
975{
976    if (_mapNode.valid()) {
977        if (!_verticalScale.valid()) {
978            _verticalScale = new osgEarth::Util::VerticalScale;
979            _mapNode->getTerrainEngine()->addEffect(_verticalScale);
980        }
981        _verticalScale->setScale(scale);
982        _needsRedraw = true;
983    }
984}
985
986void Renderer::setEphemerisTime(time_t utcTime)
987{
988    if (_skyNode.valid()) {
989        osgEarth::DateTime time(utcTime);
990        _skyNode->setDateTime(time);
991        _needsRedraw = true;
992    }
993}
994
995void Renderer::setEphemerisTime(int year, int month, int day, double hours)
996{
997    if (_skyNode.valid()) {
998        osgEarth::DateTime time(year, month, day, hours);
999        _skyNode->setDateTime(time);
1000        _needsRedraw = true;
1001    }
1002}
1003
1004void Renderer::setTerrainColor(const osg::Vec4f& color)
1005{
1006    // XXX: Don't know how to change this at runtime without a map reset
1007    _needsRedraw = true;
1008}
1009
1010void Renderer::setTerrainLighting(bool state)
1011{
1012    if (_skyNode.valid()) {
1013        _skyNode->setLighting((state ? osg::StateAttribute::ON : osg::StateAttribute::OFF));
1014    } else {
1015#if 1
1016        if (!_mapNode.valid()) {
1017            ERROR("No map node");
1018            return;
1019        }
1020        // XXX: HACK alert
1021        // Find the terrain engine container (might be above one or more decorators)
1022        osg::Group *group = _mapNode->getTerrainEngine();
1023        while (group->getParent(0) != NULL && group->getParent(0) != _mapNode.get()) {
1024            group = group->getParent(0);
1025        }
1026        if (group != NULL && group->getParent(0) == _mapNode.get()) {
1027            TRACE("Setting terrain lighting: %d", state ? 1 : 0);
1028            if (group->getOrCreateStateSet()->getUniform("oe_mode_GL_LIGHTING") != NULL) {
1029                group->getStateSet()->getUniform("oe_mode_GL_LIGHTING")->set(state);
1030            } else {
1031                ERROR("Can't get terrain lighting uniform");
1032            }
1033        } else {
1034            ERROR("Can't find terrain engine container");
1035        }
1036#else
1037        if (_stateManip.valid()) {
1038            _stateManip->setLightingEnabled(state);
1039        }
1040#endif
1041    }
1042    _needsRedraw = true;
1043}
1044
1045void Renderer::setTerrainWireframe(bool state)
1046{
1047    if (!_map.valid()) {
1048        ERROR("No map");
1049        return;
1050    }
1051#if 0
1052    if (!_mapNode.valid()) {
1053        ERROR("No map node");
1054        return;
1055    }
1056    TRACE("Setting terrain wireframe: %d", state ? 1 : 0);
1057    osg::StateSet *state = _mapNode->getOrCreateStateSet();
1058    osg::PolygonMode *pmode = dynamic_cast< osg::PolygonMode* >(state->getAttribute(osg::StateAttribute::POLYGONMODE));
1059    if (pmode == NULL) {
1060        pmode = new osg::PolygonMode;
1061        state->setAttribute(pmode);
1062    }
1063    if (state) {
1064        pmode->setMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE);
1065    } else {
1066        pmode->setMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL);
1067    }
1068    _needsRedraw = true;
1069#else
1070    if (_stateManip.valid()) {
1071        _stateManip->setPolygonMode(state ? osg::PolygonMode::LINE : osg::PolygonMode::FILL);
1072        _needsRedraw = true;
1073    }
1074#endif
1075}
1076
1077void Renderer::saveNamedViewpoint(const char *name)
1078{
1079    _viewpoints[name] = getViewpoint();
1080}
1081
1082bool Renderer::removeNamedViewpoint(const char *name)
1083{
1084    ViewpointHashmap::iterator itr = _viewpoints.find(name);
1085    if (itr != _viewpoints.end()) {
1086        _viewpoints.erase(name);
1087        return true;
1088    } else {
1089        ERROR("Unknown viewpoint: '%s'", name);
1090        return false;
1091    }
1092}
1093
1094bool Renderer::restoreNamedViewpoint(const char *name, double durationSecs)
1095{
1096    ViewpointHashmap::iterator itr = _viewpoints.find(name);
1097    if (itr != _viewpoints.end()) {
1098        setViewpoint(itr->second, durationSecs);
1099        return true;
1100    } else {
1101        ERROR("Unknown viewpoint: '%s'", name);
1102        return false;
1103    }
1104}
1105
1106void Renderer::setViewpoint(const osgEarth::Viewpoint& v, double durationSecs)
1107{
1108    if (_manipulator.valid()) {
1109        TRACE("Setting viewpoint: %g %g %g %g %g %g",
1110              v.focalPoint()->x(), v.focalPoint()->y(), v.focalPoint()->z(),
1111              v.heading()->as(osgEarth::Units::DEGREES),
1112              v.pitch()->as(osgEarth::Units::DEGREES),
1113              v.range()->as(osgEarth::Units::METERS));
1114        _manipulator->setViewpoint(v, durationSecs);
1115        _needsRedraw = true;
1116    } else {
1117        ERROR("No manipulator");
1118    }
1119}
1120
1121osgEarth::Viewpoint Renderer::getViewpoint()
1122{
1123    if (_manipulator.valid()) {
1124        return _manipulator->getViewpoint();
1125    } else {
1126        // Uninitialized, invalid viewpoint
1127        return osgEarth::Viewpoint();
1128    }
1129}
1130
1131static void srsInfo(const osgEarth::SpatialReference *srs)
1132{
1133    TRACE("SRS: %s", srs->getName().c_str());
1134    TRACE("horiz: \"%s\" vert: \"%s\"", srs->getHorizInitString().c_str(), srs->getVertInitString().c_str());
1135    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",
1136          srs->isGeographic() ? 1 : 0,
1137          srs->isGeodetic() ? 1 : 0,
1138          srs->isProjected() ? 1 : 0,
1139          srs->isECEF() ? 1 : 0,
1140          srs->isMercator() ? 1 : 0,
1141          srs->isSphericalMercator() ? 1 : 0,
1142          srs->isNorthPolar() ? 1 : 0,
1143          srs->isSouthPolar() ? 1 : 0,
1144          srs->isUserDefined() ? 1 : 0,
1145          srs->isContiguous() ? 1 : 0,
1146          srs->isCube() ? 1 : 0,
1147          srs->isLTP() ? 1 : 0,
1148          srs->isPlateCarre() ? 1 : 0);
1149}
1150
1151bool Renderer::getWorldCoords(const osgEarth::GeoPoint& mapPt, osg::Vec3d *world)
1152{
1153    if (!_mapNode.valid() || _mapNode->getTerrain() == NULL) {
1154        ERROR("No map");
1155        return false;
1156    }
1157    TRACE("Input SRS:");
1158    srsInfo(mapPt.getSRS());
1159    TRACE("Map SRS:");
1160    srsInfo(_mapNode->getMapSRS());
1161    bool ret = mapPt.toWorld(*world, _mapNode->getTerrain());
1162    TRACE("In: %g,%g,%g Out: %g,%g,%g",
1163          mapPt.x(), mapPt.y(), mapPt.z(),
1164          world->x(), world->y(), world->z());
1165    return ret;
1166}
1167
1168bool Renderer::worldToScreen(const osg::Vec3d& world, osg::Vec3d *screen, bool invertY)
1169{
1170    if (!_viewer.valid()) {
1171        ERROR("No viewer");
1172        return false;
1173    }
1174    osg::Camera *cam = _viewer->getCamera();
1175    osg::Matrixd MVP = cam->getViewMatrix() * cam->getProjectionMatrix();
1176    // Get clip coords
1177    osg::Vec4d pt;
1178    pt = osg::Vec4d(world, 1.0) * MVP;
1179    // Clip
1180    if (pt.x() < -pt.w() ||
1181        pt.x() > pt.w() ||
1182        pt.y() < -pt.w() ||
1183        pt.y() > pt.w() ||
1184        pt.z() < -pt.w() ||
1185        pt.z() > pt.w()) {
1186        // Outside frustum
1187        TRACE("invalid pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w());
1188        return false;
1189    }
1190    TRACE("clip pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w());
1191    // Perspective divide: now NDC
1192    pt /= pt.w();
1193    const osg::Viewport *viewport = cam->getViewport();
1194#if 1
1195    screen->x() = viewport->x() + viewport->width() * 0.5 + pt.x() * viewport->width() * 0.5;
1196    screen->y() = viewport->y() + viewport->height() * 0.5 + pt.y() * viewport->height() * 0.5;
1197    //double near = 0;
1198    //double far = 1;
1199    //screen->z() = (far + near) * 0.5 + (far - near) * 0.5 * pt.z();
1200    screen->z() = 0.5 + 0.5 * pt.z();
1201#else
1202    *screen = osg::Vec3d(pt.x(), pt.y(), pt.z()) * cam->getViewport()->computeWindowMatrix();
1203#endif
1204    if (invertY) {
1205        screen->y() = viewport->height() - screen->y();
1206    }
1207    TRACE("screen: %g,%g,%g", screen->x(), screen->y(), screen->z());
1208    return true;
1209}
1210
1211bool Renderer::worldToScreen(std::vector<osg::Vec3d>& coords, bool invertY)
1212{
1213    if (!_viewer.valid()) {
1214        ERROR("No viewer");
1215        return false;
1216    }
1217    bool ret = true;
1218    osg::Camera *cam = _viewer->getCamera();
1219    osg::Matrixd MVP = cam->getViewMatrix() * cam->getProjectionMatrix();
1220    const osg::Viewport *viewport = cam->getViewport();
1221    for (std::vector<osg::Vec3d>::iterator itr = coords.begin();
1222         itr != coords.end(); ++itr) {
1223        // Get clip coords
1224        osg::Vec4d pt;
1225        pt = osg::Vec4d(*itr, 1.0) * MVP;
1226        // Clip
1227        if (pt.x() < -pt.w() ||
1228            pt.x() > pt.w() ||
1229            pt.y() < -pt.w() ||
1230            pt.y() > pt.w() ||
1231            pt.z() < -pt.w() ||
1232            pt.z() > pt.w()) {
1233            // Outside frustum
1234            TRACE("invalid pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w());
1235            itr->set(std::numeric_limits<double>::quiet_NaN(),
1236                     std::numeric_limits<double>::quiet_NaN(),
1237                     std::numeric_limits<double>::quiet_NaN());
1238            ret = false;
1239            continue;
1240        }
1241        TRACE("clip pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w());
1242        // Perspective divide: now NDC
1243        pt /= pt.w();
1244#if 1
1245        itr->x() = viewport->x() + viewport->width() * 0.5 + pt.x() * viewport->width() * 0.5;
1246        itr->y() = viewport->y() + viewport->height() * 0.5 + pt.y() * viewport->height() * 0.5;
1247        //double near = 0;
1248        //double far = 1;
1249        //itr->z() = (far + near) * 0.5 + (far - near) * 0.5 * pt.z();
1250        itr->z() = 0.5 + 0.5 * pt.z();
1251#else
1252        *itr = osg::Vec3d(pt.x(), pt.y(), pt.z()) * viewport->computeWindowMatrix();
1253#endif
1254        if (invertY) {
1255            itr->y() = viewport->height() - itr->y();
1256        }
1257        TRACE("screen: %g,%g,%g", itr->x(), itr->y(), itr->z());
1258    }
1259    return ret;
1260}
1261
1262bool Renderer::mouseToLatLong(int mouseX, int mouseY,
1263                              double *latitude, double *longitude)
1264{
1265    if (getMapSRS() == NULL)
1266        return false;
1267    const osgEarth::SpatialReference *outSRS =
1268        getMapSRS()->getGeographicSRS();
1269    if (outSRS == NULL)
1270        return false;
1271    osgEarth::GeoPoint mapPoint;
1272    if (mapMouseCoords(mouseX, mouseY, mapPoint)) {
1273        mapPoint = mapPoint.transform(outSRS);
1274        *longitude = mapPoint.x();
1275        *latitude = mapPoint.y();
1276        return true;
1277    } else {
1278        return false;
1279    }
1280}
1281#if 0
1282bool Renderer::mapToLatLong(osgEarth::GeoPoint& mapPoint)
1283{
1284    if (getMapSRS() == NULL)
1285        return false;
1286    const osgEarth::SpatialReference *outSRS =
1287        getMapSRS()->getGeographicSRS();
1288    if (outSRS == NULL)
1289        return false;
1290    mapPoint = mapPoint.transform(outSRS);
1291    return true;
1292}
1293#endif
1294/**
1295 * \brief Map screen mouse coordinates to map coordinates
1296 *
1297 * This method assumes that mouse Y coordinates are 0 at the top
1298 * of the screen and increase going down if invertY is set, and
1299 * 0 at the bottom and increase going up otherwise.
1300 */
1301bool Renderer::mapMouseCoords(float mouseX, float mouseY,
1302                              osgEarth::GeoPoint& map, bool invertY)
1303{
1304    if (!_mapNode.valid() || _mapNode->getTerrain() == NULL) {
1305        ERROR("No map");
1306        return false;
1307    }
1308    if (!_viewer.valid()) {
1309        ERROR("No viewer");
1310        return false;
1311    }
1312    if (invertY) {
1313        mouseY = ((float)_windowHeight - mouseY);
1314    }
1315    osg::Vec3d world;
1316    if (_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), mouseX, mouseY, world)) {
1317        map.fromWorld(getMapSRS(), world);
1318        return true;
1319    }
1320    return false;
1321}
1322
1323bool Renderer::addImageLayer(const char *name,
1324                             osgEarth::TileSourceOptions& opts,
1325                             unsigned int pos,
1326                             bool enableCache,
1327                             bool coverage,
1328                             bool makeShared,
1329                             bool visible,
1330                             unsigned int minLOD, unsigned int maxLOD)
1331{
1332    if (!_map.valid()) {
1333        ERROR("No map");
1334        return false;
1335    }
1336    TRACE("layer: %s", name);
1337    if (!opts.tileSize().isSet()) {
1338        opts.tileSize() = 256;
1339    }
1340    osgEarth::ImageLayerOptions layerOpts(name, opts);
1341    layerOpts.textureCompression() = osg::Texture::USE_IMAGE_DATA_FORMAT;
1342    if (coverage) {
1343        layerOpts.coverage() = true;
1344    }
1345    if (makeShared) {
1346        layerOpts.shared() = true;
1347    }
1348    if (!enableCache) {
1349        TRACE("Disabling cache for layer %s", name);
1350        layerOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
1351    }
1352    if (!visible) {
1353        layerOpts.visible() = false;
1354    }
1355    layerOpts.minLevel() = minLOD;
1356    layerOpts.maxLevel() = maxLOD;
1357    osg::ref_ptr<osgEarth::ImageLayer> layer = new osgEarth::ImageLayer(layerOpts);
1358    if (pos < (unsigned int)_map->getNumImageLayers()) {
1359        _map->insertImageLayer(layer.get(), pos);
1360    } else {
1361        _map->addImageLayer(layer.get());
1362    }
1363    if (layer->getTileSource() == NULL || !layer->getTileSource()->isOK()) {
1364        ERROR("Failed to add image layer: %s", name);
1365        _map->removeImageLayer(layer.get());
1366        return false;
1367    }
1368    _needsRedraw = true;
1369    return true;
1370}
1371
1372void Renderer::addColorFilter(const char *name,
1373                              const char *shader)
1374{
1375    if (!_map.valid()) {
1376        ERROR("No map");
1377        return;
1378    }
1379    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1380    if (layer == NULL) {
1381        TRACE("Image layer not found: %s", name);
1382        return;
1383    }
1384    osgEarth::Util::GLSLColorFilter *filter = new osgEarth::Util::GLSLColorFilter;
1385    filter->setCode(shader);
1386    //filter->setCode("color.rgb = color.r > 0.5 ? vec3(1.0) : vec3(0.0);");
1387    layer->addColorFilter(filter);
1388    _needsRedraw = true;
1389}
1390
1391void Renderer::removeColorFilter(const char *name, int idx)
1392{
1393    if (!_map.valid()) {
1394        ERROR("No map");
1395        return;
1396    }
1397    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1398    if (layer == NULL) {
1399        TRACE("Image layer not found: %s", name);
1400        return;
1401    }
1402    if (idx < 0) {
1403        while (!layer->getColorFilters().empty()) {
1404            layer->removeColorFilter(layer->getColorFilters()[0]);
1405        }
1406    } else {
1407        layer->removeColorFilter(layer->getColorFilters().at(idx));
1408    }
1409    _needsRedraw = true;
1410}
1411
1412void Renderer::removeImageLayer(const char *name)
1413{
1414    if (!_map.valid()) {
1415        ERROR("No map");
1416        return;
1417    }
1418    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1419    if (layer != NULL) {
1420        _map->removeImageLayer(layer);
1421        _needsRedraw = true;
1422    } else {
1423        TRACE("Image layer not found: %s", name);
1424    }
1425}
1426
1427void Renderer::moveImageLayer(const char *name, unsigned int pos)
1428{
1429    if (!_map.valid()) {
1430        ERROR("No map");
1431        return;
1432    }
1433    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1434    if (layer != NULL) {
1435        _map->moveImageLayer(layer, pos);
1436        _needsRedraw = true;
1437    } else {
1438        TRACE("Image layer not found: %s", name);
1439    }
1440}
1441
1442void Renderer::setImageLayerOpacity(const char *name, double opacity)
1443{
1444    if (!_map.valid()) {
1445        ERROR("No map");
1446        return;
1447    }
1448    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1449    if (layer != NULL) {
1450        layer->setOpacity(opacity);
1451        _needsRedraw = true;
1452    } else {
1453        TRACE("Image layer not found: %s", name);
1454    }
1455}
1456
1457void Renderer::setImageLayerVisibleRange(const char *name, float min, float max)
1458{
1459    if (!_map.valid()) {
1460        ERROR("No map");
1461        return;
1462    }
1463    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1464    if (layer != NULL) {
1465        layer->setMinVisibleRange(min);
1466        layer->setMaxVisibleRange(max);
1467        _needsRedraw = true;
1468    } else {
1469        TRACE("Image layer not found: %s", name);
1470    }
1471}
1472
1473void Renderer::setImageLayerVisibility(const char *name, bool state)
1474{
1475    if (!_map.valid()) {
1476        ERROR("No map");
1477        return;
1478    }
1479    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1480    if (layer != NULL) {
1481        layer->setVisible(state);
1482        _needsRedraw = true;
1483    } else {
1484        TRACE("Image layer not found: %s", name);
1485    }
1486}
1487
1488bool Renderer::layerHasSequence(const char *name)
1489{
1490    if (!_map.valid()) {
1491        ERROR("No map");
1492        return false;
1493    }
1494    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1495    if (layer != NULL) {
1496        osgEarth::SequenceControl *seq = layer->getSequenceControl();
1497        if (seq == NULL) {
1498            TRACE("Image layer has no SequenceControl: %s", name);
1499            return false;
1500        }
1501        return seq->supportsSequenceControl();
1502    }
1503    TRACE("Image layer not found: %s", name);
1504    return false;
1505}
1506
1507bool Renderer::sequencePause(const char *name)
1508{
1509    if (!_map.valid()) {
1510        ERROR("No map");
1511        return false;
1512    }
1513    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1514    if (layer != NULL) {
1515        osgEarth::SequenceControl *seq = layer->getSequenceControl();
1516        if (seq == NULL || !seq->supportsSequenceControl()) {
1517            TRACE("Image layer has no SequenceControl: %s", name);
1518            return false;
1519        }
1520        seq->pauseSequence();
1521        _needsRedraw = true;
1522        return true;
1523    }
1524    TRACE("Image layer not found: %s", name);
1525    return false;
1526}
1527
1528bool Renderer::sequencePlay(const char *name)
1529{
1530    if (!_map.valid()) {
1531        ERROR("No map");
1532        return false;
1533    }
1534    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1535    if (layer != NULL) {
1536        osgEarth::SequenceControl *seq = layer->getSequenceControl();
1537        if (seq == NULL || !seq->supportsSequenceControl()) {
1538            TRACE("Image layer has no SequenceControl: %s", name);
1539            return false;
1540        }
1541        seq->playSequence();
1542        _needsRedraw = true;
1543        return true;
1544    }
1545    TRACE("Image layer not found: %s", name);
1546    return false;
1547}
1548
1549bool Renderer::sequenceSeek(const char *name, unsigned int frame)
1550{
1551    if (!_map.valid()) {
1552        ERROR("No map");
1553        return false;
1554    }
1555    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1556    if (layer != NULL) {
1557        osgEarth::SequenceControl *seq = layer->getSequenceControl();
1558        if (seq == NULL || !seq->supportsSequenceControl()) {
1559            TRACE("Image layer has no SequenceControl: %s", name);
1560            return false;
1561        }
1562        seq->seekToSequenceFrame(frame);
1563        _needsRedraw = true;
1564        return true;
1565    }
1566    TRACE("Image layer not found: %s", name);
1567    return false;
1568}
1569
1570void Renderer::addElevationLayer(const char *name,
1571                                 osgEarth::TileSourceOptions& opts,
1572                                 unsigned int pos,
1573                                 bool enableCache,
1574                                 bool visible,
1575                                 const char *verticalDatum,
1576                                 unsigned int minLOD, unsigned int maxLOD)
1577{
1578    if (!_map.valid()) {
1579        ERROR("No map");
1580        return;
1581    }
1582    TRACE("layer: %s", name);
1583    if (!opts.tileSize().isSet()) {
1584        opts.tileSize() = 17;
1585    }
1586    osgEarth::ElevationLayerOptions layerOpts(name, opts);
1587    if (!enableCache) {
1588        layerOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
1589    }
1590    if (!visible) {
1591        layerOpts.visible() = false;
1592    }
1593    // Check for vertical datum override (required for GDAL driver if not using
1594    // geodetic HAE).
1595    if (verticalDatum != NULL && strlen(verticalDatum) > 0) {
1596        layerOpts.verticalDatum() = verticalDatum;
1597    }
1598    layerOpts.minLevel() = minLOD;
1599    layerOpts.maxLevel() = maxLOD;
1600    osgEarth::ElevationLayer *layer = new osgEarth::ElevationLayer(layerOpts);
1601    _map->addElevationLayer(layer);
1602    // Map API lacks an insertElevationLayer method, so need to move it
1603    if (pos < (unsigned int)_map->getNumElevationLayers()) {
1604        _map->moveElevationLayer(layer, pos);
1605    }
1606    _needsRedraw = true;
1607}
1608
1609void Renderer::removeElevationLayer(const char *name)
1610{
1611    if (!_map.valid()) {
1612        ERROR("No map");
1613        return;
1614    }
1615    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
1616    if (layer != NULL) {
1617        _map->removeElevationLayer(layer);
1618        _needsRedraw = true;
1619    } else {
1620        TRACE("Elevation layer not found: %s", name);
1621    }
1622}
1623
1624void Renderer::moveElevationLayer(const char *name, unsigned int pos)
1625{
1626    if (!_map.valid()) {
1627        ERROR("No map");
1628        return;
1629    }
1630    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
1631    if (layer != NULL) {
1632        _map->moveElevationLayer(layer, pos);
1633        _needsRedraw = true;
1634    } else {
1635        TRACE("Elevation layer not found: %s", name);
1636    }
1637}
1638
1639void Renderer::setElevationLayerVisibility(const char *name, bool state)
1640{
1641    if (!_map.valid()) {
1642        ERROR("No map");
1643        return;
1644    }
1645    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
1646    if (layer != NULL) {
1647        layer->setVisible(state);
1648        _needsRedraw = true;
1649    } else {
1650        TRACE("Elevation layer not found: %s", name);
1651    }
1652}
1653
1654void Renderer::addTerrainMaskLayer(const char *name,
1655                                   osgEarth::MaskSourceOptions& opts,
1656                                   unsigned int minLOD)
1657{
1658    if (!_map.valid()) {
1659        ERROR("No map");
1660        return;
1661    }
1662    TRACE("layer: %s", name);
1663    osgEarth::MaskLayerOptions layerOpts(name, opts);
1664    layerOpts.minLevel() = minLOD;
1665    osgEarth::MaskLayer *layer = new osgEarth::MaskLayer(layerOpts);
1666    _map->addTerrainMaskLayer(layer);
1667    _needsRedraw = true;
1668}
1669
1670void Renderer::removeTerrainMaskLayer(const char *name)
1671{
1672    if (!_map.valid()) {
1673        ERROR("No map");
1674        return;
1675    }
1676    osgEarth::MaskLayer *layer = getTerrainMaskLayerByName(name);
1677    if (layer != NULL) {
1678        _map->removeTerrainMaskLayer(layer);
1679        _needsRedraw = true;
1680    } else {
1681        TRACE("Terrain mask layer not found: %s", name);
1682    }
1683}
1684
1685void Renderer::initAnnotations()
1686{
1687    if (!_annotations.valid()) {
1688        _annotations = new osg::Group();
1689        _annotations->setName("Annotations");
1690        _sceneRoot->addChild(_annotations.get());
1691        _placeNodes = new osg::Group();
1692        _placeNodes->setName("Place Nodes");
1693#ifdef NEW_ANNOTATION_API
1694        osgEarth::ScreenSpaceLayout::activate(_placeNodes->getOrCreateStateSet());
1695#else
1696        osgEarth::Decluttering::setEnabled(_placeNodes->getOrCreateStateSet(), true);
1697#endif
1698        _annotations->addChild(_placeNodes.get());
1699        if (_picker.valid()) {
1700            _picker->addChild(_placeNodes.get());
1701        }
1702    }
1703}
1704
1705void Renderer::setSelectMode(SelectMode mode)
1706{
1707    _selectMode = mode;
1708    switch (_selectMode) {
1709    case SELECT_OFF: {
1710#ifdef USE_RTT_PICKER
1711        if (_picker.valid()) {
1712            _viewer->removeEventHandler(_picker.get());
1713            _picker = NULL;
1714        }
1715#endif
1716    }
1717        break;
1718    case SELECT_ON: {
1719#ifdef USE_RTT_PICKER
1720        if (!_picker.valid()) {
1721            _picker = new osgEarth::Util::RTTPicker;
1722            _picker->addChild(_mapNode);
1723            osg::ref_ptr<SelectCallback> callback = new SelectCallback(this);
1724            _picker->setDefaultCallback(callback.get());
1725            _viewer->addEventHandler(_picker.get());
1726        }
1727#endif
1728    }
1729        break;
1730    default:
1731        ERROR("Unknown select mode");
1732    }
1733}
1734
1735void Renderer::enablePlacard(const char *layerName, bool state)
1736{
1737    osgEarth::ModelLayer *layer = _map->getModelLayerByName(layerName);
1738    if (layer == NULL) {
1739        ERROR("Unknown layer '%s'", layerName);
1740        return;
1741    }
1742    _placardConfigs[layerName].setEnabled(state);
1743}
1744
1745void Renderer::setPlacardConfig(const Placard& placardConf, const char *layerName)
1746{
1747    osgEarth::ModelLayer *layer = _map->getModelLayerByName(layerName);
1748    if (layer == NULL) {
1749        ERROR("Unknown layer '%s'", layerName);
1750        return;
1751    }
1752    //PlacardNode *node;
1753    //node->setConfig(placardConf);
1754    _placardConfigs[layerName] = placardConf;
1755}
1756
1757void Renderer::deselectFeatures(std::vector<unsigned long>& featureIDs, const char *layerName)
1758{
1759    // TODO: Take down placard?
1760    TRACE("Deselect features layer '%s', num features: %u", layerName, featureIDs.size());
1761    for (unsigned int i = 0; i < featureIDs.size(); i++) {
1762        _selectedFeatures[layerName].erase(featureIDs.at(i));
1763    }
1764    FeatureSelectionHashmap::iterator itr = _selectedFeatures.find(layerName);
1765    if (itr != _selectedFeatures.end()) {
1766        for (FeatureSelectionHashmap::mapped_type::iterator fitr = itr->second.begin();
1767             fitr != itr->second.end(); ++fitr) {
1768            TRACE("Selection: %lu", *fitr);
1769        }
1770    } else {
1771        TRACE("No selection");
1772    }
1773    updateDebugLabel();
1774}
1775
1776void Renderer::selectFeatures(std::vector<unsigned long>& featureIDs, const char *layerName, bool clear)
1777{
1778    bool doPlacard = false;
1779    TRACE("Select layer '%s', num features: %u", layerName, featureIDs.size());
1780    if (clear) {
1781        clearSelection();
1782    }
1783    if (featureIDs.size() == 0) {
1784        // clear selection
1785        return;
1786    }
1787    osgEarth::ModelLayer *layer = _map->getModelLayerByName(layerName);
1788    if (layer == NULL) {
1789        ERROR("Unknown layer '%s'", layerName);
1790        return;
1791    }
1792    for (unsigned int i = 0; i < featureIDs.size(); i++) {
1793        TRACE("feature ID: %u", featureIDs.at(i));
1794        _selectedFeatures[layerName].insert(featureIDs.at(i));
1795    }
1796    FeatureSelectionHashmap::iterator itr = _selectedFeatures.find(layerName);
1797    if (itr != _selectedFeatures.end()) {
1798        for (FeatureSelectionHashmap::mapped_type::iterator fitr = itr->second.begin();
1799             fitr != itr->second.end(); ++fitr) {
1800            TRACE("Selection: %lu", *fitr);
1801        }
1802    } else {
1803        TRACE("No selection");
1804    }
1805    unsigned long fid = featureIDs.at(0);
1806
1807    osgEarth::ModelSource *modelSource = layer->getModelSource();
1808    osgEarth::Features::FeatureModelSource *fms = dynamic_cast<osgEarth::Features::FeatureModelSource *>(modelSource);
1809    if (fms != NULL) {
1810        osgEarth::Features::FeatureSource *fs = fms->getFeatureSource();
1811        FindFeatureSourceIndexNodeVisitor visitor;
1812        visitor.source = fs;
1813        _sceneRoot->accept(visitor);
1814        TRACE("Num FeatureSourceIndexNodes found: %lu", visitor.nodes.size());
1815        for (size_t i = 0; i < visitor.nodes.size(); i++) {
1816            osgEarth::Features::FeatureIndex *index = visitor.nodes[i]->getIndex();
1817#if OSGEARTH_MIN_VERSION_REQUIRED(3, 0, 0)
1818            osgEarth::ObjectID id = index->getObjectID(fid);
1819            if (doPlacard) {
1820                osgEarth::Features::Feature *feature = index->getFeature(id);
1821                if (feature) {
1822                    // Find feature centroid
1823                    osgEarth::GeoPoint location;
1824                    if (feature->getGeometry()) {
1825                        osg::BoundingSphered bound;
1826                        feature->getWorldBound(getMapSRS(), bound);
1827                        location.set(getMapSRS(), bound.center(),
1828                                     osgEarth::ALTMODE_ABSOLUTE);
1829                    }
1830                    addPlacard(location, feature, layerName);
1831                }
1832            }
1833            setHighlightByObjectID(id);
1834            TRACE("FID %lu = OID %d", fid, id);
1835#endif
1836        }
1837        _needsRedraw = true;
1838    }
1839    updateDebugLabel();
1840}
1841
1842void Renderer::updateDebugLabel()
1843{
1844    if (!_debugLabel.valid()) return;
1845    std::ostringstream oss;
1846    oss << "Selection:";
1847    for (FeatureSelectionHashmap::iterator itr = _selectedFeatures.begin();
1848         itr != _selectedFeatures.end(); ++itr) {
1849        bool found = false;
1850        oss << std::endl << itr->first << ":";
1851        for (FeatureSelectionHashmap::mapped_type::iterator fitr = itr->second.begin();
1852             fitr != itr->second.end(); ++fitr) {
1853            oss << " " << *fitr;
1854            found = true;
1855        }
1856        if (!found) {
1857            oss << " None";
1858        }
1859    }
1860    _debugLabel->setText(oss.str());
1861}
1862
1863void Renderer::addPlacard(const osgEarth::GeoPoint& location,
1864                          osgEarth::Features::Feature *feature,
1865                          const char *layerName)
1866{
1867    if (feature == NULL) return;
1868    Placard placard = getPlacardConfig(layerName);
1869    if (!placard.enabled())
1870        return;
1871
1872    const osgEarth::Features::AttributeTable &attrs = feature->getAttrs();
1873    if (placard.getNumEntries() == 0) {
1874        placard.addAllAttributes(attrs);
1875    }
1876    PlacardNode *label =
1877        new PlacardNode(_mapNode.get(), location, placard, attrs);
1878#ifndef NEW_ANNOTATION_API
1879    label->getOrCreateStateSet()->setRenderBinDetails(INT_MAX, "RenderBin");
1880#endif
1881    getAnnotations()->addChild(label);
1882}
1883
1884void Renderer::clearSelection()
1885{
1886    _selectedFeatures.clear();
1887    clearHighlight();
1888    clearSelectionAnnotationNodes();
1889    clearBoxSelection();
1890    updateDebugLabel();
1891    _needsRedraw = true;
1892}
1893
1894/**
1895 * \brief Remove annotation nodes created during a pick/selection
1896 *
1897 * The primary purpose of this method is to take down the feature placard
1898 */
1899void Renderer::clearSelectionAnnotationNodes()
1900{
1901    osg::Group *nodes = getAnnotations();
1902    std::vector<osg::Node *> toRemove;
1903    for (unsigned int i = 0; i < nodes->getNumChildren(); i++) {
1904        osg::Node *node = nodes->getChild(i);
1905        // This can be Placard, PlacardLabel, Label, Place or Track Node
1906#ifdef NEW_ANNOTATION_API
1907        if (dynamic_cast<osgEarth::Annotation::GeoPositionNode *>(node) != NULL) {
1908#else
1909        if (dynamic_cast<osgEarth::Annotation::OrthoNode *>(node) != NULL) {
1910#endif
1911            toRemove.push_back(node);
1912        }
1913    }
1914    for (std::vector<osg::Node *>::iterator itr = toRemove.begin();
1915         itr != toRemove.end(); ++itr) {
1916        osgEarth::Annotation::AnnotationNode *anno = dynamic_cast<osgEarth::Annotation::AnnotationNode *>(*itr);
1917        if (anno != NULL) {
1918            if (_hovered.find(anno) != _hovered.end()) {
1919                _hovered.erase(anno);
1920            }
1921            if (_selected.find(anno) != _selected.end()) {
1922                _selected.erase(anno);
1923            }
1924        }
1925        nodes->removeChild(*itr);
1926    }
1927    _needsRedraw = true;
1928}
1929
1930void Renderer::initBoxSelection(int x, int y)
1931{
1932    double latitude, longitude;
1933    if (!mouseToLatLong(x, y, &latitude, &longitude)) {
1934        return;
1935    }
1936    _anchorLat = latitude;
1937    _anchorLong = longitude;
1938    addRhumbBox(latitude, latitude, longitude, longitude);
1939}
1940
1941void Renderer::updateBoxSelection(int x, int y)
1942{
1943    double nlat, nlong;
1944    if (!mouseToLatLong(x, y, &nlat, &nlong)) {
1945        return;
1946    }
1947    double latMin, latMax, longMin, longMax;
1948    if (nlong >= _anchorLong && nlat >= _anchorLat) {
1949        // +x +y
1950        longMin = _anchorLong;
1951        latMin = _anchorLat;
1952        longMax = nlong;
1953        latMax = nlat;
1954    } else if (nlong < _anchorLong && nlat >= _anchorLat) {
1955        // -x +y
1956        longMin = nlong;
1957        latMin = _anchorLat;
1958        longMax = _anchorLong;
1959        latMax = nlat;
1960    } else if (nlong < _anchorLong && nlat < _anchorLat) {
1961        // -x -y
1962        longMin = nlong;
1963        latMin = nlat;
1964        longMax = _anchorLong;
1965        latMax = _anchorLat;
1966    } else {
1967        // +x -y
1968        longMin = _anchorLong;
1969        latMin = nlat;
1970        longMax = nlong;
1971        latMax = _anchorLat;
1972    }
1973    osgEarth::Annotation::FeatureNode *node = _selectionBox.get();
1974    osgEarth::Symbology::Geometry *geom = node->getFeature()->getGeometry();
1975    (*geom)[0] = osg::Vec3d(longMin, latMin, 0);
1976    (*geom)[1] = osg::Vec3d(longMax, latMin, 0);
1977    (*geom)[2] = osg::Vec3d(longMax, latMax, 0);
1978    (*geom)[3] = osg::Vec3d(longMin, latMax, 0);
1979    node->init();
1980#ifndef NEW_ANNOTATION_API
1981    for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = _selected.begin();
1982         itr != _selected.end(); ++itr) {
1983        (*itr)->clearDecoration();
1984    }
1985#endif
1986    _selected.clear();
1987    SelectPlaceNodesVisitor spnv(this, latMin, latMax, longMin, longMax);
1988    _placeNodes->accept(spnv);
1989    _needsRedraw = true;
1990}
1991
1992void Renderer::getBoxSelection(double *latMin, double *latMax,
1993                               double *longMin, double *longMax,
1994                               const osgEarth::SpatialReference *outSRS)
1995{
1996    osgEarth::Annotation::FeatureNode *node = _selectionBox.get();
1997    if (node == NULL)
1998        return;
1999    osgEarth::Symbology::Geometry *geom = node->getFeature()->getGeometry();
2000    if (geom == NULL)
2001        return;
2002    *latMin = (*geom)[0].y();
2003    *latMax = (*geom)[2].y();
2004    *longMin = (*geom)[0].x();
2005    *longMax = (*geom)[2].x();
2006    if (outSRS == NULL)
2007        return;
2008
2009    const osgEarth::SpatialReference* fromSRS = _mapNode->getMapSRS()->getGeographicSRS();
2010    osgEarth::GeoPoint pt1(fromSRS, *longMin, *latMin);
2011    osgEarth::GeoPoint pt2(fromSRS, *longMax, *latMax);
2012    pt1.transform(outSRS);
2013    pt2.transform(outSRS);
2014    *latMin = pt1.y();
2015    *latMax = pt2.y();
2016    *longMin = pt1.x();
2017    *longMax = pt2.x();
2018}
2019
2020void SelectPlaceNodesVisitor::apply(osg::Node& node)
2021{
2022    osgEarth::Annotation::PlaceNode *placeNode =
2023        dynamic_cast<osgEarth::Annotation::PlaceNode*>(&node);
2024    if (placeNode != NULL) {
2025        const osgEarth::SpatialReference *outSRS =
2026            _renderer->getMapSRS()->getGeographicSRS();
2027        osgEarth::GeoPoint pt = placeNode->getPosition();
2028        pt = pt.transform(outSRS);
2029        if (pt.x() >= _longMin && pt.x() <= _longMax &&
2030            pt.y() >= _latMin && pt.y() <= _latMax) {
2031            if (_renderer->select(placeNode)) {
2032                TRACE("Select PlaceNode: %g %g n:'%s' t:'%s'",
2033                     pt.x(), pt.y(), placeNode->getName().c_str(),
2034                     placeNode->getText().c_str());
2035#ifndef NEW_ANNOTATION_API
2036                placeNode->setDecoration("select");
2037#endif
2038            }
2039        }
2040    }
2041    traverse(node);
2042}
2043
2044void Renderer::clearBoxSelection()
2045{
2046    if (_annotations.valid() && _selectionBox.valid()) {
2047        _annotations->removeChild(_selectionBox.get());
2048        _selectionBox = NULL;
2049    }
2050#ifndef NEW_ANNOTATION_API
2051    for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = _selected.begin();
2052         itr != _selected.end(); ++itr) {
2053        (*itr)->clearDecoration();
2054    }
2055#endif
2056    _selected.clear();
2057    _needsRedraw = true;
2058}
2059
2060void Renderer::addRhumbBox(double latMin, double latMax,
2061                           double longMin, double longMax)
2062{
2063    if (!_mapNode.valid()) {
2064        ERROR("No map node");
2065        return;
2066    }
2067    initAnnotations();
2068
2069    if (_selectionBox.valid()) {
2070        osgEarth::Symbology::Geometry *geom = _selectionBox->getFeature()->getGeometry();
2071        (*geom)[0] = osg::Vec3d(longMin, latMin, 0);
2072        (*geom)[1] = osg::Vec3d(longMax, latMin, 0);
2073        (*geom)[2] = osg::Vec3d(longMax, latMax, 0);
2074        (*geom)[3] = osg::Vec3d(longMin, latMax, 0);
2075        _selectionBox->init();
2076    } else {
2077        const osgEarth::SpatialReference* geoSRS = _mapNode->getMapSRS()->getGeographicSRS();
2078        osgEarth::Symbology::Geometry *geom = new osgEarth::Symbology::Polygon();
2079        geom->push_back(osg::Vec3d(longMin, latMin, 0));
2080        geom->push_back(osg::Vec3d(longMax, latMin, 0));
2081        geom->push_back(osg::Vec3d(longMax, latMax, 0));
2082        geom->push_back(osg::Vec3d(longMin, latMax, 0));
2083        osgEarth::Symbology::Style boxStyle;
2084#if 1
2085        osgEarth::Symbology::PolygonSymbol *poly = boxStyle.getOrCreate<osgEarth::Symbology::PolygonSymbol>();
2086        poly->fill()->color() = osgEarth::Symbology::Color::Cyan;
2087        poly->fill()->color().a() = 0.5;
2088#else
2089        osgEarth::Symbology::LineSymbol *line = boxStyle.getOrCreate<osgEarth::Symbology::LineSymbol>();
2090        line->stroke()->color() = osgEarth::Symbology::Color::Yellow;
2091        line->stroke()->width() = 2.0f;
2092        //line->creaseAngle() = 45.0f;
2093        line->tessellation() = 10;
2094#endif
2095        osgEarth::Symbology::AltitudeSymbol *alt = boxStyle.getOrCreate<osgEarth::Symbology::AltitudeSymbol>();
2096        alt->clamping() = osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
2097        //alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
2098        alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_DRAPE;
2099        //alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_SCENE;
2100#if 0
2101        osgEarth::Symbology::RenderSymbol* rs = boxStyle.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
2102        rs->depthOffset()->enabled() = true;
2103        rs->depthOffset()->minBias() = 1000;
2104#endif
2105        osgEarth::Features::Feature *feature = new osgEarth::Features::Feature(geom, geoSRS, boxStyle);
2106        //feature->geoInterp() = osgEarth::GEOINTERP_GREAT_CIRCLE;
2107        feature->geoInterp() = osgEarth::GEOINTERP_RHUMB_LINE;
2108        _selectionBox =
2109            new osgEarth::Annotation::FeatureNode(_mapNode, feature);
2110        _annotations->addChild(_selectionBox.get());
2111    }
2112
2113    _needsRedraw = true;
2114}
2115
2116void Renderer::addPlaceNode(double latitude, double longitude, char *labelText)
2117{
2118    if (!_mapNode.valid()) {
2119        ERROR("No map node");
2120        return;
2121    }
2122    initAnnotations();
2123
2124    const osgEarth::SpatialReference *geoSRS = _mapNode->getMapSRS()->getGeographicSRS();
2125
2126    osgEarth::Symbology::Style pin;
2127    pin.getOrCreate<osgEarth::Symbology::IconSymbol>()->url()->setLiteral(getPinIcon());
2128    osgEarth::Annotation::AnnotationNode *anno =
2129        new osgEarth::Annotation::PlaceNode(_mapNode, osgEarth::GeoPoint(geoSRS, longitude, latitude), labelText, pin);
2130    anno->setName(labelText);
2131    _placeNodes->addChild(anno);
2132#ifdef USE_RTT_PICKER
2133    osgEarth::Registry::objectIndex()->tagNode(anno, anno);
2134#else
2135    osgEarth::Annotation::DecorationInstaller
2136        highlightInstaller("select", new osgEarth::Annotation::HighlightDecoration(osg::Vec4f(1,1,0,0.5)));
2137    _placeNodes->accept(highlightInstaller);
2138
2139    // scale labels when hovering:
2140    osgEarth::Annotation::DecorationInstaller
2141        scaleInstaller("hover", new osgEarth::Annotation::ScaleDecoration(1.1f));
2142    _placeNodes->accept(scaleInstaller);
2143#endif
2144#if 0
2145    writeScene("/tmp/test.osg");
2146#endif
2147    _needsRedraw = true;
2148}
2149#if 0
2150void Renderer::hoverFeatureNodes(int x, int y, bool invertY)
2151{
2152    if (!_mapNode.valid()) {
2153        ERROR("No map node");
2154        return;
2155    }
2156    osgEarth::IntersectionPicker picker(_viewer.get(), _mapNode.get());
2157    osgEarth::IntersectionPicker::Hits hits;
2158    float mouseX = (float)x;
2159    float mouseY = (float)y;
2160    if (invertY) {
2161        mouseY = ((float)_windowHeight - mouseY);
2162    }
2163    std::set<osgEarth::Annotation::FeatureNode*> toUnHover;
2164    for (std::set<osgEarth::Annotation::FeatureNode*>::iterator itr = _hovered.begin();
2165         itr != _hovered.end(); ++itr) {
2166        toUnHover.insert(*itr);
2167    }
2168    if (picker.pick(mouseX, mouseY, hits)) {
2169        TRACE("Picker hits: %d", hits.size());
2170        for (osgEarth::IntersectionPicker::Hits::const_iterator hitr = hits.begin();
2171             hitr != hits.end(); ++hitr) {
2172            osgEarth::Annotation::FeatureNode *anno =
2173                picker.getNode<osgEarth::Annotation::FeatureNode>(*hitr);
2174            if (anno != NULL) {
2175                TRACE("Hit FeatureNode: %p", anno);
2176                if (_hovered.find(anno) == _hovered.end()) {
2177                    _hovered.insert(anno);
2178                    anno->setDecoration("hover");
2179                    _needsRedraw = true;
2180                }
2181                toUnHover.erase(anno);
2182            }
2183        }
2184#if 0
2185        writeScene("/tmp/test.osgt");
2186#endif
2187    }
2188    for (std::set<osgEarth::Annotation::FeatureNode *>::iterator itr = toUnHover.begin();
2189         itr != toUnHover.end(); ++itr) {
2190        _hovered.erase(*itr);
2191        (*itr)->clearDecoration();
2192        _needsRedraw = true;
2193    }
2194}
2195#endif
2196
2197#ifdef USE_RTT_PICKER
2198void Renderer::hoverPlaceNode(int x, int y, bool invertY)
2199{
2200    if (!_placeNodes.valid()) {
2201        TRACE("No place nodes");
2202        return;
2203    }
2204    return;
2205
2206    float mouseX = (float)x;
2207    float mouseY = (float)y;
2208    if (invertY) {
2209        mouseY = ((float)_windowHeight - mouseY);
2210    }
2211    if (_picker->pick(_viewer.get(), mouseX, mouseY)) {
2212        TRACE("Hover pick queued: %g %g", mouseX, mouseY);
2213    } else {
2214        TRACE("Failed to queue pick: %g %g", mouseX, mouseY);
2215    }
2216}
2217
2218void Renderer::deletePlaceNode(int x, int y, bool invertY)
2219{
2220    if (!_placeNodes.valid()) {
2221        TRACE("No place nodes");
2222        return;
2223    }
2224    return;
2225
2226    osg::ref_ptr<osgEarth::Util::RTTPicker> picker = new osgEarth::Util::RTTPicker;
2227    picker->addChild(_placeNodes.get());
2228    osg::ref_ptr<DeleteCallback> callback = new DeleteCallback(this);
2229    float mouseX = (float)x;
2230    float mouseY = (float)y;
2231    if (invertY) {
2232        mouseY = ((float)_windowHeight - mouseY);
2233    }
2234    if (picker->pick(_viewer.get(), mouseX, mouseY, callback)) {
2235        TRACE("Delete pick queued: %g %g", mouseX, mouseY);
2236    }
2237}
2238#else
2239void Renderer::hoverPlaceNode(int x, int y, bool invertY)
2240{
2241    if (!_placeNodes.valid()) {
2242        TRACE("No place nodes");
2243        return;
2244    }
2245
2246    osgEarth::IntersectionPicker picker(_viewer.get(), _placeNodes.get());
2247    osgEarth::IntersectionPicker::Hits hits;
2248    float mouseX = (float)x;
2249    float mouseY = (float)y;
2250    if (invertY) {
2251        mouseY = ((float)_windowHeight - mouseY);
2252    }
2253    std::set<osgEarth::Annotation::AnnotationNode*> toUnHover;
2254    for (std::set<osgEarth::Annotation::AnnotationNode*>::iterator itr = _hovered.begin();
2255         itr != _hovered.end(); ++itr) {
2256        toUnHover.insert(*itr);
2257    }
2258    if (picker.pick(mouseX, mouseY, hits)) {
2259        TRACE("Picker hits: %d", hits.size());
2260        for (osgEarth::IntersectionPicker::Hits::const_iterator hitr = hits.begin();
2261             hitr != hits.end(); ++hitr) {
2262            TRACE("Hit: node %p drawable %p idx %d", picker.getNode<osg::Node>(*hitr), hitr->drawable.get(), hitr->primitiveIndex);
2263            osgEarth::Annotation::AnnotationNode *anno =
2264                picker.getNode<osgEarth::Annotation::AnnotationNode>(*hitr);
2265            if (anno != NULL && anno->getDecoration().empty()) {
2266                TRACE("Hit AnnotationNode: %p", anno);
2267                if (_hovered.find(anno) == _hovered.end()) {
2268                    _hovered.insert(anno);
2269                    anno->setDecoration("hover");
2270                    _needsRedraw = true;
2271                }
2272                toUnHover.erase(anno);
2273            }
2274        }
2275#if 0
2276        writeScene("/tmp/test.osgt");
2277#endif
2278    }
2279    for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = toUnHover.begin();
2280         itr != toUnHover.end(); ++itr) {
2281        _hovered.erase(*itr);
2282        (*itr)->clearDecoration();
2283        _needsRedraw = true;
2284    }
2285}
2286
2287void Renderer::deletePlaceNode(int x, int y, bool invertY)
2288{
2289    if (!_placeNodes.valid()) {
2290        TRACE("No place nodes");
2291        return;
2292    }
2293    osgEarth::IntersectionPicker picker(_viewer.get(), _placeNodes.get());
2294    osgEarth::IntersectionPicker::Hits hits;
2295    float mouseX = (float)x;
2296    float mouseY = (float)y;
2297    if (invertY) {
2298        mouseY = ((float)_windowHeight - mouseY);
2299    }
2300    if (picker.pick(mouseX, mouseY, hits)) {
2301        TRACE("Picker hit!");
2302        // prevent multiple hits on the same instance
2303        std::set<osgEarth::Annotation::AnnotationNode *> fired;
2304        for (osgEarth::IntersectionPicker::Hits::const_iterator hitr = hits.begin();
2305             hitr != hits.end(); ++hitr) {
2306            osgEarth::Annotation::AnnotationNode *anno =
2307                picker.getNode<osgEarth::Annotation::AnnotationNode>(*hitr);
2308            if (anno != NULL && fired.find(anno) == fired.end()) {
2309                fired.insert(anno);
2310                _needsRedraw = true;
2311            }
2312        }
2313        for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = fired.begin();
2314             itr != fired.end(); ++itr) {
2315            (*itr)->clearDecoration();
2316            if (_hovered.find(*itr) != _hovered.end()) {
2317                _hovered.erase(*itr);
2318            }
2319            if (_selected.find(*itr) != _selected.end()) {
2320                _selected.erase(*itr);
2321            }
2322           _placeNodes->removeChild(*itr);
2323        }
2324    } else {
2325        TRACE("NO Picker hits");
2326    }
2327#if 0
2328    writeScene("/tmp/test.osg");
2329#endif
2330}
2331#endif
2332
2333void Renderer::addModelLayer(const char *name,
2334                             osgEarth::ModelSourceOptions& opts,
2335                             unsigned int pos,
2336                             bool enableCache,
2337                             bool lighting,
2338                             bool visible,
2339                             bool terrainPatch)
2340{
2341    if (!_map.valid()) {
2342        ERROR("No map");
2343        return;
2344    }
2345    TRACE("layer: %s", name);
2346    osgEarth::ModelLayerOptions layerOpts(name, opts);
2347    if (!enableCache) {
2348        TRACE("Disabling cache for layer %s", name);
2349        layerOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
2350    }
2351    if (!visible) {
2352        layerOpts.visible() = false;
2353    }
2354    layerOpts.lightingEnabled() = lighting;
2355    if (terrainPatch) {
2356        layerOpts.terrainPatch() = true;
2357    }
2358    osgEarth::ModelLayer *layer = new osgEarth::ModelLayer(layerOpts);
2359    if (pos < (unsigned int)_map->getNumModelLayers()) {
2360        _map->insertModelLayer(layer, pos);
2361    } else {
2362        _map->addModelLayer(layer);
2363    }
2364    _needsRedraw = true;
2365}
2366
2367void Renderer::removeModelLayer(const char *name)
2368{
2369    if (!_map.valid()) {
2370        ERROR("No map");
2371        return;
2372    }
2373    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
2374    if (layer != NULL) {
2375        _map->removeModelLayer(layer);
2376        _needsRedraw = true;
2377    } else {
2378        TRACE("Model layer not found: %s", name);
2379    }
2380}
2381
2382void Renderer::moveModelLayer(const char *name, unsigned int pos)
2383{
2384    if (!_map.valid()) {
2385        ERROR("No map");
2386        return;
2387    }
2388    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
2389    if (layer != NULL) {
2390        _map->moveModelLayer(layer, pos);
2391        _needsRedraw = true;
2392    } else {
2393        TRACE("Model layer not found: %s", name);
2394    }
2395}
2396
2397void Renderer::setModelLayerOpacity(const char *name, double opacity)
2398{
2399    if (!_map.valid()) {
2400        ERROR("No map");
2401        return;
2402    }
2403    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
2404    if (layer != NULL) {
2405        layer->setOpacity(opacity);
2406        _needsRedraw = true;
2407    } else {
2408        TRACE("Model layer not found: %s", name);
2409    }
2410}
2411#if 0
2412void Renderer::setModelLayerVisibleRange(const char *name, float min, float max)
2413{
2414    if (!_map.valid()) {
2415        ERROR("No map");
2416        return;
2417    }
2418    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
2419    if (layer != NULL) {
2420        layer->minVisibleRange(min);
2421        layer->maxVisibleRange(max);
2422        _needsRedraw = true;
2423    } else {
2424        TRACE("Model layer not found: %s", name);
2425    }
2426}
2427#endif
2428void Renderer::setModelLayerVisibility(const char *name, bool state)
2429{
2430    if (!_map.valid()) {
2431        ERROR("No map");
2432        return;
2433    }
2434    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
2435    if (layer != NULL) {
2436        layer->setVisible(state);
2437        _needsRedraw = true;
2438    } else {
2439        TRACE("Model layer not found: %s", name);
2440    }
2441}
2442
2443/**
2444 * \brief Resize the render window (image size for renderings)
2445 */
2446void Renderer::setWindowSize(int width, int height)
2447{
2448    if (_windowWidth == width &&
2449        _windowHeight == height) {
2450        TRACE("No change");
2451        return;
2452    }
2453
2454    TRACE("Setting window size to %dx%d", width, height);
2455
2456    double origBitrate = getMaximumBitrate();
2457
2458    _windowWidth = width;
2459    _windowHeight = height;
2460
2461    setMaximumBitrate(origBitrate);
2462
2463    if (_viewer.valid()) {
2464#ifdef USE_OFFSCREEN_RENDERING
2465#ifdef USE_PBUFFER
2466        osg::ref_ptr<osg::GraphicsContext> pbuffer;
2467        osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
2468        traits->x = 0;
2469        traits->y = 0;
2470        traits->width = _windowWidth;
2471        traits->height = _windowHeight;
2472        traits->red = 8;
2473        traits->green = 8;
2474        traits->blue = 8;
2475        traits->alpha = 8;
2476        traits->windowDecoration = false;
2477        traits->pbuffer = true;
2478        traits->doubleBuffer = true;
2479        traits->sharedContext = 0;
2480
2481        pbuffer = osg::GraphicsContext::createGraphicsContext(traits.get());
2482        if (pbuffer.valid()) {
2483            TRACE("Pixel buffer has been created successfully.");
2484        } else {
2485            ERROR("Pixel buffer has not been created successfully.");
2486        }
2487        osg::Camera *camera = new osg::Camera;
2488        camera->setGraphicsContext(pbuffer.get());
2489        //camera->getOrCreateStateSet()->setGlobalDefaults();
2490        camera->setViewport(new osg::Viewport(0, 0, _windowWidth, _windowHeight));
2491        GLenum buffer = pbuffer->getTraits()->doubleBuffer ? GL_BACK : GL_FRONT;
2492        camera->setDrawBuffer(buffer);
2493        camera->setReadBuffer(buffer);
2494        camera->setFinalDrawCallback(_captureCallback.get());
2495        _viewer->addSlave(camera, osg::Matrixd(), osg::Matrixd());
2496        _viewer->realize();
2497#else
2498        if (_captureCallback.valid()) {
2499            _captureCallback->getTexture()->setTextureSize(_windowWidth, _windowHeight);
2500        }
2501        osgViewer::ViewerBase::Windows windows;
2502        _viewer->getWindows(windows);
2503        if (windows.size() == 1) {
2504            windows[0]->setWindowRectangle(0, 0, _windowWidth, _windowHeight);
2505        } else {
2506            ERROR("Num windows: %lu", windows.size());
2507        }
2508#endif
2509#else
2510        osgViewer::ViewerBase::Windows windows;
2511        _viewer->getWindows(windows);
2512#if 1
2513        for (osgViewer::Viewer::Windows::iterator itr = windows.begin();
2514             itr != windows.end(); ++itr) {
2515            osgViewer::GraphicsWindow *window = *itr;
2516            window->setWindowRectangle(0, 0, _windowWidth, _windowHeight);
2517            //window->grabFocusIfPointerInWindow();
2518        }
2519        //_viewer->getCamera()->setViewport(new osg::Viewport(0, 0, _windowWidth, _windowHeight));
2520#else
2521        if (windows.size() == 1) {
2522            windows[0]->setWindowRectangle(0, 0, _windowWidth, _windowHeight);
2523        } else {
2524            ERROR("Num windows: %lu", windows.size());
2525        }
2526#endif
2527#endif
2528        // HACK: Without this, the mouse coordinate mapping uses the old size
2529        // for 1 frame.
2530        assert(_viewer->getEventQueue() != NULL);
2531        //TRACE("Window EventQueue: %p", getEventQueue());
2532        //TRACE("Viewer EventQueue: %p", _viewer->getEventQueue());
2533        _viewer->getEventQueue()->windowResize(0, 0, _windowWidth, _windowHeight);
2534        _needsRedraw = true;
2535    }
2536}
2537
2538/**
2539 * \brief Set the orientation of the camera from a quaternion
2540 * rotation
2541 *
2542 * \param[in] quat A quaternion with scalar part first: w,x,y,z
2543 * \param[in] absolute Is rotation absolute or relative?
2544 */
2545void Renderer::setCameraOrientation(const double quat[4], bool absolute)
2546{
2547    if (_manipulator.valid()) {
2548        _manipulator->setRotation(osg::Quat(quat[1], quat[2], quat[3], quat[0]));
2549        _needsRedraw = true;
2550    }
2551}
2552
2553/**
2554 * \brief Reset pan, zoom, clipping planes and optionally rotation
2555 *
2556 * \param[in] resetOrientation Reset the camera rotation/orientation also
2557 */
2558void Renderer::resetCamera(bool resetOrientation)
2559{
2560    TRACE("Enter: resetOrientation=%d", resetOrientation ? 1 : 0);
2561    if (_viewer.valid()) {
2562        _viewer->home();
2563        _needsRedraw = true;
2564    }
2565}
2566
2567/**
2568 * \brief Perform a 2D translation of the camera
2569 *
2570 * x,y pan amount are specified as signed absolute pan amount in viewport
2571 * units -- i.e. 0 is no pan, .5 is half the viewport, 2 is twice the viewport,
2572 * etc.
2573 *
2574 * \param[in] x Viewport coordinate horizontal panning (positive number pans
2575 * camera left, object right)
2576 * \param[in] y Viewport coordinate vertical panning (positive number pans
2577 * camera up, object down)
2578 */
2579void Renderer::panCamera(double x, double y)
2580{
2581    TRACE("Enter: %g %g", x, y);
2582
2583    if (_manipulator.valid()) {
2584        // Wants mouse delta x,y in normalized screen coords
2585        _manipulator->pan(x, y);
2586        _needsRedraw = true;
2587    }
2588}
2589
2590void Renderer::rotateCamera(double x, double y)
2591{
2592    TRACE("Enter: %g %g", x, y);
2593
2594    if (_manipulator.valid()) {
2595        _manipulator->rotate(x, y);
2596        _needsRedraw = true;
2597    }
2598}
2599
2600/**
2601 * \brief Dolly camera or set orthographic scaling based on camera type
2602 *
2603 * \param[in] y Mouse y coordinate in normalized screen coords
2604 */
2605void Renderer::zoomCamera(double y)
2606{
2607    TRACE("Enter: y: %g", y);
2608
2609    if (_manipulator.valid()) {
2610        // +y = zoom out, -y = zoom in
2611        TRACE("camDist: %g", _manipulator->getDistance());
2612#ifdef LIMIT_ZOOM_BY_MAP_SCALE
2613        if ((_mapScale < 0.0 && y > 0.0) ||
2614            (_mapScale < 0.1 && y < 0.0) ||
2615            (_mapScale > 40000.0 && y > 0.0))
2616            return;
2617#endif
2618#if 1
2619       _manipulator->zoom(0, y);
2620#else
2621        double dist = _manipulator->getDistance();
2622        dist *= (1.0 + y);
2623        _manipulator->setDistance(dist);
2624#endif
2625        _needsRedraw = true;
2626    }
2627}
2628
2629bool Renderer::getImageLayerExtent(const char *name, osgEarth::GeoExtent &ext)
2630{
2631    if (!_map.valid()) {
2632        ERROR("No map");
2633        return false;
2634    }
2635    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
2636    if (layer != NULL) {
2637        osgEarth::TileSource *ts = layer->getTileSource();
2638        if (ts != NULL) {
2639            ext = ts->getDataExtentsUnion();
2640            if (!ext.isValid() && ts->getProfile() != NULL) {
2641                ext = ts->getProfile()->getExtent();
2642            }
2643            TRACE("Image layer %s srs: %s extent: %g %g %g %g",
2644                  name, ext.getSRS() ? ext.getSRS()->getName().c_str() : "nil",
2645                  ext.xMin(), ext.yMin(), ext.xMax(), ext.yMax());
2646            return true;
2647        }
2648    } else {
2649        TRACE("Image layer not found: %s", name);
2650    }
2651    return false;
2652}
2653
2654bool Renderer::getElevationLayerExtent(const char *name, osgEarth::GeoExtent &ext)
2655{
2656    if (!_map.valid()) {
2657        ERROR("No map");
2658        return false;
2659    }
2660    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
2661    if (layer != NULL) {
2662        osgEarth::TileSource *ts = layer->getTileSource();
2663        if (ts != NULL) {
2664            ext = ts->getDataExtentsUnion();
2665            if (!ext.isValid() && ts->getProfile() != NULL) {
2666                ext = ts->getProfile()->getExtent();
2667            }
2668            TRACE("Elevation Layer %s srs: %s extent: %g %g %g %g",
2669                  name, ext.getSRS() ? ext.getSRS()->getName().c_str() : "nil",
2670                  ext.xMin(), ext.yMin(), ext.xMax(), ext.yMax());
2671            return true;
2672        }
2673    } else {
2674        TRACE("Elevation layer not found: %s", name);
2675    }
2676    return false;
2677}
2678
2679bool Renderer::getTerrainMaskLayerExtent(const char *name, osgEarth::GeoExtent &ext)
2680{
2681    if (!_map.valid()) {
2682        ERROR("No map");
2683        return false;
2684    }
2685    osgEarth::MaskLayer *layer = getTerrainMaskLayerByName(name);
2686    if (layer != NULL) {
2687        const osgEarth::SpatialReference *srs = getMapSRS();
2688        osgEarth::GeoExtent newExt(srs);
2689        osg::Vec3dArray *polypts = layer->getOrCreateMaskBoundary(1.0f, srs, NULL);
2690        if (polypts) {
2691            for (size_t i = 0; i < polypts->size(); i++) {
2692                osg::Vec3d pt = (*polypts)[i];
2693                newExt.expandToInclude(pt);
2694            }
2695        }
2696        if (!newExt.isValid()) {
2697            ERROR("Couldn't find extent of layer %s", name);
2698            return false;
2699        }
2700        ext = newExt;
2701        TRACE("Mask Layer %s srs: %s extent: %g %g %g %g",
2702              name, ext.getSRS() ? ext.getSRS()->getName().c_str() : "nil",
2703              ext.xMin(), ext.yMin(), ext.xMax(), ext.yMax());
2704        return true;
2705    } else {
2706        TRACE("Terrain mask layer not found: %s", name);
2707    }
2708    return false;
2709}
2710
2711bool Renderer::getModelLayerExtent(const char *name, osgEarth::GeoExtent &ext)
2712{
2713    if (!_map.valid()) {
2714        ERROR("No map");
2715        return false;
2716    }
2717    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
2718    if (layer != NULL) {
2719        osgEarth::ModelSource *ms = layer->getModelSource();
2720        if (ms != NULL) {
2721            osgEarth::DataExtentList& dataExtents = ms->getDataExtents();
2722            if (dataExtents.size() > 0) {
2723                ext = dataExtents[0];
2724                for (unsigned int i = 1; i < dataExtents.size(); i++) {
2725                    ext.expandToInclude(dataExtents[i]);
2726                }
2727            }
2728            if (!ext.isValid()) {
2729                ERROR("Couldn't find extent of layer %s", name);
2730                return false;
2731            }
2732            TRACE("Model Layer %s srs: %s extent: %g %g %g %g",
2733                  name, ext.getSRS() ? ext.getSRS()->getName().c_str() : "nil",
2734                  ext.xMin(), ext.yMin(), ext.xMax(), ext.yMax());
2735            return true;
2736        }
2737    } else {
2738        TRACE("Model layer not found: %s", name);
2739    }
2740    return false;
2741}
2742
2743/**
2744 * \brief Set view to fit a bounding rectangle
2745 *
2746 * A camera distance is chosen such that the extent rectangle
2747 * will fit in the vertical field of view.
2748 *
2749 * \param ext input GeoExtent
2750 * \param durationSecs Animation duration for the view change.
2751 */
2752void Renderer::setViewpointFromExtent(const osgEarth::GeoExtent& ext,
2753                                      double durationSecs)
2754{
2755    setViewpointFromRect(ext.xMin(), ext.yMin(), ext.xMax(), ext.yMax(),
2756                         ext.getSRS(), durationSecs);
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 xmin Minimum X coord in the given SRS
2766 * \param ymin Minimum Y coord in the given SRS
2767 * \param xmax Maximum X coord in the given SRS
2768 * \param ymax Maximum Y coord in the given SRS
2769 * \param srs Optional SRS of bounding points.  If omitted, wgs84 is assumed.
2770 * \param durationSecs Animation duration for the view change.
2771 */
2772void Renderer::setViewpointFromRect(double xmin, double ymin,
2773                                    double xmax, double ymax,
2774                                    const osgEarth::SpatialReference *srs,
2775                                    double durationSecs)
2776{
2777    if (!_viewer.valid() || !_manipulator.valid() || getMapSRS() == NULL)
2778        return;
2779
2780    double x = 0.0, y = 0.0, distance = 0.0;
2781    double x1 = xmin, y1 = ymin, x2 = xmax, y2 = ymax;
2782    osgEarth::Units distanceUnits = getMapSRS()->getUnits();
2783    if (getMapSRS()->isProjected() && !getMapSRS()->isPlateCarre()) {
2784        TRACE("Projected");
2785        osg::ref_ptr<const osgEarth::SpatialReference> fromSRS =
2786            srs ? srs : osgEarth::SpatialReference::create("wgs84");
2787        fromSRS->transformExtentToMBR(getMapSRS(), x1, y1, x2, y2);
2788        double height = y2 - y1;
2789        double fovy, aspect, near, far;
2790        _viewer->getCamera()->getProjectionMatrixAsPerspective(fovy, aspect, near, far);
2791        distance = height / (2. * tan(osg::DegreesToRadians(fovy)/2.));
2792        TRACE("fov: %g a: %g n: %g f: %g (%g, %g, %g, %g)", fovy, aspect, near, far, x1, y1, x2, y2);
2793        x = x1 + (x2 - x1)/2.0;
2794        y = y1 + (y2 - y1)/2.0;
2795        TRACE("x: %g y: %g, dist: %g", x, y, distance);
2796    } else if (getMapSRS()->isGeographic() && !getMapSRS()->isPlateCarre()) {
2797        // World coords are ECEF
2798        TRACE("Geocentric");
2799        osg::ref_ptr<const osgEarth::SpatialReference> fromSRS =
2800            srs ? srs : osgEarth::SpatialReference::create("wgs84");
2801        fromSRS->transformExtentToMBR(getMapSRS(), x1, y1, x2, y2);
2802        TRACE("(%g, %g, %g, %g)", x1, y1, x2, y2);
2803        // These are angular units
2804        x = x1 + (x2 - x1)/2.0;
2805        y = y1 + (y2 - y1)/2.0;
2806        // These will be ECEF
2807        osg::Vec3d world1, world2, world3, world4;
2808        // bottom
2809        getMapSRS()->transformToWorld(osg::Vec3d(x, y1, 0), world1);
2810        // top
2811        getMapSRS()->transformToWorld(osg::Vec3d(x, y2, 0), world2);
2812        // Focal point on surface
2813        getMapSRS()->transformToWorld(osg::Vec3d(x, y, 0), world3);
2814        // point on line between top and bottom points
2815        world4 = world1 + (world2 - world1)/2.0;
2816        TRACE("world1: %g,%g,%g world2: %g,%g,%g",
2817             world1.x(), world1.y(), world1.z(),
2818             world2.x(), world2.y(), world2.z());
2819        TRACE("world3: %g,%g,%g world4: %g,%g,%g",
2820             world3.x(), world3.y(), world3.z(),
2821             world4.x(), world4.y(), world4.z());
2822        double height = (world2 - world1).length();
2823        double fovy, aspect, near, far;
2824        _viewer->getCamera()->getProjectionMatrixAsPerspective(fovy, aspect, near, far);
2825        distance = height / (2. * tan(osg::DegreesToRadians(fovy)/2.));
2826        distance -= (world4 - world3).length();
2827        if (distance < 0.0) distance = 0.0;
2828        distanceUnits = osgEarth::Units::METERS;
2829        TRACE("fov: %g a: %g n: %g f: %g (%g, %g, %g, %g)", fovy, aspect, near, far, x1, y1, x2, y2);
2830        TRACE("x: %g y: %g, dist: %g", x, y, distance);
2831    } else {
2832        assert(getMapSRS()->isPlateCarre());
2833        ERROR("Plate Carree not supported");
2834        return;
2835    }
2836    TRACE("Map units: %d", getMapSRS()->getUnits().getType());
2837    osgEarth::Viewpoint vpt;
2838    vpt.focalPoint()->set(getMapSRS(), x, y, 0.0, osgEarth::ALTMODE_ABSOLUTE);
2839    vpt.range()->set(distance, distanceUnits);
2840    vpt.heading()->set(0.0, osgEarth::Units::DEGREES);
2841    vpt.pitch()->set(-90.0, osgEarth::Units::DEGREES);
2842
2843    _manipulator->setViewpoint(vpt, durationSecs);
2844    _needsRedraw = true;
2845}
2846
2847double Renderer::getMaxDistanceFromExtent(const osgEarth::GeoExtent& extent)
2848{
2849    if (!_viewer.valid() || extent.getSRS() == NULL)
2850        return 2.0e7;
2851
2852    double x = 0.0, y = 0.0, distance = 0.0;
2853    double x1 = extent.xMin(), y1 = extent.yMin(), x2 = extent.xMax(), y2 = extent.yMax();
2854    osgEarth::Units distanceUnits = extent.getSRS()->getUnits();
2855    if (extent.getSRS()->isProjected() && !extent.getSRS()->isPlateCarre()) {
2856        TRACE("Projected");
2857        double height = y2 - y1;
2858        double fovy, aspect, near, far;
2859        _viewer->getCamera()->getProjectionMatrixAsPerspective(fovy, aspect, near, far);
2860        distance = height / (2. * tan(osg::DegreesToRadians(fovy)/2.));
2861        TRACE("fov: %g a: %g n: %g f: %g (%g, %g, %g, %g)", fovy, aspect, near, far, x1, y1, x2, y2);
2862        x = x1 + (x2 - x1)/2.0;
2863        y = y1 + (y2 - y1)/2.0;
2864        TRACE("x: %g y: %g, dist: %g", x, y, distance);
2865    } else if (extent.getSRS()->isGeographic() && !extent.getSRS()->isPlateCarre()) {
2866        // World coords are ECEF
2867        TRACE("Geocentric");
2868        TRACE("(%g, %g, %g, %g)", x1, y1, x2, y2);
2869        // These are angular units
2870        x = x1 + (x2 - x1)/2.0;
2871        y = y1 + (y2 - y1)/2.0;
2872        // These will be ECEF
2873        osg::Vec3d world1, world2, world3, world4;
2874        // bottom
2875        getMapSRS()->transformToWorld(osg::Vec3d(x, y1, 0), world1);
2876        // top
2877        getMapSRS()->transformToWorld(osg::Vec3d(x, y2, 0), world2);
2878        // Focal point on surface
2879        getMapSRS()->transformToWorld(osg::Vec3d(x, y, 0), world3);
2880        // point on line between top and bottom points
2881        world4 = world1 + (world2 - world1)/2.0;
2882        double height = (world2 - world1).length();
2883        double fovy, aspect, near, far;
2884        _viewer->getCamera()->getProjectionMatrixAsPerspective(fovy, aspect, near, far);
2885        distance = height / (2. * tan(osg::DegreesToRadians(fovy)/2.));
2886        distance -= (world4 - world3).length();
2887        if (distance < 0.0) distance = 0.0;
2888        distanceUnits = osgEarth::Units::METERS;
2889        TRACE("fov: %g a: %g n: %g f: %g (%g, %g, %g, %g)", fovy, aspect, near, far, x1, y1, x2, y2);
2890        TRACE("x: %g y: %g, dist: %g", x, y, distance);
2891    } else {
2892        assert(extent.getSRS()->isPlateCarre());
2893        ERROR("Plate Carree not supported");
2894    }
2895
2896    return distance;
2897}
2898
2899/**
2900 * \brief Dolly camera to set distance from focal point
2901 *
2902 * \param[in] dist distance in world coordinate units (typically meters)
2903 */
2904void Renderer::setCameraDistance(double dist)
2905{
2906    TRACE("Enter: dist: %g", dist);
2907
2908    if (_manipulator.valid()) {
2909        TRACE("camDist: %g", _manipulator->getDistance());
2910
2911        _manipulator->setDistance(dist);
2912
2913        _needsRedraw = true;
2914    }
2915}
2916
2917void Renderer::keyPress(int key)
2918{
2919    osgGA::EventQueue *queue = getEventQueue();
2920    if (queue != NULL) {
2921        queue->keyPress(key);
2922        _needsRedraw = true;
2923    }
2924}
2925
2926void Renderer::keyRelease(int key)
2927{
2928    osgGA::EventQueue *queue = getEventQueue();
2929    if (queue != NULL) {
2930        queue->keyRelease(key);
2931        _needsRedraw = true;
2932    }
2933}
2934
2935void Renderer::setThrowingEnabled(bool state)
2936{
2937    if (_manipulator.valid()) {
2938        _manipulator->getSettings()->setThrowingEnabled(state);
2939    }
2940}
2941
2942void Renderer::mouseDoubleClick(int button, double x, double y)
2943{
2944    osgGA::EventQueue *queue = getEventQueue();
2945    if (queue != NULL) {
2946        queue->mouseDoubleButtonPress((float)x, (float)y, button);
2947        _needsRedraw = true;
2948    }
2949}
2950
2951void Renderer::mouseClick(int button, double x, double y)
2952{
2953    osgGA::EventQueue *queue = getEventQueue();
2954    if (queue != NULL) {
2955        queue->mouseButtonPress((float)x, (float)y, button);
2956        _needsRedraw = true;
2957    }
2958}
2959
2960void Renderer::mouseDrag(int button, double x, double y)
2961{
2962    osgGA::EventQueue *queue = getEventQueue();
2963    if (queue != NULL) {
2964        queue->mouseMotion((float)x, (float)y);
2965        _needsRedraw = true;
2966    }
2967}
2968
2969void Renderer::mouseRelease(int button, double x, double y)
2970{
2971    osgGA::EventQueue *queue = getEventQueue();
2972    if (queue != NULL) {
2973        queue->mouseButtonRelease((float)x, (float)y, button);
2974        _needsRedraw = true;
2975    }
2976}
2977
2978void Renderer::mouseMotion(double x, double y)
2979{
2980#if 1
2981    osgGA::EventQueue *queue = getEventQueue();
2982    if (queue != NULL) {
2983        queue->mouseMotion((float)x, (float)y);
2984        _needsRedraw = true;
2985    }
2986#else
2987    if (_mouseCoordsTool.valid()) {
2988        if (_viewer.valid() && _coordsCallback.valid()) {
2989            osgEarth::GeoPoint mapPt;
2990            if (mapMouseCoords((float)x, (float)y, mapPt)) {
2991                _coordsCallback->set(mapPt, _viewer->asView(), _mapNode);
2992            } else {
2993                _coordsCallback->reset(_viewer->asView(), _mapNode);
2994            }
2995            _needsRedraw = true;
2996        }
2997    }
2998#endif
2999}
3000
3001void Renderer::mouseScroll(int direction)
3002{
3003    osgGA::EventQueue *queue = getEventQueue();
3004    if (queue != NULL) {
3005        queue->mouseScroll((direction > 0 ? osgGA::GUIEventAdapter::SCROLL_UP : osgGA::GUIEventAdapter::SCROLL_DOWN));
3006        _needsRedraw = true;
3007    }
3008}
3009
3010/**
3011 * \brief Set the RGB background color to render into the image
3012 */
3013void Renderer::setBackgroundColor(float color[3])
3014{
3015    _bgColor[0] = color[0];
3016    _bgColor[1] = color[1];
3017    _bgColor[2] = color[2];
3018
3019    if (_viewer.valid()) {
3020        _viewer->getCamera()->setClearColor(osg::Vec4(color[0], color[1], color[2], 1));
3021
3022        _needsRedraw = true;
3023    }
3024}
3025
3026/**
3027 * \brief Sets flag to trigger rendering next time render() is called
3028 */
3029void Renderer::eventuallyRender()
3030{
3031    _needsRedraw = true;
3032}
3033
3034/**
3035 * \brief Get a timeout for select()
3036 *
3037 * If the paging thread is idle, returns <0 indicating that the
3038 * select call can block until data is available.  Otherwise,
3039 * if the frame render time was faster than the target frame
3040 * rate, return the remaining frame time.
3041 */
3042void Renderer::getTimeout(struct timeval *tv)
3043{
3044    if (!checkNeedToDoFrame()) {
3045        // <0 means no timeout, block until socket has data
3046        // If _idleTimeout is positive, server will disconnect after timeout
3047        tv->tv_sec = _idleTimeout;
3048        tv->tv_usec = 0L;
3049    } else if (_lastFrameTime < _minFrameTime) {
3050        tv->tv_sec = 0L;
3051        tv->tv_usec = (long)1.0e6*(_minFrameTime - _lastFrameTime);
3052    } else {
3053        // No timeout (poll)
3054        tv->tv_sec = tv->tv_usec = 0L;
3055    }
3056}
3057
3058/**
3059 * \brief Check if paging thread is quiescent
3060 */
3061bool Renderer::isPagerIdle()
3062{
3063    if (!_viewer.valid())
3064        return true;
3065    else
3066        return (!_viewer->getDatabasePager()->requiresUpdateSceneGraph() &&
3067                !_viewer->getDatabasePager()->getRequestsInProgress());
3068}
3069
3070/**
3071 * \brief Check is frame call is necessary to render and/or update
3072 * in response to events or timed actions
3073 */
3074bool Renderer::checkNeedToDoFrame()
3075{
3076    return (_needsRedraw || _pickPending ||
3077            (_viewer.valid() && _viewer->checkNeedToDoFrame()));
3078}
3079
3080/**
3081 * \brief MapNode event phase
3082 *
3083 * This is called by the MapNode's event callback during the event
3084 * traversal of the viewer
3085 */
3086void Renderer::mapNodeUpdate()
3087{
3088    computeMapScale();
3089}
3090
3091void Renderer::markFrameStart()
3092{
3093    _startFrameTime = osg::Timer::instance()->tick();
3094}
3095
3096void Renderer::markFrameEnd()
3097{
3098    osg::Timer_t endFrameTick = osg::Timer::instance()->tick();
3099    _lastFrameTime = osg::Timer::instance()->delta_s(_startFrameTime, endFrameTick);
3100    if (_lastFrameTime > _minFrameTime) {
3101        FRAME("BROKE FRAME by %.2f msec", (_lastFrameTime - _minFrameTime)*1000.0f);
3102    } else {
3103        FRAME("Frame time: %.2f msec", _lastFrameTime*1000.0f);
3104    }
3105#ifdef USE_THROTTLING_SLEEP
3106    if (_lastFrameTime < _minFrameTime) {
3107        FRAME("Sleeping for %.2f msec", (_minFrameTime - _lastFrameTime)*1000.0f);
3108        OpenThreads::Thread::microSleep(static_cast<unsigned int>(1000000.0*(_minFrameTime - _lastFrameTime)));
3109    }
3110#endif
3111}
3112
3113/**
3114 * \brief Cause the rendering to render a new image if needed
3115 *
3116 * The _needsRedraw flag indicates if a state change has occured since
3117 * the last rendered frame
3118 */
3119bool Renderer::render()
3120{
3121    if (_viewer.valid() && checkNeedToDoFrame()) {
3122        FRAME("Enter needsRedraw=%d",  _needsRedraw ? 1 : 0);
3123        _renderStartTime = osg::Timer::instance()->tick();
3124        FRAME("Before frame()");
3125        _viewer->frame();
3126        FRAME("After frame()");
3127        _renderStopTime = osg::Timer::instance()->tick();
3128        _renderTime = osg::Timer::instance()->delta_s(_renderStartTime, _renderStopTime);
3129        FRAME("Render time: %g msec", _renderTime * 1000.0);
3130#ifndef SLEEP_AFTER_QUEUE_FRAME
3131        _lastFrameTime = _renderTime;
3132#ifdef USE_THROTTLING_SLEEP
3133        if (_lastFrameTime < _minFrameTime) {
3134            FRAME("Sleeping for %.2f msec", (_minFrameTime - _lastFrameTime)*1000.0f);
3135            OpenThreads::Thread::microSleep(static_cast<unsigned int>(1.0e6*(_minFrameTime - _lastFrameTime)));
3136        }
3137#endif
3138#endif
3139#ifdef WANT_FRAME
3140        if (_viewer->getViewerStats() != NULL) {
3141            _viewer->getViewerStats()->report(std::cerr, _viewer->getViewerStats()->getLatestFrameNumber());
3142        }
3143#endif
3144        _needsRedraw = false;
3145        return true;
3146    } else {
3147        _renderStartTime = _renderStopTime = osg::Timer::instance()->tick();
3148        _renderTime = 0;
3149        return false;
3150    }
3151}
3152
3153/**
3154 * \brief Read back the rendered framebuffer image
3155 */
3156osg::Image *Renderer::getRenderedFrame()
3157{
3158    if (_captureCallback.valid())
3159        return _captureCallback->getImage();
3160    else
3161        return NULL;
3162}
3163
3164void Renderer::setScaleBar(bool state)
3165{
3166    if (_scaleLabel.valid()) {
3167        _scaleLabel->setVisible(state);
3168    }
3169    if (_scaleBar.valid()) {
3170        _scaleBar->setVisible(state);
3171    }
3172    _needsRedraw = true;
3173}
3174
3175void Renderer::setScaleBarUnits(ScaleBarUnits units)
3176{
3177    _scaleBarUnits = units;
3178    _needsRedraw = true;
3179}
3180
3181/**
3182 * \brief Compute the scale ratio of the map based on a horizontal center line
3183 *
3184 * The idea here is to take 2 screen points on a horizontal line in the center
3185 * of the screen and convert to lat/long.  The lat/long coordinates are then
3186 * used to compute the great circle distance (assuming spherical earth) between
3187 * the points.
3188 *
3189 * We could use local projected map coordinates for the distance computation,
3190 * which would be faster; however, this would not show e.g. the change in
3191 * scale at different latitudes
3192 */
3193double Renderer::computeMapScale()
3194{
3195    if (!_scaleLabel.valid() || !_scaleLabel->visible()) {
3196        return -1.0;
3197    }
3198    if (!_mapNode.valid() || _mapNode->getTerrain() == NULL) {
3199        ERROR("No map");
3200        return -1.0;
3201    }
3202    if (!_viewer.valid()) {
3203        ERROR("No viewer");
3204        return -1.0;
3205    }
3206
3207    double x, y;
3208    double pixelWidth = _windowWidth * 0.1 * 2.0;
3209    if (pixelWidth < 10)
3210        pixelWidth = 10;
3211    if (pixelWidth > 150)
3212        pixelWidth = 150;
3213    x = (double)(_windowWidth -1)/2.0 - pixelWidth / 2.0;
3214    y = (double)(_windowHeight-1)/2.0;
3215
3216    osg::Vec3d world1, world2;
3217    if (!_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), x, y, world1)) {
3218        // off map
3219        TRACE("Off map coords: %g %g", x, y);
3220        _scaleLabel->setText("");
3221        _scaleBar->setWidth(0);
3222        return -1.0;
3223    }
3224    x += pixelWidth;
3225    if (!_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), x, y, world2)) {
3226        // off map
3227        TRACE("Off map coords: %g %g", x, y);
3228        _scaleLabel->setText("");
3229        _scaleBar->setWidth(0);
3230        return -1.0;
3231    }
3232
3233#if 0
3234    TRACE("w1: %g %g %g w2: %g %g %g",
3235          world1.x(), world1.y(), world1.z(),
3236          world2.x(), world2.y(), world2.z());
3237#endif
3238
3239    double meters;
3240    double radius = 6378137.0;
3241    if (_mapNode->getMapSRS() &&
3242        _mapNode->getMapSRS()->getEllipsoid()) {
3243        radius = _mapNode->getMapSRS()->getEllipsoid()->getRadiusEquator();
3244    }
3245    if (!_map->isGeocentric() &&
3246        _mapNode->getMapSRS() &&
3247        _mapNode->getMapSRS()->isGeographic()) {
3248        TRACE("Map is geographic");
3249        // World cords are already lat/long
3250        // Compute great circle distance
3251        meters =
3252            osgEarth::GeoMath::distance(world1, world2, _mapNode->getMapSRS());
3253    } else if (_mapNode->getMapSRS()) {
3254        // Get map coords in lat/long
3255        osgEarth::GeoPoint mapPoint1, mapPoint2;
3256        mapPoint1.fromWorld(_mapNode->getMapSRS(), world1);
3257        mapPoint1.makeGeographic();
3258        mapPoint2.fromWorld(_mapNode->getMapSRS(), world2);
3259        mapPoint2.makeGeographic();
3260        // Compute great circle distance
3261        meters =
3262            osgEarth::GeoMath::distance(osg::DegreesToRadians(mapPoint1.y()),
3263                                        osg::DegreesToRadians(mapPoint1.x()),
3264                                        osg::DegreesToRadians(mapPoint2.y()),
3265                                        osg::DegreesToRadians(mapPoint2.x()),
3266                                        radius);
3267    } else {
3268        // Assume geocentric?
3269        ERROR("No map SRS");
3270        _scaleLabel->setText("");
3271        _scaleBar->setWidth(0);
3272        return -1.0;
3273    }
3274
3275    double scale = meters / pixelWidth;
3276    // 1mi = 5280 feet
3277    //double scaleMiles = scale / 1609.344; // International mile = 1609.344m
3278    //double scaleNauticalMiles = scale / 1852.0; // nautical mile = 1852m
3279    //double scaleUSSurveyMiles = scale / 1609.347218694; // US survey mile = 5280 US survey feet
3280    //double scaleUSSurveyFeet = scale * 3937.0/1200.0; // US survey foot = 1200/3937 m
3281#if 0
3282    TRACE("m: %g px: %g m/px: %g", meters, pixelWidth, scale);
3283#endif
3284    _mapScale = scale;
3285    switch (_scaleBarUnits) {
3286    case UNITS_NAUTICAL_MILES: {
3287        double nmi = meters / 1852.0;
3288        scale = nmi / pixelWidth;
3289        nmi = normalizeScaleNauticalMiles(nmi);
3290        pixelWidth = nmi / scale;
3291        if (_scaleLabel.valid()) {
3292            _scaleLabel->setText(osgEarth::Stringify()
3293                                 << nmi
3294                                 << " nmi");
3295        }
3296    }
3297        break;
3298    case UNITS_US_SURVEY_FEET: {
3299        double feet = meters * 3937.0/1200.0;
3300        scale = feet / pixelWidth;
3301        feet = normalizeScaleFeet(feet);
3302        pixelWidth = feet / scale;
3303        if (_scaleLabel.valid()) {
3304            if (feet >= 5280) {
3305                _scaleLabel->setText(osgEarth::Stringify()
3306                                     << feet / 5280.0
3307                                     << " miUS");
3308             } else {
3309                _scaleLabel->setText(osgEarth::Stringify()
3310                                     << feet
3311                                     << " ftUS");
3312            }
3313        }
3314    }
3315        break;
3316    case UNITS_INTL_FEET: {
3317        double feet = 5280.0 * meters / 1609.344;
3318        scale = feet / pixelWidth;
3319        feet = normalizeScaleFeet(feet);
3320        pixelWidth = feet / scale;
3321        if (_scaleLabel.valid()) {
3322            if (feet >= 5280) {
3323                _scaleLabel->setText(osgEarth::Stringify()
3324                                     << feet / 5280.0
3325                                     << " mi");
3326            } else {
3327                _scaleLabel->setText(osgEarth::Stringify()
3328                                     << feet
3329                                     << " ft");
3330            }
3331        }
3332    }
3333        break;
3334    case UNITS_METERS:
3335    default: {
3336        meters = normalizeScaleMeters(meters);
3337        pixelWidth = meters / scale;
3338        if (_scaleLabel.valid()) {
3339            if (meters >= 1000) {
3340                _scaleLabel->setText(osgEarth::Stringify()
3341                                     << meters / 1000.0
3342                                     << " km");
3343            } else {
3344                _scaleLabel->setText(osgEarth::Stringify()
3345                                     << meters
3346                                     << " m");
3347            }
3348        }
3349    }
3350        break;
3351    }
3352    if (_scaleBar.valid()) {
3353        _scaleBar->setWidth(pixelWidth);
3354    }
3355    return scale;
3356}
3357
3358std::string Renderer::getCanonicalPath(const std::string& url) const
3359{
3360    std::string retStr;
3361    std::string proto = osgDB::getServerProtocol(url);
3362    if (proto.empty()) {
3363        retStr = osgDB::getRealPath(url);
3364        if (!osgDB::fileExists(retStr)) {
3365            if (!url.empty() && url[0] != '/') {
3366                // Relative URL, assume local://
3367                std::ostringstream oss;
3368                oss << getCacheDirectory() << "/" << url;
3369                retStr = oss.str();
3370            } else {
3371                retStr = "";
3372            }
3373        }
3374    } else if (proto == "local") {
3375        TRACE("Local protocol: '%s'", getLocalFilePath(url).c_str());
3376        std::ostringstream oss;
3377        oss << getCacheDirectory() << "/" << getLocalFilePath(url);
3378        retStr = oss.str();
3379    } else if (proto == "file") {
3380        TRACE("File: '/%s'", osgDB::getServerFileName(url).c_str());
3381        std::ostringstream oss;
3382        oss << "/" <<  osgDB::getServerFileName(url);
3383        retStr = oss.str();
3384    } else if (proto == "idata") {
3385        std::string fileName = osgDB::getServerFileName(url);
3386        TRACE("IData protocol: coll: '%s', '%s'", osgDB::getServerAddress(url).c_str(), fileName.c_str());
3387        int collection = atoi(osgDB::getServerAddress(url).c_str());
3388        {
3389            std::ostringstream oss;
3390            oss << getCacheDirectory() << "/" << fileName;
3391            retStr = oss.str();
3392            std::string dirPath = osgDB::getFilePath(retStr);
3393            osgDB::makeDirectory(dirPath);
3394        }
3395        // If this is a shape file, get auxiliary files (e.g. .dbf, .shx, etc)
3396        if (osgDB::getFileExtension(fileName) == "shp") {
3397            // Check for files with same basename
3398            std::vector<DirectoryItem> items;
3399            std::string path = osgDB::getFilePath(fileName);
3400            getContents(collection, path.c_str(), items);
3401            // Get name without extension
3402            std::string basename = osgDB::getStrippedName(fileName);
3403            for (std::vector<DirectoryItem>::const_iterator itr = items.begin();
3404                 itr != items.end(); ++itr) {
3405                if (!itr->isDir &&
3406                    osgDB::getStrippedName(itr->name) == basename) {
3407                    std::string localName;
3408                    {
3409                        std::ostringstream oss;
3410                        oss << getCacheDirectory() << "/" << itr->name;
3411                        localName = oss.str();
3412                    }
3413                    if (!localName.empty() && !osgDB::fileExists(localName)) {
3414                        IData::Buffer buf;
3415                        {
3416                            std::ostringstream oss;
3417                            oss << "//" << itr->name;
3418                            IData::getFile(collection, oss.str().c_str(), &buf);
3419                        }
3420                        std::ofstream file(localName.c_str());
3421                        file.write((char *)buf.data, buf.size);
3422                        file.close();
3423                    }
3424                }
3425            }
3426        }
3427        if (!osgDB::fileExists(retStr)) {
3428            std::ostringstream oss;
3429            oss << "//" << fileName;
3430            IData::Buffer buf;
3431            IData::getFile(collection, oss.str().c_str(), &buf);
3432            std::ofstream file(retStr.c_str());
3433            file.write((char *)buf.data, buf.size);
3434            file.close();
3435        }
3436    } else {
3437        TRACE("Protocol: '%s' url: '%s'", proto.c_str(), url.c_str());
3438        retStr = url;
3439    }
3440    return retStr;
3441}
3442
3443void Renderer::writeScene(const std::string& file)
3444{
3445    if (_sceneRoot.valid()) {
3446        GraphPrintVisitor gpv;
3447        _sceneRoot->accept(gpv);
3448        //osgDB::writeNodeFile(*_sceneRoot.get(), file);
3449    }
3450}
Note: See TracBrowser for help on using the repository browser.