source: geovis/trunk/RendererCmd.cpp @ 5935

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

Bypass file path check for db driver

File size: 126.6 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 <cstdlib>
9#include <cstdio>
10#include <cstring>
11#include <cfloat>
12#include <cerrno>
13#include <string>
14#include <sstream>
15#include <limits>
16#include <vector>
17#include <unistd.h>
18#include <sys/select.h>
19#include <sys/uio.h>
20#include <tcl.h>
21
22#include <osgDB/FileUtils>
23#include <osgDB/FileNameUtils>
24
25#include <osgEarth/Version>
26#include <osgEarth/Registry>
27#include <osgEarth/Units>
28#include <osgEarth/Config>
29#include <osgEarthFeatures/FeatureModelSource>
30#include <osgEarthSymbology/Color>
31#include <osgEarthSymbology/Style>
32#include <osgEarthSymbology/StyleSheet>
33#include <osgEarthSymbology/IconSymbol>
34#include <osgEarthSymbology/LineSymbol>
35#include <osgEarthSymbology/RenderSymbol>
36
37#include <osgEarthDrivers/osg/ColorRampOptions>
38#include <osgEarthDrivers/debug/DebugOptions>
39#include <osgEarthDrivers/arcgis/ArcGISOptions>
40#include <osgEarthDrivers/gdal/GDALOptions>
41#include <osgEarthDrivers/tms/TMSOptions>
42#include <osgEarthDrivers/wcs/WCSOptions>
43#include <osgEarthDrivers/wms/WMSOptions>
44#include <osgEarthDrivers/xyz/XYZOptions>
45#include <osgEarthDrivers/model_feature_geom/FeatureGeomModelOptions>
46#include <osgEarthDrivers/feature_ogr/OGRFeatureOptions>
47#include <osgEarthDrivers/feature_tfs/TFSFeatureOptions>
48#include <osgEarthDrivers/feature_wfs/WFSFeatureOptions>
49
50#include "Trace.h"
51#include "CmdProc.h"
52#include "ReadBuffer.h"
53#include "Types.h"
54#include "RendererCmd.h"
55#include "RenderServer.h"
56#include "Renderer.h"
57#include "IData.h"
58#include "Stats.h"
59#include "PPMWriter.h"
60#include "TGAWriter.h"
61#include "ResponseQueue.h"
62#ifdef USE_READ_THREAD
63#include "CommandQueue.h"
64#endif
65#include "Util.h"
66
67#if OSGEARTH_MIN_VERSION_REQUIRED(2, 7, 0)
68#define NEW_VIEWPOINT_API
69#endif
70
71using namespace GeoVis;
72
73static int lastCmdStatus;
74
75#ifndef USE_THREADS
76static ssize_t
77SocketWrite(const void *bytes, size_t len)
78{
79    size_t ofs = 0;
80    ssize_t bytesWritten;
81    while ((bytesWritten = write(g_fdOut, (const char *)bytes + ofs, len - ofs)) > 0) {
82        ofs += bytesWritten;
83        if (ofs == len)
84            break;
85    }
86    if (bytesWritten < 0) {
87        ERROR("write: %s", strerror(errno));
88    }
89    return bytesWritten;
90}
91#endif
92
93static bool
94SocketRead(char *bytes, size_t len)
95{
96    ReadBuffer::BufferStatus status;
97    status = g_inBufPtr->followingData((unsigned char *)bytes, len);
98    TRACE("followingData status: %d", status);
99    return (status == ReadBuffer::OK);
100}
101
102ssize_t
103GeoVis::queueResponse(const void *bytes, size_t len,
104                      Response::AllocationType allocType,
105                      Response::ResponseType type)
106{
107#ifdef USE_THREADS
108    Response *response = new Response(type);
109    response->setMessage((unsigned char *)bytes, len, allocType);
110    g_outQueue->enqueue(response);
111    return (ssize_t)len;
112#else
113    return SocketWrite(bytes, len);
114#endif
115}
116
117static int
118ExecuteCommand(Tcl_Interp *interp, Tcl_DString *dsPtr)
119{
120    int result;
121#ifdef WANT_TRACE
122    char *str = Tcl_DStringValue(dsPtr);
123    std::string cmd(str);
124    cmd.erase(cmd.find_last_not_of(" \n\r\t")+1);
125    TRACE("command %lu: '%s'", g_stats.nCommands, cmd.c_str());
126#endif
127    lastCmdStatus = TCL_OK;
128    result = Tcl_EvalEx(interp, Tcl_DStringValue(dsPtr),
129                        Tcl_DStringLength(dsPtr),
130                        TCL_EVAL_DIRECT | TCL_EVAL_GLOBAL);
131    Tcl_DStringSetLength(dsPtr, 0);
132    if (lastCmdStatus == TCL_BREAK) {
133        return TCL_BREAK;
134    }
135    lastCmdStatus = result;
136    if (result != TCL_OK) {
137        TRACE("Error: %d", result);
138    }
139    return result;
140}
141
142static int
143GetBooleanFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, bool *boolPtr)
144{
145    int value;
146
147    if (Tcl_GetBooleanFromObj(interp, objPtr, &value) != TCL_OK) {
148        return TCL_ERROR;
149    }
150    *boolPtr = (bool)value;
151    return TCL_OK;
152}
153#if 0
154static short
155GetShortFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, short *valuePtr)
156{
157    int value;
158
159    if (Tcl_GetIntFromObj(interp, objPtr, &value) != TCL_OK) {
160        return TCL_ERROR;
161    }
162    *valuePtr = (short)value;
163    return TCL_OK;
164}
165#endif
166static unsigned short
167GetUShortFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, unsigned short *valuePtr)
168{
169    int value;
170
171    if (Tcl_GetIntFromObj(interp, objPtr, &value) != TCL_OK) {
172        return TCL_ERROR;
173    }
174    *valuePtr = (unsigned short)value;
175    return TCL_OK;
176}
177
178static unsigned int
179GetUIntFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, unsigned int *valuePtr)
180{
181    long value;
182
183    if (Tcl_GetLongFromObj(interp, objPtr, &value) != TCL_OK) {
184        return TCL_ERROR;
185    }
186    *valuePtr = (unsigned int)value;
187    return TCL_OK;
188}
189
190static int
191GetFloatFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, float *valuePtr)
192{
193    double value;
194
195    if (Tcl_GetDoubleFromObj(interp, objPtr, &value) != TCL_OK) {
196        return TCL_ERROR;
197    }
198    *valuePtr = (float)value;
199    return TCL_OK;
200}
201
202static osgEarth::Symbology::TextSymbol::Alignment
203ParseTextAlignment(const char *str,
204                   osgEarth::Symbology::TextSymbol::Alignment defAlign =
205                   osgEarth::Symbology::TextSymbol::ALIGN_LEFT_BASE_LINE)
206{
207    osgEarth::Symbology::TextSymbol::Alignment alignment = defAlign;
208    if (str[0] == 'l' && strcmp(str, "left_top") == 0) {
209        alignment = osgEarth::Symbology::TextSymbol::ALIGN_LEFT_TOP;
210    } else if (str[0] == 'l' && strcmp(str, "left_center") == 0) {
211        alignment = osgEarth::Symbology::TextSymbol::ALIGN_LEFT_CENTER;
212    } else if (str[0] == 'l' && strcmp(str, "left_bottom") == 0) {
213        alignment = osgEarth::Symbology::TextSymbol::ALIGN_LEFT_BOTTOM;
214    } else if (str[0] == 'c' && strcmp(str, "center_top") == 0) {
215        alignment = osgEarth::Symbology::TextSymbol::ALIGN_CENTER_TOP;
216    } else if (str[0] == 'c' && strcmp(str, "center_center") == 0) {
217        alignment = osgEarth::Symbology::TextSymbol::ALIGN_CENTER_CENTER;
218    } else if (str[0] == 'c' && strcmp(str, "center_bottom") == 0) {
219        alignment = osgEarth::Symbology::TextSymbol::ALIGN_CENTER_BOTTOM;
220    } else if (str[0] == 'r' && strcmp(str, "right_top") == 0) {
221        alignment = osgEarth::Symbology::TextSymbol::ALIGN_RIGHT_TOP;
222    } else if (str[0] == 'r' && strcmp(str, "right_center") == 0) {
223        alignment = osgEarth::Symbology::TextSymbol::ALIGN_RIGHT_CENTER;
224    } else if (str[0] == 'r' && strcmp(str, "right_bottom") == 0) {
225        alignment = osgEarth::Symbology::TextSymbol::ALIGN_RIGHT_BOTTOM;
226    } else if (str[0] == 'l' && strcmp(str, "left_baseline") == 0) {
227        alignment = osgEarth::Symbology::TextSymbol::ALIGN_LEFT_BASE_LINE;
228    } else if (str[0] == 'c' && strcmp(str, "center_baseline") == 0) {
229        alignment = osgEarth::Symbology::TextSymbol::ALIGN_CENTER_BASE_LINE;
230    } else if (str[0] == 'r' && strcmp(str, "right_baseline") == 0) {
231        alignment = osgEarth::Symbology::TextSymbol::ALIGN_RIGHT_BASE_LINE;
232    } else  if (str[0] == 'l' && strcmp(str, "left_bottom_baseline") == 0) {
233        alignment = osgEarth::Symbology::TextSymbol::ALIGN_LEFT_BOTTOM_BASE_LINE;
234    } else if (str[0] == 'c' && strcmp(str, "center_bottom_baseline") == 0) {
235        alignment = osgEarth::Symbology::TextSymbol::ALIGN_CENTER_BOTTOM_BASE_LINE;
236    } else if (str[0] == 'r' && strcmp(str, "right_bottom_baseline") == 0) {
237        alignment = osgEarth::Symbology::TextSymbol::ALIGN_RIGHT_BOTTOM_BASE_LINE;
238    }
239    return alignment;
240}
241
242static osgEarth::Symbology::IconSymbol::Alignment
243ParseIconAlignment(const char *str,
244                   osgEarth::Symbology::IconSymbol::Alignment defAlign =
245                   osgEarth::Symbology::IconSymbol::ALIGN_CENTER_BOTTOM)
246{
247    osgEarth::Symbology::IconSymbol::Alignment alignment = defAlign;
248    if (str[0] == 'l' && strcmp(str, "left_top") == 0) {
249        alignment = osgEarth::Symbology::IconSymbol::ALIGN_LEFT_TOP;
250    } else if (str[0] == 'l' && strcmp(str, "left_center") == 0) {
251        alignment = osgEarth::Symbology::IconSymbol::ALIGN_LEFT_CENTER;
252    } else if (str[0] == 'l' && strcmp(str, "left_bottom") == 0) {
253        alignment = osgEarth::Symbology::IconSymbol::ALIGN_LEFT_BOTTOM;
254    } else if (str[0] == 'c' && strcmp(str, "center_top") == 0) {
255        alignment = osgEarth::Symbology::IconSymbol::ALIGN_CENTER_TOP;
256    } else if (str[0] == 'c' && strcmp(str, "center_center") == 0) {
257        alignment = osgEarth::Symbology::IconSymbol::ALIGN_CENTER_CENTER;
258    } else if (str[0] == 'c' && strcmp(str, "center_bottom") == 0) {
259        alignment = osgEarth::Symbology::IconSymbol::ALIGN_CENTER_BOTTOM;
260    } else if (str[0] == 'r' && strcmp(str, "right_top") == 0) {
261        alignment = osgEarth::Symbology::IconSymbol::ALIGN_RIGHT_TOP;
262    } else if (str[0] == 'r' && strcmp(str, "right_center") == 0) {
263        alignment = osgEarth::Symbology::IconSymbol::ALIGN_RIGHT_CENTER;
264    } else if (str[0] == 'r' && strcmp(str, "right_bottom") == 0) {
265        alignment = osgEarth::Symbology::IconSymbol::ALIGN_RIGHT_BOTTOM;
266    }
267    return alignment;
268}
269
270static osgEarth::Symbology::InstanceSymbol::Placement
271ParseInstancePlacement(const char *str,
272                       osgEarth::Symbology::InstanceSymbol::Placement defPlacement =
273                       osgEarth::Symbology::InstanceSymbol::PLACEMENT_VERTEX)
274{
275    osgEarth::Symbology::InstanceSymbol::Placement placement = defPlacement;
276    if (str[0] == 'v' && strcmp(str, "vertex") == 0) {
277        placement = osgEarth::Symbology::InstanceSymbol::PLACEMENT_VERTEX;
278    } else if (str[0] == 'c' && strcmp(str, "centroid") == 0) {
279        placement = osgEarth::Symbology::InstanceSymbol::PLACEMENT_CENTROID;
280    } else if (str[0] == 'i' && strcmp(str, "interval") == 0) {
281        placement = osgEarth::Symbology::InstanceSymbol::PLACEMENT_INTERVAL;
282    } else if (str[0] == 'r' && strcmp(str, "random") == 0) {
283        placement = osgEarth::Symbology::InstanceSymbol::PLACEMENT_RANDOM;
284    }
285    return placement;
286}
287
288static osgEarth::Symbology::AltitudeSymbol::Clamping
289ParseClamping(const char *str,
290              osgEarth::Symbology::AltitudeSymbol::Clamping defTechnique =
291              osgEarth::Symbology::AltitudeSymbol::CLAMP_NONE)
292{
293    osgEarth::Symbology::AltitudeSymbol::Clamping clamping = defTechnique;
294    if (str[0] == 'n' && strcmp(str, "none") == 0) {
295        clamping = osgEarth::Symbology::AltitudeSymbol::CLAMP_NONE;
296    } else if (str[0] == 't' && strcmp(str, "terrain") == 0) {
297        clamping = osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
298    } else if (str[0] == 'r' && strcmp(str, "relative") == 0) {
299        clamping = osgEarth::Symbology::AltitudeSymbol::CLAMP_RELATIVE_TO_TERRAIN;
300    } else if (str[0] == 'a' && strcmp(str, "absolute") == 0) {
301        clamping = osgEarth::Symbology::AltitudeSymbol::CLAMP_ABSOLUTE;
302    }
303    return clamping;
304}
305
306static osgEarth::Symbology::AltitudeSymbol::Technique
307ParseClampingTechnique(const char *str,
308                       osgEarth::Symbology::AltitudeSymbol::Technique defTechnique =
309                       osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_DRAPE)
310{
311    osgEarth::Symbology::AltitudeSymbol::Technique technique = defTechnique;
312    if (str[0] == 'd' && strcmp(str, "drape") == 0) {
313        technique = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_DRAPE;
314    } else if (str[0] == 'g' && strcmp(str, "gpu") == 0) {
315        technique = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
316    } else if (str[0] == 's' && strcmp(str, "scene") == 0) {
317        technique = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_SCENE;
318    } else if (str[0] == 'm' && strcmp(str, "map") == 0) {
319        technique = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_MAP;
320    }
321    return technique;
322}
323
324static osgEarth::Symbology::Stroke::LineCapStyle
325ParseLineCapStyle(const char *str,
326                  osgEarth::Symbology::Stroke::LineCapStyle defLineCap =
327                  osgEarth::Symbology::Stroke::LINECAP_FLAT)
328{
329    osgEarth::Symbology::Stroke::LineCapStyle lineCap = defLineCap;
330    if (str[0] == 'f' && strcmp(str, "flat") == 0) {
331        lineCap = osgEarth::Symbology::Stroke::LINECAP_FLAT;
332    } else if (str[0] == 's' && strcmp(str, "square") == 0) {
333        lineCap = osgEarth::Symbology::Stroke::LINECAP_SQUARE;
334    } else if (str[0] == 'r' && strcmp(str, "round") == 0) {
335        lineCap = osgEarth::Symbology::Stroke::LINECAP_ROUND;
336    }
337    return lineCap;
338}
339
340static osgEarth::Symbology::Stroke::LineJoinStyle
341ParseLineJoinStyle(const char *str,
342                   osgEarth::Symbology::Stroke::LineJoinStyle defLineJoin =
343                   osgEarth::Symbology::Stroke::LINEJOIN_MITRE)
344{
345    osgEarth::Symbology::Stroke::LineJoinStyle lineJoin = defLineJoin;
346    if (str[0] == 'm' &&
347       (strcmp(str, "mitre") == 0 || strcmp(str, "miter") == 0)) {
348        lineJoin = osgEarth::Symbology::Stroke::LINEJOIN_MITRE;
349    } else if (str[0] == 'r' && strcmp(str, "round") == 0) {
350        lineJoin = osgEarth::Symbology::Stroke::LINEJOIN_ROUND;
351    }
352    return lineJoin;
353}
354
355static int
356CameraDeleteViewpointOp(ClientData clientData, Tcl_Interp *interp, int objc,
357                        Tcl_Obj *const *objv)
358{
359    char *name = Tcl_GetString(objv[2]);
360
361    g_renderer->removeNamedViewpoint(name);
362    return TCL_OK;
363}
364
365static int
366CameraCenterOp(ClientData clientData, Tcl_Interp *interp, int objc,
367               Tcl_Obj *const *objv)
368{
369    double x, y;
370    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
371        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
372        return TCL_ERROR;
373    }
374    double zoom = 1.0;
375    if (objc > 4) {
376        if (Tcl_GetDoubleFromObj(interp, objv[4], &zoom) != TCL_OK) {
377            return TCL_ERROR;
378        }
379    }
380    double duration = 1.0;
381    if (objc > 5) {
382        if (Tcl_GetDoubleFromObj(interp, objv[5], &duration) != TCL_OK) {
383            return TCL_ERROR;
384        }
385    }
386
387    osgEarth::Viewpoint vpt = g_renderer->getViewpoint();
388#ifdef NEW_VIEWPOINT_API
389    vpt.focalPoint()->x() = x;
390    vpt.focalPoint()->y() = y;
391    //vpt.focalPoint()->z() = mapPoint.z();
392    vpt.range()->set(vpt.range()->getValue() * zoom, osgEarth::Units::METERS);
393#else
394    vpt.x() = x;
395    vpt.y() = y;
396    //vpt.z() = mapPoint.z();
397    vpt.setRange(vpt.getRange() * zoom);
398
399#endif
400    g_renderer->setViewpoint(vpt, duration);
401
402    return TCL_OK;
403}
404
405static int
406CameraGetViewpointOp(ClientData clientData, Tcl_Interp *interp, int objc,
407                     Tcl_Obj *const *objv)
408{
409    osgEarth::Viewpoint view = g_renderer->getViewpoint();
410
411    std::ostringstream oss;
412    size_t len = 0;
413    oss << "nv>camera get "
414#ifdef NEW_VIEWPOINT_API
415        << view.focalPoint()->x() << " "
416        << view.focalPoint()->y() << " "
417        << view.focalPoint()->z() << " "
418        << view.heading()->getValue() << " "
419        << view.pitch()->getValue() << " "
420        << view.range()->getValue()
421        << " {" << ((view.focalPoint()->getSRS() == NULL) ? "" : view.focalPoint()->getSRS()->getHorizInitString()) << "}"
422        << " {" << ((view.focalPoint()->getSRS() == NULL) ? "" : view.focalPoint()->getSRS()->getVertInitString()) << "}"
423#else
424        << view.x() << " "
425        << view.y() << " "
426        << view.z() << " "
427        << view.getHeading() << " "
428        << view.getPitch() << " "
429        << view.getRange()
430        << " {" << ((view.getSRS() == NULL) ? "" : view.getSRS()->getHorizInitString()) << "}"
431        << " {" << ((view.getSRS() == NULL) ? "" : view.getSRS()->getVertInitString()) << "}"
432#endif
433        << "\n";
434    std::string ostr = oss.str();
435    len = ostr.size();
436#ifdef USE_THREADS
437    queueResponse(ostr.c_str(), len, Response::VOLATILE);
438#else
439    ssize_t bytesWritten = SocketWrite(ostr.c_str(), len);
440
441    if (bytesWritten < 0) {
442        return TCL_ERROR;
443    }
444#endif /*USE_THREADS*/
445    return TCL_OK;
446}
447
448static int
449CameraGoOp(ClientData clientData, Tcl_Interp *interp, int objc,
450           Tcl_Obj *const *objv)
451{
452    int x, y;
453    if (Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK ||
454        Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK) {
455        return TCL_ERROR;
456    }
457    double zoom = 1.0;
458    if (objc > 4) {
459        if (Tcl_GetDoubleFromObj(interp, objv[4], &zoom) != TCL_OK) {
460            return TCL_ERROR;
461        }
462    }
463    double duration = 1.0;
464    if (objc > 5) {
465        if (Tcl_GetDoubleFromObj(interp, objv[5], &duration) != TCL_OK) {
466            return TCL_ERROR;
467        }
468    }
469
470    osgEarth::GeoPoint mapPoint;
471    if (g_renderer->mapMouseCoords(x, y, mapPoint)) {
472        osgEarth::Viewpoint vpt = g_renderer->getViewpoint();
473#ifdef NEW_VIEWPOINT_API
474        vpt.focalPoint() = mapPoint;
475        vpt.range()->set(vpt.range()->getValue() * zoom, osgEarth::Units::METERS);
476#else
477        vpt.x() = mapPoint.x();
478        vpt.y() = mapPoint.y();
479        vpt.z() = mapPoint.z();
480        vpt.setRange(vpt.getRange() * zoom);
481#endif
482        g_renderer->setViewpoint(vpt, duration);
483    } else {
484        // Out of map bounds
485    }
486    return TCL_OK;
487}
488
489static int
490CameraOrientOp(ClientData clientData, Tcl_Interp *interp, int objc,
491               Tcl_Obj *const *objv)
492{
493    double quat[4];
494
495    if (Tcl_GetDoubleFromObj(interp, objv[2], &quat[0]) != TCL_OK ||
496        Tcl_GetDoubleFromObj(interp, objv[3], &quat[1]) != TCL_OK ||
497        Tcl_GetDoubleFromObj(interp, objv[4], &quat[2]) != TCL_OK ||
498        Tcl_GetDoubleFromObj(interp, objv[5], &quat[3]) != TCL_OK) {
499        return TCL_ERROR;
500    }
501
502    g_renderer->setCameraOrientation(quat);
503    return TCL_OK;
504}
505
506static int
507CameraPanOp(ClientData clientData, Tcl_Interp *interp, int objc,
508            Tcl_Obj *const *objv)
509{
510    double x, y;
511
512    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
513        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
514        return TCL_ERROR;
515    }
516
517    g_renderer->panCamera(x, y);
518    return TCL_OK;
519}
520
521static int
522CameraResetOp(ClientData clientData, Tcl_Interp *interp, int objc,
523              Tcl_Obj *const *objv)
524{
525    if (objc == 3) {
526        const char *string = Tcl_GetString(objv[2]);
527        char c = string[0];
528        if ((c != 'a') || (strcmp(string, "all") != 0)) {
529            Tcl_AppendResult(interp, "bad camera reset option \"", string,
530                         "\": should be all", (char*)NULL);
531            return TCL_ERROR;
532        }
533        g_renderer->resetCamera(true);
534    } else {
535        g_renderer->resetCamera(false);
536    }
537    return TCL_OK;
538}
539
540static int
541CameraRestoreViewpointOp(ClientData clientData, Tcl_Interp *interp, int objc,
542                         Tcl_Obj *const *objv)
543{
544    char *name = Tcl_GetString(objv[2]);
545
546    double duration = 0.0;
547    if (objc > 3) {
548        if (Tcl_GetDoubleFromObj(interp, objv[3], &duration) != TCL_OK) {
549            return TCL_ERROR;
550        }
551    }
552    if (!g_renderer->restoreNamedViewpoint(name, duration)) {
553        Tcl_AppendResult(interp, "camera viewpoint \"", name,
554                         "\" not found", (char*)NULL);
555        return TCL_ERROR;
556    }
557    return TCL_OK;
558}
559
560static int
561CameraRotateOp(ClientData clientData, Tcl_Interp *interp, int objc,
562               Tcl_Obj *const *objv)
563{
564    double x, y;
565
566    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
567        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
568        return TCL_ERROR;
569    }
570
571    g_renderer->rotateCamera(x, y);
572    return TCL_OK;
573}
574
575static int
576CameraSaveViewpointOp(ClientData clientData, Tcl_Interp *interp, int objc,
577                      Tcl_Obj *const *objv)
578{
579    char *name = Tcl_GetString(objv[2]);
580
581    g_renderer->saveNamedViewpoint(name);
582    return TCL_OK;
583}
584
585static int
586CameraSetByExtentOp(ClientData clientData, Tcl_Interp *interp, int objc,
587                    Tcl_Obj *const *objv)
588{
589    double xmin, ymin, xmax, ymax;
590    double duration = 0.0;
591    if (Tcl_GetDoubleFromObj(interp, objv[2], &xmin) != TCL_OK ||
592        Tcl_GetDoubleFromObj(interp, objv[3], &ymin) != TCL_OK ||
593        Tcl_GetDoubleFromObj(interp, objv[4], &xmax) != TCL_OK ||
594        Tcl_GetDoubleFromObj(interp, objv[5], &ymax) != TCL_OK) {
595        return TCL_ERROR;
596    }
597    if (objc > 6) {
598        if (Tcl_GetDoubleFromObj(interp, objv[6], &duration) != TCL_OK) {
599            return TCL_ERROR;
600        }
601    }
602    osg::ref_ptr<const osgEarth::SpatialReference> srs = g_renderer->getMapSRS();
603    if (objc > 7) {
604        char *srsInit = Tcl_GetString(objv[7]);
605        if (strlen(srsInit) > 0) {
606            srs = osgEarth::SpatialReference::get(srsInit);
607        }
608    }
609    g_renderer->setViewpointFromRect(xmin, ymin, xmax, ymax, srs.get(), duration);
610    return TCL_OK;
611}
612
613static int
614CameraSetByLayerExtentOp(ClientData clientData, Tcl_Interp *interp, int objc,
615                         Tcl_Obj *const *objv)
616{
617    const char *name = Tcl_GetString(objv[2]);
618    double duration = 0.0;
619    if (objc > 3) {
620        if (Tcl_GetDoubleFromObj(interp, objv[3], &duration) != TCL_OK) {
621            return TCL_ERROR;
622        }
623    }
624    osgEarth::GeoExtent ext;
625    if (g_renderer->getImageLayerExtent(name, ext)) {
626        g_renderer->setViewpointFromExtent(ext, duration);
627        return TCL_OK;
628    } else if (g_renderer->getElevationLayerExtent(name, ext)) {
629        g_renderer->setViewpointFromExtent(ext, duration);
630        return TCL_OK;
631    } else if (g_renderer->getModelLayerExtent(name, ext)) {
632        g_renderer->setViewpointFromExtent(ext, duration);
633        return TCL_OK;
634    }
635    return TCL_ERROR;
636}
637
638static int
639CameraSetDistanceOp(ClientData clientData, Tcl_Interp *interp, int objc,
640                    Tcl_Obj *const *objv)
641{
642    double dist;
643
644    if (Tcl_GetDoubleFromObj(interp, objv[2], &dist) != TCL_OK) {
645        return TCL_ERROR;
646    }
647
648    g_renderer->setCameraDistance(dist);
649    return TCL_OK;
650}
651
652static int
653CameraSetViewpointOp(ClientData clientData, Tcl_Interp *interp, int objc,
654                     Tcl_Obj *const *objv)
655{
656    double x, y, z, heading, pitch, distance;
657    double duration = 0.0;
658    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
659        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK ||
660        Tcl_GetDoubleFromObj(interp, objv[4], &z) != TCL_OK ||
661        Tcl_GetDoubleFromObj(interp, objv[5], &heading) != TCL_OK ||
662        Tcl_GetDoubleFromObj(interp, objv[6], &pitch) != TCL_OK ||
663        Tcl_GetDoubleFromObj(interp, objv[7], &distance) != TCL_OK) {
664        return TCL_ERROR;
665    }
666    if (objc > 8) {
667        if (Tcl_GetDoubleFromObj(interp, objv[8], &duration) != TCL_OK) {
668            return TCL_ERROR;
669        }
670    }
671    const osgEarth::SpatialReference *srs = g_renderer->getMapSRS();
672    if (objc > 9) {
673        char *srsInit = Tcl_GetString(objv[9]);
674        if (strlen(srsInit) > 0) {
675            if (objc > 10) {
676                char *vertDatum = Tcl_GetString(objv[10]);
677                srs = osgEarth::SpatialReference::get(srsInit, vertDatum);
678            } else {
679                srs = osgEarth::SpatialReference::get(srsInit);
680            }
681            if (srs == NULL) {
682                ERROR("Couldn't get SRS from init string: %s", srsInit);
683                return TCL_ERROR;
684            }
685        }
686    }
687#ifdef NEW_VIEWPOINT_API
688    osgEarth::Viewpoint view;
689    view.focalPoint() = osgEarth::GeoPoint(srs, x, y, z);
690    view.heading() = osgEarth::Angle(heading, osgEarth::Units::DEGREES);
691    view.pitch() = osgEarth::Angle(pitch, osgEarth::Units::DEGREES);
692    view.range() = osgEarth::Distance(distance, osgEarth::Units::METERS);
693#else
694    osgEarth::Viewpoint view(x, y, z, heading, pitch, distance, srs);
695#endif
696    g_renderer->setViewpoint(view, duration);
697    return TCL_OK;
698}
699
700static int
701CameraThrowOp(ClientData clientData, Tcl_Interp *interp, int objc,
702              Tcl_Obj *const *objv)
703{
704    bool state;
705
706    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
707        return TCL_ERROR;
708    }
709
710    g_renderer->setThrowingEnabled(state);
711    return TCL_OK;
712}
713
714static int
715CameraZoomOp(ClientData clientData, Tcl_Interp *interp, int objc,
716             Tcl_Obj *const *objv)
717{
718    double z;
719
720    if (Tcl_GetDoubleFromObj(interp, objv[2], &z) != TCL_OK) {
721        return TCL_ERROR;
722    }
723
724    g_renderer->zoomCamera(z);
725    return TCL_OK;
726}
727
728static CmdSpec cameraOps[] = {
729    {"center",  1, CameraCenterOp,           4, 6, "x y ?zoomFactor? ?duration?"},
730    {"delete",  2, CameraDeleteViewpointOp,  3, 3, "name"},
731    {"dist",    2, CameraSetDistanceOp,      3, 3, "distance"},
732    {"extent",  1, CameraSetByExtentOp,      6, 8, "xmin ymin xmax ymax ?duration? ?srs?"},
733    {"get",     2, CameraGetViewpointOp,     2, 2, ""},
734    {"go",      2, CameraGoOp,               4, 6, "x y ?zoomFactor? ?duration?"},
735    {"lextent", 1, CameraSetByLayerExtentOp, 3, 4, "layerName ?duration?"},
736    {"orient",  1, CameraOrientOp,           6, 6, "qw qx qy qz"},
737    {"pan",     1, CameraPanOp,              4, 4, "panX panY"},
738    {"reset",   4, CameraResetOp,            2, 3, "?all?"},
739    {"restore", 4, CameraRestoreViewpointOp, 3, 4, "name ?duration?"},
740    {"rotate",  2, CameraRotateOp,           4, 4, "azimuth elevation"},
741    {"save",    2, CameraSaveViewpointOp,    3, 3, "name"},
742    {"set",     2, CameraSetViewpointOp,     8, 11, "x y z heading pitch distance ?duration? ?srs? ?vertDatum?"},
743    {"throw",   1, CameraThrowOp,            3, 3, "bool"},
744    {"zoom",    1, CameraZoomOp,             3, 3, "zoomAmount"}
745};
746static int nCameraOps = NumCmdSpecs(cameraOps);
747
748static int
749CameraCmd(ClientData clientData, Tcl_Interp *interp, int objc,
750          Tcl_Obj *const *objv)
751{
752    Tcl_ObjCmdProc *proc;
753
754    proc = GetOpFromObj(interp, nCameraOps, cameraOps,
755                        CMDSPEC_ARG1, objc, objv, 0);
756    if (proc == NULL) {
757        return TCL_ERROR;
758    }
759    return (*proc) (clientData, interp, objc, objv);
760}
761
762static int
763ClientInfoCmd(ClientData clientData, Tcl_Interp *interp, int objc,
764              Tcl_Obj *const *objv)
765{
766    Tcl_DString ds;
767    Tcl_Obj *objPtr, *listObjPtr, **items;
768    int numItems;
769    char buf[BUFSIZ];
770    const char *string;
771    int length;
772    int result;
773    static bool first = true;
774
775    /* Client arguments. */
776    if (Tcl_ListObjGetElements(interp, objv[1], &numItems, &items) != TCL_OK ||
777        numItems % 2 != 0) {
778        return TCL_ERROR;
779    }
780    const char *username = NULL;
781    const char *hub = NULL;
782    //const char *session = NULL;
783    for (int i = 0; i < numItems; i+=2) {
784        const char *name = Tcl_GetString(items[i]);
785        const char *val = Tcl_GetString(items[i+1]);
786        if (strcmp(name, "user") == 0) {
787            username = val;
788        }
789        if (strcmp(name, "hub") == 0) {
790            hub = val;
791        }
792        //if (strcmp(name, "session") == 0) {
793        //    session = val;
794        //}
795    }
796    if (username != NULL) {
797        IData::iDataInit(username, hub);
798    }
799
800    /* First try to see if we already have an open descriptor */
801    int fd = getStatsFile();
802    if (fd < 0) {
803        /* Use the initial client key value pairs as the parts for generating
804         * a unique file name. */
805        fd = GeoVis::getStatsFile(Tcl_GetString(objv[1]));
806        if (fd < 0) {
807            Tcl_AppendResult(interp, "can't open stats file: ",
808                             Tcl_PosixError(interp), (char *)NULL);
809            return TCL_ERROR;
810        }
811    }
812    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
813    Tcl_IncrRefCount(listObjPtr);
814    if (first) {
815        first = false;
816        objPtr = Tcl_NewStringObj("render_start", 12);
817        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
818        /* renderer */
819        objPtr = Tcl_NewStringObj("renderer", 8);
820        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
821        objPtr = Tcl_NewStringObj("geovis", 6);
822        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
823        /* pid */
824        objPtr = Tcl_NewStringObj("pid", 3);
825        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
826        Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewIntObj(getpid()));
827        /* host */
828        objPtr = Tcl_NewStringObj("host", 4);
829        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
830        gethostname(buf, BUFSIZ-1);
831        buf[BUFSIZ-1] = '\0';
832        objPtr = Tcl_NewStringObj(buf, -1);
833        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
834    } else {
835        objPtr = Tcl_NewStringObj("render_info", 11);
836        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
837    }
838    /* date */
839    Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("date", 4));
840    strcpy(buf, ctime(&GeoVis::g_stats.start.tv_sec));
841    buf[strlen(buf) - 1] = '\0';
842    Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj(buf, -1));
843    /* date_secs */
844    Tcl_ListObjAppendElement(interp, listObjPtr,
845                             Tcl_NewStringObj("date_secs", 9));
846    Tcl_ListObjAppendElement(interp, listObjPtr,
847                             Tcl_NewLongObj(GeoVis::g_stats.start.tv_sec));
848    /* client items */
849    for (int i = 0; i < numItems; i++) {
850        Tcl_ListObjAppendElement(interp, listObjPtr, items[i]);
851    }
852    Tcl_DStringInit(&ds);
853    string = Tcl_GetStringFromObj(listObjPtr, &length);
854    Tcl_DStringAppend(&ds, string, length);
855    Tcl_DStringAppend(&ds, "\n", 1);
856    result = GeoVis::writeToStatsFile(fd, Tcl_DStringValue(&ds),
857                                      Tcl_DStringLength(&ds));
858    Tcl_DStringFree(&ds);
859    Tcl_DecrRefCount(listObjPtr);
860    return result;
861}
862
863static int
864ColorMapAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
865              Tcl_Obj *const *objv)
866{
867    const char *name = Tcl_GetString(objv[2]);
868    int cmapc;
869    Tcl_Obj **cmapv = NULL;
870
871    if (Tcl_ListObjGetElements(interp, objv[3], &cmapc, &cmapv) != TCL_OK) {
872        return TCL_ERROR;
873    }
874    if ((cmapc % 5) != 0) {
875        Tcl_AppendResult(interp, "wrong # elements in colormap: should be ",
876                         "{ value r g b a... }", (char*)NULL);
877        return TCL_ERROR;
878    }
879
880    osg::TransferFunction1D *colorMap = new osg::TransferFunction1D;
881    colorMap->allocate(256);
882
883    for (int i = 0; i < cmapc; i += 5) {
884        double val[5];
885        for (int j = 0; j < 5; j++) {
886            if (Tcl_GetDoubleFromObj(interp, cmapv[i+j], &val[j]) != TCL_OK) {
887                delete colorMap;
888                return TCL_ERROR;
889            }
890            // Need to permit un-normalized values
891            if (j > 0 && (val[j] < 0.0 || val[j] > 1.0)) {
892                Tcl_AppendResult(interp, "bad colormap value \"",
893                                 Tcl_GetString(cmapv[i+j]),
894                                 "\": should be in the range [0,1]", (char*)NULL);
895                delete colorMap;
896                return TCL_ERROR;
897            }
898        }
899        colorMap->setColor(val[0], osg::Vec4f(val[1], val[2], val[3], val[4]), false);
900    }
901
902    colorMap->updateImage();
903
904    g_renderer->addColorMap(name, colorMap);
905    return TCL_OK;
906}
907
908static int
909ColorMapDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
910                 Tcl_Obj *const *objv)
911{
912    if (objc == 3) {
913        const char *name = Tcl_GetString(objv[2]);
914#if 0
915        if (strcmp(name, "default") == 0 ||
916            strcmp(name, "grayDefault") == 0) {
917            WARN("Cannot delete a default color map");
918            return TCL_ERROR;
919        }
920#endif
921        g_renderer->deleteColorMap(name);
922    } else {
923        g_renderer->deleteColorMap("all");
924    }
925
926    return TCL_OK;
927}
928
929static int
930ColorMapNumTableEntriesOp(ClientData clientData, Tcl_Interp *interp, int objc,
931                          Tcl_Obj *const *objv)
932{
933    int numEntries;
934    if (Tcl_GetIntFromObj(interp, objv[2], &numEntries) != TCL_OK) {
935        const char *str = Tcl_GetString(objv[2]);
936        if (str[0] == 'd' && strcmp(str, "default") == 0) {
937            numEntries = -1;
938        } else {
939            Tcl_AppendResult(interp, "bad colormap resolution value \"", str,
940                             "\": should be a positive integer or \"default\"", (char*)NULL);
941            return TCL_ERROR;
942        }
943    } else if (numEntries < 1) {
944        Tcl_AppendResult(interp, "bad colormap resolution value \"", Tcl_GetString(objv[2]),
945                         "\": should be a positive integer or \"default\"", (char*)NULL);
946        return TCL_ERROR;
947    }
948    if (objc == 4) {
949        const char *name = Tcl_GetString(objv[3]);
950
951        g_renderer->setColorMapNumberOfTableEntries(name, numEntries);
952    } else {
953        g_renderer->setColorMapNumberOfTableEntries("all", numEntries);
954    }
955    return TCL_OK;
956}
957
958static CmdSpec colorMapOps[] = {
959    {"add",    1, ColorMapAddOp,             4, 4, "colorMapName colormap"},
960    {"define", 3, ColorMapAddOp,             4, 4, "colorMapName colormap"},
961    {"delete", 3, ColorMapDeleteOp,          2, 3, "?colorMapName?"},
962    {"res",    1, ColorMapNumTableEntriesOp, 3, 4, "numTableEntries ?colorMapName?"}
963};
964static int nColorMapOps = NumCmdSpecs(colorMapOps);
965
966static int
967ColorMapCmd(ClientData clientData, Tcl_Interp *interp, int objc,
968            Tcl_Obj *const *objv)
969{
970    Tcl_ObjCmdProc *proc;
971
972    proc = GetOpFromObj(interp, nColorMapOps, colorMapOps,
973                        CMDSPEC_ARG1, objc, objv, 0);
974    if (proc == NULL) {
975        return TCL_ERROR;
976    }
977    return (*proc) (clientData, interp, objc, objv);
978}
979
980static int
981FilePutOp(ClientData clientData, Tcl_Interp *interp, int objc,
982          Tcl_Obj *const *objv)
983{
984    long size;
985    const char *path = Tcl_GetString(objv[2]);
986    if (Tcl_GetLongFromObj(interp, objv[4], &size) != TCL_OK ||
987        size < 1) {
988        return TCL_ERROR;
989    }
990    char *data = (char *)malloc((size_t)size);
991    if (!SocketRead(data, size)) {
992        free(data);
993        return TCL_ERROR;
994    }
995    if (g_renderer->getCacheDirectory().empty()) {
996        g_renderer->setupCache();
997    }
998    std::string dirPath = osgDB::getFilePath(path);
999    std::string fileName = osgDB::getSimpleFileName(path);
1000    std::ostringstream fullPath;
1001    fullPath << g_renderer->getCacheDirectory() << "/" << dirPath;
1002    if (!dirPath.empty()) {
1003        INFO("Make dir: %s", fullPath.str().c_str());
1004        osgDB::makeDirectory(fullPath.str());
1005        fullPath << "/";
1006    }
1007    fullPath << fileName;
1008    FILE *fp = fopen(fullPath.str().c_str(), "wb");
1009    if (fp == NULL) {
1010        free(data);
1011        return TCL_ERROR;
1012    }
1013    int bytesWritten = fwrite(data, 1, (size_t)size, fp);
1014    fclose(fp);
1015    free(data);
1016    INFO("Wrote %d bytes to %s", bytesWritten, fullPath.str().c_str());
1017    return (bytesWritten == size) ? TCL_OK : TCL_ERROR;
1018}
1019
1020static CmdSpec fileOps[] = {
1021    {"put",    1, FilePutOp, 5, 5, "path type size"}
1022};
1023static int nFileOps = NumCmdSpecs(fileOps);
1024
1025static int
1026FileCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1027          Tcl_Obj *const *objv)
1028{
1029    Tcl_ObjCmdProc *proc;
1030
1031    proc = GetOpFromObj(interp, nFileOps, fileOps,
1032                        CMDSPEC_ARG1, objc, objv, 0);
1033    if (proc == NULL) {
1034        return TCL_ERROR;
1035    }
1036    return (*proc) (clientData, interp, objc, objv);
1037}
1038
1039static int
1040ImageFlushCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1041              Tcl_Obj *const *objv)
1042{
1043    lastCmdStatus = TCL_BREAK;
1044    return TCL_OK;
1045}
1046
1047static int
1048KeyPressOp(ClientData clientData, Tcl_Interp *interp, int objc,
1049           Tcl_Obj *const *objv)
1050{
1051    int key;
1052    if (Tcl_GetIntFromObj(interp, objv[2], &key) != TCL_OK) {
1053        return TCL_ERROR;
1054    }
1055
1056    g_renderer->keyPress(key);
1057    return TCL_OK;
1058}
1059
1060static int
1061KeyReleaseOp(ClientData clientData, Tcl_Interp *interp, int objc,
1062               Tcl_Obj *const *objv)
1063{
1064    int key;
1065    if (Tcl_GetIntFromObj(interp, objv[2], &key) != TCL_OK) {
1066        return TCL_ERROR;
1067    }
1068
1069    g_renderer->keyRelease(key);
1070    return TCL_OK;
1071}
1072
1073static CmdSpec keyOps[] = {
1074    {"press",    1, KeyPressOp,       3, 3, "key"},
1075    {"release",  1, KeyReleaseOp,     3, 3, "key"},
1076};
1077static int nKeyOps = NumCmdSpecs(keyOps);
1078
1079static int
1080KeyCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1081         Tcl_Obj *const *objv)
1082{
1083    Tcl_ObjCmdProc *proc;
1084
1085    proc = GetOpFromObj(interp, nKeyOps, keyOps,
1086                        CMDSPEC_ARG1, objc, objv, 0);
1087    if (proc == NULL) {
1088        return TCL_ERROR;
1089    }
1090    return (*proc) (clientData, interp, objc, objv);
1091}
1092
1093static int
1094LegendCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1095          Tcl_Obj *const *objv)
1096{
1097    if (objc < 4) {
1098        Tcl_AppendResult(interp, "wrong # args: should be \"",
1099                Tcl_GetString(objv[0]), " colormapName width height\"", (char*)NULL);
1100        return TCL_ERROR;
1101    }
1102    const char *colorMapName = Tcl_GetString(objv[1]);
1103
1104    int width, height;
1105    if (Tcl_GetIntFromObj(interp, objv[2], &width) != TCL_OK ||
1106        Tcl_GetIntFromObj(interp, objv[3], &height) != TCL_OK) {
1107        return TCL_ERROR;
1108    }
1109    bool opaque = true;
1110    if (objc > 4) {
1111        if (GetBooleanFromObj(interp, objv[4], &opaque) != TCL_OK) {
1112            return TCL_ERROR;
1113        }
1114    }
1115    float bgColor[3];
1116    if (objc > 5) {
1117        if (GetFloatFromObj(interp, objv[5], &bgColor[0]) != TCL_OK ||
1118            GetFloatFromObj(interp, objv[6], &bgColor[1]) != TCL_OK ||
1119            GetFloatFromObj(interp, objv[7], &bgColor[2]) != TCL_OK) {
1120            return TCL_ERROR;
1121        }
1122    } else {
1123        memset(bgColor, 0, sizeof(float)*3);
1124    }
1125    osg::ref_ptr<osg::Image> imgData = new osg::Image();
1126
1127#if defined(RENDER_TARGA) || defined(DEBUG)
1128    if (!g_renderer->renderColorMap(colorMapName, width, height, imgData, opaque,
1129                                    bgColor, true)) {
1130        Tcl_AppendResult(interp, "Color map \"",
1131                         colorMapName, "\" was not found", (char*)NULL);
1132        return TCL_ERROR;
1133    }
1134#else
1135    if (!g_renderer->renderColorMap(colorMapName, width, height, imgData, opaque,
1136                                    bgColor)) {
1137        Tcl_AppendResult(interp, "Color map \"",
1138                         colorMapName, "\" was not found", (char*)NULL);
1139        return TCL_ERROR;
1140    }
1141#endif
1142
1143#ifdef DEBUG
1144# ifdef RENDER_TARGA
1145    writeTGAFile("/tmp/legend.tga", imgData->data(), width, height,
1146                 TARGA_BYTES_PER_PIXEL);
1147# else
1148    writeTGAFile("/tmp/legend.tga", imgData->data(), width, height,
1149                 TARGA_BYTES_PER_PIXEL, true);
1150# endif
1151#else
1152    char cmd[256];
1153    float min, max;
1154    g_renderer->getColorMapRange(colorMapName, &min, &max);
1155    snprintf(cmd, sizeof(cmd), "nv>legend {%s} %g %g", colorMapName, min, max);
1156
1157# ifdef USE_THREADS
1158#  ifdef RENDER_TARGA
1159    queueTGA(g_outQueue, cmd, imgData->data(), width, height,
1160             TARGA_BYTES_PER_PIXEL);
1161#  else
1162    queuePPM(g_outQueue, cmd, imgData->data(), width, height);
1163#  endif
1164# else
1165#  ifdef RENDER_TARGA
1166    writeTGA(g_fdOut, cmd, imgData->data(), width, height,
1167             TARGA_BYTES_PER_PIXEL);
1168#  else
1169    writePPM(g_fdOut, cmd, imgData->data(), width, height);
1170#  endif
1171# endif // USE_THREADS
1172#endif // DEBUG
1173
1174    return TCL_OK;
1175}
1176
1177static int
1178MapAttributionOp(ClientData clientData, Tcl_Interp *interp, int objc,
1179                 Tcl_Obj *const *objv)
1180{
1181    int len;
1182    char *str = Tcl_GetStringFromObj(objv[2], &len);
1183#if 0
1184    Tcl_DString attrib;
1185    Tcl_DStringInit(&attrib);
1186    Tcl_Encoding encoding = Tcl_GetEncoding(interp, "identity");
1187    Tcl_ExternalToUtfDString(encoding, str, len, &attrib);
1188    Tcl_FreeEncoding(encoding);
1189    str = Tcl_DStringValue(&attrib);
1190#endif
1191    g_renderer->setAttribution(str);
1192#if 0
1193    Tcl_DStringFree(&attrib);
1194#endif
1195    return TCL_OK;
1196}
1197
1198static int
1199MapBoxClearOp(ClientData clientData, Tcl_Interp *interp, int objc,
1200              Tcl_Obj *const *objv)
1201{
1202    g_renderer->clearBoxSelection();
1203    return TCL_OK;
1204}
1205
1206static int
1207MapBoxInitOp(ClientData clientData, Tcl_Interp *interp, int objc,
1208             Tcl_Obj *const *objv)
1209{
1210    int x, y;
1211    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
1212        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
1213        return TCL_ERROR;
1214    }
1215    g_renderer->initBoxSelection(x, y);
1216    return TCL_OK;
1217}
1218
1219static int
1220MapBoxUpdateOp(ClientData clientData, Tcl_Interp *interp, int objc,
1221               Tcl_Obj *const *objv)
1222{
1223    int x, y;
1224    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
1225        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
1226        return TCL_ERROR;
1227    }
1228    g_renderer->updateBoxSelection(x, y);
1229    return TCL_OK;
1230}
1231
1232static CmdSpec mapBoxOps[] = {
1233    {"clear",   1, MapBoxClearOp,   3, 3, ""},
1234    {"init",    1, MapBoxInitOp,    5, 5, "x y"},
1235    {"update",  1, MapBoxUpdateOp,  5, 5, "x y"},
1236};
1237static int nMapBoxOps = NumCmdSpecs(mapBoxOps);
1238
1239static int
1240MapBoxOp(ClientData clientData, Tcl_Interp *interp, int objc,
1241         Tcl_Obj *const *objv)
1242{
1243    Tcl_ObjCmdProc *proc;
1244
1245    proc = GetOpFromObj(interp, nMapBoxOps, mapBoxOps,
1246                        CMDSPEC_ARG2, objc, objv, 0);
1247    if (proc == NULL) {
1248        return TCL_ERROR;
1249    }
1250    return (*proc) (clientData, interp, objc, objv);
1251}
1252
1253static int
1254MapCoordsOp(ClientData clientData, Tcl_Interp *interp, int objc,
1255            Tcl_Obj *const *objv)
1256{
1257    int tokenLength;
1258    const char *token = Tcl_GetStringFromObj(objv[2], &tokenLength);
1259    int numCoords;
1260    Tcl_Obj **coords;
1261    if (Tcl_ListObjGetElements(interp, objv[3], &numCoords, &coords) != TCL_OK) {
1262        return TCL_ERROR;
1263    }
1264    if (numCoords == 0) {
1265        Tcl_AppendResult(interp, "no x,y coordinates in list", (char *)NULL);
1266        return TCL_ERROR;
1267    }
1268    if (numCoords & 1) {
1269        Tcl_AppendResult(interp, "odd number of x, y coordinates in list",
1270                         (char *)NULL);
1271        return TCL_ERROR;
1272    }
1273    const osgEarth::SpatialReference *outSRS = NULL;
1274    if (objc > 4) {
1275        std::string srsInit;
1276        std::string verticalDatum;
1277        srsInit = Tcl_GetString(objv[4]);
1278        if (objc > 5) {
1279            verticalDatum = Tcl_GetString(objv[5]);
1280        }
1281        outSRS = osgEarth::SpatialReference::get(srsInit, verticalDatum);
1282        if (outSRS == NULL) {
1283            Tcl_AppendResult(interp, "bad SRS \"", srsInit.c_str(), "\"",
1284                             (char*)NULL);
1285            return TCL_ERROR;
1286        }
1287    }
1288    Tcl_Obj *listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
1289    std::vector<osg::Vec3d> coordVec;
1290    for (int i = 0; i < numCoords; i += 2) {
1291        int x, y;
1292        if ((Tcl_GetIntFromObj(interp, coords[i], &x) != TCL_OK) ||
1293            (Tcl_GetIntFromObj(interp, coords[i+1], &y) != TCL_OK)) {
1294            return TCL_ERROR;
1295        }
1296        osgEarth::GeoPoint mapPoint;
1297        if (g_renderer->mapMouseCoords(x, y, mapPoint)) {
1298            // altmode absolute
1299            coordVec.push_back(osg::Vec3d(mapPoint.x(), mapPoint.y(), mapPoint.z()));
1300        } else {
1301            coordVec.push_back(osg::Vec3d(std::numeric_limits<double>::quiet_NaN(),
1302                                          std::numeric_limits<double>::quiet_NaN(),
1303                                          std::numeric_limits<double>::quiet_NaN()));
1304        }
1305    }
1306    if (outSRS != NULL) {
1307        g_renderer->getMapSRS()->transform(coordVec, outSRS);
1308    } else {
1309        outSRS = g_renderer->getMapSRS();
1310    }
1311    for (std::vector<osg::Vec3d>::iterator itr = coordVec.begin();
1312         itr != coordVec.end(); ++itr) {
1313        Tcl_Obj *objPtr = Tcl_NewDoubleObj(itr->x());
1314        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
1315        objPtr = Tcl_NewDoubleObj(itr->y());
1316        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
1317        objPtr = Tcl_NewDoubleObj(itr->z());
1318        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
1319    }
1320    // send coords to client
1321    int listLength;
1322    const char *string = Tcl_GetStringFromObj(listObjPtr, &listLength);
1323    size_t length = listLength + tokenLength +
1324        outSRS->getHorizInitString().length() +
1325        outSRS->getVertInitString().length() +
1326        25;
1327    char *mesg = new char[length];
1328    length = snprintf(mesg, length, "nv>map coords %s {%s} {%s} {%s}\n", token, string,
1329                      outSRS->getHorizInitString().c_str(),
1330                      outSRS->getVertInitString().c_str());
1331    Tcl_DecrRefCount(listObjPtr);
1332    queueResponse(mesg, length, Response::VOLATILE);
1333    delete [] mesg;
1334    return TCL_OK;
1335}
1336
1337static int
1338MapGraticuleOp(ClientData clientData, Tcl_Interp *interp, int objc,
1339               Tcl_Obj *const *objv)
1340{
1341    bool state;
1342    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
1343        return TCL_ERROR;
1344    }
1345    if (objc > 3) {
1346        Renderer::GraticuleType type;
1347        char *typeStr = Tcl_GetString(objv[3]);
1348        if (typeStr[0] == 'g' && strcmp(typeStr, "geodetic") == 0) {
1349            type = Renderer::GRATICULE_GEODETIC;
1350        } else if (typeStr[0] == 's' && strcmp(typeStr, "shader") == 0) {
1351            type = Renderer::GRATICULE_SHADER;
1352        } else if (typeStr[0] == 'u' && strcmp(typeStr, "utm") == 0) {
1353            type = Renderer::GRATICULE_UTM;
1354        } else if (typeStr[0] == 'm' && strcmp(typeStr, "mgrs") == 0) {
1355            type = Renderer::GRATICULE_MGRS;
1356        } else {
1357            return TCL_ERROR;
1358        }
1359        g_renderer->setGraticule(state, type);
1360    } else {
1361        g_renderer->setGraticule(state);
1362    }
1363    return TCL_OK;
1364}
1365
1366static int
1367MapLayerAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
1368              Tcl_Obj *const *objv)
1369{
1370    char *name = Tcl_GetString(objv[3]);
1371    char *type = Tcl_GetString(objv[4]);
1372    bool cache = true;
1373    bool visible = true;
1374    if (type[0] == 'i' && strcmp(type, "image") == 0) {
1375        bool ret;
1376        char *driver = Tcl_GetString(objv[5]);
1377        std::string url;
1378        if (objc > 6) {
1379            char *urlIn = Tcl_GetString(objv[6]);
1380            url = g_renderer->getCanonicalPath(std::string(urlIn));
1381            if (url.empty()) {
1382                Tcl_AppendResult(interp, "file not found: \"",
1383                                 urlIn, "\"", (char*)NULL);
1384                return TCL_ERROR;
1385            }
1386        }
1387        if (objc > 7) {
1388            if (GetBooleanFromObj(interp, objv[7], &cache) != TCL_OK) {
1389                return TCL_ERROR;
1390            }
1391        }
1392        bool coverage = false;
1393        if (objc > 8) {
1394            if (GetBooleanFromObj(interp, objv[8], &coverage) != TCL_OK) {
1395                return TCL_ERROR;
1396            }
1397        }
1398        bool shared = false;
1399        unsigned int minLOD = 0;
1400        unsigned int maxLOD = 23;
1401        float minRange = 0.f;
1402        float maxRange = FLT_MAX;
1403        if (driver[0] == 'c' && strcmp(driver, "colorramp") == 0) {
1404            osgEarth::Drivers::ColorRampOptions colorRampOpts;
1405            char *edriver = Tcl_GetString(objv[9]);
1406            char *profile = Tcl_GetString(objv[10]);
1407            char *colormap = Tcl_GetString(objv[11]);
1408            if (edriver[0] == 'g' && strcmp(edriver, "gdal") == 0) {
1409                osgEarth::Drivers::GDALOptions opts;
1410                opts.url() = url;
1411                osgEarth::ElevationLayerOptions elevOpts(name, opts);
1412                if (!cache) {
1413                    elevOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
1414                }
1415                if (profile != NULL) {
1416                    elevOpts.driver()->profile() = osgEarth::ProfileOptions(profile);
1417                }
1418#if 0
1419                {
1420                    osgEarth::ProfileOptions profile;
1421                    profile.srsString() = srsString;
1422                    profile.vsrsString() = vsrsString;
1423                    profile.bounds() = osgEarth::Bounds(xmin, ymin, xmax, ymax);
1424                    elevOpts.driver()->profile() = profile;
1425                }
1426#endif
1427                colorRampOpts.elevationLayer() = elevOpts;
1428            } else if (edriver[0] == 't' && strcmp(edriver, "tms") == 0) {
1429                osgEarth::Drivers::TMSOptions opts;
1430                //char *tmsType = Tcl_GetString(objv[9]);
1431                //char *format = Tcl_GetString(objv[10]);
1432                opts.url() = url;
1433                //opts.tmsType() = tmsType;
1434                //opts.format() = format;
1435                osgEarth::ElevationLayerOptions elevOpts(name, opts);
1436                if (!cache) {
1437                    elevOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
1438                }
1439                if (profile != NULL) {
1440                    elevOpts.driver()->profile() = osgEarth::ProfileOptions(profile);
1441                }
1442                colorRampOpts.elevationLayer() = elevOpts;
1443            }
1444            colorRampOpts.ramp() = g_renderer->getColorMapFilePath(colormap);
1445            ret = g_renderer->addImageLayer(name, colorRampOpts, cache, coverage, shared, visible, minLOD, maxLOD);
1446        } else if (driver[0] == 'd' && strcmp(driver, "debug") == 0) {
1447            osgEarth::Drivers::DebugOptions opts;
1448            ret = g_renderer->addImageLayer(name, opts, cache, coverage, shared, visible, minLOD, maxLOD);
1449        } else if (driver[0] == 'a' && strcmp(driver, "arcgis") == 0) {
1450            osgEarth::Drivers::ArcGISOptions opts;
1451            opts.url() = url;
1452            if (objc > 9) {
1453                opts.token() = Tcl_GetString(objv[9]);
1454            }
1455            if (objc > 10) {
1456                opts.layers() = Tcl_GetString(objv[10]);
1457            }
1458            //opts.format() = Tcl_GetString(objv[11]);
1459            ret = g_renderer->addImageLayer(name, opts, cache, coverage, shared, visible, minLOD, maxLOD);
1460            g_renderer->setImageLayerVisibleRange(name, minRange, maxRange);
1461        } else if (driver[0] == 'g' && strcmp(driver, "gdal") == 0) {
1462            osgEarth::Drivers::GDALOptions opts;
1463            opts.url() = url;
1464            ret = g_renderer->addImageLayer(name, opts, cache, coverage, shared, visible, minLOD, maxLOD);
1465            g_renderer->setImageLayerVisibleRange(name, minRange, maxRange);
1466        } else if (driver[0] == 't' && strcmp(driver, "tms") == 0) {
1467            osgEarth::Drivers::TMSOptions opts;
1468            opts.url() = url;
1469            //opts.tmsType() = Tcl_GetString(objv[9]);
1470            //opts.format() = Tcl_GetString(objv[10]);
1471            ret = g_renderer->addImageLayer(name, opts, cache, coverage, shared, visible, minLOD, maxLOD);
1472            g_renderer->setImageLayerVisibleRange(name, minRange, maxRange);
1473        } else if (driver[0] == 'w' && strcmp(driver, "wms") == 0) {
1474            osgEarth::Drivers::WMSOptions opts;
1475            char *wmsLayers = Tcl_GetString(objv[9]);
1476            char *format = Tcl_GetString(objv[10]);
1477            bool transparent;
1478            if (GetBooleanFromObj(interp, objv[11], &transparent) != TCL_OK) {
1479                return TCL_ERROR;
1480            }
1481            opts.url() = url;
1482            opts.layers() = wmsLayers;
1483            opts.format() = format;
1484            opts.transparent() = transparent;
1485
1486            ret = g_renderer->addImageLayer(name, opts, cache, coverage, shared, visible, minLOD, maxLOD);
1487            g_renderer->setImageLayerVisibleRange(name, minRange, maxRange);
1488        } else if (driver[0] == 'x' && strcmp(driver, "xyz") == 0) {
1489            osgEarth::Drivers::XYZOptions opts;
1490            opts.url() = url;
1491            opts.profile() = osgEarth::ProfileOptions("global-mercator");
1492            //bool invertY = false;
1493            //opts.invertY() = invertY;
1494            //opts.format() = Tcl_GetString(objv[9]);
1495            ret = g_renderer->addImageLayer(name, opts, cache, coverage, shared, visible, minLOD, maxLOD);
1496            g_renderer->setImageLayerVisibleRange(name, minRange, maxRange);
1497        } else {
1498            Tcl_AppendResult(interp, "unknown image driver \"", driver,
1499                             "\": should be 'debug', 'gdal', 'tms', 'wms' or 'xyz'", (char*)NULL);
1500            return TCL_ERROR;
1501        }
1502        if (!ret) {
1503            Tcl_AppendResult(interp, "Failed to add image layer \"", name, "\"", (char*)NULL);
1504            return TCL_ERROR;
1505        }
1506    } else if (type[0] == 'e' && strcmp(type, "elevation") == 0) {
1507        char *driver = Tcl_GetString(objv[5]);
1508        char *urlIn = Tcl_GetString(objv[6]);
1509        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
1510        if (url.empty()) {
1511            Tcl_AppendResult(interp, "file not found: \"",
1512                             urlIn, "\"", (char*)NULL);
1513            return TCL_ERROR;
1514        }
1515        if (objc > 7) {
1516            if (GetBooleanFromObj(interp, objv[7], &cache) != TCL_OK) {
1517                return TCL_ERROR;
1518            }
1519        }
1520        int minLOD = 0;
1521        int maxLOD = 23;
1522        if (driver[0] == 'g' && strcmp(driver, "gdal") == 0) {
1523            osgEarth::Drivers::GDALOptions opts;
1524            opts.url() = url;
1525            g_renderer->addElevationLayer(name, opts, cache, visible, minLOD, maxLOD);
1526        } else if (driver[0] == 't' && strcmp(driver, "tms") == 0) {
1527            osgEarth::Drivers::TMSOptions opts;
1528            opts.url() = url;
1529            //opts.tmsType() = Tcl_GetString(objv[8]);
1530            //opts.format() = Tcl_GetString(objv[9]);
1531            g_renderer->addElevationLayer(name, opts, cache, visible, minLOD, maxLOD);
1532        } else if (driver[0] == 'w' && strcmp(driver, "wcs") == 0) {
1533            osgEarth::Drivers::WCSOptions opts;
1534            opts.url() = url;
1535            if (objc > 8) {
1536                opts.identifier() = Tcl_GetString(objv[8]);
1537            }
1538            if (objc > 9) {
1539                // default = 'm'
1540                opts.elevationUnit() = Tcl_GetString(objv[9]);
1541            }
1542            if (objc > 10) {
1543                // default = 'image/GeoTIFF'
1544                opts.format() = Tcl_GetString(objv[10]);
1545            }
1546            //opts.srs() = Tcl_GetString(objv[11]);
1547            //opts.rangeSubset() = Tcl_GetString(objv[12]);
1548            g_renderer->addElevationLayer(name, opts, cache, visible, minLOD, maxLOD);
1549        } else {
1550            Tcl_AppendResult(interp, "unknown elevation driver \"", driver,
1551                             "\": should be 'gdal' or 'tms'", (char*)NULL);
1552            return TCL_ERROR;
1553        }
1554    } else if (type[0] == 'f' && strcmp(type, "feature") == 0) {
1555        // Generic feature geometry layer
1556        char *driver = Tcl_GetString(objv[5]);
1557        char *format = Tcl_GetString(objv[6]);
1558        if (format && strlen(format) > 0 &&
1559            strcmp(format, "json") != 0 && strcmp(format, "gml") != 0) {
1560            Tcl_AppendResult(interp, "unknown format \"", format,
1561                             "\": should be 'json' or 'gml'", (char*)NULL);
1562            return TCL_ERROR;
1563        }
1564        char *typeName = Tcl_GetString(objv[7]);
1565        char *urlIn = Tcl_GetString(objv[8]);
1566        std::string url;
1567        if (driver[0] == 'd' && strcmp(driver, "db") == 0) {
1568            url = urlIn;
1569        } else {
1570            url = g_renderer->getCanonicalPath(std::string(urlIn));
1571            if (url.empty()) {
1572                Tcl_AppendResult(interp, "file not found: \"",
1573                                 urlIn, "\"", (char*)NULL);
1574                return TCL_ERROR;
1575            }
1576        }
1577        if (GetBooleanFromObj(interp, objv[9], &cache) != TCL_OK) {
1578            return TCL_ERROR;
1579        }
1580
1581        bool lighting = true;
1582        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
1583        osgEarth::Config styleConf("style", Tcl_GetString(objv[10]));
1584        styleConf.add("type", "text/css");
1585        INFO("style CSS: %s", styleConf.value().c_str());
1586        osgEarth::Config stylesheetConf;
1587        stylesheetConf.add(styleConf);
1588        geomOpts.styles() = new osgEarth::Symbology::StyleSheet(stylesheetConf);
1589
1590        if (objc > 11) {
1591            std::string scripts(Tcl_GetString(objv[11]));
1592            if (!scripts.empty()) {
1593                INFO("script: %s", scripts.c_str());
1594                osg::ref_ptr<osgEarth::Symbology::StyleSheet::ScriptDef> scriptDef =
1595                    new osgEarth::Symbology::StyleSheet::ScriptDef(scripts);
1596                geomOpts.styles()->setScript(scriptDef.get());
1597            } else {
1598                INFO("no script");
1599            }
1600        }
1601        if (objc > 12) {
1602            int numSelectors;
1603            Tcl_Obj **selectors;
1604            if (Tcl_ListObjGetElements(interp, objv[12], &numSelectors, &selectors) != TCL_OK) {
1605                return TCL_ERROR;
1606            }
1607            for (int i = 0; i < numSelectors; i++) {
1608                int numFields;
1609                Tcl_Obj **fields;
1610                if (Tcl_ListObjGetElements(interp, selectors[i], &numFields, &fields) != TCL_OK ||
1611                    numFields % 2 != 0) {
1612                    return TCL_ERROR;
1613                }
1614                std::map<std::string, std::string> fieldMap;
1615                std::map<std::string, std::string>::iterator itr;
1616                for (int f = 0; f < numFields; f+=2) {
1617                    char *name = Tcl_GetString(fields[f]);
1618                    char *value = Tcl_GetString(fields[f+1]);
1619                    fieldMap[name] = value;
1620                    INFO("selector[%s] = %s", name, value);
1621                }
1622                osgEarth::Symbology::StyleSelector ss;
1623                itr = fieldMap.find("name");
1624                if (itr != fieldMap.end()) {
1625                    ss.name() = itr->second;
1626                }
1627                itr = fieldMap.find("style");
1628                if (itr != fieldMap.end()) {
1629                    ss.styleName() = itr->second;
1630                }
1631                itr = fieldMap.find("styleExpression");
1632                if (itr != fieldMap.end()) {
1633                    INFO("selector: %s", itr->second.c_str());
1634                    ss.styleExpression() = osgEarth::Symbology::StringExpression(itr->second);
1635                }
1636                itr = fieldMap.find("query");
1637                if (itr != fieldMap.end()) {
1638                    osgEarth::Symbology::Query query;
1639                    query.expression() = itr->second;
1640                    itr = fieldMap.find("queryOrderBy");
1641                    if (itr != fieldMap.end()) {
1642                        query.orderby() = itr->second;
1643                    }
1644                    itr = fieldMap.find("queryBounds");
1645                    if (itr !=  fieldMap.end()) {
1646                        double xmin, ymin, xmax, ymax;
1647                        if (sscanf(itr->second.c_str(), "%lf %lf %lf %lf", &xmin, &ymin, &xmax, &ymax) != 4) {
1648                            return TCL_ERROR;
1649                        }
1650                        INFO("query bounds: %g %g %g %g", xmin, ymin, xmax, ymax);
1651                        query.bounds() = osgEarth::Bounds(xmin, ymin, xmax, ymax);
1652                    }
1653                    ss.query() = query;
1654                }
1655                geomOpts.styles()->selectors().push_back(ss);
1656            }
1657        }
1658        geomOpts.enableLighting() = lighting;
1659        geomOpts.minRange() = 0.f;
1660        geomOpts.maxRange() = FLT_MAX;
1661        if (objc > 13) {
1662            float min, max;
1663            if (GetFloatFromObj(interp, objv[13], &min) != TCL_OK ||
1664                GetFloatFromObj(interp, objv[14], &max) != TCL_OK) {
1665                return TCL_ERROR;
1666            }
1667            geomOpts.minRange() = min;
1668            geomOpts.maxRange() = max;
1669        }
1670        if (driver[0] == 'd' && strcmp(driver, "db") == 0) {
1671            osgEarth::Drivers::OGRFeatureOptions opts;
1672            opts.name() = name;
1673            opts.connection() = url;
1674            opts.layer() = typeName;
1675            geomOpts.featureOptions() = opts;
1676        } else if (driver[0] == 'o' && strcmp(driver, "ogr") == 0) {
1677            osgEarth::Drivers::OGRFeatureOptions opts;
1678            opts.name() = name;
1679            if (osgDB::getLowerCaseFileExtension(url) == "csv") {
1680                url = loadCSVLayer(url.c_str());
1681            }
1682            opts.url() = url;
1683            geomOpts.featureOptions() = opts;
1684        } else if (driver[0] == 't' && strcmp(driver, "tfs") == 0) {
1685            osgEarth::Drivers::TFSFeatureOptions opts;
1686            opts.name() = name;
1687            opts.url() = url;
1688            opts.format() = format;
1689            geomOpts.featureOptions() = opts;
1690        } else if (driver[0] == 'w' && strcmp(driver, "wfs") == 0) {
1691            osgEarth::Drivers::WFSFeatureOptions opts;
1692            opts.name() = name;
1693            opts.url() = url;
1694            opts.outputFormat() = format;
1695            opts.typeName() = typeName;
1696            geomOpts.featureOptions() = opts;
1697        } else {
1698            Tcl_AppendResult(interp, "unknown feature driver \"", driver,
1699                             "\": should be 'ogr', 'tfs', or 'wfs'", (char*)NULL);
1700            return TCL_ERROR;
1701        }
1702        g_renderer->addModelLayer(name, geomOpts, cache, lighting, visible);
1703    } else if (type[0] == 'i' && strcmp(type, "icon") == 0) {
1704        char *driver = Tcl_GetString(objv[5]);
1705        char *format = Tcl_GetString(objv[6]);
1706        if (format && strlen(format) > 0 &&
1707            strcmp(format, "json") != 0 && strcmp(format, "gml") != 0) {
1708            Tcl_AppendResult(interp, "unknown format \"", format,
1709                             "\": should be 'json' or 'gml'", (char*)NULL);
1710            return TCL_ERROR;
1711        }
1712        char *typeName = Tcl_GetString(objv[7]);
1713        char *urlIn = Tcl_GetString(objv[8]);
1714        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
1715        if (url.empty()) {
1716            Tcl_AppendResult(interp, "file not found: \"",
1717                             urlIn, "\"", (char*)NULL);
1718            return TCL_ERROR;
1719        }
1720        if (GetBooleanFromObj(interp, objv[9], &cache) != TCL_OK) {
1721            return TCL_ERROR;
1722        }
1723        char *icon = Tcl_GetString(objv[10]);
1724        char *scaleExpr = Tcl_GetString(objv[11]);
1725        char *headingExpr = Tcl_GetString(objv[12]);
1726        bool declutter = false;
1727        if (GetBooleanFromObj(interp, objv[13], &declutter) != TCL_OK) {
1728            return TCL_ERROR;
1729        }
1730        char *placementStr = Tcl_GetString(objv[14]);
1731        char *alignStr = Tcl_GetString(objv[15]);
1732
1733        bool lighting = true;
1734        osgEarth::Symbology::Style style;
1735        osgEarth::Symbology::IconSymbol *is = style.getOrCreateSymbol<osgEarth::Symbology::IconSymbol>();
1736        if (icon != NULL && strlen(icon) > 0) {
1737            if (strcmp(icon, "pin") == 0) {
1738                is->url()->setLiteral(g_renderer->getPinIcon());
1739            } else {
1740                is->url() = osgEarth::Symbology::StringExpression(icon);
1741            }
1742        } else {
1743            is->url()->setLiteral(g_renderer->getPinIcon());
1744        }
1745        if (scaleExpr != NULL && strlen(scaleExpr) > 0) {
1746            is->scale() = osgEarth::Symbology::NumericExpression(scaleExpr);
1747        }
1748        if (headingExpr != NULL && strlen(headingExpr) > 0) {
1749            is->heading() = osgEarth::Symbology::NumericExpression(headingExpr);
1750        }
1751        is->declutter() = declutter;
1752        is->occlusionCull() = false;
1753        is->occlusionCullAltitude() = 200000;
1754        is->alignment() = ParseIconAlignment(alignStr, osgEarth::Symbology::IconSymbol::ALIGN_CENTER_BOTTOM);
1755        is->placement() = ParseInstancePlacement(placementStr, osgEarth::Symbology::InstanceSymbol::PLACEMENT_VERTEX);
1756        //is->density() = 1.0f; // For PLACEMENT_INTERVAL/RANDOM
1757#if 0
1758        osgEarth::Symbology::AltitudeSymbol *alt = style.getOrCreateSymbol<osgEarth::Symbology::AltitudeSymbol>();
1759        alt->clamping() = osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
1760        alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_DRAPE;
1761        //alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
1762        //alt->verticalOffset() = osgEarth::Symbology::NumericExpression();
1763        //alt->verticalScale() = osgEarth::Symbology::NumericExpression();
1764#endif
1765#ifdef USE_DEPTH_OFFSET
1766        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
1767        rs->depthOffset()->enabled() = true;
1768        rs->depthOffset()->minBias() = 1000;
1769#endif
1770        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
1771        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
1772        geomOpts.styles()->addStyle(style);
1773        geomOpts.enableLighting() = false;
1774        geomOpts.minRange() = 0.f;
1775        geomOpts.maxRange() = FLT_MAX;
1776        if (objc > 16) {
1777            float min, max;
1778            if (GetFloatFromObj(interp, objv[16], &min) != TCL_OK ||
1779                GetFloatFromObj(interp, objv[17], &max) != TCL_OK) {
1780                return TCL_ERROR;
1781            }
1782            geomOpts.minRange() = min;
1783            geomOpts.maxRange() = max;
1784        }
1785        //geomOpts.renderOrder() = int;
1786        //geomOpts.depthTestEnabled() = bool;
1787        if (driver[0] == 'o' && strcmp(driver, "ogr") == 0) {
1788            osgEarth::Drivers::OGRFeatureOptions opts;
1789            opts.name() = name;
1790            if (osgDB::getLowerCaseFileExtension(url) == "csv") {
1791                url = loadCSVLayer(url.c_str());
1792            }
1793            opts.url() = url;
1794            geomOpts.featureOptions() = opts;
1795        } else if (driver[0] == 't' && strcmp(driver, "tfs") == 0) {
1796            osgEarth::Drivers::TFSFeatureOptions opts;
1797            opts.name() = name;
1798            opts.url() = url;
1799            opts.format() = format;
1800            geomOpts.featureOptions() = opts;
1801        } else if (driver[0] == 'w' && strcmp(driver, "wfs") == 0) {
1802            osgEarth::Drivers::WFSFeatureOptions opts;
1803            opts.name() = name;
1804            opts.url() = url;
1805            opts.outputFormat() = format;
1806            opts.typeName() = typeName;
1807            geomOpts.featureOptions() = opts;
1808        } else {
1809            Tcl_AppendResult(interp, "unknown feature driver \"", driver,
1810                             "\": should be 'ogr', 'tfs', or 'wfs'", (char*)NULL);
1811            return TCL_ERROR;
1812        }
1813        g_renderer->addModelLayer(name, geomOpts, cache, lighting, visible);
1814    } else if (type[0] == 'p' && strcmp(type, "point") == 0) {
1815        char *driver = Tcl_GetString(objv[5]);
1816        char *format = Tcl_GetString(objv[6]);
1817        if (format && strlen(format) > 0 &&
1818            strcmp(format, "json") != 0 && strcmp(format, "gml") != 0) {
1819            Tcl_AppendResult(interp, "unknown format \"", format,
1820                             "\": should be 'json' or 'gml'", (char*)NULL);
1821            return TCL_ERROR;
1822        }
1823        char *typeName = Tcl_GetString(objv[7]);
1824        char *urlIn = Tcl_GetString(objv[8]);
1825        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
1826        if (url.empty()) {
1827            Tcl_AppendResult(interp, "file not found: \"",
1828                             urlIn, "\"", (char*)NULL);
1829            return TCL_ERROR;
1830        }
1831        if (GetBooleanFromObj(interp, objv[9], &cache) != TCL_OK) {
1832            return TCL_ERROR;
1833        }
1834        float r, g, b;
1835        if (GetFloatFromObj(interp, objv[10], &r) != TCL_OK ||
1836            GetFloatFromObj(interp, objv[11], &g) != TCL_OK ||
1837            GetFloatFromObj(interp, objv[12], &b) != TCL_OK) {
1838            return TCL_ERROR;
1839        }
1840        float ptSize;
1841        if (GetFloatFromObj(interp, objv[13], &ptSize) != TCL_OK) {
1842            return TCL_ERROR;
1843        }
1844        bool lighting = true;
1845        osgEarth::Symbology::Style style;
1846        osgEarth::Symbology::PointSymbol *ps = style.getOrCreateSymbol<osgEarth::Symbology::PointSymbol>();
1847        ps->fill()->color() = osgEarth::Symbology::Color(r, g, b);
1848        ps->size() = ptSize;
1849
1850        osgEarth::Symbology::AltitudeSymbol::Clamping clamping =
1851            osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
1852        if (objc > 14) {
1853            clamping = ParseClamping(Tcl_GetString(objv[14]), clamping);
1854        }
1855        osgEarth::Symbology::AltitudeSymbol::Technique technique =
1856            osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
1857        if (objc > 15) {
1858            technique = ParseClampingTechnique(Tcl_GetString(objv[15]), technique);
1859        }
1860        osgEarth::Symbology::AltitudeSymbol *alt = style.getOrCreateSymbol<osgEarth::Symbology::AltitudeSymbol>();
1861        alt->clamping() = clamping;
1862        alt->technique() = technique;
1863        //alt->verticalOffset() = osgEarth::Symbology::NumericExpression();
1864        //alt->verticalScale() = osgEarth::Symbology::NumericExpression();
1865
1866#if 1 || defined(USE_DEPTH_OFFSET)
1867        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
1868        rs->depthOffset()->enabled() = true;
1869        rs->depthOffset()->minBias() = 1000;
1870#endif
1871        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
1872        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
1873        geomOpts.styles()->addStyle(style);
1874        geomOpts.enableLighting() = false;
1875        geomOpts.minRange() = 0.f;
1876        geomOpts.maxRange() = FLT_MAX;
1877        if (objc > 16) {
1878            float min, max;
1879            if (GetFloatFromObj(interp, objv[16], &min) != TCL_OK ||
1880                GetFloatFromObj(interp, objv[17], &max) != TCL_OK) {
1881                return TCL_ERROR;
1882            }
1883            geomOpts.minRange() = min;
1884            geomOpts.maxRange() = max;
1885        }
1886        //geomOpts.renderOrder() = int;
1887        //geomOpts.depthTestEnabled() = bool;
1888        if (driver[0] == 'o' && strcmp(driver, "ogr") == 0) {
1889            osgEarth::Drivers::OGRFeatureOptions opts;
1890            opts.name() = name;
1891            if (osgDB::getLowerCaseFileExtension(url) == "csv") {
1892                url = loadCSVLayer(url.c_str());
1893            }
1894            opts.url() = url;
1895            geomOpts.featureOptions() = opts;
1896        } else if (driver[0] == 't' && strcmp(driver, "tfs") == 0) {
1897            osgEarth::Drivers::TFSFeatureOptions opts;
1898            opts.name() = name;
1899            opts.url() = url;
1900            opts.format() = format;
1901            geomOpts.featureOptions() = opts;
1902        } else if (driver[0] == 'w' && strcmp(driver, "wfs") == 0) {
1903            osgEarth::Drivers::WFSFeatureOptions opts;
1904            opts.name() = name;
1905            opts.url() = url;
1906            opts.outputFormat() = format;
1907            opts.typeName() = typeName;
1908            geomOpts.featureOptions() = opts;
1909        } else {
1910            Tcl_AppendResult(interp, "unknown feature driver \"", driver,
1911                             "\": should be 'ogr', 'tfs', or 'wfs'", (char*)NULL);
1912            return TCL_ERROR;
1913        }
1914        g_renderer->addModelLayer(name, geomOpts, cache, lighting, visible);
1915    } else if (type[0] == 'p' && strcmp(type, "polygon") == 0) {
1916        char *driver = Tcl_GetString(objv[5]);
1917        char *format = Tcl_GetString(objv[6]);
1918        if (format && strlen(format) > 0 &&
1919            strcmp(format, "json") != 0 && strcmp(format, "gml") != 0) {
1920            Tcl_AppendResult(interp, "unknown format \"", format,
1921                             "\": should be 'json' or 'gml'", (char*)NULL);
1922            return TCL_ERROR;
1923        }
1924        char *typeName = Tcl_GetString(objv[7]);
1925        char *urlIn = Tcl_GetString(objv[8]);
1926        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
1927        if (url.empty()) {
1928            Tcl_AppendResult(interp, "file not found: \"",
1929                             urlIn, "\"", (char*)NULL);
1930            return TCL_ERROR;
1931        }
1932        if (GetBooleanFromObj(interp, objv[9], &cache) != TCL_OK) {
1933            return TCL_ERROR;
1934        }
1935        float r, g, b;
1936        if (GetFloatFromObj(interp, objv[10], &r) != TCL_OK ||
1937            GetFloatFromObj(interp, objv[11], &g) != TCL_OK ||
1938            GetFloatFromObj(interp, objv[12], &b) != TCL_OK) {
1939            return TCL_ERROR;
1940        }
1941        float lineWidth = 0.0f;
1942        if (objc > 13) {
1943            if (GetFloatFromObj(interp, objv[13], &lineWidth) != TCL_OK) {
1944                return TCL_ERROR;
1945            }
1946        }
1947        float strokeR = 0, strokeG = 0, strokeB = 0;
1948        if (objc > 14) {
1949            if (GetFloatFromObj(interp, objv[14], &strokeR) != TCL_OK ||
1950                GetFloatFromObj(interp, objv[15], &strokeG) != TCL_OK ||
1951                GetFloatFromObj(interp, objv[16], &strokeB) != TCL_OK) {
1952                return TCL_ERROR;
1953            }
1954        }
1955        bool lighting = true;
1956        osgEarth::Symbology::Style style;
1957        osgEarth::Symbology::PolygonSymbol *ps = style.getOrCreateSymbol<osgEarth::Symbology::PolygonSymbol>();
1958        ps->fill()->color() = osgEarth::Symbology::Color(r, g, b);
1959
1960        if (lineWidth > 0.0f) {
1961            osgEarth::Symbology::LineSymbol *ls = style.getOrCreateSymbol<osgEarth::Symbology::LineSymbol>();
1962            ls->stroke()->color() = osgEarth::Symbology::Color(strokeR, strokeG, strokeB);
1963            ls->stroke()->width() = lineWidth;
1964            ls->stroke()->widthUnits() = osgEarth::Units::PIXELS;
1965            ls->stroke()->lineCap() = osgEarth::Symbology::Stroke::LINECAP_FLAT; // _SQUARE, _ROUND
1966            //ls->stroke()->roundingRatio() = 0.4f;
1967            ls->stroke()->lineJoin() = osgEarth::Symbology::Stroke::LINEJOIN_MITRE; // _ROUND
1968            //ls->stroke()->stipplePattern() = 0; // Bitmask: unsigned short
1969            //ls->stroke()->stippleFactor() = 0; // num reps: unsigned
1970        }
1971
1972        osgEarth::Symbology::AltitudeSymbol::Clamping clamping =
1973            osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
1974        if (objc > 17) {
1975            clamping = ParseClamping(Tcl_GetString(objv[17]), clamping);
1976        }
1977        osgEarth::Symbology::AltitudeSymbol::Technique technique =
1978            osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
1979        if (objc > 18) {
1980            technique = ParseClampingTechnique(Tcl_GetString(objv[18]), technique);
1981        }
1982        osgEarth::Symbology::AltitudeSymbol *alt = style.getOrCreateSymbol<osgEarth::Symbology::AltitudeSymbol>();
1983        alt->clamping() = clamping;
1984        alt->technique() = technique;
1985        //alt->verticalOffset() = osgEarth::Symbology::NumericExpression();
1986        //alt->verticalScale() = osgEarth::Symbology::NumericExpression();
1987
1988#if 1 || defined(USE_DEPTH_OFFSET)
1989        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
1990        rs->depthOffset()->enabled() = true;
1991        rs->depthOffset()->minBias() = 1000;
1992#endif
1993        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
1994        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
1995        geomOpts.styles()->addStyle(style);
1996        geomOpts.enableLighting() = false;
1997        geomOpts.minRange() = 0.f;
1998        geomOpts.maxRange() = FLT_MAX;
1999        if (objc > 19) {
2000            float min, max;
2001            if (GetFloatFromObj(interp, objv[19], &min) != TCL_OK ||
2002                GetFloatFromObj(interp, objv[20], &max) != TCL_OK) {
2003                return TCL_ERROR;
2004            }
2005            geomOpts.minRange() = min;
2006            geomOpts.maxRange() = max;
2007        }
2008        if (driver[0] == 'o' && strcmp(driver, "ogr") == 0) {
2009            osgEarth::Drivers::OGRFeatureOptions opts;
2010            opts.name() = name;
2011            if (osgDB::getLowerCaseFileExtension(url) == "csv") {
2012                url = loadCSVLayer(url.c_str());
2013            }
2014            opts.url() = url;
2015            geomOpts.featureOptions() = opts;
2016        } else if (driver[0] == 't' && strcmp(driver, "tfs") == 0) {
2017            osgEarth::Drivers::TFSFeatureOptions opts;
2018            opts.name() = name;
2019            opts.url() = url;
2020            opts.format() = format;
2021            geomOpts.featureOptions() = opts;
2022        } else if (driver[0] == 'w' && strcmp(driver, "wfs") == 0) {
2023            osgEarth::Drivers::WFSFeatureOptions opts;
2024            opts.name() = name;
2025            opts.url() = url;
2026            opts.outputFormat() = format;
2027            opts.typeName() = typeName;
2028            geomOpts.featureOptions() = opts;
2029        } else {
2030            Tcl_AppendResult(interp, "unknown feature driver \"", driver,
2031                             "\": should be 'ogr', 'tfs', or 'wfs'", (char*)NULL);
2032            return TCL_ERROR;
2033        }
2034        g_renderer->addModelLayer(name, geomOpts, cache, lighting, visible);
2035    } else if (type[0] == 'l' && strcmp(type, "line") == 0) {
2036        char *driver = Tcl_GetString(objv[5]);
2037        char *format = Tcl_GetString(objv[6]);
2038        if (format && strlen(format) > 0 &&
2039            strcmp(format, "json") != 0 && strcmp(format, "gml") != 0) {
2040            Tcl_AppendResult(interp, "unknown format \"", format,
2041                             "\": should be 'json' or 'gml'", (char*)NULL);
2042            return TCL_ERROR;
2043        }
2044        char *typeName = Tcl_GetString(objv[7]);
2045        char *urlIn = Tcl_GetString(objv[8]);
2046        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
2047        if (url.empty()) {
2048            Tcl_AppendResult(interp, "file not found: \"",
2049                             urlIn, "\"", (char*)NULL);
2050            return TCL_ERROR;
2051        }
2052        if (GetBooleanFromObj(interp, objv[9], &cache) != TCL_OK) {
2053            return TCL_ERROR;
2054        }
2055        float r, g, b;
2056        if (GetFloatFromObj(interp, objv[10], &r) != TCL_OK ||
2057            GetFloatFromObj(interp, objv[11], &g) != TCL_OK ||
2058            GetFloatFromObj(interp, objv[12], &b) != TCL_OK) {
2059            return TCL_ERROR;
2060        }
2061        float lineWidth;
2062        osgEarth::Units units = osgEarth::Units::PIXELS;
2063        if (!osgEarth::Units::parse(Tcl_GetString(objv[13]), lineWidth,
2064                                    units, osgEarth::Units::PIXELS)) {
2065            Tcl_AppendResult(interp, "Unkown units: \"",
2066                             Tcl_GetString(objv[13]), "\"", (char*)NULL);
2067            return TCL_ERROR;
2068        }
2069
2070        osgEarth::Symbology::Stroke::LineCapStyle cap = osgEarth::Symbology::Stroke::LINECAP_FLAT;
2071        osgEarth::Symbology::Stroke::LineJoinStyle join =  osgEarth::Symbology::Stroke::LINEJOIN_MITRE;
2072        if (objc > 14) {
2073            cap = ParseLineCapStyle(Tcl_GetString(objv[14]), cap);
2074        }
2075        if (objc > 15) {
2076            join = ParseLineJoinStyle(Tcl_GetString(objv[15]), join);
2077        }
2078        bool lighting = true;
2079        osgEarth::Symbology::Style style;
2080        osgEarth::Symbology::LineSymbol *ls = style.getOrCreateSymbol<osgEarth::Symbology::LineSymbol>();
2081        ls->stroke()->color() = osgEarth::Symbology::Color(r, g, b);
2082        ls->stroke()->width() = lineWidth;
2083        ls->stroke()->widthUnits() = units;
2084        ls->stroke()->minPixels() = 1.0f;
2085        ls->stroke()->lineCap() = cap;
2086        //ls->stroke()->roundingRatio() = 0.4f;
2087        ls->stroke()->lineJoin() = join;
2088        if (objc > 16) {
2089            unsigned short stipplePattern = 0U;
2090            unsigned int stippleFactor = 1U;
2091            if (GetUShortFromObj(interp, objv[16], &stipplePattern) != TCL_OK ||
2092                GetUIntFromObj(interp, objv[17], &stippleFactor) != TCL_OK) {
2093                return TCL_ERROR;
2094            }
2095            if (stipplePattern > 0U) {
2096                ls->stroke()->stipplePattern() = stipplePattern; // Bitmask: unsigned short
2097                ls->stroke()->stippleFactor() = stippleFactor; // num reps: unsigned
2098            }
2099        }
2100
2101        osgEarth::Symbology::AltitudeSymbol::Clamping clamping =
2102            osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
2103        if (objc > 18) {
2104            clamping = ParseClamping(Tcl_GetString(objv[18]), clamping);
2105        }
2106        osgEarth::Symbology::AltitudeSymbol::Technique technique =
2107            osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
2108        if (objc > 18) {
2109            technique = ParseClampingTechnique(Tcl_GetString(objv[19]), technique);
2110        }
2111        osgEarth::Symbology::AltitudeSymbol *alt = style.getOrCreateSymbol<osgEarth::Symbology::AltitudeSymbol>();
2112        alt->clamping() = clamping;
2113        alt->technique() = technique;
2114        //alt->clampingResolution() = 1.0f;
2115        //alt->binding() = osgEarth::Symbology::AltitudeSymbol::BINDING_VERTEX; //BINDING_CENTROID
2116        //alt->verticalOffset() = osgEarth::Symbology::NumericExpression();
2117        //alt->verticalScale() = osgEarth::Symbology::NumericExpression();
2118
2119#if 1 || defined(USE_DEPTH_OFFSET)
2120        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
2121        rs->depthOffset()->enabled() = true;
2122        rs->depthOffset()->minBias() = 1000;
2123#endif
2124        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
2125        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
2126        geomOpts.styles()->addStyle(style);
2127        geomOpts.enableLighting() = false;
2128        geomOpts.minRange() = 0.f;
2129        geomOpts.maxRange() = FLT_MAX;
2130        if (objc > 20) {
2131            float min, max;
2132            if (GetFloatFromObj(interp, objv[20], &min) != TCL_OK ||
2133                GetFloatFromObj(interp, objv[21], &max) != TCL_OK) {
2134                return TCL_ERROR;
2135            }
2136            geomOpts.minRange() = min;
2137            geomOpts.maxRange() = max;
2138        }
2139        if (driver[0] == 'o' && strcmp(driver, "ogr") == 0) {
2140            osgEarth::Drivers::OGRFeatureOptions opts;
2141            opts.name() = name;
2142            if (osgDB::getLowerCaseFileExtension(url) == "csv") {
2143                url = loadCSVLayer(url.c_str());
2144            }
2145            opts.url() = url;
2146            geomOpts.featureOptions() = opts;
2147        } else if (driver[0] == 't' && strcmp(driver, "tfs") == 0) {
2148            osgEarth::Drivers::TFSFeatureOptions opts;
2149            opts.name() = name;
2150            opts.url() = url;
2151            opts.format() = format;
2152            geomOpts.featureOptions() = opts;
2153        } else if (driver[0] == 'w' && strcmp(driver, "wfs") == 0) {
2154            osgEarth::Drivers::WFSFeatureOptions opts;
2155            opts.name() = name;
2156            opts.url() = url;
2157            opts.outputFormat() = format;
2158            opts.typeName() = typeName;
2159            geomOpts.featureOptions() = opts;
2160        } else {
2161            Tcl_AppendResult(interp, "unknown feature driver \"", driver,
2162                             "\": should be 'ogr', 'tfs', or 'wfs'", (char*)NULL);
2163            return TCL_ERROR;
2164        }
2165        g_renderer->addModelLayer(name, geomOpts, cache, lighting, visible);
2166   } else if (type[0] == 't' && strcmp(type, "text") == 0) {
2167        char *driver = Tcl_GetString(objv[5]);
2168        char *format = Tcl_GetString(objv[6]);
2169        if (format && strlen(format) > 0 &&
2170            strcmp(format, "json") != 0 && strcmp(format, "gml") != 0) {
2171            Tcl_AppendResult(interp, "unknown format \"", format,
2172                             "\": should be 'json' or 'gml'", (char*)NULL);
2173            return TCL_ERROR;
2174        }
2175        char *typeName = Tcl_GetString(objv[7]);
2176        char *urlIn = Tcl_GetString(objv[8]);
2177        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
2178        if (url.empty()) {
2179            Tcl_AppendResult(interp, "file not found: \"",
2180                             urlIn, "\"", (char*)NULL);
2181            return TCL_ERROR;
2182        }
2183        if (GetBooleanFromObj(interp, objv[9], &cache) != TCL_OK) {
2184            return TCL_ERROR;
2185        }
2186        char *content = Tcl_GetString(objv[10]);
2187        char *priority = Tcl_GetString(objv[11]);
2188        float fgR, fgG, fgB;
2189        float bgR, bgG, bgB;
2190        float haloWidth, ftSize;
2191        osg::Vec2s pixelOffset(0, 0);
2192        if (GetFloatFromObj(interp, objv[12], &fgR) != TCL_OK ||
2193            GetFloatFromObj(interp, objv[13], &fgG) != TCL_OK ||
2194            GetFloatFromObj(interp, objv[14], &fgB) != TCL_OK ||
2195            GetFloatFromObj(interp, objv[15], &bgR) != TCL_OK ||
2196            GetFloatFromObj(interp, objv[16], &bgG) != TCL_OK ||
2197            GetFloatFromObj(interp, objv[17], &bgB) != TCL_OK ||
2198            GetFloatFromObj(interp, objv[18], &haloWidth) != TCL_OK ||
2199            GetFloatFromObj(interp, objv[19], &ftSize) != TCL_OK) {
2200            return TCL_ERROR;
2201        }
2202        bool removeDupes, declutter;
2203        if (GetBooleanFromObj(interp, objv[20], &removeDupes) != TCL_OK ||
2204            GetBooleanFromObj(interp, objv[21], &declutter) != TCL_OK) {
2205            return TCL_ERROR;
2206        }
2207        osgEarth::Symbology::TextSymbol::Alignment alignment =
2208            ParseTextAlignment(Tcl_GetString(objv[22]));
2209        int xoff, yoff;
2210        if (Tcl_GetIntFromObj(interp, objv[23], &xoff) != TCL_OK ||
2211            Tcl_GetIntFromObj(interp, objv[24], &yoff) != TCL_OK) {
2212            return TCL_ERROR;
2213        }
2214        pixelOffset.x() = (short)xoff;
2215        pixelOffset.y() = (short)yoff;
2216        bool lighting = true;
2217        osgEarth::Symbology::Style style;
2218        osgEarth::Symbology::TextSymbol *ts = style.getOrCreateSymbol<osgEarth::Symbology::TextSymbol>();
2219        ts->halo()->color() = osgEarth::Symbology::Color(bgR, bgG, bgB);
2220        ts->halo()->width() = haloWidth;
2221        ts->fill()->color() = osgEarth::Symbology::Color(fgR, fgG, fgB);
2222        ts->content() = osgEarth::Symbology::StringExpression(content);
2223        if (priority != NULL && strlen(priority) > 0) {
2224            ts->priority() = osgEarth::Symbology::NumericExpression(priority);
2225        }
2226        ts->removeDuplicateLabels() = removeDupes;
2227        //ts->font() = "Arial";
2228        ts->size() = ftSize;
2229        ts->alignment() = alignment;
2230        ts->declutter() = declutter;
2231        ts->pixelOffset() = pixelOffset;
2232        ts->encoding() = osgEarth::Symbology::TextSymbol::ENCODING_UTF8;
2233#ifdef USE_DEPTH_OFFSET
2234        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
2235        rs->depthOffset()->enabled() = true;
2236        rs->depthOffset()->minBias() = 1000;
2237#endif
2238        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
2239        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
2240        geomOpts.styles()->addStyle(style);
2241        geomOpts.enableLighting() = false;
2242        geomOpts.minRange() = 0.f;
2243        geomOpts.maxRange() = FLT_MAX;
2244        if (objc > 25) {
2245            float min, max;
2246            if (GetFloatFromObj(interp, objv[25], &min) != TCL_OK ||
2247                GetFloatFromObj(interp, objv[26], &max) != TCL_OK) {
2248                return TCL_ERROR;
2249            }
2250            geomOpts.minRange() = min;
2251            geomOpts.maxRange() = max;
2252        }
2253        if (driver[0] == 'o' && strcmp(driver, "ogr") == 0) {
2254            osgEarth::Drivers::OGRFeatureOptions opts;
2255            opts.name() = name;
2256            if (osgDB::getLowerCaseFileExtension(url) == "csv") {
2257                url = loadCSVLayer(url.c_str());
2258            }
2259            opts.url() = url;
2260            geomOpts.featureOptions() = opts;
2261        } else if (driver[0] == 't' && strcmp(driver, "tfs") == 0) {
2262            osgEarth::Drivers::TFSFeatureOptions opts;
2263            opts.name() = name;
2264            opts.url() = url;
2265            opts.format() = format;
2266            geomOpts.featureOptions() = opts;
2267        } else if (driver[0] == 'w' && strcmp(driver, "wfs") == 0) {
2268            osgEarth::Drivers::WFSFeatureOptions opts;
2269            opts.name() = name;
2270            opts.url() = url;
2271            opts.outputFormat() = format;
2272            opts.typeName() = typeName;
2273            geomOpts.featureOptions() = opts;
2274        } else {
2275            Tcl_AppendResult(interp, "unknown feature driver \"", driver,
2276                             "\": should be 'ogr', 'tfs', or 'wfs'", (char*)NULL);
2277            return TCL_ERROR;
2278        }
2279        g_renderer->addModelLayer(name, geomOpts, cache, lighting, visible);
2280    } else {
2281        Tcl_AppendResult(interp, "unknown map layer type \"", type,
2282                         "\": should be 'image', 'elevation' or 'model'", (char*)NULL);
2283        return TCL_ERROR;
2284    }
2285    return TCL_OK;
2286}
2287
2288static int
2289MapLayerDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
2290                 Tcl_Obj *const *objv)
2291{
2292    if (objc > 3) {
2293        char *name = Tcl_GetString(objv[3]);
2294        g_renderer->removeImageLayer(name);
2295        g_renderer->removeElevationLayer(name);
2296        g_renderer->removeModelLayer(name);
2297    } else {
2298        g_renderer->clearMap();
2299    }
2300
2301    return TCL_OK;
2302}
2303
2304static int
2305MapLayerMoveOp(ClientData clientData, Tcl_Interp *interp, int objc,
2306               Tcl_Obj *const *objv)
2307{
2308    int pos;
2309    if (Tcl_GetIntFromObj(interp, objv[3], &pos) != TCL_OK) {
2310        return TCL_ERROR;
2311    }
2312    char *name = Tcl_GetString(objv[4]);
2313    if (pos < 0) {
2314        Tcl_AppendResult(interp, "bad layer pos ", pos,
2315                         ": must be positive", (char*)NULL);
2316        return TCL_ERROR;
2317    }
2318    g_renderer->moveImageLayer(name, (unsigned int)pos);
2319    g_renderer->moveElevationLayer(name, (unsigned int)pos);
2320    g_renderer->moveModelLayer(name, (unsigned int)pos);
2321
2322    return TCL_OK;
2323}
2324
2325static int
2326MapLayerOpacityOp(ClientData clientData, Tcl_Interp *interp, int objc,
2327                  Tcl_Obj *const *objv)
2328{
2329    double opacity;
2330    if (Tcl_GetDoubleFromObj(interp, objv[3], &opacity) != TCL_OK) {
2331        return TCL_ERROR;
2332    }
2333    char *name = Tcl_GetString(objv[4]);
2334    if (opacity < 0.0 || opacity > 1.0) {
2335        Tcl_AppendResult(interp, "bad layer opacity ", opacity,
2336                         ": must be [0,1]", (char*)NULL);
2337        return TCL_ERROR;
2338    }
2339    g_renderer->setImageLayerOpacity(name, opacity);
2340    g_renderer->setModelLayerOpacity(name, opacity);
2341
2342    return TCL_OK;
2343}
2344
2345static int
2346MapLayerNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
2347                Tcl_Obj *const *objv)
2348{
2349    std::vector<std::string> layers;
2350    if (objc < 4) {
2351        g_renderer->getImageLayerNames(layers);
2352        g_renderer->getElevationLayerNames(layers);
2353        g_renderer->getModelLayerNames(layers);
2354    } else {
2355        char *type = Tcl_GetString(objv[3]);
2356        if (type[0] == 'i' && strcmp(type, "image") == 0) {
2357            g_renderer->getImageLayerNames(layers);
2358        } else if (type[0] == 'e' && strcmp(type, "elevation") == 0) {
2359            g_renderer->getElevationLayerNames(layers);
2360        } else if (type[0] == 'm' && strcmp(type, "model") == 0) {
2361            g_renderer->getModelLayerNames(layers);
2362        } else {
2363            Tcl_AppendResult(interp, "uknown type \"", type,
2364                         "\": must be image, elevation or model", (char*)NULL);
2365            return TCL_ERROR;
2366        }
2367    }
2368    std::ostringstream oss;
2369    size_t len = 0;
2370    oss << "nv>map names {";
2371    len += 18;
2372    for (size_t i = 0; i < layers.size(); i++) {
2373        oss << "\"" << layers[i] << "\"";
2374        len += 2 + layers[i].length();
2375        if (i < layers.size() - 1) {
2376            oss << " ";
2377            len++;
2378        }
2379    }
2380    oss << "}\n";
2381    len += 2;
2382#ifdef USE_THREADS
2383    queueResponse(oss.str().c_str(), len, Response::VOLATILE);
2384#else
2385    ssize_t bytesWritten = SocketWrite(oss.str().c_str(), len);
2386
2387    if (bytesWritten < 0) {
2388        return TCL_ERROR;
2389    }
2390#endif /*USE_THREADS*/
2391    return TCL_OK;
2392}
2393
2394static int
2395MapLayerVisibleOp(ClientData clientData, Tcl_Interp *interp, int objc,
2396                  Tcl_Obj *const *objv)
2397{
2398    bool visible;
2399    if (GetBooleanFromObj(interp, objv[3], &visible) != TCL_OK) {
2400        return TCL_ERROR;
2401    }
2402    char *name = Tcl_GetString(objv[4]);
2403
2404    g_renderer->setImageLayerVisibility(name, visible);
2405    g_renderer->setElevationLayerVisibility(name, visible);
2406    g_renderer->setModelLayerVisibility(name, visible);
2407
2408    return TCL_OK;
2409}
2410
2411static CmdSpec mapLayerOps[] = {
2412    {"add",     1, MapLayerAddOp,       6, 0, "name type driver ?url? ?args?"},
2413    {"delete",  1, MapLayerDeleteOp,    3, 4, "?name?"},
2414    {"move",    1, MapLayerMoveOp,      5, 5, "pos name"},
2415    {"names",   1, MapLayerNamesOp,     3, 4, "?type?"},
2416    {"opacity", 1, MapLayerOpacityOp,   5, 5, "opacity ?name?"},
2417    {"visible", 1, MapLayerVisibleOp,   5, 5, "bool ?name?"},
2418};
2419static int nMapLayerOps = NumCmdSpecs(mapLayerOps);
2420
2421static int
2422MapLayerOp(ClientData clientData, Tcl_Interp *interp, int objc,
2423           Tcl_Obj *const *objv)
2424{
2425    Tcl_ObjCmdProc *proc;
2426
2427    proc = GetOpFromObj(interp, nMapLayerOps, mapLayerOps,
2428                        CMDSPEC_ARG2, objc, objv, 0);
2429    if (proc == NULL) {
2430        return TCL_ERROR;
2431    }
2432    return (*proc) (clientData, interp, objc, objv);
2433}
2434
2435static int
2436MapLoadOp(ClientData clientData, Tcl_Interp *interp, int objc,
2437          Tcl_Obj *const *objv)
2438{
2439    char *opt = Tcl_GetString(objv[2]);
2440    if (opt[0] == 'f' && strcmp(opt, "file") == 0) {
2441        g_renderer->loadEarthFile(Tcl_GetString(objv[3]));
2442    } else if (opt[0] == 'u' && strcmp(opt, "url") == 0) {
2443        std::ostringstream path;
2444        path << "server:" << Tcl_GetString(objv[3]);
2445        g_renderer->loadEarthFile(path.str().c_str());
2446    } else if (opt[0] == 'd' && strcmp(opt, "data") == 0) {
2447        opt = Tcl_GetString(objv[3]);
2448        if (opt[0] != 'f' || strcmp(opt, "follows") != 0) {
2449            return TCL_ERROR;
2450        }
2451        int len;
2452        if (Tcl_GetIntFromObj(interp, objv[4], &len) != TCL_OK) {
2453            return TCL_ERROR;
2454        }
2455        // Read Earth file from socket
2456        char *buf = (char *)malloc((size_t)len);
2457        SocketRead(buf, (size_t)len);
2458        std::ostringstream path;
2459        path << "/tmp/tmp" << getpid() << ".earth";
2460        const char *pathStr = path.str().c_str();
2461        FILE *tmpFile = fopen(pathStr, "w");
2462        fwrite(buf, len, 1, tmpFile);
2463        fclose(tmpFile);
2464        g_renderer->loadEarthFile(pathStr);
2465        unlink(pathStr);
2466        free(buf);
2467    } else {
2468        return TCL_ERROR;
2469    }
2470    return TCL_OK;
2471}
2472
2473static int
2474MapPinAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
2475            Tcl_Obj *const *objv)
2476{
2477    int x, y;
2478    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
2479        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
2480        return TCL_ERROR;
2481    }
2482    char *label = NULL;
2483    if (objc > 5) {
2484        label = Tcl_GetString(objv[5]);
2485    }
2486
2487    if (g_renderer->getMapSRS() == NULL) {
2488        Tcl_AppendResult(interp, "Could not get map SRS", (char*)NULL);
2489        return TCL_ERROR;
2490    }
2491
2492    // Get lat/long
2493    double latitude, longitude;
2494    if (!g_renderer->mouseToLatLong(x, y, &latitude, &longitude)) {
2495        USER_ERROR("Can't add pin here");
2496        return TCL_OK;
2497    }
2498
2499    g_renderer->addPlaceNode(latitude, longitude, label);
2500    return TCL_OK;
2501}
2502
2503static int
2504MapPinDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
2505               Tcl_Obj *const *objv)
2506{
2507    int x, y;
2508    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
2509        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
2510        return TCL_ERROR;
2511    }
2512
2513    g_renderer->deletePlaceNode(x, y);
2514    return TCL_OK;
2515}
2516
2517static int
2518MapPinHoverOp(ClientData clientData, Tcl_Interp *interp, int objc,
2519              Tcl_Obj *const *objv)
2520{
2521    int x, y;
2522    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
2523        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
2524        return TCL_ERROR;
2525    }
2526
2527    g_renderer->hoverPlaceNode(x, y);
2528    return TCL_OK;
2529}
2530
2531static CmdSpec mapPinOps[] = {
2532    {"add",     1, MapPinAddOp,     5, 6, "x y ?label?"},
2533    {"delete",  1, MapPinDeleteOp,  5, 5, "x y"},
2534    {"hover",   1, MapPinHoverOp,   5, 5, "x y"},
2535};
2536static int nMapPinOps = NumCmdSpecs(mapPinOps);
2537
2538static int
2539MapPinOp(ClientData clientData, Tcl_Interp *interp, int objc,
2540           Tcl_Obj *const *objv)
2541{
2542    Tcl_ObjCmdProc *proc;
2543
2544    proc = GetOpFromObj(interp, nMapPinOps, mapPinOps,
2545                        CMDSPEC_ARG2, objc, objv, 0);
2546    if (proc == NULL) {
2547        return TCL_ERROR;
2548    }
2549    return (*proc) (clientData, interp, objc, objv);
2550}
2551
2552static int
2553MapPositionDisplayOp(ClientData clientData, Tcl_Interp *interp, int objc,
2554                     Tcl_Obj *const *objv)
2555{
2556    bool state;
2557    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
2558        return TCL_ERROR;
2559    }
2560    Renderer::CoordinateDisplayType type = Renderer::COORDS_LATLONG_DECIMAL_DEGREES;
2561    if (state && objc > 3) {
2562        const char *str = Tcl_GetString(objv[3]);
2563        if (str[0] == 'l' && strcmp(str, "latlong_decimal_degrees") == 0) {
2564            type = Renderer::COORDS_LATLONG_DECIMAL_DEGREES;
2565        } else if (str[0] == 'l' && strcmp(str, "latlong_degrees_decimal_minutes") == 0) {
2566            type = Renderer::COORDS_LATLONG_DEGREES_DECIMAL_MINUTES;
2567        } else if (str[0] == 'l' && strcmp(str, "latlong_degrees_minutes_seconds") == 0) {
2568            type = Renderer::COORDS_LATLONG_DEGREES_MINUTES_SECONDS;
2569        } else if (str[0] == 'm' && strcmp(str, "mgrs") == 0) {
2570            type = Renderer::COORDS_MGRS;
2571        } else {
2572            Tcl_AppendResult(interp, "invalid type: \"", str,
2573                             "\": should be 'latlong_decimal_degrees', 'latlong_degrees_decimal_minutes', 'latlong_degrees_minutes_seconds', or 'mgrs'",
2574                             (char*)NULL);
2575            return TCL_ERROR;
2576        }
2577    }
2578    if (state && objc > 4) {
2579        int precision;
2580        if (Tcl_GetIntFromObj(interp, objv[4], &precision) != TCL_OK) {
2581            return TCL_ERROR;
2582        }
2583        g_renderer->setCoordinateReadout(state, type, precision);
2584    } else {
2585        g_renderer->setCoordinateReadout(state, type);
2586    }
2587
2588    return TCL_OK;
2589}
2590
2591static int
2592MapResetOp(ClientData clientData, Tcl_Interp *interp, int objc,
2593           Tcl_Obj *const *objv)
2594{
2595    char *typeStr = Tcl_GetString(objv[2]);
2596    osgEarth::MapOptions::CoordinateSystemType type;
2597    if (typeStr[0] == 'g' && strcmp(typeStr, "geocentric") == 0) {
2598        type = osgEarth::MapOptions::CSTYPE_GEOCENTRIC;
2599    } else if (typeStr[0] == 'g' && strcmp(typeStr, "geocentric_cube") == 0) {
2600        type = osgEarth::MapOptions::CSTYPE_GEOCENTRIC_CUBE;
2601    } else if (typeStr[0] == 'p' && strcmp(typeStr, "projected") == 0) {
2602        type = osgEarth::MapOptions::CSTYPE_PROJECTED;
2603    } else {
2604        Tcl_AppendResult(interp, "bad map type \"", typeStr,
2605                         "\": must be geocentric or projected", (char*)NULL);
2606        return TCL_ERROR;
2607    }
2608    float color[3];
2609    if (GetFloatFromObj(interp, objv[3], &color[0]) != TCL_OK ||
2610        GetFloatFromObj(interp, objv[4], &color[1]) != TCL_OK ||
2611        GetFloatFromObj(interp, objv[5], &color[2]) != TCL_OK) {
2612        return TCL_ERROR;
2613    }
2614
2615    osg::Vec4f bgColor(color[0],color[1],color[2],1);
2616    if (type == osgEarth::MapOptions::CSTYPE_PROJECTED) {
2617        if (objc < 7) {
2618            Tcl_AppendResult(interp, "wrong # arguments: profile required for projected maps", (char*)NULL);
2619            return TCL_ERROR;
2620        }
2621        char *profile = Tcl_GetString(objv[6]);
2622        if (objc > 7) {
2623            if (objc < 11) {
2624                Tcl_AppendResult(interp, "wrong # arguments: 4 bounds arguments required", (char*)NULL);
2625                return TCL_ERROR;
2626            }
2627            double bounds[4];
2628            for (int i = 0; i < 4; i++) {
2629                if (Tcl_GetDoubleFromObj(interp, objv[7+i], &bounds[i]) != TCL_OK) {
2630                    return TCL_ERROR;
2631                }
2632            }
2633            // Check if min > max
2634            if (bounds[0] > bounds[2] ||
2635                bounds[1] > bounds[3]) {
2636                Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
2637                return TCL_ERROR;
2638            }
2639            // Note: plate-carre generates same SRS as others, but with
2640            // _is_plate_carre flag set
2641            // In map profile, _is_plate_carre is forced on for
2642            // geographic+projected SRS
2643            if (strcmp(profile, "geodetic") == 0 ||
2644                strcmp(profile, "epsg:4326") == 0 ||
2645                strcmp(profile, "wgs84") == 0 ||
2646                strcmp(profile, "plate-carre") == 0 ||
2647                strcmp(profile, "plate-carree") == 0) {
2648                if (bounds[0] < -180. || bounds[0] > 180. ||
2649                    bounds[2] < -180. || bounds[2] > 180. ||
2650                    bounds[1] < -90. || bounds[1] > 90. ||
2651                    bounds[3] < -90. || bounds[3] > 90.) {
2652                    Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
2653                    return TCL_ERROR;
2654                }
2655                // As of osgearth 2.7, these are not permitted as map profiles.
2656                // Use epsg:32663 instead
2657                Tcl_AppendResult(interp, "Invalid profile: can't use geographic coordinate system as projection.  Consider using an equirectangular projection (epsg:32663) instead.", (char*)NULL);
2658                return TCL_ERROR;
2659            } else if (strcmp(profile, "spherical-mercator") == 0 ||
2660                       strcmp(profile, "epsg:900913") == 0 ||
2661                       strcmp(profile, "epsg:3785") == 0 ||
2662                       strcmp(profile, "epsg:3857") == 0 ||
2663                       strcmp(profile, "epsg:102113") == 0) {
2664                for (int i = 0; i < 4; i++) {
2665                    if (bounds[i] < -20037508.34278925 || bounds[i] > 20037508.34278925) {
2666                        Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
2667                        return TCL_ERROR;
2668                    }
2669                }
2670            } else if (strcmp(profile, "epsg:32662") == 0 ||
2671                       strcmp(profile, "epsg:32663") == 0) {
2672                // epsg:32662 is deprecated: spherical method applied to ellipsoid WGS84
2673                // Equirectangular projection (WGS84/World Equidistant Cylindrical)
2674                if (bounds[0] < -20037508.34278925 || bounds[0] > 20037508.34278925 ||
2675                    bounds[2] < -20037508.34278925 || bounds[2] > 20037508.34278925 ||
2676                    bounds[1] < -10018754.17139463 || bounds[1] > 10018754.17139463 ||
2677                    bounds[3] < -10018754.17139463 || bounds[3] > 10018754.17139463) {
2678                    Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
2679                    return TCL_ERROR;
2680                }
2681            }
2682            g_renderer->resetMap(type, bgColor, profile, bounds);
2683        } else {
2684            // If no bounds are given, this must be a named profile with implicit bounds
2685            if (osgEarth::Registry::instance()->getNamedProfile(profile) == NULL) {
2686                Tcl_AppendResult(interp, "bad named profile \"", profile,
2687                                 "\": must be e.g. 'global-geodetic', 'global-mercator'...", (char*)NULL);
2688                return TCL_ERROR;
2689            }
2690            g_renderer->resetMap(type, bgColor, profile);
2691        }
2692    } else {
2693        // No profile required for geocentric (3D) maps
2694        g_renderer->resetMap(type, bgColor);
2695    }
2696
2697    return TCL_OK;
2698}
2699
2700static int
2701MapScaleBarOp(ClientData clientData, Tcl_Interp *interp, int objc,
2702              Tcl_Obj *const *objv)
2703{
2704    bool state;
2705    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
2706        return TCL_ERROR;
2707    }
2708    g_renderer->setScaleBar(state);
2709    if (state && objc > 3) {
2710        const char *unitStr = Tcl_GetString(objv[3]);
2711        ScaleBarUnits units;
2712        if (unitStr[0] == 'm' && strcmp(unitStr, "meters") == 0) {
2713            units = UNITS_METERS;
2714        } else if (unitStr[0] == 'f' && strcmp(unitStr, "feet") == 0) {
2715            units = UNITS_INTL_FEET;
2716        } else if (unitStr[0] == 'u' && strcmp(unitStr, "us_survey_feet") == 0) {
2717            units = UNITS_US_SURVEY_FEET;
2718        } else if (unitStr[0] == 'n' && strcmp(unitStr, "nautical_miles") == 0) {
2719            units = UNITS_NAUTICAL_MILES;
2720        } else {
2721            Tcl_AppendResult(interp, "bad units \"", unitStr,
2722                             "\": must be 'meters', 'feet', 'us_survey_feet' or 'nautical_miles'", (char*)NULL);
2723            return TCL_ERROR;
2724        }
2725        g_renderer->setScaleBarUnits(units);
2726    }
2727    return TCL_OK;
2728}
2729
2730static int
2731MapSetPositionOp(ClientData clientData, Tcl_Interp *interp, int objc,
2732            Tcl_Obj *const *objv)
2733{
2734    if (objc < 3) {
2735        g_renderer->clearReadout();
2736    } else {
2737        int x, y;
2738        if (Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK ||
2739            Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK) {
2740            return TCL_ERROR;
2741        }
2742        g_renderer->setReadout(x, y);
2743    }
2744    return TCL_OK;
2745}
2746
2747static int
2748MapTerrainAmbientOp(ClientData clientData, Tcl_Interp *interp, int objc,
2749                    Tcl_Obj *const *objv)
2750{
2751    float ambient;
2752    if (GetFloatFromObj(interp, objv[3], &ambient) != TCL_OK) {
2753        return TCL_ERROR;
2754    }
2755    g_renderer->setSkyAmbient(ambient);
2756    return TCL_OK;
2757}
2758
2759static int
2760MapTerrainColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
2761                  Tcl_Obj *const *objv)
2762{
2763    float color[3];
2764    if (GetFloatFromObj(interp, objv[3], &color[0]) != TCL_OK ||
2765        GetFloatFromObj(interp, objv[4], &color[1]) != TCL_OK ||
2766        GetFloatFromObj(interp, objv[5], &color[2]) != TCL_OK) {
2767        return TCL_ERROR;
2768    }
2769    g_renderer->setTerrainColor(osg::Vec4f(color[0],color[1],color[2],1));
2770    return TCL_OK;
2771}
2772
2773static int
2774MapTerrainEdgesOp(ClientData clientData, Tcl_Interp *interp, int objc,
2775                  Tcl_Obj *const *objv)
2776{
2777    bool state;
2778    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
2779        return TCL_ERROR;
2780    }
2781    g_renderer->setTerrainEdges(state);
2782    return TCL_OK;
2783}
2784
2785static int
2786MapTerrainLightingOp(ClientData clientData, Tcl_Interp *interp, int objc,
2787                     Tcl_Obj *const *objv)
2788{
2789    bool state;
2790    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
2791        return TCL_ERROR;
2792    }
2793
2794    g_renderer->setTerrainLighting(state);
2795    return TCL_OK;
2796}
2797
2798static int
2799MapTerrainLineColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
2800                      Tcl_Obj *const *objv)
2801{
2802    float color[3];
2803    if (GetFloatFromObj(interp, objv[3], &color[0]) != TCL_OK ||
2804        GetFloatFromObj(interp, objv[4], &color[1]) != TCL_OK ||
2805        GetFloatFromObj(interp, objv[5], &color[2]) != TCL_OK) {
2806        return TCL_ERROR;
2807    }
2808    g_renderer->setTerrainLineColor(osg::Vec4f(color[0], color[1], color[2],1));
2809    return TCL_OK;
2810}
2811
2812static int
2813MapTerrainLineWidthOp(ClientData clientData, Tcl_Interp *interp, int objc,
2814                      Tcl_Obj *const *objv)
2815{
2816    float width;
2817    if (GetFloatFromObj(interp, objv[3], &width) != TCL_OK) {
2818        return TCL_ERROR;
2819    }
2820    g_renderer->setTerrainLineWidth(width);
2821    return TCL_OK;
2822}
2823
2824static int
2825MapTerrainVertScaleOp(ClientData clientData, Tcl_Interp *interp, int objc,
2826                      Tcl_Obj *const *objv)
2827{
2828    double scale;
2829    if (Tcl_GetDoubleFromObj(interp, objv[3], &scale) != TCL_OK) {
2830        return TCL_ERROR;
2831    }
2832
2833    g_renderer->setTerrainVerticalScale(scale);
2834    return TCL_OK;
2835}
2836
2837static int
2838MapTerrainWireframeOp(ClientData clientData, Tcl_Interp *interp, int objc,
2839                      Tcl_Obj *const *objv)
2840{
2841    bool state;
2842    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
2843        return TCL_ERROR;
2844    }
2845
2846    g_renderer->setTerrainWireframe(state);
2847    return TCL_OK;
2848}
2849
2850static CmdSpec mapTerrainOps[] = {
2851    {"ambient",   1, MapTerrainAmbientOp,   4, 4, "val"},
2852    {"color",     1, MapTerrainColorOp,     6, 6, "r g b"},
2853    {"edges",     1, MapTerrainEdgesOp,     4, 4, "bool"},
2854    {"lighting",  2, MapTerrainLightingOp,  4, 4, "bool"},
2855    {"linecolor", 5, MapTerrainLineColorOp, 6, 6, "r g b"},
2856    {"linewidth", 5, MapTerrainLineWidthOp, 4, 4, "val"},
2857    {"vertscale", 1, MapTerrainVertScaleOp, 4, 4, "val"},
2858    {"wireframe", 1, MapTerrainWireframeOp, 4, 4, "bool"},
2859};
2860static int nMapTerrainOps = NumCmdSpecs(mapTerrainOps);
2861
2862static int
2863MapTerrainOp(ClientData clientData, Tcl_Interp *interp, int objc,
2864           Tcl_Obj *const *objv)
2865{
2866    Tcl_ObjCmdProc *proc;
2867
2868    proc = GetOpFromObj(interp, nMapTerrainOps, mapTerrainOps,
2869                        CMDSPEC_ARG2, objc, objv, 0);
2870    if (proc == NULL) {
2871        return TCL_ERROR;
2872    }
2873    return (*proc) (clientData, interp, objc, objv);
2874}
2875
2876static int
2877MapTimeOp(ClientData clientData, Tcl_Interp *interp, int objc,
2878          Tcl_Obj *const *objv)
2879{
2880    osgEarth::DateTime now;
2881    int year, month, day;
2882    double hours;
2883    year = now.year();
2884    month = now.month();
2885    day = now.day();
2886    hours = now.hours();
2887    if (objc > 2) {
2888        if (Tcl_GetDoubleFromObj(interp, objv[2], &hours) != TCL_OK) {
2889            return TCL_ERROR;
2890        }
2891    }
2892    if (objc > 3) {
2893        if (Tcl_GetIntFromObj(interp, objv[3], &day) != TCL_OK) {
2894            return TCL_ERROR;
2895        }
2896    }
2897    if (objc > 4) {
2898        if (Tcl_GetIntFromObj(interp, objv[4], &month) != TCL_OK) {
2899            return TCL_ERROR;
2900        }
2901    }
2902    if (objc > 5) {
2903        if (Tcl_GetIntFromObj(interp, objv[5], &year) != TCL_OK) {
2904            return TCL_ERROR;
2905        }
2906    }
2907
2908    g_renderer->setEphemerisTime(year, month, day, hours);
2909    return TCL_OK;
2910}
2911
2912static CmdSpec mapOps[] = {
2913    {"attrib",   1, MapAttributionOp,     3, 3, "string"},
2914    {"box",      1, MapBoxOp,             3, 0, "op ?params..."},
2915    {"coords",   1, MapCoordsOp,          4, 6, "token coords ?srs? ?verticalDatum?"},
2916    {"grid",     1, MapGraticuleOp,       3, 4, "bool ?type?"},
2917    {"layer",    2, MapLayerOp,           3, 0, "op ?params...?"},
2918    {"load",     2, MapLoadOp,            4, 5, "options"},
2919    {"pin",      2, MapPinOp,             3, 0, "op ?params...?"},
2920    {"posdisp",  2, MapPositionDisplayOp, 3, 5, "bool ?format? ?precision?"},
2921    {"reset",    1, MapResetOp,           6, 11, "type r g b ?profile xmin ymin xmax ymax?"},
2922    {"scalebar", 1, MapScaleBarOp,        3, 4, "bool ?units?"},
2923    {"setpos",   1, MapSetPositionOp,     2, 4, "x y"},
2924    {"terrain",  1, MapTerrainOp,         3, 0, "op ?params...?"},
2925    {"time",     1, MapTimeOp,            2, 6, "?hours? ?day? ?month? ?year?"},
2926};
2927static int nMapOps = NumCmdSpecs(mapOps);
2928
2929static int
2930MapCmd(ClientData clientData, Tcl_Interp *interp, int objc,
2931       Tcl_Obj *const *objv)
2932{
2933    Tcl_ObjCmdProc *proc;
2934
2935    proc = GetOpFromObj(interp, nMapOps, mapOps,
2936                        CMDSPEC_ARG1, objc, objv, 0);
2937    if (proc == NULL) {
2938        return TCL_ERROR;
2939    }
2940    return (*proc) (clientData, interp, objc, objv);
2941}
2942
2943static int
2944MouseClickOp(ClientData clientData, Tcl_Interp *interp, int objc,
2945             Tcl_Obj *const *objv)
2946{
2947    int button;
2948    double x, y;
2949
2950    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
2951        return TCL_ERROR;
2952    }
2953    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
2954        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
2955        return TCL_ERROR;
2956    }
2957
2958    g_renderer->mouseClick(button, x, y);
2959    return TCL_OK;
2960}
2961
2962static int
2963MouseDoubleClickOp(ClientData clientData, Tcl_Interp *interp, int objc,
2964                   Tcl_Obj *const *objv)
2965{
2966    int button;
2967    double x, y;
2968
2969    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
2970        return TCL_ERROR;
2971    }
2972    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
2973        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
2974        return TCL_ERROR;
2975    }
2976
2977    g_renderer->mouseDoubleClick(button, x, y);
2978    return TCL_OK;
2979}
2980
2981static int
2982MouseDragOp(ClientData clientData, Tcl_Interp *interp, int objc,
2983            Tcl_Obj *const *objv)
2984{
2985    int button;
2986    double x, y;
2987
2988    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
2989        return TCL_ERROR;
2990    }
2991    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
2992        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
2993        return TCL_ERROR;
2994    }
2995
2996    g_renderer->mouseDrag(button, x, y);
2997    return TCL_OK;
2998}
2999
3000static int
3001MouseMotionOp(ClientData clientData, Tcl_Interp *interp, int objc,
3002              Tcl_Obj *const *objv)
3003{
3004    double x, y;
3005
3006    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
3007        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
3008        return TCL_ERROR;
3009    }
3010
3011    g_renderer->mouseMotion(x, y);
3012    return TCL_OK;
3013}
3014
3015static int
3016MouseReleaseOp(ClientData clientData, Tcl_Interp *interp, int objc,
3017               Tcl_Obj *const *objv)
3018{
3019    int button;
3020    double x, y;
3021
3022    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
3023        return TCL_ERROR;
3024    }
3025    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
3026        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
3027        return TCL_ERROR;
3028    }
3029
3030    g_renderer->mouseRelease(button, x, y);
3031    return TCL_OK;
3032}
3033
3034static int
3035MouseScrollOp(ClientData clientData, Tcl_Interp *interp, int objc,
3036              Tcl_Obj *const *objv)
3037{
3038    int direction;
3039
3040    if (Tcl_GetIntFromObj(interp, objv[2], &direction) != TCL_OK) {
3041        return TCL_ERROR;
3042    }
3043
3044    g_renderer->mouseScroll(direction);
3045    return TCL_OK;
3046}
3047
3048static CmdSpec mouseOps[] = {
3049    {"click",    1, MouseClickOp,       5, 5, "button x y"},
3050    {"dblclick", 2, MouseDoubleClickOp, 5, 5, "button x y"},
3051    {"drag",     2, MouseDragOp,        5, 5, "button x y"},
3052    {"motion",   1, MouseMotionOp,      4, 4, "x y"},
3053    {"release",  1, MouseReleaseOp,     5, 5, "button x y"},
3054    {"scroll",   1, MouseScrollOp,      3, 3, "direction"},
3055};
3056static int nMouseOps = NumCmdSpecs(mouseOps);
3057
3058static int
3059MouseCmd(ClientData clientData, Tcl_Interp *interp, int objc,
3060         Tcl_Obj *const *objv)
3061{
3062    Tcl_ObjCmdProc *proc;
3063
3064    proc = GetOpFromObj(interp, nMouseOps, mouseOps,
3065                        CMDSPEC_ARG1, objc, objv, 0);
3066    if (proc == NULL) {
3067        return TCL_ERROR;
3068    }
3069    return (*proc) (clientData, interp, objc, objv);
3070}
3071
3072static int
3073RendererRenderOp(ClientData clientData, Tcl_Interp *interp, int objc,
3074                 Tcl_Obj *const *objv)
3075{
3076    g_renderer->eventuallyRender();
3077    return TCL_OK;
3078}
3079
3080static CmdSpec rendererOps[] = {
3081    {"render",     1, RendererRenderOp, 2, 2, ""},
3082};
3083static int nRendererOps = NumCmdSpecs(rendererOps);
3084
3085static int
3086RendererCmd(ClientData clientData, Tcl_Interp *interp, int objc,
3087            Tcl_Obj *const *objv)
3088{
3089    Tcl_ObjCmdProc *proc;
3090
3091    proc = GetOpFromObj(interp, nRendererOps, rendererOps,
3092                        CMDSPEC_ARG1, objc, objv, 0);
3093    if (proc == NULL) {
3094        return TCL_ERROR;
3095    }
3096    return (*proc) (clientData, interp, objc, objv);
3097}
3098
3099static int
3100ScreenBgColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
3101                Tcl_Obj *const *objv)
3102{
3103    float color[3];
3104
3105    if (GetFloatFromObj(interp, objv[2], &color[0]) != TCL_OK ||
3106        GetFloatFromObj(interp, objv[3], &color[1]) != TCL_OK ||
3107        GetFloatFromObj(interp, objv[4], &color[2]) != TCL_OK) {
3108        return TCL_ERROR;
3109    }
3110
3111    g_renderer->setBackgroundColor(color);
3112    return TCL_OK;
3113}
3114
3115static int
3116ScreenCoordsOp(ClientData clientData, Tcl_Interp *interp, int objc,
3117               Tcl_Obj *const *objv)
3118{
3119    int tokenLength;
3120    const char *token = Tcl_GetStringFromObj(objv[2], &tokenLength);
3121    int numCoords;
3122    Tcl_Obj **coords;
3123    if (Tcl_ListObjGetElements(interp, objv[3], &numCoords, &coords) != TCL_OK) {
3124        return TCL_ERROR;
3125    }
3126    if (numCoords == 0) {
3127        Tcl_AppendResult(interp, "no x,y,z coordinates in list", (char *)NULL);
3128        return TCL_ERROR;
3129    }
3130    if (numCoords % 3 != 0) {
3131        Tcl_AppendResult(interp, "invalid number of coordinates in list",
3132                         (char *)NULL);
3133        return TCL_ERROR;
3134    }
3135
3136    const osgEarth::SpatialReference *srs = NULL;
3137    if (objc < 5) {
3138        srs = g_renderer->getMapSRS();
3139        if (srs == NULL) {
3140            Tcl_AppendResult(interp, "Could not determine map SRS", (char*)NULL);
3141            return TCL_ERROR;
3142        }
3143    } else {
3144        std::string srsInit(Tcl_GetString(objv[4]));
3145        std::string verticalDatum;
3146        if (objc > 5) {
3147            verticalDatum = Tcl_GetString(objv[5]);
3148        }
3149        srs = osgEarth::SpatialReference::get(srsInit, verticalDatum);
3150        if (srs == NULL) {
3151            Tcl_AppendResult(interp, "bad SRS \"", srsInit.c_str(), "\"", (char*)NULL);
3152            return TCL_ERROR;
3153        }
3154    }
3155    Tcl_Obj *listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
3156    std::vector<osg::Vec3d> coordVec;
3157    for (int i = 0; i < numCoords; i += 3) {
3158        double x, y, z;
3159        if (Tcl_GetDoubleFromObj(interp, coords[i], &x) != TCL_OK ||
3160            Tcl_GetDoubleFromObj(interp, coords[i+1], &y) != TCL_OK ||
3161            Tcl_GetDoubleFromObj(interp, coords[i+2], &z) != TCL_OK) {
3162            return TCL_ERROR;
3163        }
3164        // ALTMODE_RELATIVE is height above terrain, ALTMODE_ABSOLUTE means
3165        // relative to the vertical datum
3166        osgEarth::GeoPoint mapPoint(srs, x, y, z, osgEarth::ALTMODE_ABSOLUTE);
3167        osg::Vec3d world;
3168        if (g_renderer->getWorldCoords(mapPoint, &world)) {
3169            coordVec.push_back(world);
3170        } else {
3171            coordVec.push_back(osg::Vec3d(std::numeric_limits<double>::quiet_NaN(),
3172                                          std::numeric_limits<double>::quiet_NaN(),
3173                                          std::numeric_limits<double>::quiet_NaN()));
3174        }
3175    }
3176    g_renderer->worldToScreen(coordVec);
3177    for (std::vector<osg::Vec3d>::iterator itr = coordVec.begin();
3178         itr != coordVec.end(); ++itr) {
3179        Tcl_Obj *objPtr = Tcl_NewDoubleObj(itr->x());
3180        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
3181        objPtr = Tcl_NewDoubleObj(itr->y());
3182        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
3183        objPtr = Tcl_NewDoubleObj(itr->z());
3184        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
3185    }
3186    // send coords to client
3187    int listLength;
3188    const char *string = Tcl_GetStringFromObj(listObjPtr, &listLength);
3189    size_t length = listLength + tokenLength + 22;
3190    char *mesg = new char[length];
3191    length = snprintf(mesg, length, "nv>screen coords %s {%s}\n", token, string);
3192    Tcl_DecrRefCount(listObjPtr);
3193    queueResponse(mesg, length, Response::VOLATILE);
3194    delete [] mesg;
3195    return TCL_OK;
3196}
3197
3198static int
3199ScreenSizeOp(ClientData clientData, Tcl_Interp *interp, int objc,
3200             Tcl_Obj *const *objv)
3201{
3202    int width, height;
3203
3204    if (Tcl_GetIntFromObj(interp, objv[2], &width) != TCL_OK ||
3205        Tcl_GetIntFromObj(interp, objv[3], &height) != TCL_OK) {
3206        return TCL_ERROR;
3207    }
3208
3209    g_renderer->setWindowSize(width, height);
3210    return TCL_OK;
3211}
3212
3213static CmdSpec screenOps[] = {
3214    {"bgcolor", 1, ScreenBgColorOp, 5, 5, "r g b"},
3215    {"coords",  1, ScreenCoordsOp, 4, 6, "token coords ?srs? ?verticalDatum?"},
3216    {"size",    1, ScreenSizeOp, 4, 4, "width height"}
3217};
3218static int nScreenOps = NumCmdSpecs(screenOps);
3219
3220static int
3221ScreenCmd(ClientData clientData, Tcl_Interp *interp, int objc,
3222          Tcl_Obj *const *objv)
3223{
3224    Tcl_ObjCmdProc *proc;
3225
3226    proc = GetOpFromObj(interp, nScreenOps, screenOps,
3227                        CMDSPEC_ARG1, objc, objv, 0);
3228    if (proc == NULL) {
3229        return TCL_ERROR;
3230    }
3231    return (*proc) (clientData, interp, objc, objv);
3232}
3233
3234static int
3235SelectFeatureOp(ClientData clientData, Tcl_Interp *interp, int objc,
3236                Tcl_Obj *const *objv)
3237{
3238    int numIDs;
3239    Tcl_Obj **ids;
3240    if (Tcl_ListObjGetElements(interp, objv[2], &numIDs, &ids) != TCL_OK) {
3241        return TCL_ERROR;
3242    }
3243    if (numIDs == 0) {
3244        Tcl_AppendResult(interp, "no IDs in list", (char *)NULL);
3245        return TCL_ERROR;
3246    }
3247    std::vector<unsigned long> featureIDs;
3248    for (int i = 0; i < numIDs; i++) {
3249        long id;
3250        if (Tcl_GetLongFromObj(interp, ids[i], &id) != TCL_OK) {
3251            return TCL_ERROR;
3252        }
3253        featureIDs.push_back((unsigned long)id);
3254    }
3255    const char *layerName = Tcl_GetString(objv[3]);
3256
3257    g_renderer->selectFeatures(featureIDs, layerName);
3258    return TCL_OK;
3259}
3260
3261static CmdSpec selectOps[] = {
3262    {"feature", 1, SelectFeatureOp, 4, 4, "idlist layerName"},
3263};
3264static int nSelectOps = NumCmdSpecs(selectOps);
3265
3266static int
3267SelectCmd(ClientData clientData, Tcl_Interp *interp, int objc,
3268          Tcl_Obj *const *objv)
3269{
3270    Tcl_ObjCmdProc *proc;
3271
3272    proc = GetOpFromObj(interp, nSelectOps, selectOps,
3273                        CMDSPEC_ARG1, objc, objv, 0);
3274    if (proc == NULL) {
3275        return TCL_ERROR;
3276    }
3277    return (*proc) (clientData, interp, objc, objv);
3278}
3279
3280#ifdef USE_READ_THREAD
3281int
3282GeoVis::queueCommands(Tcl_Interp *interp,
3283                      ClientData clientData,
3284                      ReadBuffer *inBufPtr)
3285{
3286    Tcl_DString commandString;
3287    Tcl_DStringInit(&commandString);
3288    fd_set readFds;
3289
3290    FD_ZERO(&readFds);
3291    FD_SET(inBufPtr->file(), &readFds);
3292    while (inBufPtr->isLineAvailable() ||
3293           (select(inBufPtr->file()+1, &readFds, NULL, NULL, NULL) > 0)) {
3294        size_t numBytes;
3295        unsigned char *buffer;
3296
3297        /* A short read is treated as an error here because we assume that we
3298         * will always get commands line by line. */
3299        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
3300            /* Terminate the server if we can't communicate with the client
3301             * anymore. */
3302            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
3303                TRACE("Exiting server on EOF from client");
3304                return -1;
3305            } else {
3306                ERROR("Exiting server, failed to read from client: %s",
3307                      strerror(errno));
3308                return -1;
3309            }
3310        }
3311        Tcl_DStringAppend(&commandString, (char *)buffer, numBytes);
3312        if (Tcl_CommandComplete(Tcl_DStringValue(&commandString))) {
3313            // Add to queue
3314            Command *command = new Command(Command::COMMAND);
3315            command->setMessage((unsigned char *)Tcl_DStringValue(&commandString),
3316                                Tcl_DStringLength(&commandString), Command::VOLATILE);
3317            g_inQueue->enqueue(command);
3318            Tcl_DStringSetLength(&commandString, 0);
3319        }
3320        FD_SET(inBufPtr->file(), &readFds);
3321    }
3322
3323    return 1;
3324}
3325#endif
3326
3327/**
3328 * \brief Execute commands from client in Tcl interpreter
3329 *
3330 * In this threaded model, the select call is for event compression.  We
3331 * want to execute render server commands as long as they keep coming. 
3332 * This lets us execute a stream of many commands but render once.  This
3333 * benefits camera movements, screen resizing, and opacity changes
3334 * (using a slider on the client).  The down side is you don't render
3335 * until there's a lull in the command stream.  If the client needs an
3336 * image, it can issue an "imgflush" command.  That breaks us out of the
3337 * read loop.
3338 */
3339int
3340GeoVis::processCommands(Tcl_Interp *interp,
3341                        ClientData clientData,
3342                        ReadBuffer *inBufPtr,
3343                        int fdOut,
3344                        long timeout)
3345{
3346    int ret = 1;
3347    int status = TCL_OK;
3348
3349    Tcl_DString command;
3350    Tcl_DStringInit(&command);
3351    fd_set readFds;
3352    struct timeval tv, *tvPtr;
3353
3354    FD_ZERO(&readFds);
3355    FD_SET(inBufPtr->file(), &readFds);
3356    tvPtr = NULL;                       /* Wait for the first read. This is so
3357                                         * that we don't spin when no data is
3358                                         * available. */
3359    if (timeout >= 0L) {
3360        tv.tv_sec = 0L;
3361        tv.tv_usec = timeout;
3362        tvPtr = &tv;
3363    } else {
3364        TRACE("Blocking on select()");
3365    }
3366    while (inBufPtr->isLineAvailable() ||
3367           (ret = select(inBufPtr->file()+1, &readFds, NULL, NULL, tvPtr)) > 0) {
3368        size_t numBytes;
3369        unsigned char *buffer;
3370
3371        /* A short read is treated as an error here because we assume that we
3372         * will always get commands line by line. */
3373        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
3374            /* Terminate the server if we can't communicate with the client
3375             * anymore. */
3376            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
3377                TRACE("Exiting server on EOF from client");
3378                return -1;
3379            } else {
3380                ERROR("Exiting server, failed to read from client: %s",
3381                      strerror(errno));
3382                return -1;
3383            }
3384        }
3385#if 0
3386        Tcl_DString tmp;
3387        Tcl_DStringInit(&tmp);
3388        Tcl_Encoding encoding = Tcl_GetEncoding(interp, "identity");
3389        TRACE("Encoding name: %s", Tcl_GetEncodingName(encoding));
3390        Tcl_ExternalToUtfDString(encoding, (const char *)buffer, numBytes, &tmp);
3391        Tcl_FreeEncoding(encoding);
3392        Tcl_DStringAppend(&command, Tcl_DStringValue(&tmp), Tcl_DStringLength(&tmp));
3393        Tcl_DStringFree(&tmp);
3394#else
3395        Tcl_DStringAppend(&command, (char *)buffer, numBytes);
3396#endif
3397        if (Tcl_CommandComplete(Tcl_DStringValue(&command))) {
3398            struct timeval start, finish;
3399            gettimeofday(&start, NULL);
3400            g_stats.nCommands++;
3401            status = ExecuteCommand(interp, &command);
3402            gettimeofday(&finish, NULL);
3403            g_stats.cmdTime += (MSECS_ELAPSED(start, finish) / 1.0e+3);
3404            if (status == TCL_BREAK) {
3405                return 2;               /* This was caused by a "imgflush"
3406                                         * command. Break out of the read loop
3407                                         * and allow a new image to be
3408                                         * rendered. */
3409            } else { //if (status != TCL_OK) {
3410                ret = 0;
3411                if (handleError(interp, clientData, status, fdOut) < 0) {
3412                    return -1;
3413                }
3414            }
3415            if (status == TCL_OK) {
3416                ret = 3;
3417            }
3418        }
3419
3420        tv.tv_sec = tv.tv_usec = 0L;    /* On successive reads, we break out
3421                                         * if no data is available. */
3422        FD_SET(inBufPtr->file(), &readFds);
3423        tvPtr = &tv;
3424    }
3425
3426    return ret;
3427}
3428
3429/**
3430 * \brief Send error message to client socket
3431 */
3432int
3433GeoVis::handleError(Tcl_Interp *interp,
3434                    ClientData clientData,
3435                    int status, int fdOut)
3436{
3437    const char *string;
3438    int nBytes;
3439
3440    if (status != TCL_OK) {
3441        string = Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY);
3442        nBytes = strlen(string);
3443        if (nBytes > 0) {
3444            TRACE("status=%d errorInfo=(%s)", status, string);
3445
3446            std::ostringstream oss;
3447            oss << "nv>viserror -type internal_error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
3448            std::string ostr = oss.str();
3449            nBytes = ostr.length();
3450
3451            if (queueResponse(ostr.c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
3452                return -1;
3453            }
3454        }
3455    }
3456
3457    std::string msg = getUserMessages();
3458    nBytes = msg.length();
3459    if (nBytes > 0) {
3460        string = msg.c_str();
3461        TRACE("userError=(%s)", string);
3462
3463        std::ostringstream oss;
3464        oss << "nv>viserror -type error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
3465        std::string ostr = oss.str();
3466        nBytes = ostr.length();
3467
3468        if (queueResponse(ostr.c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
3469            return -1;
3470        }
3471
3472        clearUserMessages();
3473    }
3474
3475    return 0;
3476}
3477
3478/**
3479 * \brief Create Tcl interpreter and add commands
3480 *
3481 * \return The initialized Tcl interpreter
3482 */
3483void
3484GeoVis::initTcl(Tcl_Interp *interp, ClientData clientData)
3485{
3486    TRACE("LANG: %s", getenv("LANG"));
3487    Tcl_GetEncodingNames(interp);
3488    TRACE("Supported encodings: %s", Tcl_GetStringResult(interp));
3489    int result = Tcl_Eval(interp, "encoding system\n");
3490    if (result == TCL_OK) {
3491        TRACE("Current system encoding: %s", Tcl_GetStringResult(interp));
3492    } else {
3493        ERROR("Couldn't determine system encoding");
3494    }
3495    const char *encoding = "utf-8";
3496    if (Tcl_SetSystemEncoding(interp, encoding) != TCL_OK) {
3497        TRACE("Failed to set Tcl encoding to %s", encoding);
3498    } else {
3499        TRACE("Set system encoding to %s", encoding);
3500    }
3501
3502    Tcl_MakeSafe(interp);
3503
3504    Tcl_CreateObjCommand(interp, "camera",     CameraCmd,     clientData, NULL);
3505    Tcl_CreateObjCommand(interp, "clientinfo", ClientInfoCmd, clientData, NULL);
3506    Tcl_CreateObjCommand(interp, "colormap",   ColorMapCmd,   clientData, NULL);
3507    Tcl_CreateObjCommand(interp, "file",       FileCmd,       clientData, NULL);
3508    Tcl_CreateObjCommand(interp, "imgflush",   ImageFlushCmd, clientData, NULL);
3509    Tcl_CreateObjCommand(interp, "key",        KeyCmd,        clientData, NULL);
3510    Tcl_CreateObjCommand(interp, "legend",     LegendCmd,     clientData, NULL);
3511    Tcl_CreateObjCommand(interp, "map",        MapCmd,        clientData, NULL);
3512    Tcl_CreateObjCommand(interp, "mouse",      MouseCmd,      clientData, NULL);
3513    Tcl_CreateObjCommand(interp, "renderer",   RendererCmd,   clientData, NULL);
3514    Tcl_CreateObjCommand(interp, "screen",     ScreenCmd,     clientData, NULL);
3515    Tcl_CreateObjCommand(interp, "select",     SelectCmd,     clientData, NULL);
3516}
3517
3518/**
3519 * \brief Delete Tcl commands and interpreter
3520 */
3521void GeoVis::exitTcl(Tcl_Interp *interp)
3522{
3523    Tcl_DeleteCommand(interp, "camera");
3524    Tcl_DeleteCommand(interp, "clientinfo");
3525    Tcl_DeleteCommand(interp, "colormap");
3526    Tcl_DeleteCommand(interp, "file");
3527    Tcl_DeleteCommand(interp, "imgflush");
3528    Tcl_DeleteCommand(interp, "key");
3529    Tcl_DeleteCommand(interp, "legend");
3530    Tcl_DeleteCommand(interp, "map");
3531    Tcl_DeleteCommand(interp, "mouse");
3532    Tcl_DeleteCommand(interp, "renderer");
3533    Tcl_DeleteCommand(interp, "screen");
3534    Tcl_DeleteCommand(interp, "select");
3535
3536    Tcl_DeleteInterp(interp);
3537}
Note: See TracBrowser for help on using the repository browser.