source: geovis/trunk/Renderer.cpp @ 6074

Last change on this file since 6074 was 6074, checked in by ldelgass, 9 years ago

Look for relative paths in cache directory if not found in working directory.

File size: 99.8 KB
Line 
1/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 * Copyright (C) 2004-2015  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#include <cerrno>
14
15#include <limits>
16#include <set>
17#include <fstream>
18#include <sstream>
19
20#include <sys/types.h>
21#include <unistd.h> // For getpid()
22
23#include <GL/gl.h>
24
25#ifdef WANT_TRACE
26#include <sys/time.h>
27#endif
28
29#include <osgDB/WriteFile>
30#include <osgDB/FileUtils>
31#include <osgDB/FileNameUtils>
32#include <osgGA/StateSetManipulator>
33#include <osgGA/GUIEventAdapter>
34#include <osgViewer/ViewerEventHandlers>
35
36#include <osgEarth/Version>
37#include <osgEarth/Registry>
38#include <osgEarth/FileUtils>
39#include <osgEarth/Cache>
40#include <osgEarth/CachePolicy>
41#include <osgEarthDrivers/cache_filesystem/FileSystemCache>
42#include <osgEarth/MapNode>
43#include <osgEarth/TerrainEngineNode>
44#include <osgEarth/Bounds>
45#include <osgEarth/Profile>
46#include <osgEarth/Viewpoint>
47#include <osgEarth/GeoMath>
48#include <osgEarth/TerrainLayer>
49#include <osgEarth/ImageLayer>
50#include <osgEarth/ElevationLayer>
51#include <osgEarth/ModelLayer>
52#include <osgEarth/DateTime>
53#ifdef USE_RTT_PICKER
54#include <osgEarthUtil/RTTPicker>
55#include <osgEarthFeatures/FeatureIndex>
56#else
57#include <osgEarth/IntersectionPicker>
58#endif
59#include <osgEarthUtil/GraticuleNode>
60#include <osgEarthFeatures/FeatureModelSource>
61#include <osgEarthFeatures/Feature>
62#include <osgEarthSymbology/Color>
63#include <osgEarthSymbology/Geometry>
64#include <osgEarthSymbology/Style>
65#include <osgEarthSymbology/StyleSheet>
66#include <osgEarthSymbology/IconSymbol>
67#include <osgEarthSymbology/LineSymbol>
68
69#include <osgEarthAnnotation/AnnotationNode>
70#include <osgEarthAnnotation/FeatureNode>
71#include <osgEarthAnnotation/PlaceNode>
72#include <osgEarthAnnotation/HighlightDecoration>
73#include <osgEarthAnnotation/ScaleDecoration>
74#include <osgEarthUtil/EarthManipulator>
75#include <osgEarthUtil/Sky>
76#include <osgEarthDrivers/sky_simple/SimpleSkyOptions>
77#include <osgEarthUtil/AutoClipPlaneHandler>
78#include <osgEarthUtil/MouseCoordsTool>
79#include <osgEarthUtil/UTMGraticule>
80#include <osgEarthUtil/MGRSGraticule>
81#include <osgEarthUtil/GeodeticGraticule>
82#include <osgEarthUtil/LatLongFormatter>
83#include <osgEarthUtil/MGRSFormatter>
84#include <osgEarthUtil/GLSLColorFilter>
85#include <osgEarthUtil/VerticalScale>
86#include <osgEarthDrivers/gdal/GDALOptions>
87#include <osgEarthDrivers/engine_mp/MPTerrainEngineOptions>
88#ifdef USE_REX
89#include <osgEarthDrivers/engine_rex/RexTerrainEngineOptions>
90#endif
91
92#include "Renderer.h"
93#ifdef USE_RTT_PICKER
94#include "Picker.h"
95#endif
96#include "IData.h"
97#include "ColorMap.h"
98#if 0
99#include "SingleWindow.h"
100#endif
101#include "ScaleBar.h"
102#include "FileUtil.h"
103#include "Util.h"
104#include "Icons.h"
105#include "Trace.h"
106
107#define MSECS_ELAPSED(t1, t2) \
108    ((t1).tv_sec == (t2).tv_sec ? (((t2).tv_usec - (t1).tv_usec)/1.0e+3) : \
109     (((t2).tv_sec - (t1).tv_sec))*1.0e+3 + (double)((t2).tv_usec - (t1).tv_usec)/1.0e+3)
110
111#ifndef CACHE_DIR
112#define CACHE_DIR "/var/cache/geovis"
113#endif
114#define BASE_IMAGE "world.tif"
115#define PIN_ICON "placemark32.png"
116
117using namespace GeoVis;
118using namespace IData;
119
120Renderer::Renderer() :
121    _needsRedraw(false),
122    _windowWidth(500),
123    _windowHeight(500),
124    _scaleBarUnits(UNITS_METERS)
125{
126    TRACE("Enter");
127
128    initIconMap();
129    _bgColor[0] = 0;
130    _bgColor[1] = 0;
131    _bgColor[2] = 0;
132    //setMaximumFrameRateInHertz(15.0);
133    // 100 Mbps
134    setMaximumBitrate(1.0e8);
135    _lastFrameTime = _minFrameTime;
136    TRACE("Bandwidth target: %.2f Mbps", (float)(getMaximumBitrate()/1.0e6));
137    TRACE("Frame rate target: %.2f Hz", (float)getMaximumFrameRateInHertz());
138    TRACE("Frame time target: %.2f msec", _minFrameTime * 1000.0f);
139
140    iDataInit(getenv("USER"));
141    setCacheBaseDirectory(CACHE_DIR);
142    char *base = getenv("MAP_BASE_URI");
143    if (base != NULL) {
144        _baseURI = base;
145        TRACE("Setting base URI: %s", _baseURI.c_str());
146    }
147}
148
149osgGA::EventQueue *Renderer::getEventQueue()
150{
151    if (_viewer.valid()) {
152        osgViewer::ViewerBase::Windows windows;
153        _viewer->getWindows(windows);
154        if (windows.size() > 0) {
155            return windows[0]->getEventQueue();
156        }
157    }
158    return NULL;
159}
160
161Renderer::~Renderer()
162{
163    TRACE("Enter");
164
165    // Removes temp files
166    deleteColorMap("all");
167
168    iDataCleanup();
169
170    // Cache is deleted in main() to allow ref-counted objects to
171    // be deleted and threads to exit first
172
173    TRACE("Leave");
174}
175
176std::string Renderer::getIconFile(const char *name) const
177{
178    std::ostringstream oss;
179    oss << _resourcePath << "/" << GeoVis::getIconFile(name);
180    return oss.str();
181}
182
183std::string Renderer::getBaseImage() const
184{
185    std::ostringstream oss;
186    oss << _resourcePath << "/" << BASE_IMAGE;
187    return oss.str();
188}
189
190std::string Renderer::getPinIcon() const
191{
192    std::ostringstream oss;
193    oss << _resourcePath << "/" << PIN_ICON;
194    return oss.str();
195}
196
197void Renderer::setupCache()
198{
199    std::ostringstream dir;
200    dir << _cacheBaseDir << "/geovis_cache" << getpid();
201    _cacheDir = dir.str();
202    const char *path = _cacheDir.c_str();
203    TRACE("Cache dir: %s", path);
204    removeDirectory(path);
205    if (!osgDB::makeDirectory(_cacheDir)) {
206        ERROR("Failed to create directory '%s'", path);
207    }
208}
209
210void Renderer::initColorMaps()
211{
212    if (!_colorMaps.empty())
213        return;
214#if 0
215    osg::TransferFunction1D *defaultColorMap = new osg::TransferFunction1D;
216    defaultColorMap->allocate(256);
217    defaultColorMap->setColor(0.00, osg::Vec4f(0,0,1,1), false);
218    defaultColorMap->setColor(0.25, osg::Vec4f(0,1,1,1), false);
219    defaultColorMap->setColor(0.50, osg::Vec4f(0,1,0,1), false);
220    defaultColorMap->setColor(0.75, osg::Vec4f(1,1,0,1), false);
221    defaultColorMap->setColor(1.00, osg::Vec4f(1,0,0,1), false);
222    defaultColorMap->updateImage();
223    addColorMap("default", defaultColorMap);
224    osg::TransferFunction1D *defaultGrayColorMap = new osg::TransferFunction1D;
225    defaultGrayColorMap->allocate(256);
226    defaultGrayColorMap->setColor(0, osg::Vec4f(0,0,0,1), false);
227    defaultGrayColorMap->setColor(1, osg::Vec4f(1,1,1,1), false);
228    defaultGrayColorMap->updateImage();
229    addColorMap("grayDefault", defaultGrayColorMap);
230#endif
231}
232
233void Renderer::saveCLRFile(const std::string& path, osg::TransferFunction1D *xfer)
234{
235    std::ofstream out(path.c_str());
236    float value;
237    unsigned int r, g, b, a;
238    for (osg::TransferFunction1D::ColorMap::const_iterator itr = xfer->getColorMap().begin();
239         itr != xfer->getColorMap().end(); ++itr) {
240        value = itr->first;
241        r = (unsigned int)(255.0 * itr->second.r());
242        g = (unsigned int)(255.0 * itr->second.g());
243        b = (unsigned int)(255.0 * itr->second.b());
244        a = (unsigned int)(255.0 * itr->second.a());
245        out << value << " " << r << " " << g << " " << b << " " << a << std::endl;
246    }
247}
248
249void Renderer::getColorMapRange(const ColorMapId& id, float *min, float *max) const
250{
251    ColorMapHashmap::const_iterator itr = _colorMaps.find(id);
252    if (itr == _colorMaps.end()) {
253        ERROR("Unknown ColorMap %s", id.c_str());
254        return;
255    }
256
257    *min = itr->second->getMinimum();
258    *max = itr->second->getMaximum();
259}
260
261std::string Renderer::getColorMapFilePath(const ColorMapId& id) const
262{
263    std::ostringstream path;
264    path << "/tmp/" << id << "_" << getpid() << ".clr";
265    return path.str();
266}
267
268void Renderer::addColorMap(const ColorMapId& id, osg::TransferFunction1D *xfer)
269{
270    _colorMaps[id] = xfer;
271    saveCLRFile(getColorMapFilePath(id), xfer);
272}
273
274void Renderer::deleteColorMap(const ColorMapId& id)
275{
276    ColorMapHashmap::iterator itr;
277    bool doAll = false;
278
279    if (id.compare("all") == 0) {
280        itr = _colorMaps.begin();
281        doAll = true;
282    } else {
283        itr = _colorMaps.find(id);
284    }
285
286    if (itr == _colorMaps.end()) {
287        ERROR("Unknown ColorMap %s", id.c_str());
288        return;
289    }
290
291    do {
292        TRACE("Deleting ColorMap %s", itr->first.c_str());
293        std::string path = getColorMapFilePath(itr->first);
294        if (remove(path.c_str()) < 0) {
295            ERROR("Failed to delete colormap file '%s': %s",
296                  path.c_str(), strerror(errno));
297        }
298        itr = _colorMaps.erase(itr);
299    } while (doAll && itr != _colorMaps.end());
300}
301
302void Renderer::setColorMapNumberOfTableEntries(const ColorMapId& id,
303                                               int numEntries)
304{
305    ColorMapHashmap::iterator itr;
306    bool doAll = false;
307
308    if (id.compare("all") == 0) {
309        itr = _colorMaps.begin();
310        doAll = true;
311    } else {
312        itr = _colorMaps.find(id);
313    }
314
315    if (itr == _colorMaps.end()) {
316        ERROR("Unknown ColorMap %s", id.c_str());
317        return;
318    }
319
320    do {
321        itr->second->allocate(numEntries);
322    } while (doAll && ++itr != _colorMaps.end());
323}
324
325bool
326Renderer::renderColorMap(const ColorMapId& id, int width, int height,
327                         osg::Image *imgData,
328                         bool opaque, float bgColor[3],
329                         bool bgr, int bytesPerPixel) const
330{
331    ColorMapHashmap::const_iterator itr = _colorMaps.find(id);
332    if (itr == _colorMaps.end()) {
333        ERROR("Unknown ColorMap %s", id.c_str());
334        return false;
335    }
336
337    ColorMap::renderColorMap(itr->second, width, height, imgData, opaque, bgColor, bgr, bytesPerPixel);
338    return true;
339}
340
341void Renderer::initViewer() {
342    if (_viewer.valid())
343        return;
344    _viewer = new osgViewer::Viewer();
345#if 1
346    osg::DisplaySettings *ds = _viewer->getDisplaySettings();
347    if (ds == NULL) {
348        ds = osg::DisplaySettings::instance().get();
349    }
350    ds->setDoubleBuffer(false);
351    ds->setMinimumNumAlphaBits(8);
352    ds->setMinimumNumStencilBits(8);
353    ds->setNumMultiSamples(0);
354#endif
355    _viewer->setThreadingModel(osgViewer::ViewerBase::SingleThreaded);
356    _viewer->getDatabasePager()->setUnrefImageDataAfterApplyPolicy(false, false);
357    _viewer->setReleaseContextAtEndOfFrameHint(false);
358    //_viewer->setLightingMode(osg::View::SKY_LIGHT);
359    _viewer->getCamera()->setClearColor(osg::Vec4(_bgColor[0], _bgColor[1], _bgColor[2], 1));
360    _viewer->getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
361    _viewer->getCamera()->setNearFarRatio(0.00002);
362    _viewer->getCamera()->setSmallFeatureCullingPixelSize(-1.0f);
363    _stateManip = new osgGA::StateSetManipulator(_viewer->getCamera()->getOrCreateStateSet());
364    _viewer->addEventHandler(_stateManip);
365    //_viewer->addEventHandler(new osgViewer::StatsHandler());
366
367#ifdef DEBUG
368    if (_viewer->getViewerStats() != NULL) {
369        TRACE("Enabling stats");
370        _viewer->getViewerStats()->collectStats("scene", true);
371    }
372#endif
373#if 0
374    osgViewer::ViewerBase::Windows windows;
375    _viewer->getWindows(windows);
376    if (windows.size() == 1) {
377        windows[0]->setSyncToVBlank(false);
378    } else {
379        ERROR("Num windows: %lu", windows.size());
380    }
381#endif
382}
383
384void Renderer::finalizeViewer() {
385    initViewer();
386    TRACE("Before _viewer->isRealized()");
387    if (!_viewer->isRealized()) {
388        int screen = 0;
389        const char *displayEnv = getenv("DISPLAY");
390        if (displayEnv != NULL) {
391            TRACE("DISPLAY: %s", displayEnv);
392            // 3 parts: host, display, screen
393            int part = 0;
394            for (size_t c = 0; c < strlen(displayEnv); c++) {
395                if (displayEnv[c] == ':') {
396                    part = 1;
397                } else if (part == 1 && displayEnv[c] == '.') {
398                    part = 2;
399                } else if (part == 2) {
400                    screen = atoi(&displayEnv[c]);
401                    break;
402                }
403            }
404        }
405        TRACE("Using screen: %d", screen);
406#ifdef USE_OFFSCREEN_RENDERING
407#ifdef USE_PBUFFER
408        osg::ref_ptr<osg::GraphicsContext> pbuffer;
409        osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
410        traits->x = 0;
411        traits->y = 0;
412        traits->width = _windowWidth;
413        traits->height = _windowHeight;
414        traits->red = 8;
415        traits->green = 8;
416        traits->blue = 8;
417        traits->alpha = 8;
418        traits->windowDecoration = false;
419        traits->pbuffer = true;
420        traits->doubleBuffer = true;
421        traits->sharedContext = 0;
422
423        pbuffer = osg::GraphicsContext::createGraphicsContext(traits.get());
424        if (pbuffer.valid()) {
425            TRACE("Pixel buffer has been created successfully.");
426        } else {
427            ERROR("Pixel buffer has not been created successfully.");
428        }
429        osg::Camera *camera = new osg::Camera;
430        camera->setGraphicsContext(pbuffer.get());
431        //camera->getOrCreateStateSet()->setGlobalDefaults();
432        camera->setViewport(new osg::Viewport(0, 0, _windowWidth, _windowHeight));
433        GLenum buffer = pbuffer->getTraits()->doubleBuffer ? GL_BACK : GL_FRONT;
434        camera->setDrawBuffer(buffer);
435        camera->setReadBuffer(buffer);
436        _captureCallback = new ScreenCaptureCallback();
437        camera->setFinalDrawCallback(_captureCallback.get());
438        _viewer->addSlave(camera, osg::Matrixd(), osg::Matrixd());
439#else
440        _viewer->getCamera()->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT);
441        osg::Texture2D* texture2D = new osg::Texture2D;
442        texture2D->setTextureSize(_windowWidth, _windowHeight);
443        texture2D->setInternalFormat(GL_RGBA);
444        texture2D->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::NEAREST);
445        texture2D->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::NEAREST);
446
447        _viewer->getCamera()->setImplicitBufferAttachmentMask(0, 0);
448        _viewer->getCamera()->attach(osg::Camera::COLOR_BUFFER0, texture2D);
449        //_viewer->getCamera()->attach(osg::Camera::DEPTH_BUFFER, GL_DEPTH_COMPONENT24);
450        _viewer->getCamera()->attach(osg::Camera::PACKED_DEPTH_STENCIL_BUFFER, GL_DEPTH24_STENCIL8_EXT);
451        _captureCallback = new ScreenCaptureCallback(texture2D);
452        _viewer->getCamera()->setFinalDrawCallback(_captureCallback.get());
453        _viewer->setUpViewInWindow(0, 0, _windowWidth, _windowHeight, screen);
454#endif
455#else
456        _captureCallback = new ScreenCaptureCallback();
457        _viewer->getCamera()->setFinalDrawCallback(_captureCallback.get());
458#if 1
459        _viewer->setUpViewInWindow(0, 0, _windowWidth, _windowHeight, screen);
460#else
461        SingleWindow *windowConfig = new SingleWindow(0, 0, _windowWidth, _windowHeight, screen);
462        osg::DisplaySettings *ds = windowConfig->getActiveDisplaySetting(*_viewer.get());
463        ds->setDoubleBuffer(false);
464        ds->setMinimumNumAlphaBits(8);
465        ds->setMinimumNumStencilBits(8);
466        ds->setNumMultiSamples(0);
467        windowConfig->setWindowDecoration(false);
468        windowConfig->setOverrideRedirect(false);
469        _viewer->apply(windowConfig);
470#endif
471#endif
472        _viewer->realize();
473        initColorMaps();
474        // HACK: This seems to initialize something required for properly
475        // mapping mouse coords
476        assert(getEventQueue() != NULL);
477        getEventQueue()->mouseMotion(_windowWidth/2, _windowHeight/2);
478    }
479}
480
481void Renderer::setAttribution(const std::string& attrib)
482{
483    TRACE("Setting map attribution: '%s'", attrib.c_str());
484    _attribution = attrib;
485    if (_copyrightLabel.valid()) {
486        _copyrightLabel->setText(_attribution);
487        _needsRedraw = true;
488    }
489}
490
491void Renderer::initControls()
492{
493    if (_hbox.valid())
494        return;
495    _hbox =
496        new osgEarth::Util::Controls::HBox(osgEarth::Util::Controls::Control::ALIGN_RIGHT,
497                                           osgEarth::Util::Controls::Control::ALIGN_BOTTOM,
498                                           osgEarth::Util::Controls::Gutter(0, 2, 2, 0), 2.0f);
499    _attribution = "Map data © OpenStreetMap";
500    _copyrightLabel =
501        new osgEarth::Util::Controls::LabelControl(_attribution, 12.0f);
502    _copyrightLabel->setForeColor(osg::Vec4f(1, 1, 1, 1));
503    _copyrightLabel->setHaloColor(osg::Vec4f(0, 0, 0, 1));
504    _copyrightLabel->setEncoding(osgText::String::ENCODING_UTF8);
505    _scaleLabel =
506        new osgEarth::Util::Controls::LabelControl("- km", 12.0f);
507    _scaleLabel->setForeColor(osg::Vec4f(1, 1, 1, 1));
508    _scaleLabel->setHaloColor(osg::Vec4f(0, 0, 0, 1));
509    _scaleBar =
510        new osgEarth::Util::Controls::Frame();
511    _scaleBar->setVertFill(true);
512    _scaleBar->setForeColor(osg::Vec4f(0, 0, 0, 1));
513    _scaleBar->setBackColor(osg::Vec4f(1, 1, 1, 0.6));
514    _scaleBar->setBorderColor(osg::Vec4f(0, 0, 0 ,1));
515    _scaleBar->setBorderWidth(1.0);
516    _hbox->addControl(_copyrightLabel.get());
517    _hbox->addControl(_scaleLabel.get());
518    _hbox->addControl(_scaleBar.get());
519    osgEarth::Util::Controls::ControlCanvas::getOrCreate(_viewer.get())->addControl(_hbox.get());
520    // Install an event callback to handle scale bar updates
521    // Can't use an update callback since that will trigger
522    // constant rendering
523    _mapNode->setEventCallback(new MapNodeCallback(this));
524}
525
526void Renderer::setGraticule(bool enable, GraticuleType type)
527{
528    if (!_mapNode.valid() || !_sceneRoot.valid())
529        return;
530    if (enable) {
531        if (_graticule.valid()) {
532            _sceneRoot->removeChild(_graticule.get());
533            _graticule = NULL;
534        }
535        switch (type) {
536        case GRATICULE_UTM: {
537            osgEarth::Util::UTMGraticule *gr = new osgEarth::Util::UTMGraticule(_mapNode.get());
538            _sceneRoot->addChild(gr);
539            _graticule = gr;
540        }
541            break;
542        case GRATICULE_MGRS: {
543            osgEarth::Util::MGRSGraticule *gr = new osgEarth::Util::MGRSGraticule(_mapNode.get());
544            _sceneRoot->addChild(gr);
545            _graticule = gr;
546        }
547            break;
548        case GRATICULE_SHADER: {
549            osgEarth::Util::GraticuleOptions opt;
550            opt.color() = osgEarth::Symbology::Color(1, 1, 0);
551            opt.labelColor() = osgEarth::Symbology::Color(1, 1, 0);
552            opt.lineWidth() = 1.0;
553            osgEarth::Util::GraticuleNode *gr = new osgEarth::Util::GraticuleNode(_mapNode.get(), opt);
554            _sceneRoot->addChild(gr);
555            _graticule = gr;
556        }
557            break;
558        case GRATICULE_GEODETIC:
559        default:
560            osgEarth::Util::GeodeticGraticule *gr = new osgEarth::Util::GeodeticGraticule(_mapNode.get());
561            osgEarth::Util::GeodeticGraticuleOptions opt = gr->getOptions();
562            opt.lineStyle()->getOrCreate<osgEarth::Symbology::LineSymbol>()->stroke()->color().set(1,0,0,1);
563            gr->setOptions(opt);
564            _sceneRoot->addChild(gr);
565            _graticule = gr;
566        }
567    } else if (_graticule.valid()) {
568        _sceneRoot->removeChild(_graticule.get());
569        _graticule = NULL;
570    }
571    _needsRedraw = true;
572}
573
574void Renderer::setReadout(int x, int y)
575{
576    if (!_coordsCallback.valid() || !_mapNode.valid() || !_viewer.valid())
577        return;
578
579    osgEarth::GeoPoint mapCoord;
580    if (mapMouseCoords(x, y, mapCoord)) {
581        _coordsCallback->set(mapCoord, _viewer->asView(), _mapNode);
582    } else {
583        _coordsCallback->reset(_viewer->asView(), _mapNode);
584    }
585    _needsRedraw = true;
586}
587
588void Renderer::clearReadout()
589{
590    if (_coordsCallback.valid()) {
591        _coordsCallback->reset(_viewer->asView(), _mapNode);
592    }
593    _needsRedraw = true;
594}
595
596void Renderer::setCoordinateReadout(bool state, CoordinateDisplayType type,
597                                    int precision)
598{
599    if (!state) {
600        if (_mouseCoordsTool.valid() && _viewer.valid()) {
601            _viewer->removeEventHandler(_mouseCoordsTool.get());
602            _mouseCoordsTool = NULL;
603        }
604        if (_coordsCallback.valid() && _viewer.valid()) {
605            osgEarth::Util::Controls::LabelControl *readout =
606                _coordsCallback->getLabel();
607            osgEarth::Util::Controls::ControlCanvas::getOrCreate(_viewer.get())->removeControl(readout);
608            _coordsCallback = NULL;
609        }
610    } else {
611        initMouseCoordsTool(type, precision);
612    }
613    _needsRedraw = true;
614}
615
616osgEarth::Util::MGRSFormatter::Precision
617Renderer::getMGRSPrecision(int precisionInMeters)
618{
619    switch (precisionInMeters) {
620    case 1:
621        return osgEarth::Util::MGRSFormatter::PRECISION_1M;
622    case 10:
623        return osgEarth::Util::MGRSFormatter::PRECISION_10M;
624    case 100:
625        return osgEarth::Util::MGRSFormatter::PRECISION_100M;
626    case 1000:
627        return osgEarth::Util::MGRSFormatter::PRECISION_1000M;
628    case 10000:
629        return osgEarth::Util::MGRSFormatter::PRECISION_10000M;
630    case 100000:
631        return osgEarth::Util::MGRSFormatter::PRECISION_100000M;
632    default:
633        ERROR("Invalid precision: %d", precisionInMeters);
634        return osgEarth::Util::MGRSFormatter::PRECISION_1M;
635    }
636}
637
638void Renderer::initMouseCoordsTool(CoordinateDisplayType type, int precision)
639{
640    if (!_viewer.valid())
641        return;
642    if (_mouseCoordsTool.valid()) {
643        _viewer->removeEventHandler(_mouseCoordsTool.get());
644    }
645    _mouseCoordsTool = new MouseCoordsTool(_mapNode.get());
646    osgEarth::Util::Controls::LabelControl *readout;
647    if (_coordsCallback.valid()) {
648        readout = _coordsCallback->getLabel();
649        _coordsCallback = NULL;
650    } else {
651        readout = new osgEarth::Util::Controls::LabelControl("", 12.0f);
652        osgEarth::Util::Controls::ControlCanvas::getOrCreate(_viewer.get())->addControl(readout);
653        readout->setForeColor(osg::Vec4f(1, 1, 1, 1));
654        readout->setHaloColor(osg::Vec4f(0, 0, 0, 1));
655    }
656
657    osgEarth::Util::Formatter *formatter = NULL;
658    if (type == COORDS_MGRS) {
659        osgEarth::Util::MGRSFormatter::Precision prec =
660            osgEarth::Util::MGRSFormatter::PRECISION_1M;
661        if (precision > 0) {
662            prec = getMGRSPrecision(precision);
663        }
664        unsigned int opts = 0u;
665        formatter = new osgEarth::Util::MGRSFormatter(prec, NULL, opts);
666    } else {
667        osgEarth::Util::LatLongFormatter::AngularFormat af;
668        unsigned int opts =  osgEarth::Util::LatLongFormatter::USE_SYMBOLS;
669        switch (type) {
670        case COORDS_LATLONG_DEGREES_DECIMAL_MINUTES:
671            af = osgEarth::Util::LatLongFormatter::FORMAT_DEGREES_DECIMAL_MINUTES;
672            break;
673        case COORDS_LATLONG_DEGREES_MINUTES_SECONDS:
674            af = osgEarth::Util::LatLongFormatter::FORMAT_DEGREES_MINUTES_SECONDS;
675            break;
676        default:
677            af = osgEarth::Util::LatLongFormatter::FORMAT_DECIMAL_DEGREES;
678        }
679        osgEarth::Util::LatLongFormatter *latlong = new osgEarth::Util::LatLongFormatter(af, opts);
680        if (precision > 0) {
681            latlong->setPrecision(precision);
682        }
683        formatter = latlong;
684    }
685    _coordsCallback = new MouseCoordsCallback(readout, formatter);
686
687    _mouseCoordsTool->addCallback(_coordsCallback.get());
688    _viewer->addEventHandler(_mouseCoordsTool.get());
689}
690
691void Renderer::initEarthManipulator()
692{
693    _manipulator = new osgEarth::Util::EarthManipulator;
694#if 1
695    osgEarth::Util::EarthManipulator::Settings *settings = _manipulator->getSettings();
696
697    settings->bindMouse(osgEarth::Util::EarthManipulator::ACTION_ROTATE,
698                        osgGA::GUIEventAdapter::MIDDLE_MOUSE_BUTTON,
699                        osgGA::GUIEventAdapter::MODKEY_ALT);
700    osgEarth::Util::EarthManipulator::ActionOptions options;
701    options.clear();
702    options.add(osgEarth::Util::EarthManipulator::OPTION_CONTINUOUS, true);
703    settings->bindMouse(osgEarth::Util::EarthManipulator::ACTION_ZOOM,
704                        osgGA::GUIEventAdapter::RIGHT_MOUSE_BUTTON,
705                        osgGA::GUIEventAdapter::MODKEY_ALT, options);
706
707    if (_map->isGeocentric()) {
708        settings->setMinMaxDistance(1.0, 2.0e7);
709    } else {
710        osgEarth::GeoExtent extent = _map->getProfile()->getExtent();
711        settings->setMinMaxDistance(1.0, getMaxDistanceFromExtent(extent));
712    }
713
714    _manipulator->applySettings(settings);
715#endif
716    _viewer->setCameraManipulator(_manipulator.get());
717    _manipulator->setNode(NULL);
718    _manipulator->setNode(_sceneRoot.get());
719    //_manipulator->computeHomePosition();
720}
721
722void Renderer::loadEarthFile(const char *path)
723{
724    TRACE("Loading %s", path);
725    osg::Node *node = osgDB::readNodeFile(path);
726    if (node == NULL) {
727        ERROR("Couldn't load %s", path);
728        return;
729    }
730    osgEarth::MapNode *mapNode = osgEarth::MapNode::findMapNode(node);
731    if (mapNode == NULL) {
732        ERROR("Couldn't find MapNode");
733        return;
734    } else {
735        initViewer();
736        _sceneRoot = new osg::Group;
737        _sceneRoot->addChild(node);
738        _map = mapNode->getMap();
739    }
740    _mapNode = mapNode;
741
742    if (_clipPlaneCullCallback.valid()) {
743        _viewer->getCamera()->removeCullCallback(_clipPlaneCullCallback.get());
744        _clipPlaneCullCallback = NULL;
745    }
746    if (_map->isGeocentric()) {
747        _clipPlaneCullCallback = new osgEarth::Util::AutoClipPlaneCullCallback(mapNode);
748        _viewer->getCamera()->addCullCallback(_clipPlaneCullCallback.get());
749    }
750    _viewer->setSceneData(_sceneRoot.get());
751
752    if (_mouseCoordsTool.valid()) {
753        initMouseCoordsTool();
754    }
755    initControls();
756    initEarthManipulator();
757   
758    _viewer->home();
759    finalizeViewer();
760    _needsRedraw = true;
761}
762
763void Renderer::resetMap(osgEarth::MapOptions::CoordinateSystemType type,
764                        const osg::Vec4f& bgColor,
765                        const char *profile,
766                        double bounds[4])
767{
768    TRACE("Restting map with type %d, profile %s", type, profile);
769
770    osgEarth::MapOptions mapOpts;
771    mapOpts.coordSysType() = type;
772    if (profile != NULL) {
773        if (bounds != NULL) {
774            mapOpts.profile() = osgEarth::ProfileOptions();
775            if (strcmp(profile, "geodetic") == 0) {
776                mapOpts.profile()->srsString() = "epsg:4326"; // WGS84
777            } else if (strcmp(profile, "spherical-mercator") == 0) {
778                // Projection used by Google/Bing/OSM
779                // aka epsg:900913 meters in x/y
780                // aka WGS84 Web Mercator (Auxiliary Sphere)
781                // X/Y: -20037508.34m to 20037508.34m
782                mapOpts.profile()->srsString() = "epsg:3857";
783            } else {
784                mapOpts.profile()->srsString() = profile;
785            }
786            TRACE("Setting profile bounds: %g %g %g %g",
787                  bounds[0], bounds[1], bounds[2], bounds[3]);
788            if (1) {
789                osg::ref_ptr<osgEarth::SpatialReference> fromSRS = osgEarth::SpatialReference::create("wgs84");
790                osg::ref_ptr<osgEarth::SpatialReference> toSRS = osgEarth::SpatialReference::create(mapOpts.profile()->srsString().get());
791                osgEarth::Bounds extents(bounds[0], bounds[1], bounds[2], bounds[3]);
792                extents.transform(fromSRS, toSRS);
793                mapOpts.profile()->bounds() = extents;
794                //mapOpts.profile()->numTilesWideAtLod0() = 1;
795                //mapOpts.profile()->numTilesHighAtLod0() = 2;
796            } else {
797                mapOpts.profile()->bounds() =
798                    osgEarth::Bounds(bounds[0], bounds[1], bounds[2], bounds[3]);
799            }
800        } else {
801            mapOpts.profile() = osgEarth::ProfileOptions(profile);
802        }
803    } else if (type == osgEarth::MapOptions::CSTYPE_PROJECTED) {
804        mapOpts.profile() = osgEarth::ProfileOptions("global-mercator");
805    }
806
807#ifdef USE_CACHE
808    setupCache();
809    osgEarth::Drivers::FileSystemCacheOptions cacheOpts;
810    cacheOpts.rootPath() = _cacheDir;
811    mapOpts.cache() = cacheOpts;
812#endif
813
814    initViewer();
815
816    //mapOpts.referenceURI() = _baseURI;
817    osgEarth::Map *map = new osgEarth::Map(mapOpts);
818    _map = map;
819    osgEarth::Drivers::GDALOptions bopts;
820    bopts.url() = getBaseImage();
821    addImageLayer("base", bopts);
822#ifdef USE_REX
823    osgEarth::Drivers::RexTerrainEngine::RexTerrainEngineOptions engineOpt;
824#else
825    osgEarth::Drivers::MPTerrainEngine::MPTerrainEngineOptions engineOpt;
826#endif
827    // Set background layer color
828    engineOpt.color() = bgColor;
829    //engineOpt.minLOD() = 1;
830    // Sets shader uniform for terrain renderer (config var defaults to false)
831    if (!_map->isGeocentric()) {
832        engineOpt.enableLighting() = false;
833    }
834    osgEarth::MapNodeOptions mapNodeOpts(engineOpt);
835    // Sets GL_LIGHTING state in MapNode StateSet (config var defaults to true)
836    //mapNodeOpts.enableLighting() = true;
837    //mapNodeOpts.getTerrainOptions().loadingPolicy().mapLoadingThreadsPerCore() = 1;
838    //mapNodeOpts.getTerrainOptions().loadingPolicy().numLoadingThreads() = 1;
839    //mapNodeOpts.getTerrainOptions().loadingPolicy().numCompileThreadsPerCore() = 1;
840    //mapNodeOpts.getTerrainOptions().loadingPolicy().numCompileThreads() = 1;
841    osgEarth::MapNode *mapNode = new osgEarth::MapNode(map, mapNodeOpts);
842    _mapNode = mapNode;
843    if (_map->isGeocentric()) {
844        osgEarth::DateTime now;
845        TRACE("Creating SkyNode");
846        osgEarth::Drivers::SimpleSky::SimpleSkyOptions skyOpts;
847        skyOpts.hours() = now.hours();
848        skyOpts.ambient() = 0.2f;
849        skyOpts.atmosphericLighting() = true;
850        skyOpts.exposure() = 3.0;
851        _skyNode = osgEarth::Util::SkyNode::create(skyOpts, mapNode);
852        _skyNode->addChild(mapNode);
853        _skyNode->attach(_viewer.get(), 0);
854        _sceneRoot = new osg::Group();
855        _sceneRoot->addChild(_skyNode.get());
856        //_sceneRoot = _skyNode;
857    } else {
858        _sceneRoot = new osg::Group();
859        _sceneRoot->addChild(_mapNode.get());
860    }
861
862    if (_clipPlaneCullCallback.valid()) {
863        _viewer->getCamera()->removeCullCallback(_clipPlaneCullCallback.get());
864        _clipPlaneCullCallback = NULL;
865    }
866    if (_map->isGeocentric()) {
867        _clipPlaneCullCallback = new osgEarth::Util::AutoClipPlaneCullCallback(mapNode);
868        _viewer->getCamera()->addCullCallback(_clipPlaneCullCallback.get());
869    }
870    _viewer->setSceneData(_sceneRoot.get());
871    if (_mouseCoordsTool.valid()) {
872        initMouseCoordsTool();
873    }
874#ifdef USE_RTT_PICKER
875    if (_picker.valid()) {
876        _viewer->removeEventHandler(_picker.get());
877        _picker = NULL;
878    }
879    if (!_picker.valid()) {
880        _picker = new osgEarth::Util::RTTPicker;
881        _picker->addChild(mapNode);
882        //osg::ref_ptr<HoverCallback> callback = new HoverCallback(this);
883        osg::ref_ptr<SelectCallback> callback = new SelectCallback(this);
884        _picker->setDefaultCallback(callback.get());
885        _viewer->addEventHandler(_picker.get());
886    }
887#endif
888    initControls();
889    //_viewer->setSceneData(_sceneRoot.get());
890    initEarthManipulator();
891    _viewer->home();
892
893    finalizeViewer();
894    _needsRedraw = true;
895}
896
897void Renderer::clearMap()
898{
899    if (_map.valid()) {
900        _map->clear();
901        _needsRedraw = true;
902    }
903}
904
905void Renderer::setSkyAmbient(float value)
906{
907#if OSG_MIN_VERSION_REQUIRED(2, 7, 0)
908    if (_skyNode.valid()) {
909        _skyNode->setMinimumAmbient(osg::Vec4f(value, value, value, 1.0f));
910        _needsRedraw = true;
911    }
912#endif
913}
914
915void Renderer::setLighting(bool state)
916{
917    if (_mapNode.valid()) {
918        TRACE("Setting lighting: %d", state ? 1 : 0);
919        _mapNode->getOrCreateStateSet()
920            ->setMode(GL_LIGHTING, state ? 1 : 0);
921        _needsRedraw = true;
922    }
923}
924
925void Renderer::setViewerLightType(osg::View::LightingMode mode)
926{
927    if (_viewer.valid()) {
928        _viewer->setLightingMode(mode);
929        _needsRedraw = true;
930    }
931}
932
933void Renderer::setTerrainVerticalScale(double scale)
934{
935    if (_mapNode.valid()) {
936        if (!_verticalScale.valid()) {
937            _verticalScale = new osgEarth::Util::VerticalScale;
938            _mapNode->getTerrainEngine()->addEffect(_verticalScale);
939        }
940        _verticalScale->setScale(scale);
941        _needsRedraw = true;
942    }
943}
944
945void Renderer::setEphemerisTime(time_t utcTime)
946{
947    if (_skyNode.valid()) {
948        osgEarth::DateTime time(utcTime);
949        _skyNode->setDateTime(time);
950        _needsRedraw = true;
951    }
952}
953
954void Renderer::setEphemerisTime(int year, int month, int day, double hours)
955{
956    if (_skyNode.valid()) {
957        osgEarth::DateTime time(year, month, day, hours);
958        _skyNode->setDateTime(time);
959        _needsRedraw = true;
960    }
961}
962
963void Renderer::setTerrainColor(const osg::Vec4f& color)
964{
965    // XXX: Don't know how to change this at runtime without a map reset
966    _needsRedraw = true;
967}
968
969void Renderer::setTerrainLighting(bool state)
970{
971    if (_skyNode.valid()) {
972        _skyNode->setLighting((state ? osg::StateAttribute::ON : osg::StateAttribute::OFF));
973    } else {
974#if 1
975        if (!_mapNode.valid()) {
976            ERROR("No map node");
977            return;
978        }
979        // XXX: HACK alert
980        // Find the terrain engine container (might be above one or more decorators)
981        osg::Group *group = _mapNode->getTerrainEngine();
982        while (group->getParent(0) != NULL && group->getParent(0) != _mapNode.get()) {
983            group = group->getParent(0);
984        }
985        if (group != NULL && group->getParent(0) == _mapNode.get()) {
986            TRACE("Setting terrain lighting: %d", state ? 1 : 0);
987            if (group->getOrCreateStateSet()->getUniform("oe_mode_GL_LIGHTING") != NULL) {
988                group->getStateSet()->getUniform("oe_mode_GL_LIGHTING")->set(state);
989            } else {
990                ERROR("Can't get terrain lighting uniform");
991            }
992        } else {
993            ERROR("Can't find terrain engine container");
994        }
995#else
996        if (_stateManip.valid()) {
997            _stateManip->setLightingEnabled(state);
998        }
999#endif
1000    }
1001    _needsRedraw = true;
1002}
1003
1004void Renderer::setTerrainWireframe(bool state)
1005{
1006    if (!_map.valid()) {
1007        ERROR("No map");
1008        return;
1009    }
1010#if 0
1011    if (!_mapNode.valid()) {
1012        ERROR("No map node");
1013        return;
1014    }
1015    TRACE("Setting terrain wireframe: %d", state ? 1 : 0);
1016    osg::StateSet *state = _mapNode->getOrCreateStateSet();
1017    osg::PolygonMode *pmode = dynamic_cast< osg::PolygonMode* >(state->getAttribute(osg::StateAttribute::POLYGONMODE));
1018    if (pmode == NULL) {
1019        pmode = new osg::PolygonMode;
1020        state->setAttribute(pmode);
1021    }
1022    if (state) {
1023        pmode->setMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE);
1024    } else {
1025        pmode->setMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::FILL);
1026    }
1027    _needsRedraw = true;
1028#else
1029    if (_stateManip.valid()) {
1030        _stateManip->setPolygonMode(state ? osg::PolygonMode::LINE : osg::PolygonMode::FILL);
1031        _needsRedraw = true;
1032    }
1033#endif
1034}
1035
1036void Renderer::saveNamedViewpoint(const char *name)
1037{
1038    _viewpoints[name] = getViewpoint();
1039}
1040
1041bool Renderer::removeNamedViewpoint(const char *name)
1042{
1043    ViewpointHashmap::iterator itr = _viewpoints.find(name);
1044    if (itr != _viewpoints.end()) {
1045        _viewpoints.erase(name);
1046        return true;
1047    } else {
1048        ERROR("Unknown viewpoint: '%s'", name);
1049        return false;
1050    }
1051}
1052
1053bool Renderer::restoreNamedViewpoint(const char *name, double durationSecs)
1054{
1055    ViewpointHashmap::iterator itr = _viewpoints.find(name);
1056    if (itr != _viewpoints.end()) {
1057        setViewpoint(itr->second, durationSecs);
1058        return true;
1059    } else {
1060        ERROR("Unknown viewpoint: '%s'", name);
1061        return false;
1062    }
1063}
1064
1065void Renderer::setViewpoint(const osgEarth::Viewpoint& v, double durationSecs)
1066{
1067    if (_manipulator.valid()) {
1068        TRACE("Setting viewpoint: %g %g %g %g %g %g",
1069              v.focalPoint()->x(), v.focalPoint()->y(), v.focalPoint()->z(),
1070              v.heading()->as(osgEarth::Units::DEGREES),
1071              v.pitch()->as(osgEarth::Units::DEGREES),
1072              v.range()->as(osgEarth::Units::METERS));
1073        _manipulator->setViewpoint(v, durationSecs);
1074        _needsRedraw = true;
1075    } else {
1076        ERROR("No manipulator");
1077    }
1078}
1079
1080osgEarth::Viewpoint Renderer::getViewpoint()
1081{
1082    if (_manipulator.valid()) {
1083        return _manipulator->getViewpoint();
1084    } else {
1085        // Uninitialized, invalid viewpoint
1086        return osgEarth::Viewpoint();
1087    }
1088}
1089
1090static void srsInfo(const osgEarth::SpatialReference *srs)
1091{
1092    TRACE("SRS: %s", srs->getName().c_str());
1093    TRACE("horiz: \"%s\" vert: \"%s\"", srs->getHorizInitString().c_str(), srs->getVertInitString().c_str());
1094    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",
1095          srs->isGeographic() ? 1 : 0,
1096          srs->isGeodetic() ? 1 : 0,
1097          srs->isProjected() ? 1 : 0,
1098          srs->isECEF() ? 1 : 0,
1099          srs->isMercator() ? 1 : 0,
1100          srs->isSphericalMercator() ? 1 : 0,
1101          srs->isNorthPolar() ? 1 : 0,
1102          srs->isSouthPolar() ? 1 : 0,
1103          srs->isUserDefined() ? 1 : 0,
1104          srs->isContiguous() ? 1 : 0,
1105          srs->isCube() ? 1 : 0,
1106          srs->isLTP() ? 1 : 0,
1107          srs->isPlateCarre() ? 1 : 0);
1108}
1109
1110bool Renderer::getWorldCoords(const osgEarth::GeoPoint& mapPt, osg::Vec3d *world)
1111{
1112    if (!_mapNode.valid() || _mapNode->getTerrain() == NULL) {
1113        ERROR("No map");
1114        return false;
1115    }
1116    TRACE("Input SRS:");
1117    srsInfo(mapPt.getSRS());
1118    TRACE("Map SRS:");
1119    srsInfo(_mapNode->getMapSRS());
1120    bool ret = mapPt.toWorld(*world, _mapNode->getTerrain());
1121    TRACE("In: %g,%g,%g Out: %g,%g,%g",
1122          mapPt.x(), mapPt.y(), mapPt.z(),
1123          world->x(), world->y(), world->z());
1124    return ret;
1125}
1126
1127bool Renderer::worldToScreen(const osg::Vec3d& world, osg::Vec3d *screen, bool invertY)
1128{
1129    if (!_viewer.valid()) {
1130        ERROR("No viewer");
1131        return false;
1132    }
1133    osg::Camera *cam = _viewer->getCamera();
1134    osg::Matrixd MVP = cam->getViewMatrix() * cam->getProjectionMatrix();
1135    // Get clip coords
1136    osg::Vec4d pt;
1137    pt = osg::Vec4d(world, 1.0) * MVP;
1138    // Clip
1139    if (pt.x() < -pt.w() ||
1140        pt.x() > pt.w() ||
1141        pt.y() < -pt.w() ||
1142        pt.y() > pt.w() ||
1143        pt.z() < -pt.w() ||
1144        pt.z() > pt.w()) {
1145        // Outside frustum
1146        TRACE("invalid pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w());
1147        return false;
1148    }
1149    TRACE("clip pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w());
1150    // Perspective divide: now NDC
1151    pt /= pt.w();
1152    const osg::Viewport *viewport = cam->getViewport();
1153#if 1
1154    screen->x() = viewport->x() + viewport->width() * 0.5 + pt.x() * viewport->width() * 0.5;
1155    screen->y() = viewport->y() + viewport->height() * 0.5 + pt.y() * viewport->height() * 0.5;
1156    //double near = 0;
1157    //double far = 1;
1158    //screen->z() = (far + near) * 0.5 + (far - near) * 0.5 * pt.z();
1159    screen->z() = 0.5 + 0.5 * pt.z();
1160#else
1161    *screen = osg::Vec3d(pt.x(), pt.y(), pt.z()) * cam->getViewport()->computeWindowMatrix();
1162#endif
1163    if (invertY) {
1164        screen->y() = viewport->height() - screen->y();
1165    }
1166    TRACE("screen: %g,%g,%g", screen->x(), screen->y(), screen->z());
1167    return true;
1168}
1169
1170bool Renderer::worldToScreen(std::vector<osg::Vec3d>& coords, bool invertY)
1171{
1172    if (!_viewer.valid()) {
1173        ERROR("No viewer");
1174        return false;
1175    }
1176    bool ret = true;
1177    osg::Camera *cam = _viewer->getCamera();
1178    osg::Matrixd MVP = cam->getViewMatrix() * cam->getProjectionMatrix();
1179    const osg::Viewport *viewport = cam->getViewport();
1180    for (std::vector<osg::Vec3d>::iterator itr = coords.begin();
1181         itr != coords.end(); ++itr) {
1182        // Get clip coords
1183        osg::Vec4d pt;
1184        pt = osg::Vec4d(*itr, 1.0) * MVP;
1185        // Clip
1186        if (pt.x() < -pt.w() ||
1187            pt.x() > pt.w() ||
1188            pt.y() < -pt.w() ||
1189            pt.y() > pt.w() ||
1190            pt.z() < -pt.w() ||
1191            pt.z() > pt.w()) {
1192            // Outside frustum
1193            TRACE("invalid pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w());
1194            itr->set(std::numeric_limits<double>::quiet_NaN(),
1195                     std::numeric_limits<double>::quiet_NaN(),
1196                     std::numeric_limits<double>::quiet_NaN());
1197            ret = false;
1198            continue;
1199        }
1200        TRACE("clip pt: %g,%g,%g,%g", pt.x(), pt.y(), pt.z(), pt.w());
1201        // Perspective divide: now NDC
1202        pt /= pt.w();
1203#if 1
1204        itr->x() = viewport->x() + viewport->width() * 0.5 + pt.x() * viewport->width() * 0.5;
1205        itr->y() = viewport->y() + viewport->height() * 0.5 + pt.y() * viewport->height() * 0.5;
1206        //double near = 0;
1207        //double far = 1;
1208        //itr->z() = (far + near) * 0.5 + (far - near) * 0.5 * pt.z();
1209        itr->z() = 0.5 + 0.5 * pt.z();
1210#else
1211        *itr = osg::Vec3d(pt.x(), pt.y(), pt.z()) * viewport->computeWindowMatrix();
1212#endif
1213        if (invertY) {
1214            itr->y() = viewport->height() - itr->y();
1215        }
1216        TRACE("screen: %g,%g,%g", itr->x(), itr->y(), itr->z());
1217    }
1218    return ret;
1219}
1220
1221bool Renderer::mouseToLatLong(int mouseX, int mouseY,
1222                              double *latitude, double *longitude)
1223{
1224    if (getMapSRS() == NULL)
1225        return false;
1226    const osgEarth::SpatialReference *outSRS =
1227        getMapSRS()->getGeographicSRS();
1228    if (outSRS == NULL)
1229        return false;
1230    osgEarth::GeoPoint mapPoint;
1231    if (mapMouseCoords(mouseX, mouseY, mapPoint)) {
1232        mapPoint = mapPoint.transform(outSRS);
1233        *longitude = mapPoint.x();
1234        *latitude = mapPoint.y();
1235        return true;
1236    } else {
1237        return false;
1238    }
1239}
1240#if 0
1241bool Renderer::mapToLatLong(osgEarth::GeoPoint& mapPoint)
1242{
1243    if (getMapSRS() == NULL)
1244        return false;
1245    const osgEarth::SpatialReference *outSRS =
1246        getMapSRS()->getGeographicSRS();
1247    if (outSRS == NULL)
1248        return false;
1249    mapPoint = mapPoint.transform(outSRS);
1250    return true;
1251}
1252#endif
1253/**
1254 * \brief Map screen mouse coordinates to map coordinates
1255 *
1256 * This method assumes that mouse Y coordinates are 0 at the top
1257 * of the screen and increase going down if invertY is set, and
1258 * 0 at the bottom and increase going up otherwise.
1259 */
1260bool Renderer::mapMouseCoords(float mouseX, float mouseY,
1261                              osgEarth::GeoPoint& map, bool invertY)
1262{
1263    if (!_mapNode.valid() || _mapNode->getTerrain() == NULL) {
1264        ERROR("No map");
1265        return false;
1266    }
1267    if (!_viewer.valid()) {
1268        ERROR("No viewer");
1269        return false;
1270    }
1271    if (invertY) {
1272        mouseY = ((float)_windowHeight - mouseY);
1273    }
1274    osg::Vec3d world;
1275    if (_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), mouseX, mouseY, world)) {
1276        map.fromWorld(getMapSRS(), world);
1277        return true;
1278    }
1279    return false;
1280}
1281
1282bool Renderer::addImageLayer(const char *name,
1283                             osgEarth::TileSourceOptions& opts,
1284                             bool enableCache,
1285                             bool coverage,
1286                             bool makeShared,
1287                             bool visible,
1288                             unsigned int minLOD, unsigned int maxLOD)
1289{
1290    if (!_map.valid()) {
1291        ERROR("No map");
1292        return false;
1293    }
1294    TRACE("layer: %s", name);
1295    if (!opts.tileSize().isSet()) {
1296        opts.tileSize() = 256;
1297    }
1298    osgEarth::ImageLayerOptions layerOpts(name, opts);
1299    layerOpts.textureCompression() = osg::Texture::USE_IMAGE_DATA_FORMAT;
1300    if (coverage) {
1301        layerOpts.coverage() = true;
1302    }
1303    if (makeShared) {
1304        layerOpts.shared() = true;
1305    }
1306    if (!enableCache) {
1307        TRACE("Disabling cache for layer %s", name);
1308        layerOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
1309    }
1310    if (!visible) {
1311        layerOpts.visible() = false;
1312    }
1313    layerOpts.minLevel() = minLOD;
1314    layerOpts.maxLevel() = maxLOD;
1315    osg::ref_ptr<osgEarth::ImageLayer> layer = new osgEarth::ImageLayer(layerOpts);
1316    _map->addImageLayer(layer.get());
1317    if (layer->getTileSource() == NULL || !layer->getTileSource()->isOK()) {
1318        ERROR("Failed to add image layer: %s", name);
1319        _map->removeImageLayer(layer.get());
1320        return false;
1321    }
1322    _needsRedraw = true;
1323    return true;
1324}
1325
1326void Renderer::addColorFilter(const char *name,
1327                              const char *shader)
1328{
1329    if (!_map.valid()) {
1330        ERROR("No map");
1331        return;
1332    }
1333    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1334    if (layer == NULL) {
1335        TRACE("Image layer not found: %s", name);
1336        return;
1337    }
1338    osgEarth::Util::GLSLColorFilter *filter = new osgEarth::Util::GLSLColorFilter;
1339    filter->setCode(shader);
1340    //filter->setCode("color.rgb = color.r > 0.5 ? vec3(1.0) : vec3(0.0);");
1341    layer->addColorFilter(filter);
1342    _needsRedraw = true;
1343}
1344
1345void Renderer::removeColorFilter(const char *name, int idx)
1346{
1347    if (!_map.valid()) {
1348        ERROR("No map");
1349        return;
1350    }
1351    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1352    if (layer == NULL) {
1353        TRACE("Image layer not found: %s", name);
1354        return;
1355    }
1356    if (idx < 0) {
1357        while (!layer->getColorFilters().empty()) {
1358            layer->removeColorFilter(layer->getColorFilters()[0]);
1359        }
1360    } else {
1361        layer->removeColorFilter(layer->getColorFilters().at(idx));
1362    }
1363    _needsRedraw = true;
1364}
1365
1366void Renderer::removeImageLayer(const char *name)
1367{
1368    if (!_map.valid()) {
1369        ERROR("No map");
1370        return;
1371    }
1372    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1373    if (layer != NULL) {
1374        _map->removeImageLayer(layer);
1375        _needsRedraw = true;
1376    } else {
1377        TRACE("Image layer not found: %s", name);
1378    }
1379}
1380
1381void Renderer::moveImageLayer(const char *name, unsigned int pos)
1382{
1383    if (!_map.valid()) {
1384        ERROR("No map");
1385        return;
1386    }
1387    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1388    if (layer != NULL) {
1389        _map->moveImageLayer(layer, pos);
1390        _needsRedraw = true;
1391    } else {
1392        TRACE("Image layer not found: %s", name);
1393    }
1394}
1395
1396void Renderer::setImageLayerOpacity(const char *name, double opacity)
1397{
1398    if (!_map.valid()) {
1399        ERROR("No map");
1400        return;
1401    }
1402    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1403    if (layer != NULL) {
1404        layer->setOpacity(opacity);
1405        _needsRedraw = true;
1406    } else {
1407        TRACE("Image layer not found: %s", name);
1408    }
1409}
1410
1411void Renderer::setImageLayerVisibleRange(const char *name, float min, float max)
1412{
1413    if (!_map.valid()) {
1414        ERROR("No map");
1415        return;
1416    }
1417    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1418    if (layer != NULL) {
1419        layer->setMinVisibleRange(min);
1420        layer->setMaxVisibleRange(max);
1421        _needsRedraw = true;
1422    } else {
1423        TRACE("Image layer not found: %s", name);
1424    }
1425}
1426
1427void Renderer::setImageLayerVisibility(const char *name, bool state)
1428{
1429    if (!_map.valid()) {
1430        ERROR("No map");
1431        return;
1432    }
1433    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1434    if (layer != NULL) {
1435        layer->setVisible(state);
1436        _needsRedraw = true;
1437    } else {
1438        TRACE("Image layer not found: %s", name);
1439    }
1440}
1441
1442void Renderer::addElevationLayer(const char *name,
1443                                 osgEarth::TileSourceOptions& opts,
1444                                 bool enableCache,
1445                                 bool visible,
1446                                 unsigned int minLOD, unsigned int maxLOD)
1447{
1448    if (!_map.valid()) {
1449        ERROR("No map");
1450        return;
1451    }
1452    TRACE("layer: %s", name);
1453    if (!opts.tileSize().isSet()) {
1454        opts.tileSize() = 17;
1455    }
1456    osgEarth::ElevationLayerOptions layerOpts(name, opts);
1457    if (!enableCache) {
1458        layerOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
1459    }
1460    if (!visible) {
1461        layerOpts.visible() = false;
1462    }
1463    layerOpts.minLevel() = minLOD;
1464    layerOpts.maxLevel() = maxLOD;
1465    // XXX: GDAL does not report vertical datum, it should be specified here
1466    // Common options: geodetic (default), egm96, egm84, egm2008
1467    //layerOpts.verticalDatum() = "egm96";
1468    _map->addElevationLayer(new osgEarth::ElevationLayer(layerOpts));
1469    _needsRedraw = true;
1470}
1471
1472void Renderer::removeElevationLayer(const char *name)
1473{
1474    if (!_map.valid()) {
1475        ERROR("No map");
1476        return;
1477    }
1478    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
1479    if (layer != NULL) {
1480        _map->removeElevationLayer(layer);
1481        _needsRedraw = true;
1482    } else {
1483        TRACE("Elevation layer not found: %s", name);
1484    }
1485}
1486
1487void Renderer::moveElevationLayer(const char *name, unsigned int pos)
1488{
1489    if (!_map.valid()) {
1490        ERROR("No map");
1491        return;
1492    }
1493    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
1494    if (layer != NULL) {
1495        _map->moveElevationLayer(layer, pos);
1496        _needsRedraw = true;
1497    } else {
1498        TRACE("Elevation layer not found: %s", name);
1499    }
1500}
1501
1502void Renderer::setElevationLayerVisibility(const char *name, bool state)
1503{
1504    if (!_map.valid()) {
1505        ERROR("No map");
1506        return;
1507    }
1508    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
1509    if (layer != NULL) {
1510        layer->setVisible(state);
1511        _needsRedraw = true;
1512    } else {
1513        TRACE("Elevation layer not found: %s", name);
1514    }
1515}
1516
1517void Renderer::initAnnotations()
1518{
1519    if (!_annotations.valid()) {
1520        _annotations = new osg::Group();
1521        _annotations->setName("Annotations");
1522        _sceneRoot->addChild(_annotations.get());
1523        _placeNodes = new osg::Group();
1524        _placeNodes->setName("Place Nodes");
1525        osgEarth::Decluttering::setEnabled(_placeNodes->getOrCreateStateSet(), true);
1526        _annotations->addChild(_placeNodes.get());
1527        if (_picker.valid()) {
1528            _picker->addChild(_placeNodes.get());
1529        }
1530    }
1531}
1532
1533void Renderer::setSelectMode(SelectMode mode)
1534{
1535    _selectMode = mode;
1536    switch (_selectMode) {
1537    case SELECT_OFF: {
1538#ifdef USE_RTT_PICKER
1539        if (_picker.valid()) {
1540            _viewer->removeEventHandler(_picker.get());
1541            _picker = NULL;
1542        }
1543#endif
1544    }
1545        break;
1546    case SELECT_ON: {
1547#ifdef USE_RTT_PICKER
1548        if (!_picker.valid()) {
1549            _picker = new osgEarth::Util::RTTPicker;
1550            _picker->addChild(_mapNode);
1551            osg::ref_ptr<SelectCallback> callback = new SelectCallback(this);
1552            _picker->setDefaultCallback(callback.get());
1553            _viewer->addEventHandler(_picker.get());
1554        }
1555#endif
1556    }
1557        break;
1558    default:
1559        ERROR("Unknown select mode");
1560    }
1561}
1562
1563void Renderer::setPlacardConfig(const Placard& placardConf, const char *layerName)
1564{
1565    osgEarth::ModelLayer *layer = _map->getModelLayerByName(layerName);
1566    if (layer == NULL) {
1567        ERROR("Unknown layer '%s'", layerName);
1568        return;
1569    }
1570    //PlacardNode *node;
1571    //node->setConfig(placardConf);
1572    _placardConfigs[layerName] = placardConf;
1573}
1574
1575void Renderer::selectFeatures(std::vector<unsigned long>& featureIDs, const char *layerName)
1576{
1577    TRACE("Select layer '%s', num features: %u", layerName, featureIDs.size());
1578    clearSelection();
1579    if (featureIDs.size() == 0) {
1580        // clear selection
1581        return;
1582    }
1583    osgEarth::ModelLayer *layer = _map->getModelLayerByName(layerName);
1584    if (layer == NULL) {
1585        ERROR("Unknown layer '%s'", layerName);
1586        return;
1587    }
1588#ifdef WANT_TRACE
1589    for (unsigned int i = 0; i < featureIDs.size(); i++) {
1590        TRACE("feature ID: %u", featureIDs.at(i));
1591    }
1592#endif
1593    unsigned long fid = featureIDs.at(0);
1594
1595    osgEarth::ModelSource *modelSource = layer->getModelSource();
1596    osgEarth::Features::FeatureModelSource *fms = dynamic_cast<osgEarth::Features::FeatureModelSource *>(modelSource);
1597    if (fms != NULL) {
1598        osgEarth::Features::FeatureSource *fs = fms->getFeatureSource();
1599        FindFeatureSourceIndexNodeVisitor visitor;
1600        visitor.source = fs;
1601        _sceneRoot->accept(visitor);
1602        TRACE("Num FeatureSourceIndexNodes found: %lu", visitor.nodes.size());
1603        for (size_t i = 0; i < visitor.nodes.size(); i++) {
1604            osgEarth::Features::FeatureIndex *index = visitor.nodes[i]->getIndex();
1605#if OSGEARTH_MIN_VERSION_REQUIRED(3, 0, 0)
1606            osgEarth::ObjectID id = index->getObjectID(fid);
1607#if 0
1608            osgEarth::Features::Feature *feature = index->getFeature(id);
1609            if (feature) {
1610                // Find feature centroid
1611                osgEarth::GeoPoint location;
1612                if (feature->getGeometry()) {
1613                    osg::BoundingSphered bound;
1614                    feature->getWorldBound(getMapSRS(), bound);
1615                    location.set(getMapSRS(), bound.center(),
1616                                 osgEarth::ALTMODE_ABSOLUTE);
1617                }
1618                addPlacard(location, feature, layerName);
1619            }
1620#endif
1621            setHighlightByObjectID(id);
1622            TRACE("FID %lu = OID %d", fid, id);
1623#endif
1624        }
1625        _needsRedraw = true;
1626    }
1627}
1628
1629void Renderer::addPlacard(const osgEarth::GeoPoint& location,
1630                          osgEarth::Features::Feature *feature,
1631                          const char *layerName)
1632{
1633    if (feature == NULL) return;
1634
1635    const osgEarth::Features::AttributeTable &attrs = feature->getAttrs();
1636    Placard placard = getPlacardConfig(layerName);
1637    if (placard.getNumEntries() == 0) {
1638        placard.addAllAttributes(attrs);
1639    }
1640    PlacardNode *label =
1641        new PlacardNode(_mapNode.get(), location, placard, attrs);
1642    label->getOrCreateStateSet()->setRenderBinDetails(INT_MAX, "RenderBin");
1643    getAnnotations()->addChild(label);
1644}
1645
1646void Renderer::clearSelection()
1647{
1648    clearHighlight();
1649    clearSelectionAnnotationNodes();
1650    clearBoxSelection();
1651    _needsRedraw = true;
1652}
1653
1654/**
1655 * \brief Remove annotation nodes created during a pick/selection
1656 *
1657 * The primary purpose of this method is to take down the feature placard
1658 */
1659void Renderer::clearSelectionAnnotationNodes()
1660{
1661    osg::Group *nodes = getAnnotations();
1662    std::vector<osg::Node *> toRemove;
1663    for (unsigned int i = 0; i < nodes->getNumChildren(); i++) {
1664        osg::Node *node = nodes->getChild(i);
1665        // This can be Placard, PlacardLabel, Label, Place or Track Node
1666        if (dynamic_cast<osgEarth::Annotation::OrthoNode *>(node) != NULL) {
1667            toRemove.push_back(node);
1668        }
1669    }
1670    for (std::vector<osg::Node *>::iterator itr = toRemove.begin();
1671         itr != toRemove.end(); ++itr) {
1672        nodes->removeChild(*itr);
1673    }
1674    _needsRedraw = true;
1675}
1676
1677void Renderer::initBoxSelection(int x, int y)
1678{
1679    double latitude, longitude;
1680    if (!mouseToLatLong(x, y, &latitude, &longitude)) {
1681        return;
1682    }
1683    _anchorLat = latitude;
1684    _anchorLong = longitude;
1685    addRhumbBox(latitude, latitude, longitude, longitude);
1686}
1687
1688void Renderer::updateBoxSelection(int x, int y)
1689{
1690    double nlat, nlong;
1691    if (!mouseToLatLong(x, y, &nlat, &nlong)) {
1692        return;
1693    }
1694    double latMin, latMax, longMin, longMax;
1695    if (nlong >= _anchorLong && nlat >= _anchorLat) {
1696        // +x +y
1697        longMin = _anchorLong;
1698        latMin = _anchorLat;
1699        longMax = nlong;
1700        latMax = nlat;
1701    } else if (nlong < _anchorLong && nlat >= _anchorLat) {
1702        // -x +y
1703        longMin = nlong;
1704        latMin = _anchorLat;
1705        longMax = _anchorLong;
1706        latMax = nlat;
1707    } else if (nlong < _anchorLong && nlat < _anchorLat) {
1708        // -x -y
1709        longMin = nlong;
1710        latMin = nlat;
1711        longMax = _anchorLong;
1712        latMax = _anchorLat;
1713    } else {
1714        // +x -y
1715        longMin = _anchorLong;
1716        latMin = nlat;
1717        longMax = nlong;
1718        latMax = _anchorLat;
1719    }
1720    osgEarth::Annotation::FeatureNode *node = _selectionBox.get();
1721    osgEarth::Symbology::Geometry *geom = node->getFeature()->getGeometry();
1722    (*geom)[0] = osg::Vec3d(longMax, latMin, 0);
1723    (*geom)[1] = osg::Vec3d(longMin, latMin, 0);
1724    (*geom)[2] = osg::Vec3d(longMin, latMax, 0);
1725    (*geom)[3] = osg::Vec3d(longMax, latMax, 0);
1726    node->init();
1727    for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = _selected.begin();
1728         itr != _selected.end(); ++itr) {
1729        (*itr)->clearDecoration();
1730    }
1731    _selected.clear();
1732    SelectPlaceNodesVisitor spnv(this, latMin, latMax, longMin, longMax);
1733    _sceneRoot->accept(spnv);
1734    _needsRedraw = true;
1735}
1736
1737void SelectPlaceNodesVisitor::apply(osg::Node& node)
1738{
1739    osgEarth::Annotation::PlaceNode *placeNode =
1740        dynamic_cast<osgEarth::Annotation::PlaceNode*>(&node);
1741    if (placeNode != NULL) {
1742        const osgEarth::SpatialReference *outSRS =
1743            _renderer->getMapSRS()->getGeographicSRS();
1744        osgEarth::GeoPoint pt = placeNode->getPosition();
1745        pt = pt.transform(outSRS);
1746        if (pt.x() >= _longMin && pt.x() <= _longMax &&
1747            pt.y() >= _latMin && pt.y() <= _latMax) {
1748            if (_renderer->select(placeNode)) {
1749                INFO("Select PlaceNode: %g %g n:'%s' t:'%s'",
1750                     pt.x(), pt.y(), placeNode->getName().c_str(),
1751                     placeNode->getText().c_str());
1752                placeNode->setDecoration("select");
1753            }
1754        }
1755    }
1756    traverse(node);
1757}
1758
1759void Renderer::clearBoxSelection()
1760{
1761    if (_annotations.valid() && _selectionBox.valid()) {
1762        _annotations->removeChild(_selectionBox.get());
1763        _selectionBox = NULL;
1764    }
1765    for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = _selected.begin();
1766         itr != _selected.end(); ++itr) {
1767        (*itr)->clearDecoration();
1768    }
1769    _selected.clear();
1770    _needsRedraw = true;
1771}
1772
1773void Renderer::addRhumbBox(double latMin, double latMax,
1774                           double longMin, double longMax)
1775{
1776    if (!_mapNode.valid()) {
1777        ERROR("No map node");
1778        return;
1779    }
1780    initAnnotations();
1781
1782    if (_selectionBox.valid()) {
1783        osgEarth::Symbology::Geometry *geom = _selectionBox->getFeature()->getGeometry();
1784        (*geom)[0] = osg::Vec3d(longMax, latMin, 0);
1785        (*geom)[1] = osg::Vec3d(longMin, latMin, 0);
1786        (*geom)[2] = osg::Vec3d(longMin, latMax, 0);
1787        (*geom)[3] = osg::Vec3d(longMax, latMax, 0);
1788        _selectionBox->init();
1789    } else {
1790        const osgEarth::SpatialReference* geoSRS = _mapNode->getMapSRS()->getGeographicSRS();
1791        osgEarth::Symbology::Geometry *geom = new osgEarth::Symbology::Polygon();
1792        geom->push_back(osg::Vec3d(longMax, latMin, 0));
1793        geom->push_back(osg::Vec3d(longMin, latMin, 0));
1794        geom->push_back(osg::Vec3d(longMin, latMax, 0));
1795        geom->push_back(osg::Vec3d(longMax, latMax, 0));
1796        osgEarth::Symbology::Style boxStyle;
1797#if 1
1798        osgEarth::Symbology::PolygonSymbol *poly = boxStyle.getOrCreate<osgEarth::Symbology::PolygonSymbol>();
1799        poly->fill()->color() = osgEarth::Symbology::Color::Cyan;
1800        poly->fill()->color().a() = 0.5;
1801#else
1802        osgEarth::Symbology::LineSymbol *line = boxStyle.getOrCreate<osgEarth::Symbology::LineSymbol>();
1803        line->stroke()->color() = osgEarth::Symbology::Color::Yellow;
1804        line->stroke()->width() = 2.0f;
1805        //line->creaseAngle() = 45.0f;
1806        line->tessellation() = 10;
1807#endif
1808        osgEarth::Symbology::AltitudeSymbol *alt = boxStyle.getOrCreate<osgEarth::Symbology::AltitudeSymbol>();
1809        alt->clamping() = osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
1810        //alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
1811        alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_DRAPE;
1812        //alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_SCENE;
1813#if 0
1814        osgEarth::Symbology::RenderSymbol* rs = boxStyle.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
1815        rs->depthOffset()->enabled() = true;
1816        rs->depthOffset()->minBias() = 1000;
1817#endif
1818        osgEarth::Features::Feature *feature = new osgEarth::Features::Feature(geom, geoSRS, boxStyle);
1819        //feature->geoInterp() = osgEarth::GEOINTERP_GREAT_CIRCLE;
1820        feature->geoInterp() = osgEarth::GEOINTERP_RHUMB_LINE;
1821        _selectionBox =
1822            new osgEarth::Annotation::FeatureNode(_mapNode, feature);
1823        _annotations->addChild(_selectionBox.get());
1824    }
1825
1826    _needsRedraw = true;
1827}
1828
1829void Renderer::addPlaceNode(double latitude, double longitude, char *labelText)
1830{
1831    if (!_mapNode.valid()) {
1832        ERROR("No map node");
1833        return;
1834    }
1835    initAnnotations();
1836
1837    const osgEarth::SpatialReference *geoSRS = _mapNode->getMapSRS()->getGeographicSRS();
1838
1839    osgEarth::Symbology::Style pin;
1840    pin.getOrCreate<osgEarth::Symbology::IconSymbol>()->url()->setLiteral(getPinIcon());
1841    osgEarth::Annotation::AnnotationNode *anno =
1842        new osgEarth::Annotation::PlaceNode(_mapNode, osgEarth::GeoPoint(geoSRS, longitude, latitude), labelText, pin);
1843    anno->setName(labelText);
1844    _placeNodes->addChild(anno);
1845#ifdef USE_RTT_PICKER
1846    osgEarth::Registry::objectIndex()->tagNode(anno, anno);
1847#else
1848    osgEarth::Annotation::DecorationInstaller
1849        highlightInstaller("select", new osgEarth::Annotation::HighlightDecoration(osg::Vec4f(1,1,0,0.5)));
1850    _placeNodes->accept(highlightInstaller);
1851
1852    // scale labels when hovering:
1853    osgEarth::Annotation::DecorationInstaller
1854        scaleInstaller("hover", new osgEarth::Annotation::ScaleDecoration(1.1f));
1855    _placeNodes->accept(scaleInstaller);
1856#endif
1857#if 0
1858    writeScene("/tmp/test.osg");
1859#endif
1860    _needsRedraw = true;
1861}
1862#if 0
1863void Renderer::hoverFeatureNodes(int x, int y, bool invertY)
1864{
1865    if (!_mapNode.valid()) {
1866        ERROR("No map node");
1867        return;
1868    }
1869    osgEarth::IntersectionPicker picker(_viewer.get(), _mapNode.get());
1870    osgEarth::IntersectionPicker::Hits hits;
1871    float mouseX = (float)x;
1872    float mouseY = (float)y;
1873    if (invertY) {
1874        mouseY = ((float)_windowHeight - mouseY);
1875    }
1876    std::set<osgEarth::Annotation::FeatureNode*> toUnHover;
1877    for (std::set<osgEarth::Annotation::FeatureNode*>::iterator itr = _hovered.begin();
1878         itr != _hovered.end(); ++itr) {
1879        toUnHover.insert(*itr);
1880    }
1881    if (picker.pick(mouseX, mouseY, hits)) {
1882        TRACE("Picker hits: %d", hits.size());
1883        for (osgEarth::IntersectionPicker::Hits::const_iterator hitr = hits.begin();
1884             hitr != hits.end(); ++hitr) {
1885            osgEarth::Annotation::FeatureNode *anno =
1886                picker.getNode<osgEarth::Annotation::FeatureNode>(*hitr);
1887            if (anno != NULL) {
1888                TRACE("Hit FeatureNode: %p", anno);
1889                if (_hovered.find(anno) == _hovered.end()) {
1890                    _hovered.insert(anno);
1891                    anno->setDecoration("hover");
1892                    _needsRedraw = true;
1893                }
1894                toUnHover.erase(anno);
1895            }
1896        }
1897#if 0
1898        writeScene("/tmp/test.osgt");
1899#endif
1900    }
1901    for (std::set<osgEarth::Annotation::FeatureNode *>::iterator itr = toUnHover.begin();
1902         itr != toUnHover.end(); ++itr) {
1903        _hovered.erase(*itr);
1904        (*itr)->clearDecoration();
1905        _needsRedraw = true;
1906    }
1907}
1908#endif
1909
1910#ifdef USE_RTT_PICKER
1911void Renderer::hoverPlaceNode(int x, int y, bool invertY)
1912{
1913    if (!_placeNodes.valid()) {
1914        TRACE("No place nodes");
1915        return;
1916    }
1917    return;
1918
1919    float mouseX = (float)x;
1920    float mouseY = (float)y;
1921    if (invertY) {
1922        mouseY = ((float)_windowHeight - mouseY);
1923    }
1924    if (_picker->pick(_viewer.get(), mouseX, mouseY)) {
1925        INFO("Hover pick queued: %g %g", mouseX, mouseY);
1926    } else {
1927        INFO("Failed to queue pick: %g %g", mouseX, mouseY);
1928    }
1929}
1930
1931void Renderer::deletePlaceNode(int x, int y, bool invertY)
1932{
1933    if (!_placeNodes.valid()) {
1934        TRACE("No place nodes");
1935        return;
1936    }
1937    return;
1938
1939    osg::ref_ptr<osgEarth::Util::RTTPicker> picker = new osgEarth::Util::RTTPicker;
1940    picker->addChild(_placeNodes.get());
1941    osg::ref_ptr<DeleteCallback> callback = new DeleteCallback(this);
1942    float mouseX = (float)x;
1943    float mouseY = (float)y;
1944    if (invertY) {
1945        mouseY = ((float)_windowHeight - mouseY);
1946    }
1947    if (picker->pick(_viewer.get(), mouseX, mouseY, callback)) {
1948        TRACE("Delete pick queued: %g %g", mouseX, mouseY);
1949    }
1950}
1951#else
1952void Renderer::hoverPlaceNode(int x, int y, bool invertY)
1953{
1954    if (!_placeNodes.valid()) {
1955        TRACE("No place nodes");
1956        return;
1957    }
1958
1959    osgEarth::IntersectionPicker picker(_viewer.get(), _placeNodes.get());
1960    osgEarth::IntersectionPicker::Hits hits;
1961    float mouseX = (float)x;
1962    float mouseY = (float)y;
1963    if (invertY) {
1964        mouseY = ((float)_windowHeight - mouseY);
1965    }
1966    std::set<osgEarth::Annotation::AnnotationNode*> toUnHover;
1967    for (std::set<osgEarth::Annotation::AnnotationNode*>::iterator itr = _hovered.begin();
1968         itr != _hovered.end(); ++itr) {
1969        toUnHover.insert(*itr);
1970    }
1971    if (picker.pick(mouseX, mouseY, hits)) {
1972        TRACE("Picker hits: %d", hits.size());
1973        for (osgEarth::IntersectionPicker::Hits::const_iterator hitr = hits.begin();
1974             hitr != hits.end(); ++hitr) {
1975            TRACE("Hit: node %p drawable %p idx %d", picker.getNode<osg::Node>(*hitr), hitr->drawable.get(), hitr->primitiveIndex);
1976            osgEarth::Annotation::AnnotationNode *anno =
1977                picker.getNode<osgEarth::Annotation::AnnotationNode>(*hitr);
1978            if (anno != NULL && anno->getDecoration().empty()) {
1979                TRACE("Hit AnnotationNode: %p", anno);
1980                if (_hovered.find(anno) == _hovered.end()) {
1981                    _hovered.insert(anno);
1982                    anno->setDecoration("hover");
1983                    _needsRedraw = true;
1984                }
1985                toUnHover.erase(anno);
1986            }
1987        }
1988#if 0
1989        writeScene("/tmp/test.osgt");
1990#endif
1991    }
1992    for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = toUnHover.begin();
1993         itr != toUnHover.end(); ++itr) {
1994        _hovered.erase(*itr);
1995        (*itr)->clearDecoration();
1996        _needsRedraw = true;
1997    }
1998}
1999
2000void Renderer::deletePlaceNode(int x, int y, bool invertY)
2001{
2002    if (!_placeNodes.valid()) {
2003        TRACE("No place nodes");
2004        return;
2005    }
2006    osgEarth::IntersectionPicker picker(_viewer.get(), _placeNodes.get());
2007    osgEarth::IntersectionPicker::Hits hits;
2008    float mouseX = (float)x;
2009    float mouseY = (float)y;
2010    if (invertY) {
2011        mouseY = ((float)_windowHeight - mouseY);
2012    }
2013    if (picker.pick(mouseX, mouseY, hits)) {
2014        TRACE("Picker hit!");
2015        // prevent multiple hits on the same instance
2016        std::set<osgEarth::Annotation::AnnotationNode *> fired;
2017        for (osgEarth::IntersectionPicker::Hits::const_iterator hitr = hits.begin();
2018             hitr != hits.end(); ++hitr) {
2019            osgEarth::Annotation::AnnotationNode *anno =
2020                picker.getNode<osgEarth::Annotation::AnnotationNode>(*hitr);
2021            if (anno != NULL && fired.find(anno) == fired.end()) {
2022                fired.insert(anno);
2023                _needsRedraw = true;
2024            }
2025        }
2026        for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = fired.begin();
2027             itr != fired.end(); ++itr) {
2028            (*itr)->clearDecoration();
2029            _placeNodes->removeChild(*itr);
2030            if (_hovered.find(*itr) != _hovered.end()) {
2031                _hovered.erase(*itr);
2032            }
2033        }
2034    } else {
2035        TRACE("NO Picker hits");
2036    }
2037#if 0
2038    writeScene("/tmp/test.osg");
2039#endif
2040}
2041#endif
2042
2043void Renderer::addModelLayer(const char *name,
2044                             osgEarth::ModelSourceOptions& opts,
2045                             bool enableCache,
2046                             bool lighting,
2047                             bool visible)
2048{
2049    if (!_map.valid()) {
2050        ERROR("No map");
2051        return;
2052    }
2053    TRACE("layer: %s", name);
2054    osgEarth::ModelLayerOptions layerOpts(name, opts);
2055    if (!enableCache) {
2056        TRACE("Disabling cache for layer %s", name);
2057        layerOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
2058    }
2059    if (!visible) {
2060        layerOpts.visible() = false;
2061    }
2062    layerOpts.lightingEnabled() = lighting;
2063    _map->addModelLayer(new osgEarth::ModelLayer(layerOpts));
2064    _needsRedraw = true;
2065}
2066
2067void Renderer::removeModelLayer(const char *name)
2068{
2069    if (!_map.valid()) {
2070        ERROR("No map");
2071        return;
2072    }
2073    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
2074    if (layer != NULL) {
2075        _map->removeModelLayer(layer);
2076        _needsRedraw = true;
2077    } else {
2078        TRACE("Model layer not found: %s", name);
2079    }
2080}
2081
2082void Renderer::moveModelLayer(const char *name, unsigned int pos)
2083{
2084    if (!_map.valid()) {
2085        ERROR("No map");
2086        return;
2087    }
2088    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
2089    if (layer != NULL) {
2090        _map->moveModelLayer(layer, pos);
2091        _needsRedraw = true;
2092    } else {
2093        TRACE("Model layer not found: %s", name);
2094    }
2095}
2096
2097void Renderer::setModelLayerOpacity(const char *name, double opacity)
2098{
2099    if (!_map.valid()) {
2100        ERROR("No map");
2101        return;
2102    }
2103    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
2104    if (layer != NULL) {
2105        layer->setOpacity(opacity);
2106        _needsRedraw = true;
2107    } else {
2108        TRACE("Model layer not found: %s", name);
2109    }
2110}
2111#if 0
2112void Renderer::setModelLayerVisibleRange(const char *name, float min, float max)
2113{
2114    if (!_map.valid()) {
2115        ERROR("No map");
2116        return;
2117    }
2118    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
2119    if (layer != NULL) {
2120        layer->minVisibleRange(min);
2121        layer->maxVisibleRange(max);
2122        _needsRedraw = true;
2123    } else {
2124        TRACE("Model layer not found: %s", name);
2125    }
2126}
2127#endif
2128void Renderer::setModelLayerVisibility(const char *name, bool state)
2129{
2130    if (!_map.valid()) {
2131        ERROR("No map");
2132        return;
2133    }
2134    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
2135    if (layer != NULL) {
2136        layer->setVisible(state);
2137        _needsRedraw = true;
2138    } else {
2139        TRACE("Model layer not found: %s", name);
2140    }
2141}
2142
2143/**
2144 * \brief Resize the render window (image size for renderings)
2145 */
2146void Renderer::setWindowSize(int width, int height)
2147{
2148    if (_windowWidth == width &&
2149        _windowHeight == height) {
2150        TRACE("No change");
2151        return;
2152    }
2153
2154    TRACE("Setting window size to %dx%d", width, height);
2155
2156    double origBitrate = getMaximumBitrate();
2157
2158    _windowWidth = width;
2159    _windowHeight = height;
2160
2161    setMaximumBitrate(origBitrate);
2162    TRACE("Bandwidth target: %.2f Mbps", (float)(getMaximumBitrate()/1.0e6));
2163    TRACE("Frame rate target: %.2f Hz", (float)getMaximumFrameRateInHertz());
2164
2165    if (_viewer.valid()) {
2166#ifdef USE_OFFSCREEN_RENDERING
2167#ifdef USE_PBUFFER
2168        osg::ref_ptr<osg::GraphicsContext> pbuffer;
2169        osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
2170        traits->x = 0;
2171        traits->y = 0;
2172        traits->width = _windowWidth;
2173        traits->height = _windowHeight;
2174        traits->red = 8;
2175        traits->green = 8;
2176        traits->blue = 8;
2177        traits->alpha = 8;
2178        traits->windowDecoration = false;
2179        traits->pbuffer = true;
2180        traits->doubleBuffer = true;
2181        traits->sharedContext = 0;
2182
2183        pbuffer = osg::GraphicsContext::createGraphicsContext(traits.get());
2184        if (pbuffer.valid()) {
2185            TRACE("Pixel buffer has been created successfully.");
2186        } else {
2187            ERROR("Pixel buffer has not been created successfully.");
2188        }
2189        osg::Camera *camera = new osg::Camera;
2190        camera->setGraphicsContext(pbuffer.get());
2191        //camera->getOrCreateStateSet()->setGlobalDefaults();
2192        camera->setViewport(new osg::Viewport(0, 0, _windowWidth, _windowHeight));
2193        GLenum buffer = pbuffer->getTraits()->doubleBuffer ? GL_BACK : GL_FRONT;
2194        camera->setDrawBuffer(buffer);
2195        camera->setReadBuffer(buffer);
2196        camera->setFinalDrawCallback(_captureCallback.get());
2197        _viewer->addSlave(camera, osg::Matrixd(), osg::Matrixd());
2198        _viewer->realize();
2199#else
2200        if (_captureCallback.valid()) {
2201            _captureCallback->getTexture()->setTextureSize(_windowWidth, _windowHeight);
2202        }
2203        osgViewer::ViewerBase::Windows windows;
2204        _viewer->getWindows(windows);
2205        if (windows.size() == 1) {
2206            windows[0]->setWindowRectangle(0, 0, _windowWidth, _windowHeight);
2207        } else {
2208            ERROR("Num windows: %lu", windows.size());
2209        }
2210#endif
2211#else
2212        osgViewer::ViewerBase::Windows windows;
2213        _viewer->getWindows(windows);
2214#if 1
2215        for (osgViewer::Viewer::Windows::iterator itr = windows.begin();
2216             itr != windows.end(); ++itr) {
2217            osgViewer::GraphicsWindow *window = *itr;
2218            window->setWindowRectangle(0, 0, _windowWidth, _windowHeight);
2219            //window->grabFocusIfPointerInWindow();
2220        }
2221        //_viewer->getCamera()->setViewport(new osg::Viewport(0, 0, _windowWidth, _windowHeight));
2222#else
2223        if (windows.size() == 1) {
2224            windows[0]->setWindowRectangle(0, 0, _windowWidth, _windowHeight);
2225        } else {
2226            ERROR("Num windows: %lu", windows.size());
2227        }
2228#endif
2229#endif
2230        // HACK: Without this, the mouse coordinate mapping uses the old size
2231        // for 1 frame.
2232        assert(_viewer->getEventQueue() != NULL);
2233        //TRACE("Window EventQueue: %p", getEventQueue());
2234        //TRACE("Viewer EventQueue: %p", _viewer->getEventQueue());
2235        _viewer->getEventQueue()->windowResize(0, 0, _windowWidth, _windowHeight);
2236        _needsRedraw = true;
2237    }
2238}
2239
2240/**
2241 * \brief Set the orientation of the camera from a quaternion
2242 * rotation
2243 *
2244 * \param[in] quat A quaternion with scalar part first: w,x,y,z
2245 * \param[in] absolute Is rotation absolute or relative?
2246 */
2247void Renderer::setCameraOrientation(const double quat[4], bool absolute)
2248{
2249    if (_manipulator.valid()) {
2250        _manipulator->setRotation(osg::Quat(quat[1], quat[2], quat[3], quat[0]));
2251        _needsRedraw = true;
2252    }
2253}
2254
2255/**
2256 * \brief Reset pan, zoom, clipping planes and optionally rotation
2257 *
2258 * \param[in] resetOrientation Reset the camera rotation/orientation also
2259 */
2260void Renderer::resetCamera(bool resetOrientation)
2261{
2262    TRACE("Enter: resetOrientation=%d", resetOrientation ? 1 : 0);
2263    if (_viewer.valid()) {
2264        _viewer->home();
2265        _needsRedraw = true;
2266    }
2267}
2268
2269/**
2270 * \brief Perform a 2D translation of the camera
2271 *
2272 * x,y pan amount are specified as signed absolute pan amount in viewport
2273 * units -- i.e. 0 is no pan, .5 is half the viewport, 2 is twice the viewport,
2274 * etc.
2275 *
2276 * \param[in] x Viewport coordinate horizontal panning (positive number pans
2277 * camera left, object right)
2278 * \param[in] y Viewport coordinate vertical panning (positive number pans
2279 * camera up, object down)
2280 */
2281void Renderer::panCamera(double x, double y)
2282{
2283    TRACE("Enter: %g %g", x, y);
2284
2285    if (_manipulator.valid()) {
2286        // Wants mouse delta x,y in normalized screen coords
2287        _manipulator->pan(x, y);
2288        _needsRedraw = true;
2289    }
2290}
2291
2292void Renderer::rotateCamera(double x, double y)
2293{
2294    TRACE("Enter: %g %g", x, y);
2295
2296    if (_manipulator.valid()) {
2297        _manipulator->rotate(x, y);
2298        _needsRedraw = true;
2299    }
2300}
2301
2302/**
2303 * \brief Dolly camera or set orthographic scaling based on camera type
2304 *
2305 * \param[in] y Mouse y coordinate in normalized screen coords
2306 */
2307void Renderer::zoomCamera(double y)
2308{
2309    TRACE("Enter: y: %g", y);
2310
2311    if (_manipulator.valid()) {
2312        // +y = zoom out, -y = zoom in
2313        TRACE("camDist: %g", _manipulator->getDistance());
2314#ifdef LIMIT_ZOOM_BY_MAP_SCALE
2315        if ((_mapScale < 0.0 && y > 0.0) ||
2316            (_mapScale < 0.1 && y < 0.0) ||
2317            (_mapScale > 40000.0 && y > 0.0))
2318            return;
2319#endif
2320#if 1
2321       _manipulator->zoom(0, y);
2322#else
2323        double dist = _manipulator->getDistance();
2324        dist *= (1.0 + y);
2325        _manipulator->setDistance(dist);
2326#endif
2327        _needsRedraw = true;
2328    }
2329}
2330
2331bool Renderer::getImageLayerExtent(const char *name, osgEarth::GeoExtent &ext)
2332{
2333    if (!_map.valid()) {
2334        ERROR("No map");
2335        return false;
2336    }
2337    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
2338    if (layer != NULL) {
2339        osgEarth::TileSource *ts = layer->getTileSource();
2340        if (ts != NULL) {
2341            ext = ts->getDataExtentsUnion();
2342            if (!ext.isValid() && ts->getProfile() != NULL) {
2343                ext = ts->getProfile()->getExtent();
2344            }
2345            INFO("Image layer %s srs: %s extent: %g %g %g %g",
2346                 name, ext.getSRS() ? ext.getSRS()->getName().c_str() : "nil",
2347                 ext.xMin(), ext.yMin(), ext.xMax(), ext.yMax());
2348            return true;
2349        }
2350    } else {
2351        TRACE("Image layer not found: %s", name);
2352    }
2353    return false;
2354}
2355
2356bool Renderer::getElevationLayerExtent(const char *name, osgEarth::GeoExtent &ext)
2357{
2358    if (!_map.valid()) {
2359        ERROR("No map");
2360        return false;
2361    }
2362    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
2363    if (layer != NULL) {
2364        osgEarth::TileSource *ts = layer->getTileSource();
2365        if (ts != NULL) {
2366            ext = ts->getDataExtentsUnion();
2367            if (!ext.isValid() && ts->getProfile() != NULL) {
2368                ext = ts->getProfile()->getExtent();
2369            }
2370            INFO("Elevation Layer %s srs: %s extent: %g %g %g %g",
2371                 name, ext.getSRS() ? ext.getSRS()->getName().c_str() : "nil",
2372                 ext.xMin(), ext.yMin(), ext.xMax(), ext.yMax());
2373            return true;
2374        }
2375    } else {
2376        TRACE("Elevation layer not found: %s", name);
2377    }
2378    return false;
2379}
2380
2381bool Renderer::getModelLayerExtent(const char *name, osgEarth::GeoExtent &ext)
2382{
2383    if (!_map.valid()) {
2384        ERROR("No map");
2385        return false;
2386    }
2387    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
2388    if (layer != NULL) {
2389        osgEarth::ModelSource *ms = layer->getModelSource();
2390        if (ms != NULL) {
2391            osgEarth::DataExtentList& dataExtents = ms->getDataExtents();
2392            if (dataExtents.size() > 0) {
2393                ext = dataExtents[0];
2394                for (unsigned int i = 1; i < dataExtents.size(); i++) {
2395                    ext.expandToInclude(dataExtents[i]);
2396                }
2397            }
2398            if (!ext.isValid()) {
2399                ERROR("Couldn't find extent of layer %s", name);
2400                return false;
2401            }
2402            INFO("Model Layer %s srs: %s extent: %g %g %g %g",
2403                 name, ext.getSRS() ? ext.getSRS()->getName().c_str() : "nil",
2404                 ext.xMin(), ext.yMin(), ext.xMax(), ext.yMax());
2405            return true;
2406        }
2407    } else {
2408        TRACE("Model layer not found: %s", name);
2409    }
2410    return false;
2411}
2412
2413/**
2414 * \brief Set view to fit a bounding rectangle
2415 *
2416 * A camera distance is chosen such that the extent rectangle
2417 * will fit in the vertical field of view.
2418 *
2419 * \param ext input GeoExtent
2420 * \param durationSecs Animation duration for the view change.
2421 */
2422void Renderer::setViewpointFromExtent(const osgEarth::GeoExtent& ext,
2423                                      double durationSecs)
2424{
2425    setViewpointFromRect(ext.xMin(), ext.yMin(), ext.xMax(), ext.yMax(),
2426                         ext.getSRS(), durationSecs);
2427}
2428
2429/**
2430 * \brief Set view to fit a bounding rectangle
2431 *
2432 * A camera distance is chosen such that the extent rectangle
2433 * will fit in the vertical field of view.
2434 *
2435 * \param xmin Minimum X coord in the given SRS
2436 * \param ymin Minimum Y coord in the given SRS
2437 * \param xmax Maximum X coord in the given SRS
2438 * \param ymax Maximum Y coord in the given SRS
2439 * \param srs Optional SRS of bounding points.  If omitted, wgs84 is assumed.
2440 * \param durationSecs Animation duration for the view change.
2441 */
2442void Renderer::setViewpointFromRect(double xmin, double ymin,
2443                                    double xmax, double ymax,
2444                                    const osgEarth::SpatialReference *srs,
2445                                    double durationSecs)
2446{
2447    if (!_viewer.valid() || !_manipulator.valid() || getMapSRS() == NULL)
2448        return;
2449
2450    double x = 0.0, y = 0.0, distance = 0.0;
2451    double x1 = xmin, y1 = ymin, x2 = xmax, y2 = ymax;
2452    osgEarth::Units distanceUnits = getMapSRS()->getUnits();
2453    if (getMapSRS()->isProjected() && !getMapSRS()->isPlateCarre()) {
2454        INFO("Projected");
2455        osg::ref_ptr<const osgEarth::SpatialReference> fromSRS =
2456            srs ? srs : osgEarth::SpatialReference::create("wgs84");
2457        fromSRS->transformExtentToMBR(getMapSRS(), x1, y1, x2, y2);
2458        double height = y2 - y1;
2459        double fovy, aspect, near, far;
2460        _viewer->getCamera()->getProjectionMatrixAsPerspective(fovy, aspect, near, far);
2461        distance = height / (2. * tan(osg::DegreesToRadians(fovy)/2.));
2462        INFO("fov: %g a: %g n: %g f: %g (%g, %g, %g, %g)", fovy, aspect, near, far, x1, y1, x2, y2);
2463        x = x1 + (x2 - x1)/2.0;
2464        y = y1 + (y2 - y1)/2.0;
2465        INFO("x: %g y: %g, dist: %g", x, y, distance);
2466    } else if (getMapSRS()->isGeographic() && !getMapSRS()->isPlateCarre()) {
2467        // World coords are ECEF
2468        INFO("Geocentric");
2469        osg::ref_ptr<const osgEarth::SpatialReference> fromSRS =
2470            srs ? srs : osgEarth::SpatialReference::create("wgs84");
2471        fromSRS->transformExtentToMBR(getMapSRS(), x1, y1, x2, y2);
2472        INFO("(%g, %g, %g, %g)", x1, y1, x2, y2);
2473        // These are angular units
2474        x = x1 + (x2 - x1)/2.0;
2475        y = y1 + (y2 - y1)/2.0;
2476        // These will be ECEF
2477        osg::Vec3d world1, world2, world3, world4;
2478        // bottom
2479        getMapSRS()->transformToWorld(osg::Vec3d(x, y1, 0), world1);
2480        // top
2481        getMapSRS()->transformToWorld(osg::Vec3d(x, y2, 0), world2);
2482        // Focal point on surface
2483        getMapSRS()->transformToWorld(osg::Vec3d(x, y, 0), world3);
2484        // point on line between top and bottom points
2485        world4 = world1 + (world2 - world1)/2.0;
2486        INFO("world1: %g,%g,%g world2: %g,%g,%g",
2487             world1.x(), world1.y(), world1.z(),
2488             world2.x(), world2.y(), world2.z());
2489        INFO("world3: %g,%g,%g world4: %g,%g,%g",
2490             world3.x(), world3.y(), world3.z(),
2491             world4.x(), world4.y(), world4.z());
2492        double height = (world2 - world1).length();
2493        double fovy, aspect, near, far;
2494        _viewer->getCamera()->getProjectionMatrixAsPerspective(fovy, aspect, near, far);
2495        distance = height / (2. * tan(osg::DegreesToRadians(fovy)/2.));
2496        distance -= (world4 - world3).length();
2497        if (distance < 0.0) distance = 0.0;
2498        distanceUnits = osgEarth::Units::METERS;
2499        INFO("fov: %g a: %g n: %g f: %g (%g, %g, %g, %g)", fovy, aspect, near, far, x1, y1, x2, y2);
2500        INFO("x: %g y: %g, dist: %g", x, y, distance);
2501    } else {
2502        assert(getMapSRS()->isPlateCarre());
2503        ERROR("Plate Carree not supported");
2504        return;
2505    }
2506    INFO("Map units: %d", getMapSRS()->getUnits().getType());
2507    osgEarth::Viewpoint vpt;
2508    vpt.focalPoint()->set(getMapSRS(), x, y, 0.0, osgEarth::ALTMODE_ABSOLUTE);
2509    vpt.range()->set(distance, distanceUnits);
2510    vpt.heading()->set(0.0, osgEarth::Units::DEGREES);
2511    vpt.pitch()->set(-90.0, osgEarth::Units::DEGREES);
2512
2513    _manipulator->setViewpoint(vpt, durationSecs);
2514    _needsRedraw = true;
2515}
2516
2517double Renderer::getMaxDistanceFromExtent(const osgEarth::GeoExtent& extent)
2518{
2519    if (!_viewer.valid() || extent.getSRS() == NULL)
2520        return 2.0e7;
2521
2522    double x = 0.0, y = 0.0, distance = 0.0;
2523    double x1 = extent.xMin(), y1 = extent.yMin(), x2 = extent.xMax(), y2 = extent.yMax();
2524    osgEarth::Units distanceUnits = extent.getSRS()->getUnits();
2525    if (extent.getSRS()->isProjected() && !extent.getSRS()->isPlateCarre()) {
2526        INFO("Projected");
2527        double height = y2 - y1;
2528        double fovy, aspect, near, far;
2529        _viewer->getCamera()->getProjectionMatrixAsPerspective(fovy, aspect, near, far);
2530        distance = height / (2. * tan(osg::DegreesToRadians(fovy)/2.));
2531        INFO("fov: %g a: %g n: %g f: %g (%g, %g, %g, %g)", fovy, aspect, near, far, x1, y1, x2, y2);
2532        x = x1 + (x2 - x1)/2.0;
2533        y = y1 + (y2 - y1)/2.0;
2534        INFO("x: %g y: %g, dist: %g", x, y, distance);
2535    } else if (extent.getSRS()->isGeographic() && !extent.getSRS()->isPlateCarre()) {
2536        // World coords are ECEF
2537        INFO("Geocentric");
2538        INFO("(%g, %g, %g, %g)", x1, y1, x2, y2);
2539        // These are angular units
2540        x = x1 + (x2 - x1)/2.0;
2541        y = y1 + (y2 - y1)/2.0;
2542        // These will be ECEF
2543        osg::Vec3d world1, world2, world3, world4;
2544        // bottom
2545        getMapSRS()->transformToWorld(osg::Vec3d(x, y1, 0), world1);
2546        // top
2547        getMapSRS()->transformToWorld(osg::Vec3d(x, y2, 0), world2);
2548        // Focal point on surface
2549        getMapSRS()->transformToWorld(osg::Vec3d(x, y, 0), world3);
2550        // point on line between top and bottom points
2551        world4 = world1 + (world2 - world1)/2.0;
2552        double height = (world2 - world1).length();
2553        double fovy, aspect, near, far;
2554        _viewer->getCamera()->getProjectionMatrixAsPerspective(fovy, aspect, near, far);
2555        distance = height / (2. * tan(osg::DegreesToRadians(fovy)/2.));
2556        distance -= (world4 - world3).length();
2557        if (distance < 0.0) distance = 0.0;
2558        distanceUnits = osgEarth::Units::METERS;
2559        INFO("fov: %g a: %g n: %g f: %g (%g, %g, %g, %g)", fovy, aspect, near, far, x1, y1, x2, y2);
2560        INFO("x: %g y: %g, dist: %g", x, y, distance);
2561    } else {
2562        assert(extent.getSRS()->isPlateCarre());
2563        ERROR("Plate Carree not supported");
2564    }
2565
2566    return distance;
2567}
2568
2569/**
2570 * \brief Dolly camera to set distance from focal point
2571 *
2572 * \param[in] dist distance in world coordinate units (typically meters)
2573 */
2574void Renderer::setCameraDistance(double dist)
2575{
2576    TRACE("Enter: dist: %g", dist);
2577
2578    if (_manipulator.valid()) {
2579        TRACE("camDist: %g", _manipulator->getDistance());
2580
2581        _manipulator->setDistance(dist);
2582
2583        _needsRedraw = true;
2584    }
2585}
2586
2587void Renderer::keyPress(int key)
2588{
2589    osgGA::EventQueue *queue = getEventQueue();
2590    if (queue != NULL) {
2591        queue->keyPress(key);
2592        _needsRedraw = true;
2593    }
2594}
2595
2596void Renderer::keyRelease(int key)
2597{
2598    osgGA::EventQueue *queue = getEventQueue();
2599    if (queue != NULL) {
2600        queue->keyRelease(key);
2601        _needsRedraw = true;
2602    }
2603}
2604
2605void Renderer::setThrowingEnabled(bool state)
2606{
2607    if (_manipulator.valid()) {
2608        _manipulator->getSettings()->setThrowingEnabled(state);
2609    }
2610}
2611
2612void Renderer::mouseDoubleClick(int button, double x, double y)
2613{
2614    osgGA::EventQueue *queue = getEventQueue();
2615    if (queue != NULL) {
2616        queue->mouseDoubleButtonPress((float)x, (float)y, button);
2617        _needsRedraw = true;
2618    }
2619}
2620
2621void Renderer::mouseClick(int button, double x, double y)
2622{
2623    osgGA::EventQueue *queue = getEventQueue();
2624    if (queue != NULL) {
2625        queue->mouseButtonPress((float)x, (float)y, button);
2626        _needsRedraw = true;
2627    }
2628}
2629
2630void Renderer::mouseDrag(int button, double x, double y)
2631{
2632    osgGA::EventQueue *queue = getEventQueue();
2633    if (queue != NULL) {
2634        queue->mouseMotion((float)x, (float)y);
2635        _needsRedraw = true;
2636    }
2637}
2638
2639void Renderer::mouseRelease(int button, double x, double y)
2640{
2641    osgGA::EventQueue *queue = getEventQueue();
2642    if (queue != NULL) {
2643        queue->mouseButtonRelease((float)x, (float)y, button);
2644        _needsRedraw = true;
2645    }
2646}
2647
2648void Renderer::mouseMotion(double x, double y)
2649{
2650#if 1
2651    osgGA::EventQueue *queue = getEventQueue();
2652    if (queue != NULL) {
2653        queue->mouseMotion((float)x, (float)y);
2654        _needsRedraw = true;
2655    }
2656#else
2657    if (_mouseCoordsTool.valid()) {
2658        if (_viewer.valid() && _coordsCallback.valid()) {
2659            osgEarth::GeoPoint mapPt;
2660            if (mapMouseCoords((float)x, (float)y, mapPt)) {
2661                _coordsCallback->set(mapPt, _viewer->asView(), _mapNode);
2662            } else {
2663                _coordsCallback->reset(_viewer->asView(), _mapNode);
2664            }
2665            _needsRedraw = true;
2666        }
2667    }
2668#endif
2669}
2670
2671void Renderer::mouseScroll(int direction)
2672{
2673    osgGA::EventQueue *queue = getEventQueue();
2674    if (queue != NULL) {
2675        queue->mouseScroll((direction > 0 ? osgGA::GUIEventAdapter::SCROLL_UP : osgGA::GUIEventAdapter::SCROLL_DOWN));
2676        _needsRedraw = true;
2677    }
2678}
2679
2680/**
2681 * \brief Set the RGB background color to render into the image
2682 */
2683void Renderer::setBackgroundColor(float color[3])
2684{
2685    _bgColor[0] = color[0];
2686    _bgColor[1] = color[1];
2687    _bgColor[2] = color[2];
2688
2689    if (_viewer.valid()) {
2690        _viewer->getCamera()->setClearColor(osg::Vec4(color[0], color[1], color[2], 1));
2691
2692        _needsRedraw = true;
2693    }
2694}
2695
2696/**
2697 * \brief Sets flag to trigger rendering next time render() is called
2698 */
2699void Renderer::eventuallyRender()
2700{
2701    _needsRedraw = true;
2702}
2703
2704/**
2705 * \brief Get a timeout in usecs for select()
2706 *
2707 * If the paging thread is idle, returns <0 indicating that the
2708 * select call can block until data is available.  Otherwise,
2709 * if the frame render time was faster than the target frame
2710 * rate, return the remaining frame time.
2711 */
2712long Renderer::getTimeout()
2713{
2714    if (!checkNeedToDoFrame())
2715        // <0 means no timeout, block until socket has data
2716        return -1L;
2717    if (_lastFrameTime < _minFrameTime) {
2718        return (long)1.0e6*(_minFrameTime - _lastFrameTime);
2719    } else {
2720        // No timeout (poll)
2721        return 0L;
2722    }
2723}
2724
2725/**
2726 * \brief Check if paging thread is quiescent
2727 */
2728bool Renderer::isPagerIdle()
2729{
2730    if (!_viewer.valid())
2731        return true;
2732    else
2733        return (!_viewer->getDatabasePager()->requiresUpdateSceneGraph() &&
2734                !_viewer->getDatabasePager()->getRequestsInProgress());
2735}
2736
2737/**
2738 * \brief Check is frame call is necessary to render and/or update
2739 * in response to events or timed actions
2740 */
2741bool Renderer::checkNeedToDoFrame()
2742{
2743    return (_needsRedraw ||
2744            (_viewer.valid() && _viewer->checkNeedToDoFrame()));
2745}
2746
2747/**
2748 * \brief MapNode event phase
2749 *
2750 * This is called by the MapNode's event callback during the event
2751 * traversal of the viewer
2752 */
2753void Renderer::mapNodeUpdate()
2754{
2755    computeMapScale();
2756}
2757
2758void Renderer::markFrameStart()
2759{
2760    _startFrameTime = osg::Timer::instance()->tick();
2761}
2762
2763void Renderer::markFrameEnd()
2764{
2765    osg::Timer_t endFrameTick = osg::Timer::instance()->tick();
2766    _lastFrameTime = osg::Timer::instance()->delta_s(_startFrameTime, endFrameTick);
2767    if (_lastFrameTime > _minFrameTime) {
2768        FRAME("BROKE FRAME by %.2f msec", (_lastFrameTime - _minFrameTime)*1000.0f);
2769    } else {
2770        FRAME("Frame time: %.2f msec", _lastFrameTime*1000.0f);
2771    }
2772#ifdef USE_THROTTLING_SLEEP
2773    if (_lastFrameTime < _minFrameTime) {
2774        FRAME("Sleeping for %.2f msec", (_minFrameTime - _lastFrameTime)*1000.0f);
2775        OpenThreads::Thread::microSleep(static_cast<unsigned int>(1000000.0*(_minFrameTime - _lastFrameTime)));
2776    }
2777#endif
2778}
2779
2780/**
2781 * \brief Cause the rendering to render a new image if needed
2782 *
2783 * The _needsRedraw flag indicates if a state change has occured since
2784 * the last rendered frame
2785 */
2786bool Renderer::render()
2787{
2788    if (_viewer.valid() && checkNeedToDoFrame()) {
2789        FRAME("Enter needsRedraw=%d",  _needsRedraw ? 1 : 0);
2790        _renderStartTime = osg::Timer::instance()->tick();
2791        FRAME("Before frame()");
2792        _viewer->frame();
2793        FRAME("After frame()");
2794        _renderStopTime = osg::Timer::instance()->tick();
2795        _renderTime = osg::Timer::instance()->delta_s(_renderStartTime, _renderStopTime);
2796        FRAME("Render time: %g msec", _renderTime * 1000.0);
2797#ifndef SLEEP_AFTER_QUEUE_FRAME
2798        _lastFrameTime = _renderTime;
2799#ifdef USE_THROTTLING_SLEEP
2800        if (_lastFrameTime < _minFrameTime) {
2801            FRAME("Sleeping for %.2f msec", (_minFrameTime - _lastFrameTime)*1000.0f);
2802            OpenThreads::Thread::microSleep(static_cast<unsigned int>(1.0e6*(_minFrameTime - _lastFrameTime)));
2803        }
2804#endif
2805#endif
2806#ifdef WANT_FRAME
2807        if (_viewer->getViewerStats() != NULL) {
2808            _viewer->getViewerStats()->report(std::cerr, _viewer->getViewerStats()->getLatestFrameNumber());
2809        }
2810#endif
2811        _needsRedraw = false;
2812        return true;
2813    } else {
2814        _renderStartTime = _renderStopTime = osg::Timer::instance()->tick();
2815        _renderTime = 0;
2816        return false;
2817    }
2818}
2819
2820/**
2821 * \brief Read back the rendered framebuffer image
2822 */
2823osg::Image *Renderer::getRenderedFrame()
2824{
2825    if (_captureCallback.valid())
2826        return _captureCallback->getImage();
2827    else
2828        return NULL;
2829}
2830
2831void Renderer::setScaleBar(bool state)
2832{
2833    if (_scaleLabel.valid()) {
2834        _scaleLabel->setVisible(state);
2835    }
2836    if (_scaleBar.valid()) {
2837        _scaleBar->setVisible(state);
2838    }
2839    _needsRedraw = true;
2840}
2841
2842void Renderer::setScaleBarUnits(ScaleBarUnits units)
2843{
2844    _scaleBarUnits = units;
2845    _needsRedraw = true;
2846}
2847
2848/**
2849 * \brief Compute the scale ratio of the map based on a horizontal center line
2850 *
2851 * The idea here is to take 2 screen points on a horizontal line in the center
2852 * of the screen and convert to lat/long.  The lat/long coordinates are then
2853 * used to compute the great circle distance (assuming spherical earth) between
2854 * the points.
2855 *
2856 * We could use local projected map coordinates for the distance computation,
2857 * which would be faster; however, this would not show e.g. the change in
2858 * scale at different latitudes
2859 */
2860double Renderer::computeMapScale()
2861{
2862    if (!_scaleLabel.valid() || !_scaleLabel->visible()) {
2863        return -1.0;
2864    }
2865    if (!_mapNode.valid() || _mapNode->getTerrain() == NULL) {
2866        ERROR("No map");
2867        return -1.0;
2868    }
2869    if (!_viewer.valid()) {
2870        ERROR("No viewer");
2871        return -1.0;
2872    }
2873
2874    double x, y;
2875    double pixelWidth = _windowWidth * 0.1 * 2.0;
2876    if (pixelWidth < 10)
2877        pixelWidth = 10;
2878    if (pixelWidth > 150)
2879        pixelWidth = 150;
2880    x = (double)(_windowWidth -1)/2.0 - pixelWidth / 2.0;
2881    y = (double)(_windowHeight-1)/2.0;
2882
2883    osg::Vec3d world1, world2;
2884    if (!_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), x, y, world1)) {
2885        // off map
2886        TRACE("Off map coords: %g %g", x, y);
2887        _scaleLabel->setText("");
2888        _scaleBar->setWidth(0);
2889        return -1.0;
2890    }
2891    x += pixelWidth;
2892    if (!_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), x, y, world2)) {
2893        // off map
2894        TRACE("Off map coords: %g %g", x, y);
2895        _scaleLabel->setText("");
2896        _scaleBar->setWidth(0);
2897        return -1.0;
2898    }
2899
2900#if 0
2901    TRACE("w1: %g %g %g w2: %g %g %g",
2902          world1.x(), world1.y(), world1.z(),
2903          world2.x(), world2.y(), world2.z());
2904#endif
2905
2906    double meters;
2907    double radius = 6378137.0;
2908    if (_mapNode->getMapSRS() &&
2909        _mapNode->getMapSRS()->getEllipsoid()) {
2910        radius = _mapNode->getMapSRS()->getEllipsoid()->getRadiusEquator();
2911    }
2912    if (!_map->isGeocentric() &&
2913        _mapNode->getMapSRS() &&
2914        _mapNode->getMapSRS()->isGeographic()) {
2915        TRACE("Map is geographic");
2916        // World cords are already lat/long
2917        // Compute great circle distance
2918        meters =
2919            osgEarth::GeoMath::distance(world1, world2, _mapNode->getMapSRS());
2920    } else if (_mapNode->getMapSRS()) {
2921        // Get map coords in lat/long
2922        osgEarth::GeoPoint mapPoint1, mapPoint2;
2923        mapPoint1.fromWorld(_mapNode->getMapSRS(), world1);
2924        mapPoint1.makeGeographic();
2925        mapPoint2.fromWorld(_mapNode->getMapSRS(), world2);
2926        mapPoint2.makeGeographic();
2927        // Compute great circle distance
2928        meters =
2929            osgEarth::GeoMath::distance(osg::DegreesToRadians(mapPoint1.y()),
2930                                        osg::DegreesToRadians(mapPoint1.x()),
2931                                        osg::DegreesToRadians(mapPoint2.y()),
2932                                        osg::DegreesToRadians(mapPoint2.x()),
2933                                        radius);
2934    } else {
2935        // Assume geocentric?
2936        ERROR("No map SRS");
2937        _scaleLabel->setText("");
2938        _scaleBar->setWidth(0);
2939        return -1.0;
2940    }
2941
2942    double scale = meters / pixelWidth;
2943    // 1mi = 5280 feet
2944    //double scaleMiles = scale / 1609.344; // International mile = 1609.344m
2945    //double scaleNauticalMiles = scale / 1852.0; // nautical mile = 1852m
2946    //double scaleUSSurveyMiles = scale / 1609.347218694; // US survey mile = 5280 US survey feet
2947    //double scaleUSSurveyFeet = scale * 3937.0/1200.0; // US survey foot = 1200/3937 m
2948#if 0
2949    TRACE("m: %g px: %g m/px: %g", meters, pixelWidth, scale);
2950#endif
2951    _mapScale = scale;
2952    switch (_scaleBarUnits) {
2953    case UNITS_NAUTICAL_MILES: {
2954        double nmi = meters / 1852.0;
2955        scale = nmi / pixelWidth;
2956        nmi = normalizeScaleNauticalMiles(nmi);
2957        pixelWidth = nmi / scale;
2958        if (_scaleLabel.valid()) {
2959            _scaleLabel->setText(osgEarth::Stringify()
2960                                 << nmi
2961                                 << " nmi");
2962        }
2963    }
2964        break;
2965    case UNITS_US_SURVEY_FEET: {
2966        double feet = meters * 3937.0/1200.0;
2967        scale = feet / pixelWidth;
2968        feet = normalizeScaleFeet(feet);
2969        pixelWidth = feet / scale;
2970        if (_scaleLabel.valid()) {
2971            if (feet >= 5280) {
2972                _scaleLabel->setText(osgEarth::Stringify()
2973                                     << feet / 5280.0
2974                                     << " miUS");
2975             } else {
2976                _scaleLabel->setText(osgEarth::Stringify()
2977                                     << feet
2978                                     << " ftUS");
2979            }
2980        }
2981    }
2982        break;
2983    case UNITS_INTL_FEET: {
2984        double feet = 5280.0 * meters / 1609.344;
2985        scale = feet / pixelWidth;
2986        feet = normalizeScaleFeet(feet);
2987        pixelWidth = feet / scale;
2988        if (_scaleLabel.valid()) {
2989            if (feet >= 5280) {
2990                _scaleLabel->setText(osgEarth::Stringify()
2991                                     << feet / 5280.0
2992                                     << " mi");
2993            } else {
2994                _scaleLabel->setText(osgEarth::Stringify()
2995                                     << feet
2996                                     << " ft");
2997            }
2998        }
2999    }
3000        break;
3001    case UNITS_METERS:
3002    default: {
3003        meters = normalizeScaleMeters(meters);
3004        pixelWidth = meters / scale;
3005        if (_scaleLabel.valid()) {
3006            if (meters >= 1000) {
3007                _scaleLabel->setText(osgEarth::Stringify()
3008                                     << meters / 1000.0
3009                                     << " km");
3010            } else {
3011                _scaleLabel->setText(osgEarth::Stringify()
3012                                     << meters
3013                                     << " m");
3014            }
3015        }
3016    }
3017        break;
3018    }
3019    if (_scaleBar.valid()) {
3020        _scaleBar->setWidth(pixelWidth);
3021    }
3022    return scale;
3023}
3024
3025std::string Renderer::getCanonicalPath(const std::string& url) const
3026{
3027    std::string retStr;
3028    std::string proto = osgDB::getServerProtocol(url);
3029    if (proto.empty()) {
3030        retStr = osgDB::getRealPath(url);
3031        if (!osgDB::fileExists(retStr)) {
3032            if (!url.empty() && url[0] != '/') {
3033                // Relative URL, assume local://
3034                std::ostringstream oss;
3035                oss << getCacheDirectory() << "/" << url;
3036                retStr = oss.str();
3037            } else {
3038                retStr = "";
3039            }
3040        }
3041    } else if (proto == "local") {
3042        INFO("Local protocol: '%s'", getLocalFilePath(url).c_str());
3043        std::ostringstream oss;
3044        oss << getCacheDirectory() << "/" << getLocalFilePath(url);
3045        retStr = oss.str();
3046    } else if (proto == "file") {
3047        INFO("File: '/%s'", osgDB::getServerFileName(url).c_str());
3048        std::ostringstream oss;
3049        oss << "/" <<  osgDB::getServerFileName(url);
3050        retStr = oss.str();
3051    } else if (proto == "idata") {
3052        std::string fileName = osgDB::getServerFileName(url);
3053        INFO("IData protocol: coll: '%s', '%s'", osgDB::getServerAddress(url).c_str(), fileName.c_str());
3054        int collection = atoi(osgDB::getServerAddress(url).c_str());
3055        {
3056            std::ostringstream oss;
3057            oss << getCacheDirectory() << "/" << fileName;
3058            retStr = oss.str();
3059            std::string dirPath = osgDB::getFilePath(retStr);
3060            osgDB::makeDirectory(dirPath);
3061        }
3062        if (!osgDB::fileExists(retStr)) {
3063            std::ostringstream oss;
3064            oss << "//" << fileName;
3065            IData::Buffer buf;
3066            IData::getFile(collection, oss.str().c_str(), &buf);
3067            std::ofstream file(retStr.c_str());
3068            file.write((char *)buf.data, buf.size);
3069            file.close();
3070        }
3071    } else {
3072        INFO("Protocol: '%s' url: '%s'", proto.c_str(), url.c_str());
3073        retStr = url;
3074    }
3075    return retStr;
3076}
3077
3078void Renderer::writeScene(const std::string& file)
3079{
3080    if (_sceneRoot.valid()) {
3081        GraphPrintVisitor gpv;
3082        _sceneRoot->accept(gpv);
3083        //osgDB::writeNodeFile(*_sceneRoot.get(), file);
3084    }
3085}
Note: See TracBrowser for help on using the repository browser.