source: geovis/trunk/Renderer.cpp @ 4648

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

Having both line and polygon styles on selection rectangle causes problems, so
use only polygon style for now.

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