source: geovis/branches/rex/Renderer.cpp @ 6570

Last change on this file since 6570 was 6570, checked in by ldelgass, 6 years ago

First pass at porting to new Map layer API

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