source: geovis/trunk/Renderer.cpp @ 4629

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

add token to map/screen coord commands

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