source: geovis/branches/rex/RendererCmd.cpp @ 6570

Last change on this file since 6570 was 6570, checked in by ldelgass, 6 years ago

First pass at porting to new Map layer API

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