source: geovis/trunk/Renderer.cpp @ 4632

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

Add pin annotations for testing

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