source: geovis/trunk/Renderer.cpp @ 6354

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

Add layer position index param to add*Layer methods. Add option to transform
selection box coords to another SRS.

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