source: geovis/trunk/Renderer.cpp @ 6276

Last change on this file since 6276 was 6276, checked in by ldelgass, 7 years ago

Add server response to notify clients of selection box region

File size: 105.9 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                             bool enableCache,
1315                             bool coverage,
1316                             bool makeShared,
1317                             bool visible,
1318                             unsigned int minLOD, unsigned int maxLOD)
1319{
1320    if (!_map.valid()) {
1321        ERROR("No map");
1322        return false;
1323    }
1324    TRACE("layer: %s", name);
1325    if (!opts.tileSize().isSet()) {
1326        opts.tileSize() = 256;
1327    }
1328    osgEarth::ImageLayerOptions layerOpts(name, opts);
1329    layerOpts.textureCompression() = osg::Texture::USE_IMAGE_DATA_FORMAT;
1330    if (coverage) {
1331        layerOpts.coverage() = true;
1332    }
1333    if (makeShared) {
1334        layerOpts.shared() = true;
1335    }
1336    if (!enableCache) {
1337        TRACE("Disabling cache for layer %s", name);
1338        layerOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
1339    }
1340    if (!visible) {
1341        layerOpts.visible() = false;
1342    }
1343    layerOpts.minLevel() = minLOD;
1344    layerOpts.maxLevel() = maxLOD;
1345    osg::ref_ptr<osgEarth::ImageLayer> layer = new osgEarth::ImageLayer(layerOpts);
1346    _map->addImageLayer(layer.get());
1347    if (layer->getTileSource() == NULL || !layer->getTileSource()->isOK()) {
1348        ERROR("Failed to add image layer: %s", name);
1349        _map->removeImageLayer(layer.get());
1350        return false;
1351    }
1352    _needsRedraw = true;
1353    return true;
1354}
1355
1356void Renderer::addColorFilter(const char *name,
1357                              const char *shader)
1358{
1359    if (!_map.valid()) {
1360        ERROR("No map");
1361        return;
1362    }
1363    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1364    if (layer == NULL) {
1365        TRACE("Image layer not found: %s", name);
1366        return;
1367    }
1368    osgEarth::Util::GLSLColorFilter *filter = new osgEarth::Util::GLSLColorFilter;
1369    filter->setCode(shader);
1370    //filter->setCode("color.rgb = color.r > 0.5 ? vec3(1.0) : vec3(0.0);");
1371    layer->addColorFilter(filter);
1372    _needsRedraw = true;
1373}
1374
1375void Renderer::removeColorFilter(const char *name, int idx)
1376{
1377    if (!_map.valid()) {
1378        ERROR("No map");
1379        return;
1380    }
1381    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1382    if (layer == NULL) {
1383        TRACE("Image layer not found: %s", name);
1384        return;
1385    }
1386    if (idx < 0) {
1387        while (!layer->getColorFilters().empty()) {
1388            layer->removeColorFilter(layer->getColorFilters()[0]);
1389        }
1390    } else {
1391        layer->removeColorFilter(layer->getColorFilters().at(idx));
1392    }
1393    _needsRedraw = true;
1394}
1395
1396void Renderer::removeImageLayer(const char *name)
1397{
1398    if (!_map.valid()) {
1399        ERROR("No map");
1400        return;
1401    }
1402    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1403    if (layer != NULL) {
1404        _map->removeImageLayer(layer);
1405        _needsRedraw = true;
1406    } else {
1407        TRACE("Image layer not found: %s", name);
1408    }
1409}
1410
1411void Renderer::moveImageLayer(const char *name, unsigned int pos)
1412{
1413    if (!_map.valid()) {
1414        ERROR("No map");
1415        return;
1416    }
1417    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1418    if (layer != NULL) {
1419        _map->moveImageLayer(layer, pos);
1420        _needsRedraw = true;
1421    } else {
1422        TRACE("Image layer not found: %s", name);
1423    }
1424}
1425
1426void Renderer::setImageLayerOpacity(const char *name, double opacity)
1427{
1428    if (!_map.valid()) {
1429        ERROR("No map");
1430        return;
1431    }
1432    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1433    if (layer != NULL) {
1434        layer->setOpacity(opacity);
1435        _needsRedraw = true;
1436    } else {
1437        TRACE("Image layer not found: %s", name);
1438    }
1439}
1440
1441void Renderer::setImageLayerVisibleRange(const char *name, float min, float max)
1442{
1443    if (!_map.valid()) {
1444        ERROR("No map");
1445        return;
1446    }
1447    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1448    if (layer != NULL) {
1449        layer->setMinVisibleRange(min);
1450        layer->setMaxVisibleRange(max);
1451        _needsRedraw = true;
1452    } else {
1453        TRACE("Image layer not found: %s", name);
1454    }
1455}
1456
1457void Renderer::setImageLayerVisibility(const char *name, bool state)
1458{
1459    if (!_map.valid()) {
1460        ERROR("No map");
1461        return;
1462    }
1463    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
1464    if (layer != NULL) {
1465        layer->setVisible(state);
1466        _needsRedraw = true;
1467    } else {
1468        TRACE("Image layer not found: %s", name);
1469    }
1470}
1471
1472void Renderer::addElevationLayer(const char *name,
1473                                 osgEarth::TileSourceOptions& opts,
1474                                 bool enableCache,
1475                                 bool visible,
1476                                 unsigned int minLOD, unsigned int maxLOD)
1477{
1478    if (!_map.valid()) {
1479        ERROR("No map");
1480        return;
1481    }
1482    TRACE("layer: %s", name);
1483    if (!opts.tileSize().isSet()) {
1484        opts.tileSize() = 17;
1485    }
1486    osgEarth::ElevationLayerOptions layerOpts(name, opts);
1487    if (!enableCache) {
1488        layerOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
1489    }
1490    if (!visible) {
1491        layerOpts.visible() = false;
1492    }
1493    layerOpts.minLevel() = minLOD;
1494    layerOpts.maxLevel() = maxLOD;
1495    // XXX: GDAL does not report vertical datum, it should be specified here
1496    // Common options: geodetic (default), egm96, egm84, egm2008
1497    //layerOpts.verticalDatum() = "egm96";
1498    _map->addElevationLayer(new osgEarth::ElevationLayer(layerOpts));
1499    _needsRedraw = true;
1500}
1501
1502void Renderer::removeElevationLayer(const char *name)
1503{
1504    if (!_map.valid()) {
1505        ERROR("No map");
1506        return;
1507    }
1508    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
1509    if (layer != NULL) {
1510        _map->removeElevationLayer(layer);
1511        _needsRedraw = true;
1512    } else {
1513        TRACE("Elevation layer not found: %s", name);
1514    }
1515}
1516
1517void Renderer::moveElevationLayer(const char *name, unsigned int pos)
1518{
1519    if (!_map.valid()) {
1520        ERROR("No map");
1521        return;
1522    }
1523    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
1524    if (layer != NULL) {
1525        _map->moveElevationLayer(layer, pos);
1526        _needsRedraw = true;
1527    } else {
1528        TRACE("Elevation layer not found: %s", name);
1529    }
1530}
1531
1532void Renderer::setElevationLayerVisibility(const char *name, bool state)
1533{
1534    if (!_map.valid()) {
1535        ERROR("No map");
1536        return;
1537    }
1538    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
1539    if (layer != NULL) {
1540        layer->setVisible(state);
1541        _needsRedraw = true;
1542    } else {
1543        TRACE("Elevation layer not found: %s", name);
1544    }
1545}
1546
1547void Renderer::initAnnotations()
1548{
1549    if (!_annotations.valid()) {
1550        _annotations = new osg::Group();
1551        _annotations->setName("Annotations");
1552        _sceneRoot->addChild(_annotations.get());
1553        _placeNodes = new osg::Group();
1554        _placeNodes->setName("Place Nodes");
1555#ifdef NEW_ANNOTATION_API
1556        osgEarth::ScreenSpaceLayout::activate(_placeNodes->getOrCreateStateSet());
1557#else
1558        osgEarth::Decluttering::setEnabled(_placeNodes->getOrCreateStateSet(), true);
1559#endif
1560        _annotations->addChild(_placeNodes.get());
1561        if (_picker.valid()) {
1562            _picker->addChild(_placeNodes.get());
1563        }
1564    }
1565}
1566
1567void Renderer::setSelectMode(SelectMode mode)
1568{
1569    _selectMode = mode;
1570    switch (_selectMode) {
1571    case SELECT_OFF: {
1572#ifdef USE_RTT_PICKER
1573        if (_picker.valid()) {
1574            _viewer->removeEventHandler(_picker.get());
1575            _picker = NULL;
1576        }
1577#endif
1578    }
1579        break;
1580    case SELECT_ON: {
1581#ifdef USE_RTT_PICKER
1582        if (!_picker.valid()) {
1583            _picker = new osgEarth::Util::RTTPicker;
1584            _picker->addChild(_mapNode);
1585            osg::ref_ptr<SelectCallback> callback = new SelectCallback(this);
1586            _picker->setDefaultCallback(callback.get());
1587            _viewer->addEventHandler(_picker.get());
1588        }
1589#endif
1590    }
1591        break;
1592    default:
1593        ERROR("Unknown select mode");
1594    }
1595}
1596
1597void Renderer::enablePlacard(const char *layerName, bool state)
1598{
1599    osgEarth::ModelLayer *layer = _map->getModelLayerByName(layerName);
1600    if (layer == NULL) {
1601        ERROR("Unknown layer '%s'", layerName);
1602        return;
1603    }
1604    _placardConfigs[layerName].setEnabled(state);
1605}
1606
1607void Renderer::setPlacardConfig(const Placard& placardConf, const char *layerName)
1608{
1609    osgEarth::ModelLayer *layer = _map->getModelLayerByName(layerName);
1610    if (layer == NULL) {
1611        ERROR("Unknown layer '%s'", layerName);
1612        return;
1613    }
1614    //PlacardNode *node;
1615    //node->setConfig(placardConf);
1616    _placardConfigs[layerName] = placardConf;
1617}
1618
1619void Renderer::deselectFeatures(std::vector<unsigned long>& featureIDs, const char *layerName)
1620{
1621    // TODO: Take down placard?
1622    TRACE("Deselect features layer '%s', num features: %u", layerName, featureIDs.size());
1623    for (unsigned int i = 0; i < featureIDs.size(); i++) {
1624        _selectedFeatures[layerName].erase(featureIDs.at(i));
1625    }
1626    FeatureSelectionHashmap::iterator itr = _selectedFeatures.find(layerName);
1627    if (itr != _selectedFeatures.end()) {
1628        for (FeatureSelectionHashmap::mapped_type::iterator fitr = itr->second.begin();
1629             fitr != itr->second.end(); ++fitr) {
1630            TRACE("Selection: %lu", *fitr);
1631        }
1632    } else {
1633        TRACE("No selection");
1634    }
1635    updateDebugLabel();
1636}
1637
1638void Renderer::selectFeatures(std::vector<unsigned long>& featureIDs, const char *layerName, bool clear)
1639{
1640    bool doPlacard = false;
1641    TRACE("Select layer '%s', num features: %u", layerName, featureIDs.size());
1642    if (clear) {
1643        clearSelection();
1644    }
1645    if (featureIDs.size() == 0) {
1646        // clear selection
1647        return;
1648    }
1649    osgEarth::ModelLayer *layer = _map->getModelLayerByName(layerName);
1650    if (layer == NULL) {
1651        ERROR("Unknown layer '%s'", layerName);
1652        return;
1653    }
1654    for (unsigned int i = 0; i < featureIDs.size(); i++) {
1655        TRACE("feature ID: %u", featureIDs.at(i));
1656        _selectedFeatures[layerName].insert(featureIDs.at(i));
1657    }
1658    FeatureSelectionHashmap::iterator itr = _selectedFeatures.find(layerName);
1659    if (itr != _selectedFeatures.end()) {
1660        for (FeatureSelectionHashmap::mapped_type::iterator fitr = itr->second.begin();
1661             fitr != itr->second.end(); ++fitr) {
1662            TRACE("Selection: %lu", *fitr);
1663        }
1664    } else {
1665        TRACE("No selection");
1666    }
1667    unsigned long fid = featureIDs.at(0);
1668
1669    osgEarth::ModelSource *modelSource = layer->getModelSource();
1670    osgEarth::Features::FeatureModelSource *fms = dynamic_cast<osgEarth::Features::FeatureModelSource *>(modelSource);
1671    if (fms != NULL) {
1672        osgEarth::Features::FeatureSource *fs = fms->getFeatureSource();
1673        FindFeatureSourceIndexNodeVisitor visitor;
1674        visitor.source = fs;
1675        _sceneRoot->accept(visitor);
1676        TRACE("Num FeatureSourceIndexNodes found: %lu", visitor.nodes.size());
1677        for (size_t i = 0; i < visitor.nodes.size(); i++) {
1678            osgEarth::Features::FeatureIndex *index = visitor.nodes[i]->getIndex();
1679#if OSGEARTH_MIN_VERSION_REQUIRED(3, 0, 0)
1680            osgEarth::ObjectID id = index->getObjectID(fid);
1681            if (doPlacard) {
1682                osgEarth::Features::Feature *feature = index->getFeature(id);
1683                if (feature) {
1684                    // Find feature centroid
1685                    osgEarth::GeoPoint location;
1686                    if (feature->getGeometry()) {
1687                        osg::BoundingSphered bound;
1688                        feature->getWorldBound(getMapSRS(), bound);
1689                        location.set(getMapSRS(), bound.center(),
1690                                     osgEarth::ALTMODE_ABSOLUTE);
1691                    }
1692                    addPlacard(location, feature, layerName);
1693                }
1694            }
1695            setHighlightByObjectID(id);
1696            TRACE("FID %lu = OID %d", fid, id);
1697#endif
1698        }
1699        _needsRedraw = true;
1700    }
1701    updateDebugLabel();
1702}
1703
1704void Renderer::updateDebugLabel()
1705{
1706    if (!_debugLabel.valid()) return;
1707    std::ostringstream oss;
1708    oss << "Selection:";
1709    for (FeatureSelectionHashmap::iterator itr = _selectedFeatures.begin();
1710         itr != _selectedFeatures.end(); ++itr) {
1711        bool found = false;
1712        oss << std::endl << itr->first << ":";
1713        for (FeatureSelectionHashmap::mapped_type::iterator fitr = itr->second.begin();
1714             fitr != itr->second.end(); ++fitr) {
1715            oss << " " << *fitr;
1716            found = true;
1717        }
1718        if (!found) {
1719            oss << " None";
1720        }
1721    }
1722    _debugLabel->setText(oss.str());
1723}
1724
1725void Renderer::addPlacard(const osgEarth::GeoPoint& location,
1726                          osgEarth::Features::Feature *feature,
1727                          const char *layerName)
1728{
1729    if (feature == NULL) return;
1730    Placard placard = getPlacardConfig(layerName);
1731    if (!placard.enabled())
1732        return;
1733
1734    const osgEarth::Features::AttributeTable &attrs = feature->getAttrs();
1735    if (placard.getNumEntries() == 0) {
1736        placard.addAllAttributes(attrs);
1737    }
1738    PlacardNode *label =
1739        new PlacardNode(_mapNode.get(), location, placard, attrs);
1740#ifndef NEW_ANNOTATION_API
1741    label->getOrCreateStateSet()->setRenderBinDetails(INT_MAX, "RenderBin");
1742#endif
1743    getAnnotations()->addChild(label);
1744}
1745
1746void Renderer::clearSelection()
1747{
1748    _selectedFeatures.clear();
1749    clearHighlight();
1750    clearSelectionAnnotationNodes();
1751    clearBoxSelection();
1752    updateDebugLabel();
1753    _needsRedraw = true;
1754}
1755
1756/**
1757 * \brief Remove annotation nodes created during a pick/selection
1758 *
1759 * The primary purpose of this method is to take down the feature placard
1760 */
1761void Renderer::clearSelectionAnnotationNodes()
1762{
1763    osg::Group *nodes = getAnnotations();
1764    std::vector<osg::Node *> toRemove;
1765    for (unsigned int i = 0; i < nodes->getNumChildren(); i++) {
1766        osg::Node *node = nodes->getChild(i);
1767        // This can be Placard, PlacardLabel, Label, Place or Track Node
1768#ifdef NEW_ANNOTATION_API
1769        if (dynamic_cast<osgEarth::Annotation::GeoPositionNode *>(node) != NULL) {
1770#else
1771        if (dynamic_cast<osgEarth::Annotation::OrthoNode *>(node) != NULL) {
1772#endif
1773            toRemove.push_back(node);
1774        }
1775    }
1776    for (std::vector<osg::Node *>::iterator itr = toRemove.begin();
1777         itr != toRemove.end(); ++itr) {
1778        nodes->removeChild(*itr);
1779    }
1780    _needsRedraw = true;
1781}
1782
1783void Renderer::initBoxSelection(int x, int y)
1784{
1785    double latitude, longitude;
1786    if (!mouseToLatLong(x, y, &latitude, &longitude)) {
1787        return;
1788    }
1789    _anchorLat = latitude;
1790    _anchorLong = longitude;
1791    addRhumbBox(latitude, latitude, longitude, longitude);
1792}
1793
1794void Renderer::updateBoxSelection(int x, int y)
1795{
1796    double nlat, nlong;
1797    if (!mouseToLatLong(x, y, &nlat, &nlong)) {
1798        return;
1799    }
1800    double latMin, latMax, longMin, longMax;
1801    if (nlong >= _anchorLong && nlat >= _anchorLat) {
1802        // +x +y
1803        longMin = _anchorLong;
1804        latMin = _anchorLat;
1805        longMax = nlong;
1806        latMax = nlat;
1807    } else if (nlong < _anchorLong && nlat >= _anchorLat) {
1808        // -x +y
1809        longMin = nlong;
1810        latMin = _anchorLat;
1811        longMax = _anchorLong;
1812        latMax = nlat;
1813    } else if (nlong < _anchorLong && nlat < _anchorLat) {
1814        // -x -y
1815        longMin = nlong;
1816        latMin = nlat;
1817        longMax = _anchorLong;
1818        latMax = _anchorLat;
1819    } else {
1820        // +x -y
1821        longMin = _anchorLong;
1822        latMin = nlat;
1823        longMax = nlong;
1824        latMax = _anchorLat;
1825    }
1826    osgEarth::Annotation::FeatureNode *node = _selectionBox.get();
1827    osgEarth::Symbology::Geometry *geom = node->getFeature()->getGeometry();
1828    (*geom)[0] = osg::Vec3d(longMax, latMin, 0);
1829    (*geom)[1] = osg::Vec3d(longMin, latMin, 0);
1830    (*geom)[2] = osg::Vec3d(longMin, latMax, 0);
1831    (*geom)[3] = osg::Vec3d(longMax, latMax, 0);
1832    node->init();
1833#ifndef NEW_ANNOTATION_API
1834    for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = _selected.begin();
1835         itr != _selected.end(); ++itr) {
1836        (*itr)->clearDecoration();
1837    }
1838#endif
1839    _selected.clear();
1840    SelectPlaceNodesVisitor spnv(this, latMin, latMax, longMin, longMax);
1841    _sceneRoot->accept(spnv);
1842    _needsRedraw = true;
1843}
1844
1845void Renderer::getBoxSelection(double *latMin, double *latMax, double *longMin, double *longMax)
1846{
1847    osgEarth::Annotation::FeatureNode *node = _selectionBox.get();
1848    if (node == NULL)
1849        return;
1850    osgEarth::Symbology::Geometry *geom = node->getFeature()->getGeometry();
1851    if (geom == NULL)
1852        return;
1853    *latMin = (*geom)[0].y();
1854    *latMax = (*geom)[2].y();
1855    *longMin = (*geom)[1].x();
1856    *longMax = (*geom)[0].x();
1857}
1858
1859void SelectPlaceNodesVisitor::apply(osg::Node& node)
1860{
1861    osgEarth::Annotation::PlaceNode *placeNode =
1862        dynamic_cast<osgEarth::Annotation::PlaceNode*>(&node);
1863    if (placeNode != NULL) {
1864        const osgEarth::SpatialReference *outSRS =
1865            _renderer->getMapSRS()->getGeographicSRS();
1866        osgEarth::GeoPoint pt = placeNode->getPosition();
1867        pt = pt.transform(outSRS);
1868        if (pt.x() >= _longMin && pt.x() <= _longMax &&
1869            pt.y() >= _latMin && pt.y() <= _latMax) {
1870            if (_renderer->select(placeNode)) {
1871                TRACE("Select PlaceNode: %g %g n:'%s' t:'%s'",
1872                     pt.x(), pt.y(), placeNode->getName().c_str(),
1873                     placeNode->getText().c_str());
1874#ifndef NEW_ANNOTATION_API
1875                placeNode->setDecoration("select");
1876#endif
1877            }
1878        }
1879    }
1880    traverse(node);
1881}
1882
1883void Renderer::clearBoxSelection()
1884{
1885    if (_annotations.valid() && _selectionBox.valid()) {
1886        _annotations->removeChild(_selectionBox.get());
1887        _selectionBox = NULL;
1888    }
1889#ifndef NEW_ANNOTATION_API
1890    for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = _selected.begin();
1891         itr != _selected.end(); ++itr) {
1892        (*itr)->clearDecoration();
1893    }
1894#endif
1895    _selected.clear();
1896    _needsRedraw = true;
1897}
1898
1899void Renderer::addRhumbBox(double latMin, double latMax,
1900                           double longMin, double longMax)
1901{
1902    if (!_mapNode.valid()) {
1903        ERROR("No map node");
1904        return;
1905    }
1906    initAnnotations();
1907
1908    if (_selectionBox.valid()) {
1909        osgEarth::Symbology::Geometry *geom = _selectionBox->getFeature()->getGeometry();
1910        (*geom)[0] = osg::Vec3d(longMax, latMin, 0);
1911        (*geom)[1] = osg::Vec3d(longMin, latMin, 0);
1912        (*geom)[2] = osg::Vec3d(longMin, latMax, 0);
1913        (*geom)[3] = osg::Vec3d(longMax, latMax, 0);
1914        _selectionBox->init();
1915    } else {
1916        const osgEarth::SpatialReference* geoSRS = _mapNode->getMapSRS()->getGeographicSRS();
1917        osgEarth::Symbology::Geometry *geom = new osgEarth::Symbology::Polygon();
1918        geom->push_back(osg::Vec3d(longMax, latMin, 0));
1919        geom->push_back(osg::Vec3d(longMin, latMin, 0));
1920        geom->push_back(osg::Vec3d(longMin, latMax, 0));
1921        geom->push_back(osg::Vec3d(longMax, latMax, 0));
1922        osgEarth::Symbology::Style boxStyle;
1923#if 1
1924        osgEarth::Symbology::PolygonSymbol *poly = boxStyle.getOrCreate<osgEarth::Symbology::PolygonSymbol>();
1925        poly->fill()->color() = osgEarth::Symbology::Color::Cyan;
1926        poly->fill()->color().a() = 0.5;
1927#else
1928        osgEarth::Symbology::LineSymbol *line = boxStyle.getOrCreate<osgEarth::Symbology::LineSymbol>();
1929        line->stroke()->color() = osgEarth::Symbology::Color::Yellow;
1930        line->stroke()->width() = 2.0f;
1931        //line->creaseAngle() = 45.0f;
1932        line->tessellation() = 10;
1933#endif
1934        osgEarth::Symbology::AltitudeSymbol *alt = boxStyle.getOrCreate<osgEarth::Symbology::AltitudeSymbol>();
1935        alt->clamping() = osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
1936        //alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
1937        alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_DRAPE;
1938        //alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_SCENE;
1939#if 0
1940        osgEarth::Symbology::RenderSymbol* rs = boxStyle.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
1941        rs->depthOffset()->enabled() = true;
1942        rs->depthOffset()->minBias() = 1000;
1943#endif
1944        osgEarth::Features::Feature *feature = new osgEarth::Features::Feature(geom, geoSRS, boxStyle);
1945        //feature->geoInterp() = osgEarth::GEOINTERP_GREAT_CIRCLE;
1946        feature->geoInterp() = osgEarth::GEOINTERP_RHUMB_LINE;
1947        _selectionBox =
1948            new osgEarth::Annotation::FeatureNode(_mapNode, feature);
1949        _annotations->addChild(_selectionBox.get());
1950    }
1951
1952    _needsRedraw = true;
1953}
1954
1955void Renderer::addPlaceNode(double latitude, double longitude, char *labelText)
1956{
1957    if (!_mapNode.valid()) {
1958        ERROR("No map node");
1959        return;
1960    }
1961    initAnnotations();
1962
1963    const osgEarth::SpatialReference *geoSRS = _mapNode->getMapSRS()->getGeographicSRS();
1964
1965    osgEarth::Symbology::Style pin;
1966    pin.getOrCreate<osgEarth::Symbology::IconSymbol>()->url()->setLiteral(getPinIcon());
1967    osgEarth::Annotation::AnnotationNode *anno =
1968        new osgEarth::Annotation::PlaceNode(_mapNode, osgEarth::GeoPoint(geoSRS, longitude, latitude), labelText, pin);
1969    anno->setName(labelText);
1970    _placeNodes->addChild(anno);
1971#ifdef USE_RTT_PICKER
1972    osgEarth::Registry::objectIndex()->tagNode(anno, anno);
1973#else
1974    osgEarth::Annotation::DecorationInstaller
1975        highlightInstaller("select", new osgEarth::Annotation::HighlightDecoration(osg::Vec4f(1,1,0,0.5)));
1976    _placeNodes->accept(highlightInstaller);
1977
1978    // scale labels when hovering:
1979    osgEarth::Annotation::DecorationInstaller
1980        scaleInstaller("hover", new osgEarth::Annotation::ScaleDecoration(1.1f));
1981    _placeNodes->accept(scaleInstaller);
1982#endif
1983#if 0
1984    writeScene("/tmp/test.osg");
1985#endif
1986    _needsRedraw = true;
1987}
1988#if 0
1989void Renderer::hoverFeatureNodes(int x, int y, bool invertY)
1990{
1991    if (!_mapNode.valid()) {
1992        ERROR("No map node");
1993        return;
1994    }
1995    osgEarth::IntersectionPicker picker(_viewer.get(), _mapNode.get());
1996    osgEarth::IntersectionPicker::Hits hits;
1997    float mouseX = (float)x;
1998    float mouseY = (float)y;
1999    if (invertY) {
2000        mouseY = ((float)_windowHeight - mouseY);
2001    }
2002    std::set<osgEarth::Annotation::FeatureNode*> toUnHover;
2003    for (std::set<osgEarth::Annotation::FeatureNode*>::iterator itr = _hovered.begin();
2004         itr != _hovered.end(); ++itr) {
2005        toUnHover.insert(*itr);
2006    }
2007    if (picker.pick(mouseX, mouseY, hits)) {
2008        TRACE("Picker hits: %d", hits.size());
2009        for (osgEarth::IntersectionPicker::Hits::const_iterator hitr = hits.begin();
2010             hitr != hits.end(); ++hitr) {
2011            osgEarth::Annotation::FeatureNode *anno =
2012                picker.getNode<osgEarth::Annotation::FeatureNode>(*hitr);
2013            if (anno != NULL) {
2014                TRACE("Hit FeatureNode: %p", anno);
2015                if (_hovered.find(anno) == _hovered.end()) {
2016                    _hovered.insert(anno);
2017                    anno->setDecoration("hover");
2018                    _needsRedraw = true;
2019                }
2020                toUnHover.erase(anno);
2021            }
2022        }
2023#if 0
2024        writeScene("/tmp/test.osgt");
2025#endif
2026    }
2027    for (std::set<osgEarth::Annotation::FeatureNode *>::iterator itr = toUnHover.begin();
2028         itr != toUnHover.end(); ++itr) {
2029        _hovered.erase(*itr);
2030        (*itr)->clearDecoration();
2031        _needsRedraw = true;
2032    }
2033}
2034#endif
2035
2036#ifdef USE_RTT_PICKER
2037void Renderer::hoverPlaceNode(int x, int y, bool invertY)
2038{
2039    if (!_placeNodes.valid()) {
2040        TRACE("No place nodes");
2041        return;
2042    }
2043    return;
2044
2045    float mouseX = (float)x;
2046    float mouseY = (float)y;
2047    if (invertY) {
2048        mouseY = ((float)_windowHeight - mouseY);
2049    }
2050    if (_picker->pick(_viewer.get(), mouseX, mouseY)) {
2051        TRACE("Hover pick queued: %g %g", mouseX, mouseY);
2052    } else {
2053        TRACE("Failed to queue pick: %g %g", mouseX, mouseY);
2054    }
2055}
2056
2057void Renderer::deletePlaceNode(int x, int y, bool invertY)
2058{
2059    if (!_placeNodes.valid()) {
2060        TRACE("No place nodes");
2061        return;
2062    }
2063    return;
2064
2065    osg::ref_ptr<osgEarth::Util::RTTPicker> picker = new osgEarth::Util::RTTPicker;
2066    picker->addChild(_placeNodes.get());
2067    osg::ref_ptr<DeleteCallback> callback = new DeleteCallback(this);
2068    float mouseX = (float)x;
2069    float mouseY = (float)y;
2070    if (invertY) {
2071        mouseY = ((float)_windowHeight - mouseY);
2072    }
2073    if (picker->pick(_viewer.get(), mouseX, mouseY, callback)) {
2074        TRACE("Delete pick queued: %g %g", mouseX, mouseY);
2075    }
2076}
2077#else
2078void Renderer::hoverPlaceNode(int x, int y, bool invertY)
2079{
2080    if (!_placeNodes.valid()) {
2081        TRACE("No place nodes");
2082        return;
2083    }
2084
2085    osgEarth::IntersectionPicker picker(_viewer.get(), _placeNodes.get());
2086    osgEarth::IntersectionPicker::Hits hits;
2087    float mouseX = (float)x;
2088    float mouseY = (float)y;
2089    if (invertY) {
2090        mouseY = ((float)_windowHeight - mouseY);
2091    }
2092    std::set<osgEarth::Annotation::AnnotationNode*> toUnHover;
2093    for (std::set<osgEarth::Annotation::AnnotationNode*>::iterator itr = _hovered.begin();
2094         itr != _hovered.end(); ++itr) {
2095        toUnHover.insert(*itr);
2096    }
2097    if (picker.pick(mouseX, mouseY, hits)) {
2098        TRACE("Picker hits: %d", hits.size());
2099        for (osgEarth::IntersectionPicker::Hits::const_iterator hitr = hits.begin();
2100             hitr != hits.end(); ++hitr) {
2101            TRACE("Hit: node %p drawable %p idx %d", picker.getNode<osg::Node>(*hitr), hitr->drawable.get(), hitr->primitiveIndex);
2102            osgEarth::Annotation::AnnotationNode *anno =
2103                picker.getNode<osgEarth::Annotation::AnnotationNode>(*hitr);
2104            if (anno != NULL && anno->getDecoration().empty()) {
2105                TRACE("Hit AnnotationNode: %p", anno);
2106                if (_hovered.find(anno) == _hovered.end()) {
2107                    _hovered.insert(anno);
2108                    anno->setDecoration("hover");
2109                    _needsRedraw = true;
2110                }
2111                toUnHover.erase(anno);
2112            }
2113        }
2114#if 0
2115        writeScene("/tmp/test.osgt");
2116#endif
2117    }
2118    for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = toUnHover.begin();
2119         itr != toUnHover.end(); ++itr) {
2120        _hovered.erase(*itr);
2121        (*itr)->clearDecoration();
2122        _needsRedraw = true;
2123    }
2124}
2125
2126void Renderer::deletePlaceNode(int x, int y, bool invertY)
2127{
2128    if (!_placeNodes.valid()) {
2129        TRACE("No place nodes");
2130        return;
2131    }
2132    osgEarth::IntersectionPicker picker(_viewer.get(), _placeNodes.get());
2133    osgEarth::IntersectionPicker::Hits hits;
2134    float mouseX = (float)x;
2135    float mouseY = (float)y;
2136    if (invertY) {
2137        mouseY = ((float)_windowHeight - mouseY);
2138    }
2139    if (picker.pick(mouseX, mouseY, hits)) {
2140        TRACE("Picker hit!");
2141        // prevent multiple hits on the same instance
2142        std::set<osgEarth::Annotation::AnnotationNode *> fired;
2143        for (osgEarth::IntersectionPicker::Hits::const_iterator hitr = hits.begin();
2144             hitr != hits.end(); ++hitr) {
2145            osgEarth::Annotation::AnnotationNode *anno =
2146                picker.getNode<osgEarth::Annotation::AnnotationNode>(*hitr);
2147            if (anno != NULL && fired.find(anno) == fired.end()) {
2148                fired.insert(anno);
2149                _needsRedraw = true;
2150            }
2151        }
2152        for (std::set<osgEarth::Annotation::AnnotationNode *>::iterator itr = fired.begin();
2153             itr != fired.end(); ++itr) {
2154            (*itr)->clearDecoration();
2155            _placeNodes->removeChild(*itr);
2156            if (_hovered.find(*itr) != _hovered.end()) {
2157                _hovered.erase(*itr);
2158            }
2159        }
2160    } else {
2161        TRACE("NO Picker hits");
2162    }
2163#if 0
2164    writeScene("/tmp/test.osg");
2165#endif
2166}
2167#endif
2168
2169void Renderer::addModelLayer(const char *name,
2170                             osgEarth::ModelSourceOptions& opts,
2171                             bool enableCache,
2172                             bool lighting,
2173                             bool visible)
2174{
2175    if (!_map.valid()) {
2176        ERROR("No map");
2177        return;
2178    }
2179    TRACE("layer: %s", name);
2180    osgEarth::ModelLayerOptions layerOpts(name, opts);
2181    if (!enableCache) {
2182        TRACE("Disabling cache for layer %s", name);
2183        layerOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
2184    }
2185    if (!visible) {
2186        layerOpts.visible() = false;
2187    }
2188    layerOpts.lightingEnabled() = lighting;
2189    _map->addModelLayer(new osgEarth::ModelLayer(layerOpts));
2190    _needsRedraw = true;
2191}
2192
2193void Renderer::removeModelLayer(const char *name)
2194{
2195    if (!_map.valid()) {
2196        ERROR("No map");
2197        return;
2198    }
2199    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
2200    if (layer != NULL) {
2201        _map->removeModelLayer(layer);
2202        _needsRedraw = true;
2203    } else {
2204        TRACE("Model layer not found: %s", name);
2205    }
2206}
2207
2208void Renderer::moveModelLayer(const char *name, unsigned int pos)
2209{
2210    if (!_map.valid()) {
2211        ERROR("No map");
2212        return;
2213    }
2214    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
2215    if (layer != NULL) {
2216        _map->moveModelLayer(layer, pos);
2217        _needsRedraw = true;
2218    } else {
2219        TRACE("Model layer not found: %s", name);
2220    }
2221}
2222
2223void Renderer::setModelLayerOpacity(const char *name, double opacity)
2224{
2225    if (!_map.valid()) {
2226        ERROR("No map");
2227        return;
2228    }
2229    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
2230    if (layer != NULL) {
2231        layer->setOpacity(opacity);
2232        _needsRedraw = true;
2233    } else {
2234        TRACE("Model layer not found: %s", name);
2235    }
2236}
2237#if 0
2238void Renderer::setModelLayerVisibleRange(const char *name, float min, float max)
2239{
2240    if (!_map.valid()) {
2241        ERROR("No map");
2242        return;
2243    }
2244    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
2245    if (layer != NULL) {
2246        layer->minVisibleRange(min);
2247        layer->maxVisibleRange(max);
2248        _needsRedraw = true;
2249    } else {
2250        TRACE("Model layer not found: %s", name);
2251    }
2252}
2253#endif
2254void Renderer::setModelLayerVisibility(const char *name, bool state)
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->setVisible(state);
2263        _needsRedraw = true;
2264    } else {
2265        TRACE("Model layer not found: %s", name);
2266    }
2267}
2268
2269/**
2270 * \brief Resize the render window (image size for renderings)
2271 */
2272void Renderer::setWindowSize(int width, int height)
2273{
2274    if (_windowWidth == width &&
2275        _windowHeight == height) {
2276        TRACE("No change");
2277        return;
2278    }
2279
2280    TRACE("Setting window size to %dx%d", width, height);
2281
2282    double origBitrate = getMaximumBitrate();
2283
2284    _windowWidth = width;
2285    _windowHeight = height;
2286
2287    setMaximumBitrate(origBitrate);
2288    TRACE("Bandwidth target: %.2f Mbps", (float)(getMaximumBitrate()/1.0e6));
2289    TRACE("Frame rate target: %.2f Hz", (float)getMaximumFrameRateInHertz());
2290
2291    if (_viewer.valid()) {
2292#ifdef USE_OFFSCREEN_RENDERING
2293#ifdef USE_PBUFFER
2294        osg::ref_ptr<osg::GraphicsContext> pbuffer;
2295        osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
2296        traits->x = 0;
2297        traits->y = 0;
2298        traits->width = _windowWidth;
2299        traits->height = _windowHeight;
2300        traits->red = 8;
2301        traits->green = 8;
2302        traits->blue = 8;
2303        traits->alpha = 8;
2304        traits->windowDecoration = false;
2305        traits->pbuffer = true;
2306        traits->doubleBuffer = true;
2307        traits->sharedContext = 0;
2308
2309        pbuffer = osg::GraphicsContext::createGraphicsContext(traits.get());
2310        if (pbuffer.valid()) {
2311            TRACE("Pixel buffer has been created successfully.");
2312        } else {
2313            ERROR("Pixel buffer has not been created successfully.");
2314        }
2315        osg::Camera *camera = new osg::Camera;
2316        camera->setGraphicsContext(pbuffer.get());
2317        //camera->getOrCreateStateSet()->setGlobalDefaults();
2318        camera->setViewport(new osg::Viewport(0, 0, _windowWidth, _windowHeight));
2319        GLenum buffer = pbuffer->getTraits()->doubleBuffer ? GL_BACK : GL_FRONT;
2320        camera->setDrawBuffer(buffer);
2321        camera->setReadBuffer(buffer);
2322        camera->setFinalDrawCallback(_captureCallback.get());
2323        _viewer->addSlave(camera, osg::Matrixd(), osg::Matrixd());
2324        _viewer->realize();
2325#else
2326        if (_captureCallback.valid()) {
2327            _captureCallback->getTexture()->setTextureSize(_windowWidth, _windowHeight);
2328        }
2329        osgViewer::ViewerBase::Windows windows;
2330        _viewer->getWindows(windows);
2331        if (windows.size() == 1) {
2332            windows[0]->setWindowRectangle(0, 0, _windowWidth, _windowHeight);
2333        } else {
2334            ERROR("Num windows: %lu", windows.size());
2335        }
2336#endif
2337#else
2338        osgViewer::ViewerBase::Windows windows;
2339        _viewer->getWindows(windows);
2340#if 1
2341        for (osgViewer::Viewer::Windows::iterator itr = windows.begin();
2342             itr != windows.end(); ++itr) {
2343            osgViewer::GraphicsWindow *window = *itr;
2344            window->setWindowRectangle(0, 0, _windowWidth, _windowHeight);
2345            //window->grabFocusIfPointerInWindow();
2346        }
2347        //_viewer->getCamera()->setViewport(new osg::Viewport(0, 0, _windowWidth, _windowHeight));
2348#else
2349        if (windows.size() == 1) {
2350            windows[0]->setWindowRectangle(0, 0, _windowWidth, _windowHeight);
2351        } else {
2352            ERROR("Num windows: %lu", windows.size());
2353        }
2354#endif
2355#endif
2356        // HACK: Without this, the mouse coordinate mapping uses the old size
2357        // for 1 frame.
2358        assert(_viewer->getEventQueue() != NULL);
2359        //TRACE("Window EventQueue: %p", getEventQueue());
2360        //TRACE("Viewer EventQueue: %p", _viewer->getEventQueue());
2361        _viewer->getEventQueue()->windowResize(0, 0, _windowWidth, _windowHeight);
2362        _needsRedraw = true;
2363    }
2364}
2365
2366/**
2367 * \brief Set the orientation of the camera from a quaternion
2368 * rotation
2369 *
2370 * \param[in] quat A quaternion with scalar part first: w,x,y,z
2371 * \param[in] absolute Is rotation absolute or relative?
2372 */
2373void Renderer::setCameraOrientation(const double quat[4], bool absolute)
2374{
2375    if (_manipulator.valid()) {
2376        _manipulator->setRotation(osg::Quat(quat[1], quat[2], quat[3], quat[0]));
2377        _needsRedraw = true;
2378    }
2379}
2380
2381/**
2382 * \brief Reset pan, zoom, clipping planes and optionally rotation
2383 *
2384 * \param[in] resetOrientation Reset the camera rotation/orientation also
2385 */
2386void Renderer::resetCamera(bool resetOrientation)
2387{
2388    TRACE("Enter: resetOrientation=%d", resetOrientation ? 1 : 0);
2389    if (_viewer.valid()) {
2390        _viewer->home();
2391        _needsRedraw = true;
2392    }
2393}
2394
2395/**
2396 * \brief Perform a 2D translation of the camera
2397 *
2398 * x,y pan amount are specified as signed absolute pan amount in viewport
2399 * units -- i.e. 0 is no pan, .5 is half the viewport, 2 is twice the viewport,
2400 * etc.
2401 *
2402 * \param[in] x Viewport coordinate horizontal panning (positive number pans
2403 * camera left, object right)
2404 * \param[in] y Viewport coordinate vertical panning (positive number pans
2405 * camera up, object down)
2406 */
2407void Renderer::panCamera(double x, double y)
2408{
2409    TRACE("Enter: %g %g", x, y);
2410
2411    if (_manipulator.valid()) {
2412        // Wants mouse delta x,y in normalized screen coords
2413        _manipulator->pan(x, y);
2414        _needsRedraw = true;
2415    }
2416}
2417
2418void Renderer::rotateCamera(double x, double y)
2419{
2420    TRACE("Enter: %g %g", x, y);
2421
2422    if (_manipulator.valid()) {
2423        _manipulator->rotate(x, y);
2424        _needsRedraw = true;
2425    }
2426}
2427
2428/**
2429 * \brief Dolly camera or set orthographic scaling based on camera type
2430 *
2431 * \param[in] y Mouse y coordinate in normalized screen coords
2432 */
2433void Renderer::zoomCamera(double y)
2434{
2435    TRACE("Enter: y: %g", y);
2436
2437    if (_manipulator.valid()) {
2438        // +y = zoom out, -y = zoom in
2439        TRACE("camDist: %g", _manipulator->getDistance());
2440#ifdef LIMIT_ZOOM_BY_MAP_SCALE
2441        if ((_mapScale < 0.0 && y > 0.0) ||
2442            (_mapScale < 0.1 && y < 0.0) ||
2443            (_mapScale > 40000.0 && y > 0.0))
2444            return;
2445#endif
2446#if 1
2447       _manipulator->zoom(0, y);
2448#else
2449        double dist = _manipulator->getDistance();
2450        dist *= (1.0 + y);
2451        _manipulator->setDistance(dist);
2452#endif
2453        _needsRedraw = true;
2454    }
2455}
2456
2457bool Renderer::getImageLayerExtent(const char *name, osgEarth::GeoExtent &ext)
2458{
2459    if (!_map.valid()) {
2460        ERROR("No map");
2461        return false;
2462    }
2463    osgEarth::ImageLayer *layer = _map->getImageLayerByName(name);
2464    if (layer != NULL) {
2465        osgEarth::TileSource *ts = layer->getTileSource();
2466        if (ts != NULL) {
2467            ext = ts->getDataExtentsUnion();
2468            if (!ext.isValid() && ts->getProfile() != NULL) {
2469                ext = ts->getProfile()->getExtent();
2470            }
2471            TRACE("Image layer %s srs: %s extent: %g %g %g %g",
2472                 name, ext.getSRS() ? ext.getSRS()->getName().c_str() : "nil",
2473                 ext.xMin(), ext.yMin(), ext.xMax(), ext.yMax());
2474            return true;
2475        }
2476    } else {
2477        TRACE("Image layer not found: %s", name);
2478    }
2479    return false;
2480}
2481
2482bool Renderer::getElevationLayerExtent(const char *name, osgEarth::GeoExtent &ext)
2483{
2484    if (!_map.valid()) {
2485        ERROR("No map");
2486        return false;
2487    }
2488    osgEarth::ElevationLayer *layer = _map->getElevationLayerByName(name);
2489    if (layer != NULL) {
2490        osgEarth::TileSource *ts = layer->getTileSource();
2491        if (ts != NULL) {
2492            ext = ts->getDataExtentsUnion();
2493            if (!ext.isValid() && ts->getProfile() != NULL) {
2494                ext = ts->getProfile()->getExtent();
2495            }
2496            TRACE("Elevation Layer %s srs: %s extent: %g %g %g %g",
2497                 name, ext.getSRS() ? ext.getSRS()->getName().c_str() : "nil",
2498                 ext.xMin(), ext.yMin(), ext.xMax(), ext.yMax());
2499            return true;
2500        }
2501    } else {
2502        TRACE("Elevation layer not found: %s", name);
2503    }
2504    return false;
2505}
2506
2507bool Renderer::getModelLayerExtent(const char *name, osgEarth::GeoExtent &ext)
2508{
2509    if (!_map.valid()) {
2510        ERROR("No map");
2511        return false;
2512    }
2513    osgEarth::ModelLayer *layer = _map->getModelLayerByName(name);
2514    if (layer != NULL) {
2515        osgEarth::ModelSource *ms = layer->getModelSource();
2516        if (ms != NULL) {
2517            osgEarth::DataExtentList& dataExtents = ms->getDataExtents();
2518            if (dataExtents.size() > 0) {
2519                ext = dataExtents[0];
2520                for (unsigned int i = 1; i < dataExtents.size(); i++) {
2521                    ext.expandToInclude(dataExtents[i]);
2522                }
2523            }
2524            if (!ext.isValid()) {
2525                ERROR("Couldn't find extent of layer %s", name);
2526                return false;
2527            }
2528            TRACE("Model Layer %s srs: %s extent: %g %g %g %g",
2529                 name, ext.getSRS() ? ext.getSRS()->getName().c_str() : "nil",
2530                 ext.xMin(), ext.yMin(), ext.xMax(), ext.yMax());
2531            return true;
2532        }
2533    } else {
2534        TRACE("Model layer not found: %s", name);
2535    }
2536    return false;
2537}
2538
2539/**
2540 * \brief Set view to fit a bounding rectangle
2541 *
2542 * A camera distance is chosen such that the extent rectangle
2543 * will fit in the vertical field of view.
2544 *
2545 * \param ext input GeoExtent
2546 * \param durationSecs Animation duration for the view change.
2547 */
2548void Renderer::setViewpointFromExtent(const osgEarth::GeoExtent& ext,
2549                                      double durationSecs)
2550{
2551    setViewpointFromRect(ext.xMin(), ext.yMin(), ext.xMax(), ext.yMax(),
2552                         ext.getSRS(), durationSecs);
2553}
2554
2555/**
2556 * \brief Set view to fit a bounding rectangle
2557 *
2558 * A camera distance is chosen such that the extent rectangle
2559 * will fit in the vertical field of view.
2560 *
2561 * \param xmin Minimum X coord in the given SRS
2562 * \param ymin Minimum Y coord in the given SRS
2563 * \param xmax Maximum X coord in the given SRS
2564 * \param ymax Maximum Y coord in the given SRS
2565 * \param srs Optional SRS of bounding points.  If omitted, wgs84 is assumed.
2566 * \param durationSecs Animation duration for the view change.
2567 */
2568void Renderer::setViewpointFromRect(double xmin, double ymin,
2569                                    double xmax, double ymax,
2570                                    const osgEarth::SpatialReference *srs,
2571                                    double durationSecs)
2572{
2573    if (!_viewer.valid() || !_manipulator.valid() || getMapSRS() == NULL)
2574        return;
2575
2576    double x = 0.0, y = 0.0, distance = 0.0;
2577    double x1 = xmin, y1 = ymin, x2 = xmax, y2 = ymax;
2578    osgEarth::Units distanceUnits = getMapSRS()->getUnits();
2579    if (getMapSRS()->isProjected() && !getMapSRS()->isPlateCarre()) {
2580        TRACE("Projected");
2581        osg::ref_ptr<const osgEarth::SpatialReference> fromSRS =
2582            srs ? srs : osgEarth::SpatialReference::create("wgs84");
2583        fromSRS->transformExtentToMBR(getMapSRS(), x1, y1, x2, y2);
2584        double height = y2 - y1;
2585        double fovy, aspect, near, far;
2586        _viewer->getCamera()->getProjectionMatrixAsPerspective(fovy, aspect, near, far);
2587        distance = height / (2. * tan(osg::DegreesToRadians(fovy)/2.));
2588        TRACE("fov: %g a: %g n: %g f: %g (%g, %g, %g, %g)", fovy, aspect, near, far, x1, y1, x2, y2);
2589        x = x1 + (x2 - x1)/2.0;
2590        y = y1 + (y2 - y1)/2.0;
2591        TRACE("x: %g y: %g, dist: %g", x, y, distance);
2592    } else if (getMapSRS()->isGeographic() && !getMapSRS()->isPlateCarre()) {
2593        // World coords are ECEF
2594        TRACE("Geocentric");
2595        osg::ref_ptr<const osgEarth::SpatialReference> fromSRS =
2596            srs ? srs : osgEarth::SpatialReference::create("wgs84");
2597        fromSRS->transformExtentToMBR(getMapSRS(), x1, y1, x2, y2);
2598        TRACE("(%g, %g, %g, %g)", x1, y1, x2, y2);
2599        // These are angular units
2600        x = x1 + (x2 - x1)/2.0;
2601        y = y1 + (y2 - y1)/2.0;
2602        // These will be ECEF
2603        osg::Vec3d world1, world2, world3, world4;
2604        // bottom
2605        getMapSRS()->transformToWorld(osg::Vec3d(x, y1, 0), world1);
2606        // top
2607        getMapSRS()->transformToWorld(osg::Vec3d(x, y2, 0), world2);
2608        // Focal point on surface
2609        getMapSRS()->transformToWorld(osg::Vec3d(x, y, 0), world3);
2610        // point on line between top and bottom points
2611        world4 = world1 + (world2 - world1)/2.0;
2612        TRACE("world1: %g,%g,%g world2: %g,%g,%g",
2613             world1.x(), world1.y(), world1.z(),
2614             world2.x(), world2.y(), world2.z());
2615        TRACE("world3: %g,%g,%g world4: %g,%g,%g",
2616             world3.x(), world3.y(), world3.z(),
2617             world4.x(), world4.y(), world4.z());
2618        double height = (world2 - world1).length();
2619        double fovy, aspect, near, far;
2620        _viewer->getCamera()->getProjectionMatrixAsPerspective(fovy, aspect, near, far);
2621        distance = height / (2. * tan(osg::DegreesToRadians(fovy)/2.));
2622        distance -= (world4 - world3).length();
2623        if (distance < 0.0) distance = 0.0;
2624        distanceUnits = osgEarth::Units::METERS;
2625        TRACE("fov: %g a: %g n: %g f: %g (%g, %g, %g, %g)", fovy, aspect, near, far, x1, y1, x2, y2);
2626        TRACE("x: %g y: %g, dist: %g", x, y, distance);
2627    } else {
2628        assert(getMapSRS()->isPlateCarre());
2629        ERROR("Plate Carree not supported");
2630        return;
2631    }
2632    TRACE("Map units: %d", getMapSRS()->getUnits().getType());
2633    osgEarth::Viewpoint vpt;
2634    vpt.focalPoint()->set(getMapSRS(), x, y, 0.0, osgEarth::ALTMODE_ABSOLUTE);
2635    vpt.range()->set(distance, distanceUnits);
2636    vpt.heading()->set(0.0, osgEarth::Units::DEGREES);
2637    vpt.pitch()->set(-90.0, osgEarth::Units::DEGREES);
2638
2639    _manipulator->setViewpoint(vpt, durationSecs);
2640    _needsRedraw = true;
2641}
2642
2643double Renderer::getMaxDistanceFromExtent(const osgEarth::GeoExtent& extent)
2644{
2645    if (!_viewer.valid() || extent.getSRS() == NULL)
2646        return 2.0e7;
2647
2648    double x = 0.0, y = 0.0, distance = 0.0;
2649    double x1 = extent.xMin(), y1 = extent.yMin(), x2 = extent.xMax(), y2 = extent.yMax();
2650    osgEarth::Units distanceUnits = extent.getSRS()->getUnits();
2651    if (extent.getSRS()->isProjected() && !extent.getSRS()->isPlateCarre()) {
2652        TRACE("Projected");
2653        double height = y2 - y1;
2654        double fovy, aspect, near, far;
2655        _viewer->getCamera()->getProjectionMatrixAsPerspective(fovy, aspect, near, far);
2656        distance = height / (2. * tan(osg::DegreesToRadians(fovy)/2.));
2657        TRACE("fov: %g a: %g n: %g f: %g (%g, %g, %g, %g)", fovy, aspect, near, far, x1, y1, x2, y2);
2658        x = x1 + (x2 - x1)/2.0;
2659        y = y1 + (y2 - y1)/2.0;
2660        TRACE("x: %g y: %g, dist: %g", x, y, distance);
2661    } else if (extent.getSRS()->isGeographic() && !extent.getSRS()->isPlateCarre()) {
2662        // World coords are ECEF
2663        TRACE("Geocentric");
2664        TRACE("(%g, %g, %g, %g)", x1, y1, x2, y2);
2665        // These are angular units
2666        x = x1 + (x2 - x1)/2.0;
2667        y = y1 + (y2 - y1)/2.0;
2668        // These will be ECEF
2669        osg::Vec3d world1, world2, world3, world4;
2670        // bottom
2671        getMapSRS()->transformToWorld(osg::Vec3d(x, y1, 0), world1);
2672        // top
2673        getMapSRS()->transformToWorld(osg::Vec3d(x, y2, 0), world2);
2674        // Focal point on surface
2675        getMapSRS()->transformToWorld(osg::Vec3d(x, y, 0), world3);
2676        // point on line between top and bottom points
2677        world4 = world1 + (world2 - world1)/2.0;
2678        double height = (world2 - world1).length();
2679        double fovy, aspect, near, far;
2680        _viewer->getCamera()->getProjectionMatrixAsPerspective(fovy, aspect, near, far);
2681        distance = height / (2. * tan(osg::DegreesToRadians(fovy)/2.));
2682        distance -= (world4 - world3).length();
2683        if (distance < 0.0) distance = 0.0;
2684        distanceUnits = osgEarth::Units::METERS;
2685        TRACE("fov: %g a: %g n: %g f: %g (%g, %g, %g, %g)", fovy, aspect, near, far, x1, y1, x2, y2);
2686        TRACE("x: %g y: %g, dist: %g", x, y, distance);
2687    } else {
2688        assert(extent.getSRS()->isPlateCarre());
2689        ERROR("Plate Carree not supported");
2690    }
2691
2692    return distance;
2693}
2694
2695/**
2696 * \brief Dolly camera to set distance from focal point
2697 *
2698 * \param[in] dist distance in world coordinate units (typically meters)
2699 */
2700void Renderer::setCameraDistance(double dist)
2701{
2702    TRACE("Enter: dist: %g", dist);
2703
2704    if (_manipulator.valid()) {
2705        TRACE("camDist: %g", _manipulator->getDistance());
2706
2707        _manipulator->setDistance(dist);
2708
2709        _needsRedraw = true;
2710    }
2711}
2712
2713void Renderer::keyPress(int key)
2714{
2715    osgGA::EventQueue *queue = getEventQueue();
2716    if (queue != NULL) {
2717        queue->keyPress(key);
2718        _needsRedraw = true;
2719    }
2720}
2721
2722void Renderer::keyRelease(int key)
2723{
2724    osgGA::EventQueue *queue = getEventQueue();
2725    if (queue != NULL) {
2726        queue->keyRelease(key);
2727        _needsRedraw = true;
2728    }
2729}
2730
2731void Renderer::setThrowingEnabled(bool state)
2732{
2733    if (_manipulator.valid()) {
2734        _manipulator->getSettings()->setThrowingEnabled(state);
2735    }
2736}
2737
2738void Renderer::mouseDoubleClick(int button, double x, double y)
2739{
2740    osgGA::EventQueue *queue = getEventQueue();
2741    if (queue != NULL) {
2742        queue->mouseDoubleButtonPress((float)x, (float)y, button);
2743        _needsRedraw = true;
2744    }
2745}
2746
2747void Renderer::mouseClick(int button, double x, double y)
2748{
2749    osgGA::EventQueue *queue = getEventQueue();
2750    if (queue != NULL) {
2751        queue->mouseButtonPress((float)x, (float)y, button);
2752        _needsRedraw = true;
2753    }
2754}
2755
2756void Renderer::mouseDrag(int button, double x, double y)
2757{
2758    osgGA::EventQueue *queue = getEventQueue();
2759    if (queue != NULL) {
2760        queue->mouseMotion((float)x, (float)y);
2761        _needsRedraw = true;
2762    }
2763}
2764
2765void Renderer::mouseRelease(int button, double x, double y)
2766{
2767    osgGA::EventQueue *queue = getEventQueue();
2768    if (queue != NULL) {
2769        queue->mouseButtonRelease((float)x, (float)y, button);
2770        _needsRedraw = true;
2771    }
2772}
2773
2774void Renderer::mouseMotion(double x, double y)
2775{
2776#if 1
2777    osgGA::EventQueue *queue = getEventQueue();
2778    if (queue != NULL) {
2779        queue->mouseMotion((float)x, (float)y);
2780        _needsRedraw = true;
2781    }
2782#else
2783    if (_mouseCoordsTool.valid()) {
2784        if (_viewer.valid() && _coordsCallback.valid()) {
2785            osgEarth::GeoPoint mapPt;
2786            if (mapMouseCoords((float)x, (float)y, mapPt)) {
2787                _coordsCallback->set(mapPt, _viewer->asView(), _mapNode);
2788            } else {
2789                _coordsCallback->reset(_viewer->asView(), _mapNode);
2790            }
2791            _needsRedraw = true;
2792        }
2793    }
2794#endif
2795}
2796
2797void Renderer::mouseScroll(int direction)
2798{
2799    osgGA::EventQueue *queue = getEventQueue();
2800    if (queue != NULL) {
2801        queue->mouseScroll((direction > 0 ? osgGA::GUIEventAdapter::SCROLL_UP : osgGA::GUIEventAdapter::SCROLL_DOWN));
2802        _needsRedraw = true;
2803    }
2804}
2805
2806/**
2807 * \brief Set the RGB background color to render into the image
2808 */
2809void Renderer::setBackgroundColor(float color[3])
2810{
2811    _bgColor[0] = color[0];
2812    _bgColor[1] = color[1];
2813    _bgColor[2] = color[2];
2814
2815    if (_viewer.valid()) {
2816        _viewer->getCamera()->setClearColor(osg::Vec4(color[0], color[1], color[2], 1));
2817
2818        _needsRedraw = true;
2819    }
2820}
2821
2822/**
2823 * \brief Sets flag to trigger rendering next time render() is called
2824 */
2825void Renderer::eventuallyRender()
2826{
2827    _needsRedraw = true;
2828}
2829
2830/**
2831 * \brief Get a timeout in usecs for select()
2832 *
2833 * If the paging thread is idle, returns <0 indicating that the
2834 * select call can block until data is available.  Otherwise,
2835 * if the frame render time was faster than the target frame
2836 * rate, return the remaining frame time.
2837 */
2838long Renderer::getTimeout()
2839{
2840    if (!checkNeedToDoFrame())
2841        // <0 means no timeout, block until socket has data
2842        return -1L;
2843    if (_lastFrameTime < _minFrameTime) {
2844        return (long)1.0e6*(_minFrameTime - _lastFrameTime);
2845    } else {
2846        // No timeout (poll)
2847        return 0L;
2848    }
2849}
2850
2851/**
2852 * \brief Check if paging thread is quiescent
2853 */
2854bool Renderer::isPagerIdle()
2855{
2856    if (!_viewer.valid())
2857        return true;
2858    else
2859        return (!_viewer->getDatabasePager()->requiresUpdateSceneGraph() &&
2860                !_viewer->getDatabasePager()->getRequestsInProgress());
2861}
2862
2863/**
2864 * \brief Check is frame call is necessary to render and/or update
2865 * in response to events or timed actions
2866 */
2867bool Renderer::checkNeedToDoFrame()
2868{
2869    return (_needsRedraw || _pickPending ||
2870            (_viewer.valid() && _viewer->checkNeedToDoFrame()));
2871}
2872
2873/**
2874 * \brief MapNode event phase
2875 *
2876 * This is called by the MapNode's event callback during the event
2877 * traversal of the viewer
2878 */
2879void Renderer::mapNodeUpdate()
2880{
2881    computeMapScale();
2882}
2883
2884void Renderer::markFrameStart()
2885{
2886    _startFrameTime = osg::Timer::instance()->tick();
2887}
2888
2889void Renderer::markFrameEnd()
2890{
2891    osg::Timer_t endFrameTick = osg::Timer::instance()->tick();
2892    _lastFrameTime = osg::Timer::instance()->delta_s(_startFrameTime, endFrameTick);
2893    if (_lastFrameTime > _minFrameTime) {
2894        FRAME("BROKE FRAME by %.2f msec", (_lastFrameTime - _minFrameTime)*1000.0f);
2895    } else {
2896        FRAME("Frame time: %.2f msec", _lastFrameTime*1000.0f);
2897    }
2898#ifdef USE_THROTTLING_SLEEP
2899    if (_lastFrameTime < _minFrameTime) {
2900        FRAME("Sleeping for %.2f msec", (_minFrameTime - _lastFrameTime)*1000.0f);
2901        OpenThreads::Thread::microSleep(static_cast<unsigned int>(1000000.0*(_minFrameTime - _lastFrameTime)));
2902    }
2903#endif
2904}
2905
2906/**
2907 * \brief Cause the rendering to render a new image if needed
2908 *
2909 * The _needsRedraw flag indicates if a state change has occured since
2910 * the last rendered frame
2911 */
2912bool Renderer::render()
2913{
2914    if (_viewer.valid() && checkNeedToDoFrame()) {
2915        FRAME("Enter needsRedraw=%d",  _needsRedraw ? 1 : 0);
2916        _renderStartTime = osg::Timer::instance()->tick();
2917        FRAME("Before frame()");
2918        _viewer->frame();
2919        FRAME("After frame()");
2920        _renderStopTime = osg::Timer::instance()->tick();
2921        _renderTime = osg::Timer::instance()->delta_s(_renderStartTime, _renderStopTime);
2922        FRAME("Render time: %g msec", _renderTime * 1000.0);
2923#ifndef SLEEP_AFTER_QUEUE_FRAME
2924        _lastFrameTime = _renderTime;
2925#ifdef USE_THROTTLING_SLEEP
2926        if (_lastFrameTime < _minFrameTime) {
2927            FRAME("Sleeping for %.2f msec", (_minFrameTime - _lastFrameTime)*1000.0f);
2928            OpenThreads::Thread::microSleep(static_cast<unsigned int>(1.0e6*(_minFrameTime - _lastFrameTime)));
2929        }
2930#endif
2931#endif
2932#ifdef WANT_FRAME
2933        if (_viewer->getViewerStats() != NULL) {
2934            _viewer->getViewerStats()->report(std::cerr, _viewer->getViewerStats()->getLatestFrameNumber());
2935        }
2936#endif
2937        _needsRedraw = false;
2938        return true;
2939    } else {
2940        _renderStartTime = _renderStopTime = osg::Timer::instance()->tick();
2941        _renderTime = 0;
2942        return false;
2943    }
2944}
2945
2946/**
2947 * \brief Read back the rendered framebuffer image
2948 */
2949osg::Image *Renderer::getRenderedFrame()
2950{
2951    if (_captureCallback.valid())
2952        return _captureCallback->getImage();
2953    else
2954        return NULL;
2955}
2956
2957void Renderer::setScaleBar(bool state)
2958{
2959    if (_scaleLabel.valid()) {
2960        _scaleLabel->setVisible(state);
2961    }
2962    if (_scaleBar.valid()) {
2963        _scaleBar->setVisible(state);
2964    }
2965    _needsRedraw = true;
2966}
2967
2968void Renderer::setScaleBarUnits(ScaleBarUnits units)
2969{
2970    _scaleBarUnits = units;
2971    _needsRedraw = true;
2972}
2973
2974/**
2975 * \brief Compute the scale ratio of the map based on a horizontal center line
2976 *
2977 * The idea here is to take 2 screen points on a horizontal line in the center
2978 * of the screen and convert to lat/long.  The lat/long coordinates are then
2979 * used to compute the great circle distance (assuming spherical earth) between
2980 * the points.
2981 *
2982 * We could use local projected map coordinates for the distance computation,
2983 * which would be faster; however, this would not show e.g. the change in
2984 * scale at different latitudes
2985 */
2986double Renderer::computeMapScale()
2987{
2988    if (!_scaleLabel.valid() || !_scaleLabel->visible()) {
2989        return -1.0;
2990    }
2991    if (!_mapNode.valid() || _mapNode->getTerrain() == NULL) {
2992        ERROR("No map");
2993        return -1.0;
2994    }
2995    if (!_viewer.valid()) {
2996        ERROR("No viewer");
2997        return -1.0;
2998    }
2999
3000    double x, y;
3001    double pixelWidth = _windowWidth * 0.1 * 2.0;
3002    if (pixelWidth < 10)
3003        pixelWidth = 10;
3004    if (pixelWidth > 150)
3005        pixelWidth = 150;
3006    x = (double)(_windowWidth -1)/2.0 - pixelWidth / 2.0;
3007    y = (double)(_windowHeight-1)/2.0;
3008
3009    osg::Vec3d world1, world2;
3010    if (!_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), x, y, world1)) {
3011        // off map
3012        TRACE("Off map coords: %g %g", x, y);
3013        _scaleLabel->setText("");
3014        _scaleBar->setWidth(0);
3015        return -1.0;
3016    }
3017    x += pixelWidth;
3018    if (!_mapNode->getTerrain()->getWorldCoordsUnderMouse(_viewer->asView(), x, y, world2)) {
3019        // off map
3020        TRACE("Off map coords: %g %g", x, y);
3021        _scaleLabel->setText("");
3022        _scaleBar->setWidth(0);
3023        return -1.0;
3024    }
3025
3026#if 0
3027    TRACE("w1: %g %g %g w2: %g %g %g",
3028          world1.x(), world1.y(), world1.z(),
3029          world2.x(), world2.y(), world2.z());
3030#endif
3031
3032    double meters;
3033    double radius = 6378137.0;
3034    if (_mapNode->getMapSRS() &&
3035        _mapNode->getMapSRS()->getEllipsoid()) {
3036        radius = _mapNode->getMapSRS()->getEllipsoid()->getRadiusEquator();
3037    }
3038    if (!_map->isGeocentric() &&
3039        _mapNode->getMapSRS() &&
3040        _mapNode->getMapSRS()->isGeographic()) {
3041        TRACE("Map is geographic");
3042        // World cords are already lat/long
3043        // Compute great circle distance
3044        meters =
3045            osgEarth::GeoMath::distance(world1, world2, _mapNode->getMapSRS());
3046    } else if (_mapNode->getMapSRS()) {
3047        // Get map coords in lat/long
3048        osgEarth::GeoPoint mapPoint1, mapPoint2;
3049        mapPoint1.fromWorld(_mapNode->getMapSRS(), world1);
3050        mapPoint1.makeGeographic();
3051        mapPoint2.fromWorld(_mapNode->getMapSRS(), world2);
3052        mapPoint2.makeGeographic();
3053        // Compute great circle distance
3054        meters =
3055            osgEarth::GeoMath::distance(osg::DegreesToRadians(mapPoint1.y()),
3056                                        osg::DegreesToRadians(mapPoint1.x()),
3057                                        osg::DegreesToRadians(mapPoint2.y()),
3058                                        osg::DegreesToRadians(mapPoint2.x()),
3059                                        radius);
3060    } else {
3061        // Assume geocentric?
3062        ERROR("No map SRS");
3063        _scaleLabel->setText("");
3064        _scaleBar->setWidth(0);
3065        return -1.0;
3066    }
3067
3068    double scale = meters / pixelWidth;
3069    // 1mi = 5280 feet
3070    //double scaleMiles = scale / 1609.344; // International mile = 1609.344m
3071    //double scaleNauticalMiles = scale / 1852.0; // nautical mile = 1852m
3072    //double scaleUSSurveyMiles = scale / 1609.347218694; // US survey mile = 5280 US survey feet
3073    //double scaleUSSurveyFeet = scale * 3937.0/1200.0; // US survey foot = 1200/3937 m
3074#if 0
3075    TRACE("m: %g px: %g m/px: %g", meters, pixelWidth, scale);
3076#endif
3077    _mapScale = scale;
3078    switch (_scaleBarUnits) {
3079    case UNITS_NAUTICAL_MILES: {
3080        double nmi = meters / 1852.0;
3081        scale = nmi / pixelWidth;
3082        nmi = normalizeScaleNauticalMiles(nmi);
3083        pixelWidth = nmi / scale;
3084        if (_scaleLabel.valid()) {
3085            _scaleLabel->setText(osgEarth::Stringify()
3086                                 << nmi
3087                                 << " nmi");
3088        }
3089    }
3090        break;
3091    case UNITS_US_SURVEY_FEET: {
3092        double feet = meters * 3937.0/1200.0;
3093        scale = feet / pixelWidth;
3094        feet = normalizeScaleFeet(feet);
3095        pixelWidth = feet / scale;
3096        if (_scaleLabel.valid()) {
3097            if (feet >= 5280) {
3098                _scaleLabel->setText(osgEarth::Stringify()
3099                                     << feet / 5280.0
3100                                     << " miUS");
3101             } else {
3102                _scaleLabel->setText(osgEarth::Stringify()
3103                                     << feet
3104                                     << " ftUS");
3105            }
3106        }
3107    }
3108        break;
3109    case UNITS_INTL_FEET: {
3110        double feet = 5280.0 * meters / 1609.344;
3111        scale = feet / pixelWidth;
3112        feet = normalizeScaleFeet(feet);
3113        pixelWidth = feet / scale;
3114        if (_scaleLabel.valid()) {
3115            if (feet >= 5280) {
3116                _scaleLabel->setText(osgEarth::Stringify()
3117                                     << feet / 5280.0
3118                                     << " mi");
3119            } else {
3120                _scaleLabel->setText(osgEarth::Stringify()
3121                                     << feet
3122                                     << " ft");
3123            }
3124        }
3125    }
3126        break;
3127    case UNITS_METERS:
3128    default: {
3129        meters = normalizeScaleMeters(meters);
3130        pixelWidth = meters / scale;
3131        if (_scaleLabel.valid()) {
3132            if (meters >= 1000) {
3133                _scaleLabel->setText(osgEarth::Stringify()
3134                                     << meters / 1000.0
3135                                     << " km");
3136            } else {
3137                _scaleLabel->setText(osgEarth::Stringify()
3138                                     << meters
3139                                     << " m");
3140            }
3141        }
3142    }
3143        break;
3144    }
3145    if (_scaleBar.valid()) {
3146        _scaleBar->setWidth(pixelWidth);
3147    }
3148    return scale;
3149}
3150
3151std::string Renderer::getCanonicalPath(const std::string& url) const
3152{
3153    std::string retStr;
3154    std::string proto = osgDB::getServerProtocol(url);
3155    if (proto.empty()) {
3156        retStr = osgDB::getRealPath(url);
3157        if (!osgDB::fileExists(retStr)) {
3158            if (!url.empty() && url[0] != '/') {
3159                // Relative URL, assume local://
3160                std::ostringstream oss;
3161                oss << getCacheDirectory() << "/" << url;
3162                retStr = oss.str();
3163            } else {
3164                retStr = "";
3165            }
3166        }
3167    } else if (proto == "local") {
3168        TRACE("Local protocol: '%s'", getLocalFilePath(url).c_str());
3169        std::ostringstream oss;
3170        oss << getCacheDirectory() << "/" << getLocalFilePath(url);
3171        retStr = oss.str();
3172    } else if (proto == "file") {
3173        TRACE("File: '/%s'", osgDB::getServerFileName(url).c_str());
3174        std::ostringstream oss;
3175        oss << "/" <<  osgDB::getServerFileName(url);
3176        retStr = oss.str();
3177    } else if (proto == "idata") {
3178        std::string fileName = osgDB::getServerFileName(url);
3179        TRACE("IData protocol: coll: '%s', '%s'", osgDB::getServerAddress(url).c_str(), fileName.c_str());
3180        int collection = atoi(osgDB::getServerAddress(url).c_str());
3181        {
3182            std::ostringstream oss;
3183            oss << getCacheDirectory() << "/" << fileName;
3184            retStr = oss.str();
3185            std::string dirPath = osgDB::getFilePath(retStr);
3186            osgDB::makeDirectory(dirPath);
3187        }
3188        // If this is a shape file, get auxiliary files (e.g. .dbf, .shx, etc)
3189        if (osgDB::getFileExtension(fileName) == "shp") {
3190            // Check for files with same basename
3191            std::vector<DirectoryItem> items;
3192            std::string path = osgDB::getFilePath(fileName);
3193            getContents(collection, path.c_str(), items);
3194            // Get name without extension
3195            std::string basename = osgDB::getStrippedName(fileName);
3196            for (std::vector<DirectoryItem>::const_iterator itr = items.begin();
3197                 itr != items.end(); ++itr) {
3198                if (!itr->isDir &&
3199                    osgDB::getStrippedName(itr->name) == basename) {
3200                    std::string localName;
3201                    {
3202                        std::ostringstream oss;
3203                        oss << getCacheDirectory() << "/" << itr->name;
3204                        localName = oss.str();
3205                    }
3206                    if (!localName.empty() && !osgDB::fileExists(localName)) {
3207                        IData::Buffer buf;
3208                        {
3209                            std::ostringstream oss;
3210                            oss << "//" << itr->name;
3211                            IData::getFile(collection, oss.str().c_str(), &buf);
3212                        }
3213                        std::ofstream file(localName.c_str());
3214                        file.write((char *)buf.data, buf.size);
3215                        file.close();
3216                    }
3217                }
3218            }
3219        }
3220        if (!osgDB::fileExists(retStr)) {
3221            std::ostringstream oss;
3222            oss << "//" << fileName;
3223            IData::Buffer buf;
3224            IData::getFile(collection, oss.str().c_str(), &buf);
3225            std::ofstream file(retStr.c_str());
3226            file.write((char *)buf.data, buf.size);
3227            file.close();
3228        }
3229    } else {
3230        TRACE("Protocol: '%s' url: '%s'", proto.c_str(), url.c_str());
3231        retStr = url;
3232    }
3233    return retStr;
3234}
3235
3236void Renderer::writeScene(const std::string& file)
3237{
3238    if (_sceneRoot.valid()) {
3239        GraphPrintVisitor gpv;
3240        _sceneRoot->accept(gpv);
3241        //osgDB::writeNodeFile(*_sceneRoot.get(), file);
3242    }
3243}
Note: See TracBrowser for help on using the repository browser.