source: geovis/trunk/Renderer.cpp @ 5152

Last change on this file since 5152 was 5152, checked in by ldelgass, 10 years ago

add include

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