source: geovis/trunk/Renderer.cpp @ 6219

Last change on this file since 6219 was 6219, checked in by ldelgass, 8 years ago

Stubs for editing feature selection set

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