source: trunk/packages/vizservers/geovis/Renderer.cpp @ 4575

Last change on this file since 4575 was 4575, checked in by ldelgass, 7 years ago

Add optional coordinate conversion for 'map coords' command, add 'screen coords'
command to project map coords to screen coords.

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