source: geovis/trunk/Renderer.cpp @ 4635

Last change on this file since 4635 was 4635, checked in by ldelgass, 6 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.