source: geovis/trunk/Renderer.cpp @ 4635

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

Add resources directory command line option, preliminary testing of new map
coords syntax

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