source: geovis/trunk/Renderer.cpp @ 5972

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

Add command to configure attributes to display in feature info placard.

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