source: geovis/trunk/RendererCmd.cpp @ 6404

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

Add optional idle timeout to geovis server

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