source: geovis/trunk/Renderer.cpp @ 4636

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

screen coords command now takes a list of coordinates

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