source: geovis/trunk/Renderer.cpp @ 4957

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

Add protocol option to enable/disable disk caching of image layers

File size: 75.2 KB
Line 
1/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 * Copyright (C) 2004-2013  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
14#include <limits>
15#include <set>
16
17#include <sys/types.h>
18#include <unistd.h> // For getpid()
19
20#include <GL/gl.h>
21
22#ifdef WANT_TRACE
23#include <sys/time.h>
24#endif
25
26#include <osgDB/WriteFile>
27#include <osgDB/FileUtils>
28#include <osgDB/FileNameUtils>
29#include <osgGA/StateSetManipulator>
30#include <osgGA/GUIEventAdapter>
31#include <osgViewer/ViewerEventHandlers>
32
33#include <osgEarth/Version>
34#include <osgEarth/FileUtils>
35#include <osgEarth/Cache>
36#include <osgEarth/CachePolicy>
37#include <osgEarthDrivers/cache_filesystem/FileSystemCache>
38#include <osgEarth/MapNode>
39#include <osgEarth/Bounds>
40#include <osgEarth/Profile>
41#include <osgEarth/Viewpoint>
42#include <osgEarth/GeoMath>
43#include <osgEarth/TerrainLayer>
44#include <osgEarth/ImageLayer>
45#include <osgEarth/ElevationLayer>
46#include <osgEarth/ModelLayer>
47#include <osgEarth/DateTime>
48#include <osgEarth/Pickers>
49#include <osgEarthFeatures/Feature>
50#include <osgEarthSymbology/Color>
51#include <osgEarthSymbology/Geometry>
52#include <osgEarthSymbology/Style>
53#include <osgEarthSymbology/StyleSheet>
54#include <osgEarthSymbology/IconSymbol>
55#include <osgEarthSymbology/LineSymbol>
56
57#include <osgEarthAnnotation/AnnotationNode>
58#include <osgEarthAnnotation/FeatureNode>
59#include <osgEarthAnnotation/PlaceNode>
60#include <osgEarthAnnotation/HighlightDecoration>
61#include <osgEarthAnnotation/ScaleDecoration>
62#include <osgEarthUtil/EarthManipulator>
63#if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 1)
64#include <osgEarthUtil/Sky>
65#include <osgEarthDrivers/sky_simple/SimpleSkyOptions>
66#else
67#include <osgEarthUtil/SkyNode>
68#endif
69#include <osgEarthUtil/AutoClipPlaneHandler>
70#include <osgEarthUtil/MouseCoordsTool>
71#include <osgEarthUtil/UTMGraticule>
72#include <osgEarthUtil/MGRSGraticule>
73#include <osgEarthUtil/GeodeticGraticule>
74#include <osgEarthUtil/LatLongFormatter>
75#include <osgEarthUtil/MGRSFormatter>
76#include <osgEarthUtil/GLSLColorFilter>
77#include <osgEarthUtil/VerticalScale>
78#include <osgEarthDrivers/gdal/GDALOptions>
79#include <osgEarthDrivers/engine_mp/MPTerrainEngineOptions>
80
81#include "Renderer.h"
82#if 0
83#include "SingleWindow.h"
84#endif
85#include "ScaleBar.h"
86#include "FileUtil.h"
87#include "Trace.h"
88
89#define MSECS_ELAPSED(t1, t2) \
90    ((t1).tv_sec == (t2).tv_sec ? (((t2).tv_usec - (t1).tv_usec)/1.0e+3) : \
91     (((t2).tv_sec - (t1).tv_sec))*1.0e+3 + (double)((t2).tv_usec - (t1).tv_usec)/1.0e+3)
92
93#ifndef CACHE_DIR
94#define CACHE_DIR "/var/cache/geovis"
95#endif
96#define BASE_IMAGE "world.tif"
97#define PIN_ICON "placemark32.png"
98
99using namespace GeoVis;
100
101Renderer::Renderer() :
102    _needsRedraw(false),
103    _windowWidth(500),
104    _windowHeight(500),
105    _scaleBarUnits(UNITS_METERS)
106{
107    TRACE("Enter");
108
109    _bgColor[0] = 0;
110    _bgColor[1] = 0;
111    _bgColor[2] = 0;
112    //setMaximumFrameRateInHertz(15.0);
113    // 100 Mbps
114    setMaximumBitrate(1.0e8);
115    _lastFrameTime = _minFrameTime;
116    TRACE("Bandwidth target: %.2f Mbps", (float)(getMaximumBitrate()/1.0e6));
117    TRACE("Frame rate target: %.2f Hz", (float)getMaximumFrameRateInHertz());
118    TRACE("Frame time target: %.2f msec", _minFrameTime * 1000.0f);
119
120    setCacheBaseDirectory(CACHE_DIR);
121    char *base = getenv("MAP_BASE_URI");
122    if (base != NULL) {
123        _baseURI = base;
124        TRACE("Setting base URI: %s", _baseURI.c_str());
125    }
126}
127
128osgGA::EventQueue *Renderer::getEventQueue()
129{
130    if (_viewer.valid()) {
131        osgViewer::ViewerBase::Windows windows;
132        _viewer->getWindows(windows);
133        if (windows.size() > 0) {
134            return windows[0]->getEventQueue();
135        }
136    }
137    return NULL;
138}
139
140Renderer::~Renderer()
141{
142    TRACE("Enter");
143
144    // Cache is deleted in main() to allow ref-counted objects to
145    // be deleted and threads to exit first
146
147    TRACE("Leave");
148}
149
150std::string Renderer::getBaseImage()
151{
152    std::ostringstream oss;
153    oss << _resourcePath << "/" << BASE_IMAGE;
154    return oss.str();
155}
156
157std::string Renderer::getPinIcon()
158{
159    std::ostringstream oss;
160    oss << _resourcePath << "/" << PIN_ICON;
161    return oss.str();
162}
163
164void Renderer::setupCache()
165{
166    std::ostringstream dir;
167    dir << _cacheBaseDir << "/geovis_cache" << getpid();
168    _cacheDir = dir.str();
169    const char *path = _cacheDir.c_str();
170    TRACE("Cache dir: %s", path);
171    removeDirectory(path);
172    if (!osgDB::makeDirectory(_cacheDir)) {
173        ERROR("Failed to create directory '%s'", path);
174    }
175}
176
177void Renderer::initColorMaps()
178{
179    if (!_colorMaps.empty())
180        return;
181
182    osg::TransferFunction1D *defaultColorMap = new osg::TransferFunction1D;
183    defaultColorMap->allocate(256);
184    defaultColorMap->setColor(0.00, osg::Vec4f(0,0,1,1), false);
185    defaultColorMap->setColor(0.25, osg::Vec4f(0,1,1,1), false);
186    defaultColorMap->setColor(0.50, osg::Vec4f(0,1,0,1), false);
187    defaultColorMap->setColor(0.75, osg::Vec4f(1,1,0,1), false);
188    defaultColorMap->setColor(1.00, osg::Vec4f(1,0,0,1), false);
189    defaultColorMap->updateImage();
190    addColorMap("default", defaultColorMap);
191    osg::TransferFunction1D *defaultGrayColorMap = new osg::TransferFunction1D;
192    defaultGrayColorMap->allocate(256);
193    defaultGrayColorMap->setColor(0, osg::Vec4f(0,0,0,1), false);
194    defaultGrayColorMap->setColor(1, osg::Vec4f(1,1,1,1), false);
195    defaultGrayColorMap->updateImage();
196    addColorMap("grayDefault", defaultGrayColorMap);
197}
198
199void Renderer::addColorMap(const ColorMapId& id, osg::TransferFunction1D *xfer)
200{
201    _colorMaps[id] = xfer;
202}
203
204void Renderer::deleteColorMap(const ColorMapId& id)
205{
206    ColorMapHashmap::iterator itr;
207    bool doAll = false;
208
209    if (id.compare("all") == 0) {
210        itr = _colorMaps.begin();
211        doAll = true;
212    } else {
213        itr = _colorMaps.find(id);
214    }
215
216    if (itr == _colorMaps.end()) {
217        ERROR("Unknown ColorMap %s", id.c_str());
218        return;
219    }
220
221    do {
222        if (itr->first.compare("default") == 0 ||
223            itr->first.compare("grayDefault") == 0) {
224            if (id.compare("all") != 0) {
225                WARN("Cannot delete a default color map");
226            }
227            continue;
228        }
229
230        TRACE("Deleting ColorMap %s", itr->first.c_str());
231        itr = _colorMaps.erase(itr);
232    } while (doAll && itr != _colorMaps.end());
233}
234
235void Renderer::setColorMapNumberOfTableEntries(const ColorMapId& id,
236                                               int numEntries)
237{
238    ColorMapHashmap::iterator itr;
239    bool doAll = false;
240
241    if (id.compare("all") == 0) {
242        itr = _colorMaps.begin();
243        doAll = true;
244    } else {
245        itr = _colorMaps.find(id);
246    }
247
248    if (itr == _colorMaps.end()) {
249        ERROR("Unknown ColorMap %s", id.c_str());
250        return;
251    }
252
253    do {
254        itr->second->allocate(numEntries);
255        itr->second->updateImage();
256    } while (doAll && ++itr != _colorMaps.end());
257}
258
259void Renderer::initViewer() {
260    if (_viewer.valid())
261        return;
262    _viewer = new osgViewer::Viewer();
263#if 1
264    osg::DisplaySettings *ds = _viewer->getDisplaySettings();
265    if (ds == NULL) {
266        ds = osg::DisplaySettings::instance().get();
267    }
268    ds->setDoubleBuffer(false);
269    ds->setMinimumNumAlphaBits(8);
270    ds->setMinimumNumStencilBits(8);
271    ds->setNumMultiSamples(0);
272#endif
273    _viewer->setThreadingModel(osgViewer::ViewerBase::SingleThreaded);
274    _viewer->getDatabasePager()->setUnrefImageDataAfterApplyPolicy(false, false);
275    _viewer->setReleaseContextAtEndOfFrameHint(false);
276    //_viewer->setLightingMode(osg::View::SKY_LIGHT);
277    _viewer->getCamera()->setClearColor(osg::Vec4(_bgColor[0], _bgColor[1], _bgColor[2], 1));
278    _viewer->getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
279    _viewer->getCamera()->setNearFarRatio(0.00002);
280    _viewer->getCamera()->setSmallFeatureCullingPixelSize(-1.0f);
281    _stateManip = new osgGA::StateSetManipulator(_viewer->getCamera()->getOrCreateStateSet());
282    _viewer->addEventHandler(_stateManip);
283    //_viewer->addEventHandler(new osgViewer::StatsHandler());
284
285#ifdef DEBUG
286    if (_viewer->getViewerStats() != NULL) {
287        TRACE("Enabling stats");
288        _viewer->getViewerStats()->collectStats("scene", true);
289    }
290#endif
291#if 0
292    osgViewer::ViewerBase::Windows windows;
293    _viewer->getWindows(windows);
294    if (windows.size() == 1) {
295        windows[0]->setSyncToVBlank(false);
296    } else {
297        ERROR("Num windows: %lu", windows.size());
298    }
299#endif
300}
301
302void Renderer::finalizeViewer() {
303    initViewer();
304    TRACE("Before _viewer->isRealized()");
305    if (!_viewer->isRealized()) {
306        int screen = 0;
307        const char *displayEnv = getenv("DISPLAY");
308        if (displayEnv != NULL) {
309            TRACE("DISPLAY: %s", displayEnv);
310            // 3 parts: host, display, screen
311            int part = 0;
312            for (size_t c = 0; c < strlen(displayEnv); c++) {
313                if (displayEnv[c] == ':') {
314                    part = 1;
315                } else if (part == 1 && displayEnv[c] == '.') {
316                    part = 2;
317                } else if (part == 2) {
318                    screen = atoi(&displayEnv[c]);
319                    break;
320                }
321            }
322        }
323        TRACE("Using screen: %d", screen);
324#ifdef USE_OFFSCREEN_RENDERING
325#ifdef USE_PBUFFER
326        osg::ref_ptr<osg::GraphicsContext> pbuffer;
327        osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
328        traits->x = 0;
329        traits->y = 0;
330        traits->width = _windowWidth;
331        traits->height = _windowHeight;
332        traits->red = 8;
333        traits->green = 8;
334        traits->blue = 8;
335        traits->alpha = 8;
336        traits->windowDecoration = false;
337        traits->pbuffer = true;
338        traits->doubleBuffer = true;
339        traits->sharedContext = 0;
340
341        pbuffer = osg::GraphicsContext::createGraphicsContext(traits.get());
342        if (pbuffer.valid()) {
343            TRACE("Pixel buffer has been created successfully.");
344        } else {
345            ERROR("Pixel buffer has not been created successfully.");
346        }
347        osg::Camera *camera = new osg::Camera;
348        camera->setGraphicsContext(pbuffer.get());
349        camera->setViewport(new osg::Viewport(0, 0, _windowWidth, _windowHeight));
350        GLenum buffer = pbuffer->getTraits()->doubleBuffer ? GL_BACK : GL_FRONT;
351        camera->setDrawBuffer(buffer);
352        camera->setReadBuffer(buffer);
353        _captureCallback = new ScreenCaptureCallback();
354        camera->setFinalDrawCallback(_captureCallback.get());
355        _viewer->addSlave(camera, osg::Matrixd(), osg::Matrixd());
356#else
357        _viewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
358        osg::Texture2D* texture2D = new osg::Texture2D;
359        texture2D->setTextureSize(_windowWidth, _windowHeight);
360        texture2D->setInternalFormat(GL_RGBA);
361        texture2D->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST);
362        texture2D->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST);
363
364        _viewer->getCamera()->setImplicitBufferAttachmentMask(0, 0);
365        _viewer->getCamera()->attach(osg::Camera::COLOR_BUFFER0, texture2D);
366        //_viewer->getCamera()->attach(osg::Camera::DEPTH_BUFFER, GL_DEPTH_COMPONENT24);
367        _viewer->getCamera()->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, GL_DEPTH24_STENCIL8_EXT);
368        _captureCallback = new ScreenCaptureCallback(texture2D);
369        _viewer->getCamera()->setFinalDrawCallback(_captureCallback.get());
370        _viewer->setUpViewInWindow(0, 0, _windowWidth, _windowHeight, screen);
371#endif
372#else
373        _captureCallback = new ScreenCaptureCallback();
374        _viewer->getCamera()->setFinalDrawCallback(_captureCallback.get());
375#if 1
376        _viewer->setUpViewInWindow(0, 0, _windowWidth, _windowHeight, screen);
377#else
378        SingleWindow *windowConfig = new SingleWindow(0, 0, _windowWidth, _windowHeight, screen);
379        osg::DisplaySettings *ds = windowConfig->getActiveDisplaySetting(*_viewer.get());
380        ds->setDoubleBuffer(false);
381        ds->setMinimumNumAlphaBits(8);
382        ds->setMinimumNumStencilBits(8);
383        ds->setNumMultiSamples(0);
384        windowConfig->setWindowDecoration(false);
385        windowConfig->setOverrideRedirect(false);
386        _viewer->apply(windowConfig);
387#endif
388#endif
389        _viewer->realize();
390        initColorMaps();
391        // HACK: This seems to initialize something required for properly
392        // mapping mouse coords
393        assert(getEventQueue() != NULL);
394        getEventQueue()->mouseMotion(_windowWidth/2, _windowHeight/2);
395    }
396}
397
398void Renderer::initControls()
399{
400    if (_hbox.valid())
401        return;
402    _hbox =
403        new osgEarth::Util::Controls::HBox(osgEarth::Util::Controls::Control::ALIGN_RIGHT,
404                                           osgEarth::Util::Controls::Control::ALIGN_BOTTOM,
405                                           osgEarth::Util::Controls::Gutter(0, 2, 2, 0), 2.0f);
406    _copyrightLabel =
407        new osgEarth::Util::Controls::LabelControl("Map data © 2014 ACME Corp.", 12.0f);
408    _copyrightLabel->setForeColor(osg::Vec4f(1, 1, 1, 1));
409    _copyrightLabel->setHaloColor(osg::Vec4f(0, 0, 0, 1));
410    _copyrightLabel->setEncoding(osgText::String::ENCODING_UTF8);
411    _scaleLabel =
412        new osgEarth::Util::Controls::LabelControl("- km", 12.0f);
413    _scaleLabel->setForeColor(osg::Vec4f(1, 1, 1, 1));
414    _scaleLabel->setHaloColor(osg::Vec4f(0, 0, 0, 1));
415    _scaleBar =
416        new osgEarth::Util::Controls::Frame();
417    _scaleBar->setVertFill(true);
418    _scaleBar->setForeColor(osg::Vec4f(0, 0, 0, 1));
419    _scaleBar->setBackColor(osg::Vec4f(1, 1, 1, 0.6));
420    _scaleBar->setBorderColor(osg::Vec4f(0, 0, 0 ,1));
421    _scaleBar->setBorderWidth(1.0);
422    _hbox->addControl(_copyrightLabel.get());
423    _hbox->addControl(_scaleLabel.get());
424    _hbox->addControl(_scaleBar.get());
425#if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 1)
426    osgEarth::Util::Controls::ControlCanvas::getOrCreate(_viewer.get())->addControl(_hbox.get());
427#else
428    osgEarth::Util::Controls::ControlCanvas::get(_viewer.get(), true)->addControl(_hbox.get());
429#endif
430    // Install an event callback to handle scale bar updates
431    // Can't use an update callback since that will trigger
432    // constant rendering
433    _mapNode->setEventCallback(new MapNodeCallback(this));
434}
435
436void Renderer::setGraticule(bool enable, GraticuleType type)
437{
438    if (!_mapNode.valid() || !_sceneRoot.valid())
439        return;
440    if (enable) {
441        if (_graticule.valid()) {
442            _sceneRoot->removeChild(_graticule.get());
443            _graticule = NULL;
444        }
445        switch (type) {
446        case GRATICULE_UTM: {
447            osgEarth::Util::UTMGraticule *gr = new osgEarth::Util::UTMGraticule(_mapNode.get());
448            _sceneRoot->addChild(gr);
449            _graticule = gr;
450        }
451            break;
452        case GRATICULE_MGRS: {
453            osgEarth::Util::MGRSGraticule *gr = new osgEarth::Util::MGRSGraticule(_mapNode.get());
454            _sceneRoot->addChild(gr);
455            _graticule = gr;
456        }
457            break;
458        case GRATICULE_GEODETIC:
459        default:
460            osgEarth::Util::GeodeticGraticule *gr = new osgEarth::Util::GeodeticGraticule(_mapNode.get());
461            osgEarth::Util::GeodeticGraticuleOptions opt = gr->getOptions();
462            opt.lineStyle()->getOrCreate<osgEarth::Symbology::LineSymbol>()->stroke()->color().set(1,0,0,1);
463            gr->setOptions(opt);
464            _sceneRoot->addChild(gr);
465            _graticule = gr;
466        }
467    } else if (_graticule.valid()) {
468        _sceneRoot->removeChild(_graticule.get());
469        _graticule = NULL;
470    }
471    _needsRedraw = true;
472}
473
474void Renderer::setReadout(int x, int y)
475{
476    if (!_coordsCallback.valid() || !_mapNode.valid() || !_viewer.valid())
477        return;
478
479    osgEarth::GeoPoint mapCoord;
480    if (mapMouseCoords(x, y, mapCoord)) {
481        _coordsCallback->set(mapCoord, _viewer->asView(), _mapNode);
482    } else {
483        _coordsCallback->reset(_viewer->asView(), _mapNode);
484    }
485    _needsRedraw = true;
486}
487
488void Renderer::clearReadout()
489{
490    if (_coordsCallback.valid()) {
491        _coordsCallback->reset(_viewer->asView(), _mapNode);
492    }
493    _needsRedraw = true;
494}
495
496void Renderer::setCoordinateReadout(bool state, CoordinateDisplayType type,
497                                    int precision)
498{
499    if (!state) {
500        if (_mouseCoordsTool.valid() && _viewer.valid()) {
501            _viewer->removeEventHandler(_mouseCoordsTool.get());
502            _mouseCoordsTool = NULL;
503        }
504        if (_coordsCallback.valid() && _viewer.valid()) {
505            osgEarth::Util::Controls::LabelControl *readout =
506                _coordsCallback->getLabel();
507#if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 1)
508            osgEarth::Util::Controls::ControlCanvas::getOrCreate(_viewer.get())->removeControl(readout);
509#else
510            osgEarth::Util::Controls::ControlCanvas::get(_viewer.get(), true)->removeControl(readout);
511#endif
512            _coordsCallback = NULL;
513        }
514    } else {
515        initMouseCoordsTool(type, precision);
516    }
517    _needsRedraw = true;
518}
519
520osgEarth::Util::MGRSFormatter::Precision
521Renderer::getMGRSPrecision(int precisionInMeters)
522{
523    switch (precisionInMeters) {
524    case 1:
525        return osgEarth::Util::MGRSFormatter::PRECISION_1M;
526    case 10:
527        return osgEarth::Util::MGRSFormatter::PRECISION_10M;
528    case 100:
529        return osgEarth::Util::MGRSFormatter::PRECISION_100M;
530    case 1000:
531        return osgEarth::Util::MGRSFormatter::PRECISION_1000M;
532    case 10000:
533        return osgEarth::Util::MGRSFormatter::PRECISION_10000M;
534    case 100000:
535        return osgEarth::Util::MGRSFormatter::PRECISION_100000M;
536    default:
537        ERROR("Invalid precision: %d", precisionInMeters);
538        return osgEarth::Util::MGRSFormatter::PRECISION_1M;
539    }
540}
541
542void Renderer::initMouseCoordsTool(CoordinateDisplayType type, int precision)
543{
544    if (!_viewer.valid())
545        return;
546    if (_mouseCoordsTool.valid()) {
547        _viewer->removeEventHandler(_mouseCoordsTool.get());
548    }
549    _mouseCoordsTool = new MouseCoordsTool(_mapNode.get());
550    osgEarth::Util::Controls::LabelControl *readout;
551    if (_coordsCallback.valid()) {
552        readout = _coordsCallback->getLabel();
553        _coordsCallback = NULL;
554    } else {
555        readout = new osgEarth::Util::Controls::LabelControl("", 12.0f);
556#if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 1)
557        osgEarth::Util::Controls::ControlCanvas::getOrCreate(_viewer.get())->addControl(readout);
558#else
559        osgEarth::Util::Controls::ControlCanvas::get(_viewer.get(), true)->addControl(readout);
560#endif
561        readout->setForeColor(osg::Vec4f(1, 1, 1, 1));
562        readout->setHaloColor(osg::Vec4f(0, 0, 0, 1));
563    }
564
565    osgEarth::Util::Formatter *formatter = NULL;
566    if (type == COORDS_MGRS) {
567        osgEarth::Util::MGRSFormatter::Precision prec =
568            osgEarth::Util::MGRSFormatter::PRECISION_1M;
569        if (precision > 0) {
570            prec = getMGRSPrecision(precision);
571        }
572        unsigned int opts = 0u;
573        formatter = new osgEarth::Util::MGRSFormatter(prec, NULL, opts);
574    } else {
575        osgEarth::Util::LatLongFormatter::AngularFormat af;
576        unsigned int opts =  osgEarth::Util::LatLongFormatter::USE_SYMBOLS;
577        switch (type) {
578        case COORDS_LATLONG_DEGREES_DECIMAL_MINUTES:
579            af = osgEarth::Util::LatLongFormatter::FORMAT_DEGREES_DECIMAL_MINUTES;
580            break;
581        case COORDS_LATLONG_DEGREES_MINUTES_SECONDS:
582            af = osgEarth::Util::LatLongFormatter::FORMAT_DEGREES_MINUTES_SECONDS;
583            break;
584        default:
585            af = osgEarth::Util::LatLongFormatter::FORMAT_DECIMAL_DEGREES;
586        }
587        osgEarth::Util::LatLongFormatter *latlong = new osgEarth::Util::LatLongFormatter(af, opts);
588        if (precision > 0) {
589            latlong->setPrecision(precision);
590        }
591        formatter = latlong;
592    }
593    _coordsCallback = new MouseCoordsCallback(readout, formatter);
594
595    _mouseCoordsTool->addCallback(_coordsCallback.get());
596    _viewer->addEventHandler(_mouseCoordsTool.get());
597}
598
599void Renderer::initEarthManipulator()
600{
601    _manipulator = new osgEarth::Util::EarthManipulator;
602#if 1
603    osgEarth::Util::EarthManipulator::Settings *settings = _manipulator->getSettings();
604    settings->bindMouse(osgEarth::Util::EarthManipulator::ACTION_ROTATE,
605                        osgGA::GUIEventAdapter::MIDDLE_MOUSE_BUTTON,
606                        osgGA::GUIEventAdapter::MODKEY_ALT);
607    osgEarth::Util::EarthManipulator::ActionOptions options;
608    options.clear();
609    options.add(osgEarth::Util::EarthManipulator::OPTION_CONTINUOUS, true);
610    settings->bindMouse(osgEarth::Util::EarthManipulator::ACTION_ZOOM,
611                        osgGA::GUIEventAdapter::RIGHT_MOUSE_BUTTON,
612                        osgGA::GUIEventAdapter::MODKEY_ALT, options);
613    _manipulator->applySettings(settings);
614#endif
615    _viewer->setCameraManipulator(_manipulator.get());
616    _manipulator->setNode(NULL);
617    _manipulator->setNode(_sceneRoot.get());
618    //_manipulator->computeHomePosition();
619}
620
621void Renderer::loadEarthFile(const char *path)
622{
623    TRACE("Loading %s", path);
624    osg::Node *node = osgDB::readNodeFile(path);
625    if (node == NULL) {
626        ERROR("Couldn't load %s", path);
627        return;
628    }
629    osgEarth::MapNode *mapNode = osgEarth::MapNode::findMapNode(node);
630    if (mapNode == NULL) {
631        ERROR("Couldn't find MapNode");
632        return;
633    } else {
634        initViewer();
635        _sceneRoot = new osg::Group;
636        _sceneRoot->addChild(node);
637        _map = mapNode->getMap();
638    }
639    _mapNode = mapNode;
640
641    if (_clipPlaneCullCallback.valid()) {
642        _viewer->getCamera()->removeCullCallback(_clipPlaneCullCallback.get());
643        _clipPlaneCullCallback = NULL;
644    }
645    if (_map->isGeocentric()) {
646        _clipPlaneCullCallback = new osgEarth::Util::AutoClipPlaneCullCallback(mapNode);
647        _viewer->getCamera()->addCullCallback(_clipPlaneCullCallback.get());
648    }
649    _viewer->setSceneData(_sceneRoot.get());
650
651    if (_mouseCoordsTool.valid()) {
652        initMouseCoordsTool();
653    }
654    initControls();
655    initEarthManipulator();
656   
657    _viewer->home();
658    finalizeViewer();
659    _needsRedraw = true;
660}
661
662void Renderer::resetMap(osgEarth::MapOptions::CoordinateSystemType type,
663                        const char *profile,
664                        double bounds[4])
665{
666    TRACE("Restting map with type %d, profile %s", type, profile);
667
668    osgEarth::MapOptions mapOpts;
669    mapOpts.coordSysType() = type;
670    if (profile != NULL) {
671        if (bounds != NULL) {
672            mapOpts.profile() = osgEarth::ProfileOptions();
673            if (strcmp(profile, "geodetic") == 0) {
674                mapOpts.profile()->srsString() = "epsg:4326";
675            } else if (strcmp(profile, "spherical-mercator") == 0) {
676                // Projection used by Google/Bing/OSM
677                // aka epsg:900913 meters in x/y
678                // aka WGS84 Web Mercator (Auxiliary Sphere)
679                // X/Y: -20037508.34m to 20037508.34m
680                mapOpts.profile()->srsString() = "epsg:3857";
681            } else {
682                mapOpts.profile()->srsString() = profile;
683            }
684            TRACE("Setting profile bounds: %g %g %g %g",
685                  bounds[0], bounds[1], bounds[2], bounds[3]);
686            mapOpts.profile()->bounds() =
687                osgEarth::Bounds(bounds[0], bounds[1], bounds[2], bounds[3]);
688        } else {
689            mapOpts.profile() = osgEarth::ProfileOptions(profile);
690        }
691    } else if (type == osgEarth::MapOptions::CSTYPE_PROJECTED) {
692        mapOpts.profile() = osgEarth::ProfileOptions("global-mercator");
693    }
694
695#ifdef USE_CACHE
696    setupCache();
697    osgEarth::Drivers::FileSystemCacheOptions cacheOpts;
698    cacheOpts.rootPath() = _cacheDir;
699    mapOpts.cache() = cacheOpts;
700#endif
701
702    initViewer();
703
704    //mapOpts.referenceURI() = _baseURI;
705    osgEarth::Map *map = new osgEarth::Map(mapOpts);
706    _map = map;
707    osgEarth::Drivers::GDALOptions bopts;
708    bopts.url() = getBaseImage();
709    addImageLayer("base", bopts);
710#if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 1)
711    osgEarth::Drivers::MPTerrainEngine::MPTerrainEngineOptions mpOpt;
712#else
713    osgEarth::Drivers::MPTerrainEngineOptions mpOpt;
714#endif
715    // Set background layer color
716    mpOpt.color() = osg::Vec4(1, 1, 1, 1);
717    //mpOpt.minLOD() = 1;
718    // Sets shader uniform for terrain renderer (config var defaults to false)
719    mpOpt.enableLighting() = false;
720    osgEarth::MapNodeOptions mapNodeOpts(mpOpt);
721    // Sets GL_LIGHTING state in MapNode's StateSet (config var defaults to true)
722    mapNodeOpts.enableLighting() = true;
723    //mapNodeOpts.getTerrainOptions().loadingPolicy().mapLoadingThreadsPerCore() = 1;
724    //mapNodeOpts.getTerrainOptions().loadingPolicy().numLoadingThreads() = 1;
725    //mapNodeOpts.getTerrainOptions().loadingPolicy().numCompileThreadsPerCore() = 1;
726    //mapNodeOpts.getTerrainOptions().loadingPolicy().numCompileThreads() = 1;
727    osgEarth::MapNode *mapNode = new osgEarth::MapNode(map, mapNodeOpts);
728    _mapNode = mapNode;
729    if (_map->isGeocentric()) {
730        osgEarth::DateTime now;
731#if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 1)
732        TRACE("Creating SkyNode");
733        osgEarth::Drivers::SimpleSky::SimpleSkyOptions skyOpts;
734        skyOpts.hours() = now.hours();
735        skyOpts.ambient() = 0.2f;
736        skyOpts.atmosphericLighting() = true;
737        skyOpts.exposure() = 3.0;
738        osgEarth::Util::SkyNode *sky = osgEarth::Util::SkyNode::create(skyOpts, mapNode);
739        sky->addChild(mapNode);
740        sky->attach(_viewer.get(), 0);
741        _sceneRoot = sky;
742#else
743        _sceneRoot = new osg::Group();
744        _sceneRoot->addChild(_mapNode.get());
745
746        TRACE("Creating SkyNode");
747        osgEarth::Util::SkyNode *sky = new osgEarth::Util::SkyNode(map);
748        _sceneRoot->addChild(sky);
749        sky->setAmbientBrightness(0.2f);
750        sky->setDateTime(now);
751        sky->attach(_viewer.get(), 0);
752#endif
753    } else {
754        _sceneRoot = new osg::Group();
755        _sceneRoot->addChild(_mapNode.get());
756    }
757
758    if (_clipPlaneCullCallback.valid()) {
759        _viewer->getCamera()->removeCullCallback(_clipPlaneCullCallback.get());
760        _clipPlaneCullCallback = NULL;
761    }
762    if (_map->isGeocentric()) {
763        _clipPlaneCullCallback = new osgEarth::Util::AutoClipPlaneCullCallback(mapNode);
764        _viewer->getCamera()->addCullCallback(_clipPlaneCullCallback.get());
765    }
766    _viewer->setSceneData(_sceneRoot.get());
767    if (_mouseCoordsTool.valid()) {
768        initMouseCoordsTool();
769    }
770    initControls();
771    //_viewer->setSceneData(_sceneRoot.get());
772    initEarthManipulator();
773    _viewer->home();
774
775    finalizeViewer();
776    _needsRedraw = true;
777}
778
779void Renderer::clearMap()
780{
781    if (_map.valid()) {
782        _map->clear();
783        _needsRedraw = true;
784    }
785}
786
787void Renderer::setLighting(bool state)
788{
789    if (_mapNode.valid()) {
790        TRACE("Setting lighting: %d", state ? 1 : 0);
791        _mapNode->getOrCreateStateSet()
792            ->setMode(GL_LIGHTING, state ? 1 : 0);
793        _needsRedraw = true;
794    }
795}
796
797void Renderer::setViewerLightType(osg::View::LightingMode mode)
798{
799    if (_viewer.valid()) {
800        _viewer->setLightingMode(mode);
801        _needsRedraw = true;
802    }
803}
804
805void Renderer::setTerrainVerticalScale(double scale)
806{
807    if (_mapNode.valid()) {
808        if (!_verticalScale.valid()) {
809            _verticalScale = new osgEarth::Util::VerticalScale;
810            _mapNode->getTerrainEngine()->addEffect(_verticalScale);
811        }
812        _verticalScale->setScale(scale);
813        _needsRedraw = true;
814    }
815}
816
817void Renderer::setTerrainLighting(bool state)
818{
819#if 1
820    if (!_mapNode.valid()) {
821        ERROR("No map node");
822        return;
823    }
824    // XXX: HACK alert
825    // Find the terrain engine container (might be above one or more decorators)
826    osg::Group *group = _mapNode->getTerrainEngine();
827    while (group->getParent(0) != NULL && group->getParent(0) != _mapNode.get()) {
828        group = group->getParent(0);
829    }
830    if (group != NULL && group->getParent(0) == _mapNode.get()) {
831        TRACE("Setting terrain lighting: %d", state ? 1 : 0);
832        if (group->getOrCreateStateSet()->getUniform("oe_mode_GL_LIGHTING") != NULL) {
833            group->getStateSet()->getUniform("oe_mode_GL_LIGHTING")->set(state);
834        } else {
835            ERROR("Can't get terrain lighting uniform");
836        }
837    } else {
838        ERROR("Can't find terrain engine container");
839    }
840#else
841    if (_stateManip.valid()) {
842        _stateManip->setLightingEnabled(state);
843    }
844#endif
845    _needsRedraw = true;
846}
847
848void Renderer::setTerrainWireframe(bool state)
849{
850    if (!_map.valid()) {
851        ERROR("No map");
852        return;
853    }
854#if 0
855    if (!_mapNode.valid()) {
856        ERROR("No map node");
857        return;
858    }
859    TRACE("Setting terrain wireframe: %d", state ? 1 : 0);
860    osg::StateSet *state = _mapNode->getOrCreateStateSet();
861    osg::PolygonMode *pmode = dynamic_cast< osg::PolygonMode* >(state->getAttribute(osg::StateAttribute::POLYGONMODE));
862    if (pmode == NULL) {
863        pmode = new osg::PolygonMode;
864        state->setAttribute(pmode);
865    }
866    if (state) {
867        pmode->setMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE);
868    } else {
869        pmode->setMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL);
870    }
871    _needsRedraw = true;
872#else
873    if (_stateManip.valid()) {
874        _stateManip->setPolygonMode(state ? osg::PolygonMode::LINE : osg::PolygonMode::FILL);
875        _needsRedraw = true;
876    }
877#endif
878}
879
880void Renderer::saveNamedViewpoint(const char *name)
881{
882    _viewpoints[name] = getViewpoint();
883}
884
885bool Renderer::removeNamedViewpoint(const char *name)
886{
887    ViewpointHashmap::iterator itr = _viewpoints.find(name);
888    if (itr != _viewpoints.end()) {
889        _viewpoints.erase(name);
890        return true;
891    } else {
892        ERROR("Unknown viewpoint: '%s'", name);
893        return false;
894    }
895}
896
897bool Renderer::restoreNamedViewpoint(const char *name, double durationSecs)
898{
899    ViewpointHashmap::iterator itr = _viewpoints.find(name);
900    if (itr != _viewpoints.end()) {
901        setViewpoint(itr->second, durationSecs);
902        return true;
903    } else {
904        ERROR("Unknown viewpoint: '%s'", name);
905        return false;
906    }
907}
908
909void Renderer::setViewpoint(const osgEarth::Viewpoint& v, double durationSecs)
910{
911    if (_manipulator.valid()) {
912        TRACE("Setting viewpoint: %g %g %g %g %g %g",
913              v.x(), v.y(), v.z(), v.getHeading(), v.getPitch(), v.getRange());
914        _manipulator->setViewpoint(v, durationSecs);
915        _needsRedraw = true;
916    } else {
917        ERROR("No manipulator");
918    }
919}
920
921osgEarth::Viewpoint Renderer::getViewpoint()
922{
923    if (_manipulator.valid()) {
924        return _manipulator->getViewpoint();
925    } else {
926        // Uninitialized, invalid viewpoint
927        return osgEarth::Viewpoint();
928    }
929}
930
931static void srsInfo(const osgEarth::SpatialReference *srs)
932{
933    TRACE("SRS: %s", srs->getName().c_str());
934    TRACE("horiz: \"%s\" vert: \"%s\"", srs->getHorizInitString().c_str(), srs->getVertInitString().c_str());
935    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",
936          srs->isGeographic() ? 1 : 0,
937          srs->isGeodetic() ? 1 : 0,
938          srs->isProjected() ? 1 : 0,
939          srs->isECEF() ? 1 : 0,
940          srs->isMercator() ? 1 : 0,
941          srs->isSphericalMercator() ? 1 : 0,
942          srs->isNorthPolar() ? 1 : 0,
943          srs->isSouthPolar() ? 1 : 0,
944          srs->isUserDefined() ? 1 : 0,
945          srs->isContiguous() ? 1 : 0,
946          srs->isCube() ? 1 : 0,
947          srs->isLTP() ? 1 : 0,
948          srs->isPlateCarre() ? 1 : 0);
949}
950
951bool Renderer::getWorldCoords(const osgEarth::GeoPoint& mapPt, osg::Vec3d *world)
952{
953    if (!_mapNode.valid() || _mapNode->getTerrain() == NULL) {
954        ERROR("No map");
955        return false;
956    }
957    TRACE("Input SRS:");
958    srsInfo(mapPt.getSRS());
959    TRACE("Map SRS:");
960    srsInfo(_mapNode->getMapSRS());
961    bool ret = mapPt.toWorld(*world, _mapNode->getTerrain());
962    TRACE("In: %g,%g,%g Out: %g,%g,%g",
963          mapPt.x(), mapPt.y(), mapPt.z(),
964          world->x(), world->y(), world->z());
965    return ret;
966}
967
968bool Renderer::worldToScreen(const osg::Vec3d& world, osg::Vec3d *screen, bool invertY)
969{
970    if (!_viewer.valid()) {
971        ERROR("No viewer");
972        return false;
973    }
974    osg::Camera *cam = _viewer->getCamera();
975    osg::Matrixd MVP = cam->getViewMatrix() * cam->getProjectionMatrix();
976    // Get clip coords
977    osg::Vec4d pt;
978    pt = osg::Vec4d(world, 1.0) * MVP;
979    // Clip
980    if (pt.x() < -pt.w() ||
981        pt.x() > pt.w() ||
982        pt.y() < -pt.w() ||
983        pt.y() > pt.w() ||
984        pt.z() < -pt.w() ||
985        pt.z() > pt.w()) {
986        // Outside frustum
987        TRACE("invalid pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w());
988        return false;
989    }
990    TRACE("clip pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w());
991    // Perspective divide: now NDC
992    pt /= pt.w();
993    const osg::Viewport *viewport = cam->getViewport();
994#if 1
995    screen->x() = viewport->x() + viewport->width() * 0.5 + pt.x() * viewport->width() * 0.5;
996    screen->y() = viewport->y() + viewport->height() * 0.5 + pt.y() * viewport->height() * 0.5;
997    //double near = 0;
998    //double far = 1;
999    //screen->z() = (far + near) * 0.5 + (far - near) * 0.5 * pt.z();
1000    screen->z() = 0.5 + 0.5 * pt.z();
1001#else
1002    *screen = osg::Vec3d(pt.x(), pt.y(), pt.z()) * cam->getViewport()->computeWindowMatrix();
1003#endif
1004    if (invertY) {
1005        screen->y() = viewport->height() - screen->y();
1006    }
1007    TRACE("screen: %g,%g,%g", screen->x(), screen->y(), screen->z());
1008    return true;
1009}
1010
1011bool Renderer::worldToScreen(std::vector<osg::Vec3d>& coords, bool invertY)
1012{
1013    if (!_viewer.valid()) {
1014        ERROR("No viewer");
1015        return false;
1016    }
1017    bool ret = true;
1018    osg::Camera *cam = _viewer->getCamera();
1019    osg::Matrixd MVP = cam->getViewMatrix() * cam->getProjectionMatrix();
1020    const osg::Viewport *viewport = cam->getViewport();
1021    for (std::vector<osg::Vec3d>::iterator itr = coords.begin();
1022         itr != coords.end(); ++itr) {
1023        // Get clip coords
1024        osg::Vec4d pt;
1025        pt = osg::Vec4d(*itr, 1.0) * MVP;
1026        // Clip
1027        if (pt.x() < -pt.w() ||
1028            pt.x() > pt.w() ||
1029            pt.y() < -pt.w() ||
1030            pt.y() > pt.w() ||
1031            pt.z() < -pt.w() ||
1032            pt.z() > pt.w()) {
1033            // Outside frustum
1034            TRACE("invalid pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w());
1035            itr->set(std::numeric_limits<double>::quiet_NaN(),
1036                     std::numeric_limits<double>::quiet_NaN(),
1037                     std::numeric_limits<double>::quiet_NaN());
1038            ret = false;
1039            continue;
1040        }
1041        TRACE("clip pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w());
1042        // Perspective divide: now NDC
1043        pt /= pt.w();
1044#if 1
1045        itr->x() = viewport->x() + viewport->width() * 0.5 + pt.x() * viewport->width() * 0.5;
1046        itr->y() = viewport->y() + viewport->height() * 0.5 + pt.y() * viewport->height() * 0.5;
1047        //double near = 0;
1048        //double far = 1;
1049        //itr->z() = (far + near) * 0.5 + (far - near) * 0.5 * pt.z();
1050        itr->z() = 0.5 + 0.5 * pt.z();
1051#else
1052        *itr = osg::Vec3d(pt.x(), pt.y(), pt.z()) * viewport->computeWindowMatrix();
1053#endif
1054        if (invertY) {
1055            itr->y() = viewport->height() - itr->y();
1056        }
1057        TRACE("screen: %g,%g,%g", itr->x(), itr->y(), itr->z());
1058    }
1059    return ret;
1060}
1061
1062bool Renderer::mouseToLatLong(int mouseX, int mouseY,
1063                              double *latitude, double *longitude)
1064{
1065    if (getMapSRS() == NULL)
1066        return false;
1067    const osgEarth::SpatialReference *outSRS =
1068        getMapSRS()->getGeographicSRS();
1069    if (outSRS == NULL)
1070        return false;
1071    osgEarth::GeoPoint mapPoint;
1072    if (mapMouseCoords(mouseX, mouseY, mapPoint)) {
1073        mapPoint = mapPoint.transform(outSRS);
1074        *longitude = mapPoint.x();
1075        *latitude = mapPoint.y();
1076        return true;
1077    } else {
1078        return false;
1079    }
1080}
1081#if 0
1082bool Renderer::mapToLatLong(osgEarth::GeoPoint& mapPoint)
1083{
1084    if (getMapSRS() == NULL)
1085        return false;
1086    const osgEarth::SpatialReference *outSRS =
1087        getMapSRS()->getGeographicSRS();
1088    if (outSRS == NULL)
1089        return false;
1090    mapPoint = mapPoint.transform(outSRS);
1091    return true;
1092}
1093#endif
1094/**
1095 * \brief Map screen mouse coordinates to map coordinates
1096 *
1097 * This method assumes that mouse Y coordinates are 0 at the top
1098 * of the screen and increase going down if invertY is set, and
1099 * 0 at the bottom and increase going up otherwise.
1100 */
1101bool Renderer::mapMouseCoords(float mouseX, float mouseY,
1102                              osgEarth::GeoPoint& map, bool invertY)
1103{
1104    if (!_mapNode.valid() || _mapNode->getTerrain() == NULL) {
1105        ERROR("No map");
1106        return false;
1107    }
1108    if (!_viewer.valid()) {
1109        ERROR("No viewer");
1110        return false;
1111    }
1112    if (invertY) {
1113        mouseY = ((float)_windowHeight - mouseY);
1114    }
1115    osg::Vec3d world;
1116    if (_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), mouseX, mouseY, world)) {
1117        map.fromWorld(getMapSRS(), world);
1118        return true;
1119    }
1120    return false;
1121}
1122
1123bool Renderer::addImageLayer(const char *name,
1124                             osgEarth::TileSourceOptions& opts,
1125                             bool enableCache,
1126                             bool makeShared,
1127                             bool visible)
1128{
1129    if (!_map.valid()) {
1130        ERROR("No map");
1131        return false;
1132    }
1133    TRACE("layer: %s", name);
1134    if (!opts.tileSize().isSet()) {
1135        opts.tileSize() = 256;
1136    }
1137    osgEarth::ImageLayerOptions layerOpts(name, opts);
1138#if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 1)
1139    layerOpts.textureCompression() = osg::Texture::USE_IMAGE_DATA_FORMAT;
1140#endif
1141    if (makeShared) {
1142        layerOpts.shared() = true;
1143    }
1144    if (!enableCache) {
1145        TRACE("Disabling cache for layer %s", name);
1146        layerOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
1147    }
1148    if (!visible) {
1149        layerOpts.visible() = false;
1150    }
1151    osg::ref_ptr<osgEarth::ImageLayer> layer = new osgEarth::ImageLayer(layerOpts);
1152    _map->addImageLayer(layer.get());
1153    if (layer->getTileSource() == NULL || !layer->getTileSource()->isOK()) {
1154        ERROR("Failed to add image layer: %s", name);
1155        _map->removeImageLayer(layer.get());
1156        return false;
1157    }
1158    _needsRedraw = true;
1159    return true;
1160}
1161
1162void Renderer::addColorFilter(const char *name,
1163                              const char *shader)
1164{
1165    if (!_map.valid()) {
1166        ERROR("No map");
1167        return;
1168    }
1169    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1170    if (layer == NULL) {
1171        TRACE("Image layer not found: %s", name);
1172        return;
1173    }
1174    osgEarth::Util::GLSLColorFilter *filter = new osgEarth::Util::GLSLColorFilter;
1175    filter->setCode(shader);
1176    //filter->setCode("color.rgb = color.r > 0.5 ? vec3(1.0) : vec3(0.0);");
1177    layer->addColorFilter(filter);
1178    _needsRedraw = true;
1179}
1180
1181void Renderer::removeColorFilter(const char *name, int idx)
1182{
1183    if (!_map.valid()) {
1184        ERROR("No map");
1185        return;
1186    }
1187    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1188    if (layer == NULL) {
1189        TRACE("Image layer not found: %s", name);
1190        return;
1191    }
1192    if (idx < 0) {
1193        while (!layer->getColorFilters().empty()) {
1194            layer->removeColorFilter(layer->getColorFilters()[0]);
1195        }
1196    } else {
1197        layer->removeColorFilter(layer->getColorFilters().at(idx));
1198    }
1199    _needsRedraw = true;
1200}
1201
1202void Renderer::removeImageLayer(const char *name)
1203{
1204    if (!_map.valid()) {
1205        ERROR("No map");
1206        return;
1207    }
1208    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1209    if (layer != NULL) {
1210        _map->removeImageLayer(layer);
1211        _needsRedraw = true;
1212    } else {
1213        TRACE("Image layer not found: %s", name);
1214    }
1215}
1216
1217void Renderer::moveImageLayer(const char *name, unsigned int pos)
1218{
1219    if (!_map.valid()) {
1220        ERROR("No map");
1221        return;
1222    }
1223    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1224    if (layer != NULL) {
1225        _map->moveImageLayer(layer, pos);
1226        _needsRedraw = true;
1227    } else {
1228        TRACE("Image layer not found: %s", name);
1229    }
1230}
1231
1232void Renderer::setImageLayerOpacity(const char *name, double opacity)
1233{
1234    if (!_map.valid()) {
1235        ERROR("No map");
1236        return;
1237    }
1238    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1239    if (layer != NULL) {
1240        layer->setOpacity(opacity);
1241        _needsRedraw = true;
1242    } else {
1243        TRACE("Image layer not found: %s", name);
1244    }
1245}
1246
1247void Renderer::setImageLayerVisibility(const char *name, bool state)
1248{
1249    if (!_map.valid()) {
1250        ERROR("No map");
1251        return;
1252    }
1253    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1254    if (layer != NULL) {
1255        layer->setVisible(state);
1256        _needsRedraw = true;
1257    } else {
1258        TRACE("Image layer not found: %s", name);
1259    }
1260}
1261
1262void Renderer::addElevationLayer(const char *name,
1263                                 osgEarth::TileSourceOptions& opts,
1264                                 bool enableCache,
1265                                 bool visible)
1266{
1267    if (!_map.valid()) {
1268        ERROR("No map");
1269        return;
1270    }
1271    TRACE("layer: %s", name);
1272    if (!opts.tileSize().isSet()) {
1273        opts.tileSize() = 15;
1274    }
1275    osgEarth::ElevationLayerOptions layerOpts(name, opts);
1276    if (!enableCache) {
1277        layerOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
1278    }
1279    if (!visible) {
1280        layerOpts.visible() = false;
1281    }
1282    // XXX: GDAL does not report vertical datum, it should be specified here
1283    // Common options: geodetic (default), egm96, egm84, egm2008
1284    //layerOpts.verticalDatum() = "egm96";
1285    _map->addElevationLayer(new osgEarth::ElevationLayer(layerOpts));
1286    _needsRedraw = true;
1287}
1288
1289void Renderer::removeElevationLayer(const char *name)
1290{
1291    if (!_map.valid()) {
1292        ERROR("No map");
1293        return;
1294    }
1295    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
1296    if (layer != NULL) {
1297        _map->removeElevationLayer(layer);
1298        _needsRedraw = true;
1299    } else {
1300        TRACE("Elevation layer not found: %s", name);
1301    }
1302}
1303
1304void Renderer::moveElevationLayer(const char *name, unsigned int pos)
1305{
1306    if (!_map.valid()) {
1307        ERROR("No map");
1308        return;
1309    }
1310    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
1311    if (layer != NULL) {
1312        _map->moveElevationLayer(layer, pos);
1313        _needsRedraw = true;
1314    } else {
1315        TRACE("Elevation layer not found: %s", name);
1316    }
1317}
1318
1319void Renderer::setElevationLayerVisibility(const char *name, bool state)
1320{
1321    if (!_map.valid()) {
1322        ERROR("No map");
1323        return;
1324    }
1325    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
1326    if (layer != NULL) {
1327        layer->setVisible(state);
1328        _needsRedraw = true;
1329    } else {
1330        TRACE("Elevation layer not found: %s", name);
1331    }
1332}
1333
1334void Renderer::initAnnotations()
1335{
1336    if (!_annotations.valid()) {
1337        _annotations = new osg::Group();
1338        _annotations->setName("Annotations");
1339        _sceneRoot->addChild(_annotations.get());
1340        _placeNodes = new osg::Group();
1341        _placeNodes->setName("Place Nodes");
1342        osgEarth::Decluttering::setEnabled(_placeNodes->getOrCreateStateSet(), true);
1343        _annotations->addChild(_placeNodes.get());
1344    }
1345}
1346
1347void Renderer::initBoxSelection(int x, int y)
1348{
1349    double latitude, longitude;
1350    if (!mouseToLatLong(x, y, &latitude, &longitude)) {
1351        return;
1352    }
1353    _anchorLat = latitude;
1354    _anchorLong = longitude;
1355    addRhumbBox(latitude, latitude, longitude, longitude);
1356}
1357
1358void Renderer::updateBoxSelection(int x, int y)
1359{
1360    double nlat, nlong;
1361    if (!mouseToLatLong(x, y, &nlat, &nlong)) {
1362        return;
1363    }
1364    double latMin, latMax, longMin, longMax;
1365    if (nlong >= _anchorLong && nlat >= _anchorLat) {
1366        // +x +y
1367        longMin = _anchorLong;
1368        latMin = _anchorLat;
1369        longMax = nlong;
1370        latMax = nlat;
1371    } else if (nlong < _anchorLong && nlat >= _anchorLat) {
1372        // -x +y
1373        longMin = nlong;
1374        latMin = _anchorLat;
1375        longMax = _anchorLong;
1376        latMax = nlat;
1377    } else if (nlong < _anchorLong && nlat < _anchorLat) {
1378        // -x -y
1379        longMin = nlong;
1380        latMin = nlat;
1381        longMax = _anchorLong;
1382        latMax = _anchorLat;
1383    } else {
1384        // +x -y
1385        longMin = _anchorLong;
1386        latMin = nlat;
1387        longMax = nlong;
1388        latMax = _anchorLat;
1389    }
1390    osgEarth::Annotation::FeatureNode *node = _selectionBox.get();
1391    osgEarth::Symbology::Geometry *geom = node->getFeature()->getGeometry();
1392    (*geom)[0] = osg::Vec3d(longMax, latMin, 0);
1393    (*geom)[1] = osg::Vec3d(longMin, latMin, 0);
1394    (*geom)[2] = osg::Vec3d(longMin, latMax, 0);
1395    (*geom)[3] = osg::Vec3d(longMax, latMax, 0);
1396    node->init();
1397    for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = _selected.begin();
1398         itr != _selected.end(); ++itr) {
1399        (*itr)->clearDecoration();
1400    }
1401    _selected.clear();
1402    SelectPlaceNodesVisitor spnv(this, latMin, latMax, longMin, longMax);
1403    _sceneRoot->accept(spnv);
1404    _needsRedraw = true;
1405}
1406
1407void SelectPlaceNodesVisitor::apply(osg::Node& node)
1408{
1409    osgEarth::Annotation::PlaceNode *placeNode =
1410        dynamic_cast<osgEarth::Annotation::PlaceNode*>(&node);
1411    if (placeNode != NULL) {
1412        const osgEarth::SpatialReference *outSRS =
1413            _renderer->getMapSRS()->getGeographicSRS();
1414        osgEarth::GeoPoint pt = placeNode->getPosition();
1415        pt = pt.transform(outSRS);
1416        if (pt.x() >= _longMin && pt.x() <= _longMax &&
1417            pt.y() >= _latMin && pt.y() <= _latMax) {
1418            if (_renderer->select(placeNode)) {
1419                //TRACE("SELECT IT: %p", placeNode);
1420                placeNode->setDecoration("select");
1421            }
1422        }
1423    }
1424    traverse(node);
1425}
1426
1427void Renderer::clearBoxSelection()
1428{
1429    if (_annotations.valid() && _selectionBox.valid()) {
1430        _annotations->removeChild(_selectionBox.get());
1431        _selectionBox = NULL;
1432    }
1433    for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = _selected.begin();
1434         itr != _selected.end(); ++itr) {
1435        (*itr)->clearDecoration();
1436    }
1437    _selected.clear();
1438    _needsRedraw = true;
1439}
1440
1441void Renderer::addRhumbBox(double latMin, double latMax,
1442                           double longMin, double longMax)
1443{
1444    if (!_mapNode.valid()) {
1445        ERROR("No map node");
1446        return;
1447    }
1448    initAnnotations();
1449
1450    if (_selectionBox.valid()) {
1451        osgEarth::Symbology::Geometry *geom = _selectionBox->getFeature()->getGeometry();
1452        (*geom)[0] = osg::Vec3d(longMax, latMin, 0);
1453        (*geom)[1] = osg::Vec3d(longMin, latMin, 0);
1454        (*geom)[2] = osg::Vec3d(longMin, latMax, 0);
1455        (*geom)[3] = osg::Vec3d(longMax, latMax, 0);
1456        _selectionBox->init();
1457    } else {
1458        const osgEarth::SpatialReference* geoSRS = _mapNode->getMapSRS()->getGeographicSRS();
1459        osgEarth::Symbology::Geometry *geom = new osgEarth::Symbology::Polygon();
1460        geom->push_back(osg::Vec3d(longMax, latMin, 0));
1461        geom->push_back(osg::Vec3d(longMin, latMin, 0));
1462        geom->push_back(osg::Vec3d(longMin, latMax, 0));
1463        geom->push_back(osg::Vec3d(longMax, latMax, 0));
1464        osgEarth::Symbology::Style boxStyle;
1465#if 1
1466        osgEarth::Symbology::PolygonSymbol *poly = boxStyle.getOrCreate<osgEarth::Symbology::PolygonSymbol>();
1467        poly->fill()->color() = osgEarth::Symbology::Color::Cyan;
1468        poly->fill()->color().a() = 0.5;
1469#else
1470        osgEarth::Symbology::LineSymbol *line = boxStyle.getOrCreate<osgEarth::Symbology::LineSymbol>();
1471        line->stroke()->color() = osgEarth::Symbology::Color::Yellow;
1472        line->stroke()->width() = 2.0f;
1473        //line->creaseAngle() = 45.0f;
1474        line->tessellation() = 10;
1475#endif
1476        osgEarth::Symbology::AltitudeSymbol *alt = boxStyle.getOrCreate<osgEarth::Symbology::AltitudeSymbol>();
1477        alt->clamping() = osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
1478        //alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
1479        alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_DRAPE;
1480        //alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_SCENE;
1481#if 0
1482        osgEarth::Symbology::RenderSymbol* rs = boxStyle.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
1483        rs->depthOffset()->enabled() = true;
1484        rs->depthOffset()->minBias() = 1000;
1485#endif
1486        osgEarth::Features::Feature *feature = new osgEarth::Features::Feature(geom, geoSRS, boxStyle);
1487        //feature->geoInterp() = osgEarth::GEOINTERP_GREAT_CIRCLE;
1488        feature->geoInterp() = osgEarth::GEOINTERP_RHUMB_LINE;
1489        _selectionBox =
1490            new osgEarth::Annotation::FeatureNode(_mapNode, feature);
1491        _annotations->addChild(_selectionBox.get());
1492    }
1493
1494    _needsRedraw = true;
1495}
1496
1497void Renderer::addPlaceNode(double latitude, double longitude, char *labelText)
1498{
1499    if (!_mapNode.valid()) {
1500        ERROR("No map node");
1501        return;
1502    }
1503    initAnnotations();
1504
1505    const osgEarth::SpatialReference* geoSRS = _mapNode->getMapSRS()->getGeographicSRS();
1506
1507    osgEarth::Symbology::Style pin;
1508    pin.getOrCreate<osgEarth::Symbology::IconSymbol>()->url()->setLiteral(getPinIcon());
1509    osgEarth::Annotation::AnnotationNode *anno =
1510        new osgEarth::Annotation::PlaceNode(_mapNode, osgEarth::GeoPoint(geoSRS, longitude, latitude), labelText, pin);
1511    anno->setName(labelText);
1512    _placeNodes->addChild(anno);
1513    osgEarth::Annotation::DecorationInstaller
1514        highlightInstaller("select", new osgEarth::Annotation::HighlightDecoration(osg::Vec4f(1,1,0,0.5)));
1515    _placeNodes->accept(highlightInstaller);
1516    _mapNode->accept(highlightInstaller);
1517    // scale labels when hovering:
1518    osgEarth::Annotation::DecorationInstaller
1519        scaleInstaller("hover", new osgEarth::Annotation::ScaleDecoration(1.1f));
1520    _placeNodes->accept(scaleInstaller);
1521    _mapNode->accept(scaleInstaller);
1522#if 0
1523    writeScene("/tmp/test.osg");
1524#endif
1525    _needsRedraw = true;
1526}
1527#if 0
1528void Renderer::hoverFeatureNodes(int x, int y, bool invertY)
1529{
1530    if (!_mapNode.valid()) {
1531        ERROR("No map node");
1532        return;
1533    }
1534    osgEarth::Picker picker(_viewer.get(), _mapNode.get());
1535    osgEarth::Picker::Hits hits;
1536    float mouseX = (float)x;
1537    float mouseY = (float)y;
1538    if (invertY) {
1539        mouseY = ((float)_windowHeight - mouseY);
1540    }
1541    std::set<osgEarth::Annotation::FeatureNode*> toUnHover;
1542    for (std::set<osgEarth::Annotation::FeatureNode*>::iterator itr = _hovered.begin();
1543         itr != _hovered.end(); ++itr) {
1544        toUnHover.insert(*itr);
1545    }
1546    if (picker.pick(mouseX, mouseY, hits)) {
1547        TRACE("Picker hits: %d", hits.size());
1548        for (osgEarth::Picker::Hits::const_iterator hitr = hits.begin();
1549             hitr != hits.end(); ++hitr) {
1550            osgEarth::Annotation::FeatureNode *anno =
1551                picker.getNode<osgEarth::Annotation::FeatureNode>(*hitr);
1552            if (anno != NULL) {
1553                if (_hovered.find(anno) == _hovered.end()) {
1554                    _hovered.insert(anno);
1555                    anno->setDecoration("hover");
1556                    _needsRedraw = true;
1557                }
1558                toUnHover.erase(anno);
1559            }
1560        }
1561#if 0
1562        writeScene("/tmp/test.osgt");
1563#endif
1564    }
1565    for (std::set<osgEarth::Annotation::FeatureNode *>::iterator itr = toUnHover.begin();
1566         itr != toUnHover.end(); ++itr) {
1567        _hovered.erase(*itr);
1568        (*itr)->clearDecoration();
1569        _needsRedraw = true;
1570    }
1571}
1572#endif
1573void Renderer::hoverPlaceNode(int x, int y, bool invertY)
1574{
1575    if (!_placeNodes.valid()) {
1576        ERROR("No place nodes");
1577        return;
1578    }
1579    osgEarth::Picker picker(_viewer.get(), _sceneRoot.get());//_placeNodes.get());
1580    osgEarth::Picker::Hits hits;
1581    float mouseX = (float)x;
1582    float mouseY = (float)y;
1583    if (invertY) {
1584        mouseY = ((float)_windowHeight - mouseY);
1585    }
1586    std::set<osgEarth::Annotation::AnnotationNode*> toUnHover;
1587    for (std::set<osgEarth::Annotation::AnnotationNode*>::iterator itr = _hovered.begin();
1588         itr != _hovered.end(); ++itr) {
1589        toUnHover.insert(*itr);
1590    }
1591    if (picker.pick(mouseX, mouseY, hits)) {
1592        TRACE("Picker hits: %d", hits.size());
1593        for (osgEarth::Picker::Hits::const_iterator hitr = hits.begin();
1594             hitr != hits.end(); ++hitr) {
1595            TRACE("Hit: node %p drawable %p idx %d", picker.getNode<osg::Node>(*hitr), hitr->drawable.get(), hitr->primitiveIndex);
1596            osgEarth::Annotation::AnnotationNode *anno =
1597                picker.getNode<osgEarth::Annotation::AnnotationNode>(*hitr);
1598            if (anno != NULL && anno->getDecoration().empty()) {
1599                TRACE("Hit AnnotationNode: %p", anno);
1600                if (_hovered.find(anno) == _hovered.end()) {
1601                    _hovered.insert(anno);
1602                    anno->setDecoration("hover");
1603                    _needsRedraw = true;
1604                }
1605                toUnHover.erase(anno);
1606            }
1607        }
1608#if 0
1609        writeScene("/tmp/test.osgt");
1610#endif
1611    }
1612    for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = toUnHover.begin();
1613         itr != toUnHover.end(); ++itr) {
1614        _hovered.erase(*itr);
1615        (*itr)->clearDecoration();
1616        _needsRedraw = true;
1617    }
1618}
1619
1620void Renderer::deletePlaceNode(int x, int y, bool invertY)
1621{
1622    if (!_placeNodes.valid()) {
1623        ERROR("No place nodes");
1624        return;
1625    }
1626    osgEarth::Picker picker(_viewer.get(), _placeNodes.get());
1627    osgEarth::Picker::Hits hits;
1628    float mouseX = (float)x;
1629    float mouseY = (float)y;
1630    if (invertY) {
1631        mouseY = ((float)_windowHeight - mouseY);
1632    }
1633    if (picker.pick(mouseX, mouseY, hits)) {
1634        TRACE("Picker hit!");
1635        // prevent multiple hits on the same instance
1636        std::set<osgEarth::Annotation::AnnotationNode *> fired;
1637        for (osgEarth::Picker::Hits::const_iterator hitr = hits.begin();
1638             hitr != hits.end(); ++hitr) {
1639            osgEarth::Annotation::AnnotationNode *anno =
1640                picker.getNode<osgEarth::Annotation::AnnotationNode>(*hitr);
1641            if (anno != NULL && fired.find(anno) == fired.end()) {
1642                fired.insert(anno);
1643                _needsRedraw = true;
1644            }
1645        }
1646        for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = fired.begin();
1647             itr != fired.end(); ++itr) {
1648            (*itr)->clearDecoration();
1649            _placeNodes->removeChild(*itr);
1650            if (_hovered.find(*itr) != _hovered.end()) {
1651                _hovered.erase(*itr);
1652            }
1653        }
1654    } else {
1655        TRACE("NO Picker hits");
1656    }
1657#if 0
1658    writeScene("/tmp/test.osg");
1659#endif
1660}
1661
1662void Renderer::addModelLayer(const char *name, osgEarth::ModelSourceOptions& opts)
1663{
1664    if (!_map.valid()) {
1665        ERROR("No map");
1666        return;
1667    }
1668    TRACE("layer: %s", name);
1669    osgEarth::ModelLayerOptions layerOpts(name, opts);
1670    _map->addModelLayer(new osgEarth::ModelLayer(layerOpts));
1671    _needsRedraw = true;
1672}
1673
1674void Renderer::removeModelLayer(const char *name)
1675{
1676    if (!_map.valid()) {
1677        ERROR("No map");
1678        return;
1679    }
1680    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
1681    if (layer != NULL) {
1682        _map->removeModelLayer(layer);
1683        _needsRedraw = true;
1684    } else {
1685        TRACE("Model layer not found: %s", name);
1686    }
1687}
1688
1689void Renderer::moveModelLayer(const char *name, unsigned int pos)
1690{
1691    if (!_map.valid()) {
1692        ERROR("No map");
1693        return;
1694    }
1695    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
1696    if (layer != NULL) {
1697        _map->moveModelLayer(layer, pos);
1698        _needsRedraw = true;
1699    } else {
1700        TRACE("Model layer not found: %s", name);
1701    }
1702}
1703
1704void Renderer::setModelLayerOpacity(const char *name, double opacity)
1705{
1706#if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 0)
1707    if (!_map.valid()) {
1708        ERROR("No map");
1709        return;
1710    }
1711    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
1712    if (layer != NULL) {
1713        layer->setOpacity(opacity);
1714        _needsRedraw = true;
1715    } else {
1716        TRACE("Model layer not found: %s", name);
1717    }
1718#endif
1719}
1720
1721void Renderer::setModelLayerVisibility(const char *name, bool state)
1722{
1723    if (!_map.valid()) {
1724        ERROR("No map");
1725        return;
1726    }
1727    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
1728    if (layer != NULL) {
1729        layer->setVisible(state);
1730        _needsRedraw = true;
1731    } else {
1732        TRACE("Model layer not found: %s", name);
1733    }
1734}
1735
1736/**
1737 * \brief Resize the render window (image size for renderings)
1738 */
1739void Renderer::setWindowSize(int width, int height)
1740{
1741    if (_windowWidth == width &&
1742        _windowHeight == height) {
1743        TRACE("No change");
1744        return;
1745    }
1746
1747    TRACE("Setting window size to %dx%d", width, height);
1748
1749    double origBitrate = getMaximumBitrate();
1750
1751    _windowWidth = width;
1752    _windowHeight = height;
1753
1754    setMaximumBitrate(origBitrate);
1755    TRACE("Bandwidth target: %.2f Mbps", (float)(getMaximumBitrate()/1.0e6));
1756    TRACE("Frame rate target: %.2f Hz", (float)getMaximumFrameRateInHertz());
1757
1758    if (_viewer.valid()) {
1759#ifdef USE_OFFSCREEN_RENDERING
1760#ifdef USE_PBUFFER
1761        osg::ref_ptr<osg::GraphicsContext> pbuffer;
1762        osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
1763        traits->x = 0;
1764        traits->y = 0;
1765        traits->width = _windowWidth;
1766        traits->height = _windowHeight;
1767        traits->red = 8;
1768        traits->green = 8;
1769        traits->blue = 8;
1770        traits->alpha = 8;
1771        traits->windowDecoration = false;
1772        traits->pbuffer = true;
1773        traits->doubleBuffer = true;
1774        traits->sharedContext = 0;
1775
1776        pbuffer = osg::GraphicsContext::createGraphicsContext(traits.get());
1777        if (pbuffer.valid()) {
1778            TRACE("Pixel buffer has been created successfully.");
1779        } else {
1780            ERROR("Pixel buffer has not been created successfully.");
1781        }
1782        osg::Camera *camera = new osg::Camera;
1783        camera->setGraphicsContext(pbuffer.get());
1784        camera->setViewport(new osg::Viewport(0, 0, _windowWidth, _windowHeight));
1785        GLenum buffer = pbuffer->getTraits()->doubleBuffer ? GL_BACK : GL_FRONT;
1786        camera->setDrawBuffer(buffer);
1787        camera->setReadBuffer(buffer);
1788        camera->setFinalDrawCallback(_captureCallback.get());
1789        _viewer->addSlave(camera, osg::Matrixd(), osg::Matrixd());
1790        _viewer->realize();
1791#else
1792        if (_captureCallback.valid()) {
1793            _captureCallback->getTexture()->setTextureSize(_windowWidth, _windowHeight);
1794        }
1795        osgViewer::ViewerBase::Windows windows;
1796        _viewer->getWindows(windows);
1797        if (windows.size() == 1) {
1798            windows[0]->setWindowRectangle(0, 0, _windowWidth, _windowHeight);
1799        } else {
1800            ERROR("Num windows: %lu", windows.size());
1801        }
1802#endif
1803#else
1804        osgViewer::ViewerBase::Windows windows;
1805        _viewer->getWindows(windows);
1806        if (windows.size() == 1) {
1807            windows[0]->setWindowRectangle(0, 0, _windowWidth, _windowHeight);
1808        } else {
1809            ERROR("Num windows: %lu", windows.size());
1810        }
1811#endif
1812        // HACK: Without this, the mouse coordinate mapping uses the old size
1813        // for 1 frame.
1814        assert(_viewer->getEventQueue() != NULL);
1815        //TRACE("Window EventQueue: %p", getEventQueue());
1816        //TRACE("Viewer EventQueue: %p", _viewer->getEventQueue());
1817        _viewer->getEventQueue()->windowResize(0, 0, _windowWidth, _windowHeight);
1818        _needsRedraw = true;
1819    }
1820}
1821
1822/**
1823 * \brief Set the orientation of the camera from a quaternion
1824 * rotation
1825 *
1826 * \param[in] quat A quaternion with scalar part first: w,x,y,z
1827 * \param[in] absolute Is rotation absolute or relative?
1828 */
1829void Renderer::setCameraOrientation(const double quat[4], bool absolute)
1830{
1831    if (_manipulator.valid()) {
1832        _manipulator->setRotation(osg::Quat(quat[1], quat[2], quat[3], quat[0]));
1833        _needsRedraw = true;
1834    }
1835}
1836
1837/**
1838 * \brief Reset pan, zoom, clipping planes and optionally rotation
1839 *
1840 * \param[in] resetOrientation Reset the camera rotation/orientation also
1841 */
1842void Renderer::resetCamera(bool resetOrientation)
1843{
1844    TRACE("Enter: resetOrientation=%d", resetOrientation ? 1 : 0);
1845    if (_viewer.valid()) {
1846        _viewer->home();
1847        _needsRedraw = true;
1848    }
1849}
1850
1851/**
1852 * \brief Perform a 2D translation of the camera
1853 *
1854 * x,y pan amount are specified as signed absolute pan amount in viewport
1855 * units -- i.e. 0 is no pan, .5 is half the viewport, 2 is twice the viewport,
1856 * etc.
1857 *
1858 * \param[in] x Viewport coordinate horizontal panning (positive number pans
1859 * camera left, object right)
1860 * \param[in] y Viewport coordinate vertical panning (positive number pans
1861 * camera up, object down)
1862 */
1863void Renderer::panCamera(double x, double y)
1864{
1865    TRACE("Enter: %g %g", x, y);
1866
1867    if (_manipulator.valid()) {
1868        // Wants mouse delta x,y in normalized screen coords
1869        _manipulator->pan(x, y);
1870        _needsRedraw = true;
1871    }
1872}
1873
1874void Renderer::rotateCamera(double x, double y)
1875{
1876    TRACE("Enter: %g %g", x, y);
1877
1878    if (_manipulator.valid()) {
1879        _manipulator->rotate(x, y);
1880        _needsRedraw = true;
1881    }
1882}
1883
1884/**
1885 * \brief Dolly camera or set orthographic scaling based on camera type
1886 *
1887 * \param[in] y Mouse y coordinate in normalized screen coords
1888 */
1889void Renderer::zoomCamera(double y)
1890{
1891    TRACE("Enter: y: %g", y);
1892
1893    if (_manipulator.valid()) {
1894        // +y = zoom out, -y = zoom in
1895        TRACE("camDist: %g", _manipulator->getDistance());
1896        if ((_mapScale < 0.0 && y > 0.0) ||
1897            (_mapScale < 0.1 && y < 0.0) ||
1898            (_mapScale > 40000.0 && y > 0.0))
1899            return;
1900#if 1
1901       _manipulator->zoom(0, y);
1902#else
1903        double dist = _manipulator->getDistance();
1904        dist *= (1.0 + y);
1905        _manipulator->setDistance(dist);
1906#endif
1907        _needsRedraw = true;
1908    }
1909}
1910
1911/**
1912 * \brief Dolly camera to set distance from focal point
1913 *
1914 * \param[in] dist distance in map? coordinates
1915 */
1916void Renderer::setCameraDistance(double dist)
1917{
1918    TRACE("Enter: dist: %g", dist);
1919
1920    if (_manipulator.valid()) {
1921        TRACE("camDist: %g", _manipulator->getDistance());
1922
1923        _manipulator->setDistance(dist);
1924
1925        _needsRedraw = true;
1926    }
1927}
1928
1929void Renderer::keyPress(int key)
1930{
1931    osgGA::EventQueue *queue = getEventQueue();
1932    if (queue != NULL) {
1933        queue->keyPress(key);
1934        _needsRedraw = true;
1935    }
1936}
1937
1938void Renderer::keyRelease(int key)
1939{
1940    osgGA::EventQueue *queue = getEventQueue();
1941    if (queue != NULL) {
1942        queue->keyRelease(key);
1943        _needsRedraw = true;
1944    }
1945}
1946
1947void Renderer::setThrowingEnabled(bool state)
1948{
1949    if (_manipulator.valid()) {
1950        _manipulator->getSettings()->setThrowingEnabled(state);
1951    }
1952}
1953
1954void Renderer::mouseDoubleClick(int button, double x, double y)
1955{
1956    osgGA::EventQueue *queue = getEventQueue();
1957    if (queue != NULL) {
1958        queue->mouseDoubleButtonPress((float)x, (float)y, button);
1959        _needsRedraw = true;
1960    }
1961}
1962
1963void Renderer::mouseClick(int button, double x, double y)
1964{
1965    osgGA::EventQueue *queue = getEventQueue();
1966    if (queue != NULL) {
1967        queue->mouseButtonPress((float)x, (float)y, button);
1968        _needsRedraw = true;
1969    }
1970}
1971
1972void Renderer::mouseDrag(int button, double x, double y)
1973{
1974    osgGA::EventQueue *queue = getEventQueue();
1975    if (queue != NULL) {
1976        queue->mouseMotion((float)x, (float)y);
1977        _needsRedraw = true;
1978    }
1979}
1980
1981void Renderer::mouseRelease(int button, double x, double y)
1982{
1983    osgGA::EventQueue *queue = getEventQueue();
1984    if (queue != NULL) {
1985        queue->mouseButtonRelease((float)x, (float)y, button);
1986        _needsRedraw = true;
1987    }
1988}
1989
1990void Renderer::mouseMotion(double x, double y)
1991{
1992    if (_mouseCoordsTool.valid()) {
1993#if 1
1994        osgGA::EventQueue *queue = getEventQueue();
1995        if (queue != NULL) {
1996            queue->mouseMotion((float)x, (float)y);
1997            _needsRedraw = true;
1998        }
1999#else
2000        if (_viewer.valid() && _coordsCallback.valid()) {
2001            osgEarth::GeoPoint mapPt;
2002            if (mapMouseCoords((float)x, (float)y, mapPt)) {
2003                _coordsCallback->set(mapPt, _viewer->asView(), _mapNode);
2004            } else {
2005                _coordsCallback->reset(_viewer->asView(), _mapNode);
2006            }
2007            _needsRedraw = true;
2008        }
2009#endif
2010    }
2011}
2012
2013void Renderer::mouseScroll(int direction)
2014{
2015    osgGA::EventQueue *queue = getEventQueue();
2016    if (queue != NULL) {
2017        queue->mouseScroll((direction > 0 ? osgGA::GUIEventAdapter::SCROLL_UP : osgGA::GUIEventAdapter::SCROLL_DOWN));
2018        _needsRedraw = true;
2019    }
2020}
2021
2022/**
2023 * \brief Set the RGB background color to render into the image
2024 */
2025void Renderer::setBackgroundColor(float color[3])
2026{
2027    _bgColor[0] = color[0];
2028    _bgColor[1] = color[1];
2029    _bgColor[2] = color[2];
2030
2031    if (_viewer.valid()) {
2032        _viewer->getCamera()->setClearColor(osg::Vec4(color[0], color[1], color[2], 1));
2033
2034        _needsRedraw = true;
2035    }
2036}
2037
2038/**
2039 * \brief Sets flag to trigger rendering next time render() is called
2040 */
2041void Renderer::eventuallyRender()
2042{
2043    _needsRedraw = true;
2044}
2045
2046/**
2047 * \brief Get a timeout in usecs for select()
2048 *
2049 * If the paging thread is idle, returns <0 indicating that the
2050 * select call can block until data is available.  Otherwise,
2051 * if the frame render time was faster than the target frame
2052 * rate, return the remaining frame time.
2053 */
2054long Renderer::getTimeout()
2055{
2056    if (!checkNeedToDoFrame())
2057        // <0 means no timeout, block until socket has data
2058        return -1L;
2059    if (_lastFrameTime < _minFrameTime) {
2060        return (long)1.0e6*(_minFrameTime - _lastFrameTime);
2061    } else {
2062        // No timeout (poll)
2063        return 0L;
2064    }
2065}
2066
2067/**
2068 * \brief Check if paging thread is quiescent
2069 */
2070bool Renderer::isPagerIdle()
2071{
2072    if (!_viewer.valid())
2073        return true;
2074    else
2075        return (!_viewer->getDatabasePager()->requiresUpdateSceneGraph() &&
2076                !_viewer->getDatabasePager()->getRequestsInProgress());
2077}
2078
2079/**
2080 * \brief Check is frame call is necessary to render and/or update
2081 * in response to events or timed actions
2082 */
2083bool Renderer::checkNeedToDoFrame()
2084{
2085    return (_needsRedraw ||
2086            (_viewer.valid() && _viewer->checkNeedToDoFrame()));
2087}
2088
2089/**
2090 * \brief MapNode event phase
2091 *
2092 * This is called by the MapNode's event callback during the event
2093 * traversal of the viewer
2094 */
2095void Renderer::mapNodeUpdate()
2096{
2097    computeMapScale();
2098}
2099
2100void Renderer::markFrameStart()
2101{
2102    _startFrameTime = osg::Timer::instance()->tick();
2103}
2104
2105void Renderer::markFrameEnd()
2106{
2107    osg::Timer_t endFrameTick = osg::Timer::instance()->tick();
2108    _lastFrameTime = osg::Timer::instance()->delta_s(_startFrameTime, endFrameTick);
2109    if (_lastFrameTime > _minFrameTime) {
2110        ERROR("BROKE FRAME by %.2f msec", (_lastFrameTime - _minFrameTime)*1000.0f);
2111    } else {
2112        TRACE("Frame time: %.2f msec", _lastFrameTime*1000.0f);
2113    }
2114#ifdef USE_THROTTLING_SLEEP
2115    if (_lastFrameTime < _minFrameTime) {
2116        TRACE("Sleeping for %.2f msec", (_minFrameTime - _lastFrameTime)*1000.0f);
2117        OpenThreads::Thread::microSleep(static_cast<unsigned int>(1000000.0*(_minFrameTime - _lastFrameTime)));
2118    }
2119#endif
2120}
2121
2122/**
2123 * \brief Cause the rendering to render a new image if needed
2124 *
2125 * The _needsRedraw flag indicates if a state change has occured since
2126 * the last rendered frame
2127 */
2128bool Renderer::render()
2129{
2130    if (_viewer.valid() && checkNeedToDoFrame()) {
2131        TRACE("Enter needsRedraw=%d",  _needsRedraw ? 1 : 0);
2132        _renderStartTime = osg::Timer::instance()->tick();
2133        TRACE("Before frame()");
2134        _viewer->frame();
2135        TRACE("After frame()");
2136        _renderStopTime = osg::Timer::instance()->tick();
2137        _renderTime = osg::Timer::instance()->delta_s(_renderStartTime, _renderStopTime);
2138        TRACE("Render time: %g msec", _renderTime * 1000.0);
2139#ifndef SLEEP_AFTER_QUEUE_FRAME
2140        _lastFrameTime = _renderTime;
2141#ifdef USE_THROTTLING_SLEEP
2142        if (_lastFrameTime < _minFrameTime) {
2143            TRACE("Sleeping for %.2f msec", (_minFrameTime - _lastFrameTime)*1000.0f);
2144            OpenThreads::Thread::microSleep(static_cast<unsigned int>(1.0e6*(_minFrameTime - _lastFrameTime)));
2145        }
2146#endif
2147#endif
2148#ifdef WANT_TRACE
2149        if (_viewer->getViewerStats() != NULL) {
2150            _viewer->getViewerStats()->report(std::cerr, _viewer->getViewerStats()->getLatestFrameNumber());
2151        }
2152#endif
2153        _needsRedraw = false;
2154        return true;
2155    } else {
2156        _renderStartTime = _renderStopTime = osg::Timer::instance()->tick();
2157        _renderTime = 0;
2158        return false;
2159    }
2160}
2161
2162/**
2163 * \brief Read back the rendered framebuffer image
2164 */
2165osg::Image *Renderer::getRenderedFrame()
2166{
2167    if (_captureCallback.valid())
2168        return _captureCallback->getImage();
2169    else
2170        return NULL;
2171}
2172
2173void Renderer::setScaleBar(bool state)
2174{
2175    if (_scaleLabel.valid()) {
2176        _scaleLabel->setVisible(state);
2177    }
2178    if (_scaleBar.valid()) {
2179        _scaleBar->setVisible(state);
2180    }
2181    _needsRedraw = true;
2182}
2183
2184void Renderer::setScaleBarUnits(ScaleBarUnits units)
2185{
2186    _scaleBarUnits = units;
2187    _needsRedraw = true;
2188}
2189
2190/**
2191 * \brief Compute the scale ratio of the map based on a horizontal center line
2192 *
2193 * The idea here is to take 2 screen points on a horizontal line in the center
2194 * of the screen and convert to lat/long.  The lat/long coordinates are then
2195 * used to compute the great circle distance (assuming spherical earth) between
2196 * the points.
2197 *
2198 * We could use local projected map coordinates for the distance computation,
2199 * which would be faster; however, this would not show e.g. the change in
2200 * scale at different latitudes
2201 */
2202double Renderer::computeMapScale()
2203{
2204    if (!_scaleLabel.valid() || !_scaleLabel->visible()) {
2205        return -1.0;
2206    }
2207    if (!_mapNode.valid() || _mapNode->getTerrain() == NULL) {
2208        ERROR("No map");
2209        return -1.0;
2210    }
2211    if (!_viewer.valid()) {
2212        ERROR("No viewer");
2213        return -1.0;
2214    }
2215
2216    double x, y;
2217    double pixelWidth = _windowWidth * 0.1 * 2.0;
2218    if (pixelWidth < 10)
2219        pixelWidth = 10;
2220    if (pixelWidth > 150)
2221        pixelWidth = 150;
2222    x = (double)(_windowWidth -1)/2.0 - pixelWidth / 2.0;
2223    y = (double)(_windowHeight-1)/2.0;
2224
2225    osg::Vec3d world1, world2;
2226    if (!_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), x, y, world1)) {
2227        // off map
2228        TRACE("Off map coords: %g %g", x, y);
2229        _scaleLabel->setText("");
2230        _scaleBar->setWidth(0);
2231        return -1.0;
2232    }
2233    x += pixelWidth;
2234    if (!_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), x, y, world2)) {
2235        // off map
2236        TRACE("Off map coords: %g %g", x, y);
2237        _scaleLabel->setText("");
2238        _scaleBar->setWidth(0);
2239        return -1.0;
2240    }
2241
2242    TRACE("w1: %g %g %g w2: %g %g %g",
2243          world1.x(), world1.y(), world1.z(),
2244          world2.x(), world2.y(), world2.z());
2245
2246    double meters;
2247    double radius = 6378137.0;
2248    if (_mapNode->getMapSRS() &&
2249        _mapNode->getMapSRS()->getEllipsoid()) {
2250        radius = _mapNode->getMapSRS()->getEllipsoid()->getRadiusEquator();
2251    }
2252    if (!_map->isGeocentric() &&
2253        _mapNode->getMapSRS() &&
2254        _mapNode->getMapSRS()->isGeographic()) {
2255        TRACE("Map is geographic");
2256        // World cords are already lat/long
2257        // Compute great circle distance
2258        meters =
2259            osgEarth::GeoMath::distance(world1, world2, _mapNode->getMapSRS());
2260    } else if (_mapNode->getMapSRS()) {
2261        // Get map coords in lat/long
2262        osgEarth::GeoPoint mapPoint1, mapPoint2;
2263        mapPoint1.fromWorld(_mapNode->getMapSRS(), world1);
2264        mapPoint1.makeGeographic();
2265        mapPoint2.fromWorld(_mapNode->getMapSRS(), world2);
2266        mapPoint2.makeGeographic();
2267        // Compute great circle distance
2268        meters =
2269            osgEarth::GeoMath::distance(osg::DegreesToRadians(mapPoint1.y()),
2270                                        osg::DegreesToRadians(mapPoint1.x()),
2271                                        osg::DegreesToRadians(mapPoint2.y()),
2272                                        osg::DegreesToRadians(mapPoint2.x()),
2273                                        radius);
2274    } else {
2275        // Assume geocentric?
2276        ERROR("No map SRS");
2277        _scaleLabel->setText("");
2278        _scaleBar->setWidth(0);
2279        return -1.0;
2280    }
2281
2282    double scale = meters / pixelWidth;
2283    // 1mi = 5280 feet
2284    //double scaleMiles = scale / 1609.344; // International mile = 1609.344m
2285    //double scaleNauticalMiles = scale / 1852.0; // nautical mile = 1852m
2286    //double scaleUSSurveyMiles = scale / 1609.347218694; // US survey mile = 5280 US survey feet
2287    //double scaleUSSurveyFeet = scale * 3937.0/1200.0; // US survey foot = 1200/3937 m
2288    TRACE("m: %g px: %g m/px: %g", meters, pixelWidth, scale);
2289    _mapScale = scale;
2290    switch (_scaleBarUnits) {
2291    case UNITS_NAUTICAL_MILES: {
2292        double nmi = meters / 1852.0;
2293        scale = nmi / pixelWidth;
2294        nmi = normalizeScaleNauticalMiles(nmi);
2295        pixelWidth = nmi / scale;
2296        if (_scaleLabel.valid()) {
2297            _scaleLabel->setText(osgEarth::Stringify()
2298                                 << nmi
2299                                 << " nmi");
2300        }
2301    }
2302        break;
2303    case UNITS_US_SURVEY_FEET: {
2304        double feet = meters * 3937.0/1200.0;
2305        scale = feet / pixelWidth;
2306        feet = normalizeScaleFeet(feet);
2307        pixelWidth = feet / scale;
2308        if (_scaleLabel.valid()) {
2309            if (feet >= 5280) {
2310                _scaleLabel->setText(osgEarth::Stringify()
2311                                     << feet / 5280.0
2312                                     << " miUS");
2313             } else {
2314                _scaleLabel->setText(osgEarth::Stringify()
2315                                     << feet
2316                                     << " ftUS");
2317            }
2318        }
2319    }
2320        break;
2321    case UNITS_INTL_FEET: {
2322        double feet = 5280.0 * meters / 1609.344;
2323        scale = feet / pixelWidth;
2324        feet = normalizeScaleFeet(feet);
2325        pixelWidth = feet / scale;
2326        if (_scaleLabel.valid()) {
2327            if (feet >= 5280) {
2328                _scaleLabel->setText(osgEarth::Stringify()
2329                                     << feet / 5280.0
2330                                     << " mi");
2331            } else {
2332                _scaleLabel->setText(osgEarth::Stringify()
2333                                     << feet
2334                                     << " ft");
2335            }
2336        }
2337    }
2338        break;
2339    case UNITS_METERS:
2340    default: {
2341        meters = normalizeScaleMeters(meters);
2342        pixelWidth = meters / scale;
2343        if (_scaleLabel.valid()) {
2344            if (meters >= 1000) {
2345                _scaleLabel->setText(osgEarth::Stringify()
2346                                     << meters / 1000.0
2347                                     << " km");
2348            } else {
2349                _scaleLabel->setText(osgEarth::Stringify()
2350                                     << meters
2351                                     << " m");
2352            }
2353        }
2354    }
2355        break;
2356    }
2357    if (_scaleBar.valid()) {
2358        _scaleBar->setWidth(pixelWidth);
2359    }
2360    return scale;
2361}
2362
2363std::string Renderer::getCanonicalPath(const std::string& url) const
2364{
2365    std::string retStr;
2366    std::string proto = osgDB::getServerProtocol(url);
2367    if (proto.empty()) {
2368        retStr = osgDB::getRealPath(url);
2369        if (!osgDB::fileExists(retStr)) {
2370            retStr = "";
2371        }
2372    } else {
2373        retStr = url;
2374    }
2375    return retStr;
2376}
2377
2378void Renderer::writeScene(const std::string& file)
2379{
2380    if (_sceneRoot.valid()) {
2381        GraphPrintVisitor gpv;
2382        _sceneRoot->accept(gpv);
2383        //osgDB::writeNodeFile(*_sceneRoot.get(), file);
2384    }
2385}
Note: See TracBrowser for help on using the repository browser.