source: geovis/trunk/Renderer.cpp @ 5118

Last change on this file since 5118 was 5118, checked in by ldelgass, 9 years ago

Fix legend rendering: use colormap min/max to interpolate values (OSG transfer
functions aren't normalized)

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