source: geovis/trunk/RendererCmd.cpp @ 6666

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

fix for profile validation

File size: 152.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->removeImageLayer(name);
2743        g_renderer->removeElevationLayer(name);
2744        g_renderer->removeTerrainMaskLayer(name);
2745        g_renderer->removeModelLayer(name);
2746    } else {
2747        g_renderer->clearMap();
2748    }
2749
2750    return TCL_OK;
2751}
2752
2753static int
2754MapLayerMoveOp(ClientData clientData, Tcl_Interp *interp, int objc,
2755               Tcl_Obj *const *objv)
2756{
2757    int pos;
2758    if (Tcl_GetIntFromObj(interp, objv[3], &pos) != TCL_OK) {
2759        return TCL_ERROR;
2760    }
2761    char *name = Tcl_GetString(objv[4]);
2762    if (pos < 0) {
2763        Tcl_AppendResult(interp, "bad layer pos ", pos,
2764                         ": must be positive", (char*)NULL);
2765        return TCL_ERROR;
2766    }
2767    g_renderer->moveImageLayer(name, (unsigned int)pos);
2768    g_renderer->moveElevationLayer(name, (unsigned int)pos);
2769    g_renderer->moveModelLayer(name, (unsigned int)pos);
2770
2771    return TCL_OK;
2772}
2773
2774static int
2775MapLayerOpacityOp(ClientData clientData, Tcl_Interp *interp, int objc,
2776                  Tcl_Obj *const *objv)
2777{
2778    double opacity;
2779    if (Tcl_GetDoubleFromObj(interp, objv[3], &opacity) != TCL_OK) {
2780        return TCL_ERROR;
2781    }
2782    char *name = Tcl_GetString(objv[4]);
2783    if (opacity < 0.0 || opacity > 1.0) {
2784        Tcl_AppendResult(interp, "bad layer opacity ", opacity,
2785                         ": must be [0,1]", (char*)NULL);
2786        return TCL_ERROR;
2787    }
2788    g_renderer->setImageLayerOpacity(name, opacity);
2789    g_renderer->setModelLayerOpacity(name, opacity);
2790
2791    return TCL_OK;
2792}
2793
2794static int
2795MapLayerNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
2796                Tcl_Obj *const *objv)
2797{
2798    std::vector<std::string> layers;
2799    if (objc < 4) {
2800        g_renderer->getElevationLayerNames(layers);
2801        g_renderer->getTerrainMaskLayerNames(layers);
2802        g_renderer->getImageLayerNames(layers);
2803        g_renderer->getModelLayerNames(layers);
2804    } else {
2805        char *type = Tcl_GetString(objv[3]);
2806        if (type[0] == 'i' && strcmp(type, "image") == 0) {
2807            g_renderer->getImageLayerNames(layers);
2808        } else if (type[0] == 'e' && strcmp(type, "elevation") == 0) {
2809            g_renderer->getElevationLayerNames(layers);
2810        } else if (type[0] == 'm' && strcmp(type, "mask") == 0) {
2811            g_renderer->getTerrainMaskLayerNames(layers);
2812        } else if (type[0] == 'm' && strcmp(type, "model") == 0) {
2813            g_renderer->getModelLayerNames(layers);
2814        } else {
2815            Tcl_AppendResult(interp, "uknown type \"", type,
2816                             "\": must be image, elevation, mask or model",
2817                             (char*)NULL);
2818            return TCL_ERROR;
2819        }
2820    }
2821    std::ostringstream oss;
2822    size_t len = 0;
2823    oss << "nv>map names {";
2824    len += 14;
2825    for (size_t i = 0; i < layers.size(); i++) {
2826        oss << "\"" << layers[i] << "\"";
2827        len += 2 + layers[i].length();
2828        if (i < layers.size() - 1) {
2829            oss << " ";
2830            len++;
2831        }
2832    }
2833    oss << "}\n";
2834    len += 2;
2835#ifdef USE_THREADS
2836    queueResponse(oss.str().c_str(), len, Response::VOLATILE);
2837#else
2838    ssize_t bytesWritten = SocketWrite(oss.str().c_str(), len);
2839
2840    if (bytesWritten < 0) {
2841        return TCL_ERROR;
2842    }
2843#endif /*USE_THREADS*/
2844    return TCL_OK;
2845}
2846
2847static int
2848MapLayerVisibleOp(ClientData clientData, Tcl_Interp *interp, int objc,
2849                  Tcl_Obj *const *objv)
2850{
2851    bool visible;
2852    if (GetBooleanFromObj(interp, objv[3], &visible) != TCL_OK) {
2853        return TCL_ERROR;
2854    }
2855
2856    if (objc > 4) {
2857        char *name = Tcl_GetString(objv[4]);
2858        g_renderer->setImageLayerVisibility(name, visible);
2859        g_renderer->setElevationLayerVisibility(name, visible);
2860        g_renderer->setModelLayerVisibility(name, visible);
2861    } else {
2862        std::vector<std::string> layers;
2863        g_renderer->getImageLayerNames(layers);
2864        g_renderer->getElevationLayerNames(layers);
2865        g_renderer->getModelLayerNames(layers);
2866        for (std::vector<std::string>::iterator itr = layers.begin();
2867             itr != layers.end(); ++itr) {
2868            g_renderer->setImageLayerVisibility(itr->c_str(), visible);
2869            g_renderer->setElevationLayerVisibility(itr->c_str(), visible);
2870            g_renderer->setModelLayerVisibility(itr->c_str(), visible);
2871        }
2872    }
2873
2874    return TCL_OK;
2875}
2876
2877static CmdSpec mapLayerOps[] = {
2878    {"add",     1, MapLayerAddOp,       6, 0, "name type driver ?url? ?args?"},
2879    {"delete",  1, MapLayerDeleteOp,    3, 4, "?name?"},
2880    {"move",    1, MapLayerMoveOp,      5, 5, "pos name"},
2881    {"names",   1, MapLayerNamesOp,     3, 4, "?type?"},
2882    {"opacity", 1, MapLayerOpacityOp,   5, 5, "opacity name"},
2883    {"visible", 1, MapLayerVisibleOp,   4, 5, "bool ?name?"},
2884};
2885static int nMapLayerOps = NumCmdSpecs(mapLayerOps);
2886
2887static int
2888MapLayerOp(ClientData clientData, Tcl_Interp *interp, int objc,
2889           Tcl_Obj *const *objv)
2890{
2891    Tcl_ObjCmdProc *proc;
2892
2893    proc = GetOpFromObj(interp, nMapLayerOps, mapLayerOps,
2894                        CMDSPEC_ARG2, objc, objv, 0);
2895    if (proc == NULL) {
2896        return TCL_ERROR;
2897    }
2898    return (*proc) (clientData, interp, objc, objv);
2899}
2900
2901static int
2902MapLoadOp(ClientData clientData, Tcl_Interp *interp, int objc,
2903          Tcl_Obj *const *objv)
2904{
2905    char *opt = Tcl_GetString(objv[2]);
2906    if (opt[0] == 'f' && strcmp(opt, "file") == 0) {
2907        g_renderer->loadEarthFile(Tcl_GetString(objv[3]));
2908    } else if (opt[0] == 'u' && strcmp(opt, "url") == 0) {
2909        std::ostringstream path;
2910        path << "server:" << Tcl_GetString(objv[3]);
2911        g_renderer->loadEarthFile(path.str().c_str());
2912    } else if (opt[0] == 'd' && strcmp(opt, "data") == 0) {
2913        opt = Tcl_GetString(objv[3]);
2914        if (opt[0] != 'f' || strcmp(opt, "follows") != 0) {
2915            return TCL_ERROR;
2916        }
2917        int len;
2918        if (Tcl_GetIntFromObj(interp, objv[4], &len) != TCL_OK) {
2919            return TCL_ERROR;
2920        }
2921        // Read Earth file from socket
2922        char *buf = (char *)malloc((size_t)len);
2923        SocketRead(buf, (size_t)len);
2924        std::ostringstream path;
2925        path << "/tmp/tmp" << getpid() << ".earth";
2926        const char *pathStr = path.str().c_str();
2927        FILE *tmpFile = fopen(pathStr, "w");
2928        fwrite(buf, len, 1, tmpFile);
2929        fclose(tmpFile);
2930        g_renderer->loadEarthFile(pathStr);
2931        unlink(pathStr);
2932        free(buf);
2933    } else {
2934        return TCL_ERROR;
2935    }
2936    return TCL_OK;
2937}
2938
2939static int
2940MapPinAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
2941            Tcl_Obj *const *objv)
2942{
2943    int x, y;
2944    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
2945        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
2946        return TCL_ERROR;
2947    }
2948    char *label = NULL;
2949    if (objc > 5) {
2950        label = Tcl_GetString(objv[5]);
2951    }
2952
2953    if (g_renderer->getMapSRS() == NULL) {
2954        Tcl_AppendResult(interp, "Could not get map SRS", (char*)NULL);
2955        return TCL_ERROR;
2956    }
2957
2958    // Get lat/long
2959    double latitude, longitude;
2960    if (!g_renderer->mouseToLatLong(x, y, &latitude, &longitude)) {
2961        USER_ERROR("Can't add pin here");
2962        return TCL_OK;
2963    }
2964
2965    g_renderer->addPlaceNode(latitude, longitude, label);
2966    return TCL_OK;
2967}
2968
2969static int
2970MapPinDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
2971               Tcl_Obj *const *objv)
2972{
2973    int x, y;
2974    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
2975        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
2976        return TCL_ERROR;
2977    }
2978
2979    g_renderer->deletePlaceNode(x, y);
2980    return TCL_OK;
2981}
2982
2983static int
2984MapPinHoverOp(ClientData clientData, Tcl_Interp *interp, int objc,
2985              Tcl_Obj *const *objv)
2986{
2987    int x, y;
2988    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
2989        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
2990        return TCL_ERROR;
2991    }
2992
2993    g_renderer->hoverPlaceNode(x, y);
2994    return TCL_OK;
2995}
2996
2997static CmdSpec mapPinOps[] = {
2998    {"add",     1, MapPinAddOp,     5, 6, "x y ?label?"},
2999    {"delete",  1, MapPinDeleteOp,  5, 5, "x y"},
3000    {"hover",   1, MapPinHoverOp,   5, 5, "x y"},
3001};
3002static int nMapPinOps = NumCmdSpecs(mapPinOps);
3003
3004static int
3005MapPinOp(ClientData clientData, Tcl_Interp *interp, int objc,
3006           Tcl_Obj *const *objv)
3007{
3008    Tcl_ObjCmdProc *proc;
3009
3010    proc = GetOpFromObj(interp, nMapPinOps, mapPinOps,
3011                        CMDSPEC_ARG2, objc, objv, 0);
3012    if (proc == NULL) {
3013        return TCL_ERROR;
3014    }
3015    return (*proc) (clientData, interp, objc, objv);
3016}
3017
3018static int
3019MapPositionDisplayOp(ClientData clientData, Tcl_Interp *interp, int objc,
3020                     Tcl_Obj *const *objv)
3021{
3022    bool state;
3023    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
3024        return TCL_ERROR;
3025    }
3026    Renderer::CoordinateDisplayType type = Renderer::COORDS_LATLONG_DECIMAL_DEGREES;
3027    if (state && objc > 3) {
3028        const char *str = Tcl_GetString(objv[3]);
3029        if (str[0] == 'l' && strcmp(str, "latlong_decimal_degrees") == 0) {
3030            type = Renderer::COORDS_LATLONG_DECIMAL_DEGREES;
3031        } else if (str[0] == 'l' && strcmp(str, "latlong_degrees_decimal_minutes") == 0) {
3032            type = Renderer::COORDS_LATLONG_DEGREES_DECIMAL_MINUTES;
3033        } else if (str[0] == 'l' && strcmp(str, "latlong_degrees_minutes_seconds") == 0) {
3034            type = Renderer::COORDS_LATLONG_DEGREES_MINUTES_SECONDS;
3035        } else if (str[0] == 'm' && strcmp(str, "mgrs") == 0) {
3036            type = Renderer::COORDS_MGRS;
3037        } else {
3038            Tcl_AppendResult(interp, "invalid type: \"", str,
3039                             "\": should be 'latlong_decimal_degrees', 'latlong_degrees_decimal_minutes', 'latlong_degrees_minutes_seconds', or 'mgrs'",
3040                             (char*)NULL);
3041            return TCL_ERROR;
3042        }
3043    }
3044    if (state && objc > 4) {
3045        int precision;
3046        if (Tcl_GetIntFromObj(interp, objv[4], &precision) != TCL_OK) {
3047            return TCL_ERROR;
3048        }
3049        g_renderer->setCoordinateReadout(state, type, precision);
3050    } else {
3051        g_renderer->setCoordinateReadout(state, type);
3052    }
3053
3054    return TCL_OK;
3055}
3056
3057static int
3058MapResetOp(ClientData clientData, Tcl_Interp *interp, int objc,
3059           Tcl_Obj *const *objv)
3060{
3061    char *typeStr = Tcl_GetString(objv[2]);
3062    osgEarth::MapOptions::CoordinateSystemType type;
3063    if (typeStr[0] == 'g' && strcmp(typeStr, "geocentric") == 0) {
3064        type = osgEarth::MapOptions::CSTYPE_GEOCENTRIC;
3065    } else if (typeStr[0] == 'g' && strcmp(typeStr, "geocentric_cube") == 0) {
3066        type = osgEarth::MapOptions::CSTYPE_GEOCENTRIC_CUBE;
3067    } else if (typeStr[0] == 'p' && strcmp(typeStr, "projected") == 0) {
3068        type = osgEarth::MapOptions::CSTYPE_PROJECTED;
3069    } else {
3070        Tcl_AppendResult(interp, "bad map type \"", typeStr,
3071                         "\": must be geocentric or projected", (char*)NULL);
3072        return TCL_ERROR;
3073    }
3074    float color[3];
3075    if (GetFloatFromObj(interp, objv[3], &color[0]) != TCL_OK ||
3076        GetFloatFromObj(interp, objv[4], &color[1]) != TCL_OK ||
3077        GetFloatFromObj(interp, objv[5], &color[2]) != TCL_OK) {
3078        return TCL_ERROR;
3079    }
3080
3081    osg::Vec4f bgColor(color[0],color[1],color[2],1);
3082    if (type == osgEarth::MapOptions::CSTYPE_PROJECTED) {
3083        if (objc < 7) {
3084            Tcl_AppendResult(interp, "wrong # arguments: profile required for projected maps", (char*)NULL);
3085            return TCL_ERROR;
3086        }
3087        char *profile = Tcl_GetString(objv[6]);
3088        if (strcmp(profile, "global-geodetic") == 0 ||
3089            strcmp(profile, "geodetic") == 0 ||
3090            strcmp(profile, "epsg:4326") == 0 ||
3091            strcmp(profile, "wgs84") == 0 ||
3092            strcmp(profile, "plate-carre") == 0 ||
3093            strcmp(profile, "plate-carree") == 0) {
3094            // As of osgearth 2.7, these are not permitted as map profiles.
3095            // Use epsg:32663 instead
3096            Tcl_AppendResult(interp, "Invalid profile: can't use geographic coordinate system as projection.  Consider using an equirectangular projection (epsg:32663) instead.", (char*)NULL);
3097            return TCL_ERROR;
3098        }
3099        if (objc > 7) {
3100            if (objc < 11) {
3101                Tcl_AppendResult(interp, "wrong # arguments: 4 bounds arguments required", (char*)NULL);
3102                return TCL_ERROR;
3103            }
3104            double bounds[4];
3105            for (int i = 0; i < 4; i++) {
3106                if (Tcl_GetDoubleFromObj(interp, objv[7+i], &bounds[i]) != TCL_OK) {
3107                    return TCL_ERROR;
3108                }
3109            }
3110            // Check if min > max
3111            if (bounds[0] > bounds[2] ||
3112                bounds[1] > bounds[3]) {
3113                Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
3114                return TCL_ERROR;
3115            }
3116            if (bounds[0] < -180. || bounds[0] > 180. ||
3117                bounds[2] < -180. || bounds[2] > 180. ||
3118                bounds[1] < -90. || bounds[1] > 90. ||
3119                bounds[3] < -90. || bounds[3] > 90.) {
3120                Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
3121                return TCL_ERROR;
3122            }
3123            g_renderer->resetMap(type, bgColor, profile, bounds, "wgs84");
3124        } else {
3125            // If no bounds are given, this must be a named profile with implicit bounds
3126            if (osgEarth::Registry::instance()->getNamedProfile(profile) == NULL) {
3127                Tcl_AppendResult(interp, "bad named profile \"", profile,
3128                                 "\": must be e.g. 'global-geodetic', 'global-mercator'...", (char*)NULL);
3129                return TCL_ERROR;
3130            }
3131            g_renderer->resetMap(type, bgColor, profile);
3132        }
3133    } else {
3134        // No profile required for geocentric (3D) maps
3135        g_renderer->resetMap(type, bgColor);
3136    }
3137
3138    return TCL_OK;
3139}
3140
3141static int
3142MapScaleBarOp(ClientData clientData, Tcl_Interp *interp, int objc,
3143              Tcl_Obj *const *objv)
3144{
3145    bool state;
3146    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
3147        return TCL_ERROR;
3148    }
3149    g_renderer->setScaleBar(state);
3150    if (state && objc > 3) {
3151        const char *unitStr = Tcl_GetString(objv[3]);
3152        ScaleBarUnits units;
3153        if (unitStr[0] == 'm' && strcmp(unitStr, "meters") == 0) {
3154            units = UNITS_METERS;
3155        } else if (unitStr[0] == 'f' && strcmp(unitStr, "feet") == 0) {
3156            units = UNITS_INTL_FEET;
3157        } else if (unitStr[0] == 'u' && strcmp(unitStr, "us_survey_feet") == 0) {
3158            units = UNITS_US_SURVEY_FEET;
3159        } else if (unitStr[0] == 'n' && strcmp(unitStr, "nautical_miles") == 0) {
3160            units = UNITS_NAUTICAL_MILES;
3161        } else {
3162            Tcl_AppendResult(interp, "bad units \"", unitStr,
3163                             "\": must be 'meters', 'feet', 'us_survey_feet' or 'nautical_miles'", (char*)NULL);
3164            return TCL_ERROR;
3165        }
3166        g_renderer->setScaleBarUnits(units);
3167    }
3168    return TCL_OK;
3169}
3170
3171static int
3172MapSequencePauseOp(ClientData clientData, Tcl_Interp *interp, int objc,
3173                   Tcl_Obj *const *objv)
3174{
3175    const char *layer = Tcl_GetString(objv[3]);
3176    bool ret = g_renderer->sequencePause(layer);
3177    if (!ret) {
3178        Tcl_AppendResult(interp, "Layer \"", layer,
3179                         "\" doesn't exist or has no sequence", (char *)NULL);
3180        return TCL_ERROR;
3181    }
3182
3183    return TCL_OK;
3184}
3185
3186static int
3187MapSequencePlayOp(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->sequencePlay(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
3202MapSequenceSeekOp(ClientData clientData, Tcl_Interp *interp, int objc,
3203                  Tcl_Obj *const *objv)
3204{
3205    const char *layer = Tcl_GetString(objv[4]);
3206    int frame;
3207    if (Tcl_GetIntFromObj(interp, objv[3], &frame) != TCL_OK) {
3208        return TCL_ERROR;
3209    }
3210    bool ret = g_renderer->sequenceSeek(layer, (unsigned int)frame);
3211    if (!ret) {
3212        Tcl_AppendResult(interp, "Layer \"", layer,
3213                         "\" doesn't exist or has no sequence", (char *)NULL);
3214        return TCL_ERROR;
3215    }
3216
3217    return TCL_OK;
3218}
3219
3220static CmdSpec mapSequenceOps[] = {
3221    {"pause", 2, MapSequencePauseOp, 4, 4, "layerName"},
3222    {"play",  2, MapSequencePlayOp,  4, 4, "layerName"},
3223    {"seek",  1, MapSequenceSeekOp,  5, 5, "frame layerName"},
3224};
3225static int nMapSequenceOps = NumCmdSpecs(mapSequenceOps);
3226
3227static int
3228MapSequenceOp(ClientData clientData, Tcl_Interp *interp, int objc,
3229           Tcl_Obj *const *objv)
3230{
3231    Tcl_ObjCmdProc *proc;
3232
3233    proc = GetOpFromObj(interp, nMapSequenceOps, mapSequenceOps,
3234                        CMDSPEC_ARG2, objc, objv, 0);
3235    if (proc == NULL) {
3236        return TCL_ERROR;
3237    }
3238    return (*proc) (clientData, interp, objc, objv);
3239}
3240
3241static int
3242MapSetPositionOp(ClientData clientData, Tcl_Interp *interp, int objc,
3243            Tcl_Obj *const *objv)
3244{
3245    if (objc < 3) {
3246        g_renderer->clearReadout();
3247    } else {
3248        int x, y;
3249        if (Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK ||
3250            Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK) {
3251            return TCL_ERROR;
3252        }
3253        g_renderer->setReadout(x, y);
3254    }
3255    return TCL_OK;
3256}
3257
3258static int
3259MapTerrainAmbientOp(ClientData clientData, Tcl_Interp *interp, int objc,
3260                    Tcl_Obj *const *objv)
3261{
3262    float ambient;
3263    if (GetFloatFromObj(interp, objv[3], &ambient) != TCL_OK) {
3264        return TCL_ERROR;
3265    }
3266    g_renderer->setSkyAmbient(ambient);
3267    return TCL_OK;
3268}
3269
3270static int
3271MapTerrainColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
3272                  Tcl_Obj *const *objv)
3273{
3274    float color[3];
3275    if (GetFloatFromObj(interp, objv[3], &color[0]) != TCL_OK ||
3276        GetFloatFromObj(interp, objv[4], &color[1]) != TCL_OK ||
3277        GetFloatFromObj(interp, objv[5], &color[2]) != TCL_OK) {
3278        return TCL_ERROR;
3279    }
3280    g_renderer->setTerrainColor(osg::Vec4f(color[0],color[1],color[2],1));
3281    return TCL_OK;
3282}
3283
3284static int
3285MapTerrainEdgesOp(ClientData clientData, Tcl_Interp *interp, int objc,
3286                  Tcl_Obj *const *objv)
3287{
3288    bool state;
3289    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
3290        return TCL_ERROR;
3291    }
3292    g_renderer->setTerrainEdges(state);
3293    return TCL_OK;
3294}
3295
3296static int
3297MapTerrainLightingOp(ClientData clientData, Tcl_Interp *interp, int objc,
3298                     Tcl_Obj *const *objv)
3299{
3300    bool state;
3301    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
3302        return TCL_ERROR;
3303    }
3304
3305    g_renderer->setTerrainLighting(state);
3306    return TCL_OK;
3307}
3308
3309static int
3310MapTerrainLineColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
3311                      Tcl_Obj *const *objv)
3312{
3313    float color[3];
3314    if (GetFloatFromObj(interp, objv[3], &color[0]) != TCL_OK ||
3315        GetFloatFromObj(interp, objv[4], &color[1]) != TCL_OK ||
3316        GetFloatFromObj(interp, objv[5], &color[2]) != TCL_OK) {
3317        return TCL_ERROR;
3318    }
3319    g_renderer->setTerrainLineColor(osg::Vec4f(color[0], color[1], color[2],1));
3320    return TCL_OK;
3321}
3322
3323static int
3324MapTerrainLineWidthOp(ClientData clientData, Tcl_Interp *interp, int objc,
3325                      Tcl_Obj *const *objv)
3326{
3327    float width;
3328    if (GetFloatFromObj(interp, objv[3], &width) != TCL_OK) {
3329        return TCL_ERROR;
3330    }
3331    g_renderer->setTerrainLineWidth(width);
3332    return TCL_OK;
3333}
3334
3335static int
3336MapTerrainVertScaleOp(ClientData clientData, Tcl_Interp *interp, int objc,
3337                      Tcl_Obj *const *objv)
3338{
3339    double scale;
3340    if (Tcl_GetDoubleFromObj(interp, objv[3], &scale) != TCL_OK) {
3341        return TCL_ERROR;
3342    }
3343
3344    g_renderer->setTerrainVerticalScale(scale);
3345    return TCL_OK;
3346}
3347
3348static int
3349MapTerrainWireframeOp(ClientData clientData, Tcl_Interp *interp, int objc,
3350                      Tcl_Obj *const *objv)
3351{
3352    bool state;
3353    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
3354        return TCL_ERROR;
3355    }
3356
3357    g_renderer->setTerrainWireframe(state);
3358    return TCL_OK;
3359}
3360
3361static CmdSpec mapTerrainOps[] = {
3362    {"ambient",   1, MapTerrainAmbientOp,   4, 4, "val"},
3363    {"color",     1, MapTerrainColorOp,     6, 6, "r g b"},
3364    {"edges",     1, MapTerrainEdgesOp,     4, 4, "bool"},
3365    {"lighting",  2, MapTerrainLightingOp,  4, 4, "bool"},
3366    {"linecolor", 5, MapTerrainLineColorOp, 6, 6, "r g b"},
3367    {"linewidth", 5, MapTerrainLineWidthOp, 4, 4, "val"},
3368    {"vertscale", 1, MapTerrainVertScaleOp, 4, 4, "val"},
3369    {"wireframe", 1, MapTerrainWireframeOp, 4, 4, "bool"},
3370};
3371static int nMapTerrainOps = NumCmdSpecs(mapTerrainOps);
3372
3373static int
3374MapTerrainOp(ClientData clientData, Tcl_Interp *interp, int objc,
3375           Tcl_Obj *const *objv)
3376{
3377    Tcl_ObjCmdProc *proc;
3378
3379    proc = GetOpFromObj(interp, nMapTerrainOps, mapTerrainOps,
3380                        CMDSPEC_ARG2, objc, objv, 0);
3381    if (proc == NULL) {
3382        return TCL_ERROR;
3383    }
3384    return (*proc) (clientData, interp, objc, objv);
3385}
3386
3387static int
3388MapTimeOp(ClientData clientData, Tcl_Interp *interp, int objc,
3389          Tcl_Obj *const *objv)
3390{
3391    osgEarth::DateTime now;
3392    int year, month, day;
3393    double hours;
3394    year = now.year();
3395    month = now.month();
3396    day = now.day();
3397    hours = now.hours();
3398    if (objc > 2) {
3399        if (Tcl_GetDoubleFromObj(interp, objv[2], &hours) != TCL_OK) {
3400            return TCL_ERROR;
3401        }
3402    }
3403    if (objc > 3) {
3404        if (Tcl_GetIntFromObj(interp, objv[3], &day) != TCL_OK) {
3405            return TCL_ERROR;
3406        }
3407    }
3408    if (objc > 4) {
3409        if (Tcl_GetIntFromObj(interp, objv[4], &month) != TCL_OK) {
3410            return TCL_ERROR;
3411        }
3412    }
3413    if (objc > 5) {
3414        if (Tcl_GetIntFromObj(interp, objv[5], &year) != TCL_OK) {
3415            return TCL_ERROR;
3416        }
3417    }
3418
3419    g_renderer->setEphemerisTime(year, month, day, hours);
3420    return TCL_OK;
3421}
3422
3423static CmdSpec mapOps[] = {
3424    {"attrib",   1, MapAttributionOp,     3, 3, "string"},
3425    {"box",      1, MapBoxOp,             3, 0, "op ?params..."},
3426    {"coords",   1, MapCoordsOp,          4, 6, "token coords ?srs? ?verticalDatum?"},
3427    {"grid",     1, MapGraticuleOp,       3, 4, "bool ?type?"},
3428    {"layer",    2, MapLayerOp,           3, 0, "op ?params...?"},
3429    {"load",     2, MapLoadOp,            4, 5, "options"},
3430    {"pin",      2, MapPinOp,             3, 0, "op ?params...?"},
3431    {"posdisp",  2, MapPositionDisplayOp, 3, 5, "bool ?format? ?precision?"},
3432    {"reset",    1, MapResetOp,           6, 11, "type r g b ?profile xmin ymin xmax ymax?"},
3433    {"scalebar", 1, MapScaleBarOp,        3, 4, "bool ?units?"},
3434    {"sequence", 3, MapSequenceOp,        3, 0, "op ?params...?"},
3435    {"setpos",   3, MapSetPositionOp,     2, 4, "x y"},
3436    {"terrain",  1, MapTerrainOp,         3, 0, "op ?params...?"},
3437    {"time",     1, MapTimeOp,            2, 6, "?hours? ?day? ?month? ?year?"},
3438};
3439static int nMapOps = NumCmdSpecs(mapOps);
3440
3441static int
3442MapCmd(ClientData clientData, Tcl_Interp *interp, int objc,
3443       Tcl_Obj *const *objv)
3444{
3445    Tcl_ObjCmdProc *proc;
3446
3447    proc = GetOpFromObj(interp, nMapOps, mapOps,
3448                        CMDSPEC_ARG1, objc, objv, 0);
3449    if (proc == NULL) {
3450        return TCL_ERROR;
3451    }
3452    return (*proc) (clientData, interp, objc, objv);
3453}
3454
3455static int
3456MouseClickOp(ClientData clientData, Tcl_Interp *interp, int objc,
3457             Tcl_Obj *const *objv)
3458{
3459    int button;
3460    double x, y;
3461
3462    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
3463        return TCL_ERROR;
3464    }
3465    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
3466        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
3467        return TCL_ERROR;
3468    }
3469
3470    g_renderer->mouseClick(button, x, y);
3471    return TCL_OK;
3472}
3473
3474static int
3475MouseDoubleClickOp(ClientData clientData, Tcl_Interp *interp, int objc,
3476                   Tcl_Obj *const *objv)
3477{
3478    int button;
3479    double x, y;
3480
3481    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
3482        return TCL_ERROR;
3483    }
3484    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
3485        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
3486        return TCL_ERROR;
3487    }
3488
3489    g_renderer->mouseDoubleClick(button, x, y);
3490    return TCL_OK;
3491}
3492
3493static int
3494MouseDragOp(ClientData clientData, Tcl_Interp *interp, int objc,
3495            Tcl_Obj *const *objv)
3496{
3497    int button;
3498    double x, y;
3499
3500    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
3501        return TCL_ERROR;
3502    }
3503    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
3504        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
3505        return TCL_ERROR;
3506    }
3507
3508    g_renderer->mouseDrag(button, x, y);
3509    return TCL_OK;
3510}
3511
3512static int
3513MouseMotionOp(ClientData clientData, Tcl_Interp *interp, int objc,
3514              Tcl_Obj *const *objv)
3515{
3516    double x, y;
3517
3518    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
3519        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
3520        return TCL_ERROR;
3521    }
3522
3523    g_renderer->mouseMotion(x, y);
3524    return TCL_OK;
3525}
3526
3527static int
3528MouseReleaseOp(ClientData clientData, Tcl_Interp *interp, int objc,
3529               Tcl_Obj *const *objv)
3530{
3531    int button;
3532    double x, y;
3533
3534    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
3535        return TCL_ERROR;
3536    }
3537    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
3538        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
3539        return TCL_ERROR;
3540    }
3541
3542    g_renderer->mouseRelease(button, x, y);
3543    return TCL_OK;
3544}
3545
3546static int
3547MouseScrollOp(ClientData clientData, Tcl_Interp *interp, int objc,
3548              Tcl_Obj *const *objv)
3549{
3550    int direction;
3551
3552    if (Tcl_GetIntFromObj(interp, objv[2], &direction) != TCL_OK) {
3553        return TCL_ERROR;
3554    }
3555
3556    g_renderer->mouseScroll(direction);
3557    return TCL_OK;
3558}
3559
3560static CmdSpec mouseOps[] = {
3561    {"click",    1, MouseClickOp,       5, 5, "button x y"},
3562    {"dblclick", 2, MouseDoubleClickOp, 5, 5, "button x y"},
3563    {"drag",     2, MouseDragOp,        5, 5, "button x y"},
3564    {"motion",   1, MouseMotionOp,      4, 4, "x y"},
3565    {"release",  1, MouseReleaseOp,     5, 5, "button x y"},
3566    {"scroll",   1, MouseScrollOp,      3, 3, "direction"},
3567};
3568static int nMouseOps = NumCmdSpecs(mouseOps);
3569
3570static int
3571MouseCmd(ClientData clientData, Tcl_Interp *interp, int objc,
3572         Tcl_Obj *const *objv)
3573{
3574    Tcl_ObjCmdProc *proc;
3575
3576    proc = GetOpFromObj(interp, nMouseOps, mouseOps,
3577                        CMDSPEC_ARG1, objc, objv, 0);
3578    if (proc == NULL) {
3579        return TCL_ERROR;
3580    }
3581    return (*proc) (clientData, interp, objc, objv);
3582}
3583
3584static int
3585PlacardConfigOp(ClientData clientData, Tcl_Interp *interp, int objc,
3586                Tcl_Obj *const *objv)
3587{
3588    int namec;
3589    Tcl_Obj **namev = NULL;
3590    const char *layerName = Tcl_GetString(objv[5]);
3591
3592    if (Tcl_ListObjGetElements(interp, objv[2], &namec, &namev) != TCL_OK) {
3593        return TCL_ERROR;
3594    }
3595    if (namec % 2 != 0) {
3596        Tcl_AppendResult(interp, "invalid attribute list",
3597                         (char *)NULL);
3598        return TCL_ERROR;
3599    }
3600    Placard placardConf;
3601    for (int i = 0; i < namec; i+=2) {
3602        std::string name(Tcl_GetString(namev[i]));
3603        std::string label(Tcl_GetString(namev[i+1]));
3604        placardConf.addEntry(name, label);
3605    }
3606    osgEarth::Config styleConf("style", Tcl_GetString(objv[3]));
3607    styleConf.add("type", "text/css");
3608    placardConf.setStyle(osgEarth::Symbology::Style(styleConf));
3609
3610    float padding;
3611    if (GetFloatFromObj(interp, objv[4], &padding) != TCL_OK) {
3612        return TCL_ERROR;
3613    }
3614    placardConf.setPadding(padding);
3615    g_renderer->setPlacardConfig(placardConf, layerName);
3616    return TCL_OK;
3617}
3618
3619static int
3620PlacardEnableOp(ClientData clientData, Tcl_Interp *interp, int objc,
3621                Tcl_Obj *const *objv)
3622{
3623    const char *layerName = Tcl_GetString(objv[3]);
3624    bool enable;
3625    if (GetBooleanFromObj(interp, objv[2], &enable) != TCL_OK) {
3626        return TCL_ERROR;
3627    }
3628    g_renderer->enablePlacard(layerName, enable);
3629    return TCL_OK;
3630}
3631
3632static CmdSpec placardOps[] = {
3633    {"config", 1, PlacardConfigOp, 6, 6, "attrlist style padding layerName"},
3634    {"enable", 1, PlacardEnableOp, 4, 4, "bool layerName"},
3635};
3636static int nPlacardOps = NumCmdSpecs(placardOps);
3637
3638static int
3639PlacardCmd(ClientData clientData, Tcl_Interp *interp, int objc,
3640          Tcl_Obj *const *objv)
3641{
3642    Tcl_ObjCmdProc *proc;
3643
3644    proc = GetOpFromObj(interp, nPlacardOps, placardOps,
3645                        CMDSPEC_ARG1, objc, objv, 0);
3646    if (proc == NULL) {
3647        return TCL_ERROR;
3648    }
3649    return (*proc) (clientData, interp, objc, objv);
3650}
3651
3652static int
3653RendererRenderOp(ClientData clientData, Tcl_Interp *interp, int objc,
3654                 Tcl_Obj *const *objv)
3655{
3656    g_renderer->eventuallyRender();
3657    return TCL_OK;
3658}
3659
3660static CmdSpec rendererOps[] = {
3661    {"render",     1, RendererRenderOp, 2, 2, ""},
3662};
3663static int nRendererOps = NumCmdSpecs(rendererOps);
3664
3665static int
3666RendererCmd(ClientData clientData, Tcl_Interp *interp, int objc,
3667            Tcl_Obj *const *objv)
3668{
3669    Tcl_ObjCmdProc *proc;
3670
3671    proc = GetOpFromObj(interp, nRendererOps, rendererOps,
3672                        CMDSPEC_ARG1, objc, objv, 0);
3673    if (proc == NULL) {
3674        return TCL_ERROR;
3675    }
3676    return (*proc) (clientData, interp, objc, objv);
3677}
3678
3679static int
3680ScreenBgColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
3681                Tcl_Obj *const *objv)
3682{
3683    float color[3];
3684
3685    if (GetFloatFromObj(interp, objv[2], &color[0]) != TCL_OK ||
3686        GetFloatFromObj(interp, objv[3], &color[1]) != TCL_OK ||
3687        GetFloatFromObj(interp, objv[4], &color[2]) != TCL_OK) {
3688        return TCL_ERROR;
3689    }
3690
3691    g_renderer->setBackgroundColor(color);
3692    return TCL_OK;
3693}
3694
3695static int
3696ScreenCoordsOp(ClientData clientData, Tcl_Interp *interp, int objc,
3697               Tcl_Obj *const *objv)
3698{
3699    int tokenLength;
3700    const char *token = Tcl_GetStringFromObj(objv[2], &tokenLength);
3701    int numCoords;
3702    Tcl_Obj **coords;
3703    if (Tcl_ListObjGetElements(interp, objv[3], &numCoords, &coords) != TCL_OK) {
3704        return TCL_ERROR;
3705    }
3706    if (numCoords == 0) {
3707        Tcl_AppendResult(interp, "no x,y,z coordinates in list", (char *)NULL);
3708        return TCL_ERROR;
3709    }
3710    if (numCoords % 3 != 0) {
3711        Tcl_AppendResult(interp, "invalid number of coordinates in list",
3712                         (char *)NULL);
3713        return TCL_ERROR;
3714    }
3715
3716    const osgEarth::SpatialReference *srs = NULL;
3717    if (objc < 5) {
3718        srs = g_renderer->getMapSRS();
3719        if (srs == NULL) {
3720            Tcl_AppendResult(interp, "Could not determine map SRS", (char*)NULL);
3721            return TCL_ERROR;
3722        }
3723    } else {
3724        std::string srsInit(Tcl_GetString(objv[4]));
3725        std::string verticalDatum;
3726        if (objc > 5) {
3727            verticalDatum = Tcl_GetString(objv[5]);
3728        }
3729        srs = osgEarth::SpatialReference::get(srsInit, verticalDatum);
3730        if (srs == NULL) {
3731            Tcl_AppendResult(interp, "bad SRS \"", srsInit.c_str(), "\"", (char*)NULL);
3732            return TCL_ERROR;
3733        }
3734    }
3735    Tcl_Obj *listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
3736    std::vector<osg::Vec3d> coordVec;
3737    for (int i = 0; i < numCoords; i += 3) {
3738        double x, y, z;
3739        if (Tcl_GetDoubleFromObj(interp, coords[i], &x) != TCL_OK ||
3740            Tcl_GetDoubleFromObj(interp, coords[i+1], &y) != TCL_OK ||
3741            Tcl_GetDoubleFromObj(interp, coords[i+2], &z) != TCL_OK) {
3742            return TCL_ERROR;
3743        }
3744        // ALTMODE_RELATIVE is height above terrain, ALTMODE_ABSOLUTE means
3745        // relative to the vertical datum
3746        osgEarth::GeoPoint mapPoint(srs, x, y, z, osgEarth::ALTMODE_ABSOLUTE);
3747        osg::Vec3d world;
3748        if (g_renderer->getWorldCoords(mapPoint, &world)) {
3749            coordVec.push_back(world);
3750        } else {
3751            coordVec.push_back(osg::Vec3d(std::numeric_limits<double>::quiet_NaN(),
3752                                          std::numeric_limits<double>::quiet_NaN(),
3753                                          std::numeric_limits<double>::quiet_NaN()));
3754        }
3755    }
3756    g_renderer->worldToScreen(coordVec);
3757    for (std::vector<osg::Vec3d>::iterator itr = coordVec.begin();
3758         itr != coordVec.end(); ++itr) {
3759        Tcl_Obj *objPtr = Tcl_NewDoubleObj(itr->x());
3760        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
3761        objPtr = Tcl_NewDoubleObj(itr->y());
3762        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
3763        objPtr = Tcl_NewDoubleObj(itr->z());
3764        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
3765    }
3766    // send coords to client
3767    int listLength;
3768    const char *string = Tcl_GetStringFromObj(listObjPtr, &listLength);
3769    size_t length = listLength + tokenLength + 22;
3770    char *mesg = new char[length];
3771    length = snprintf(mesg, length, "nv>screen coords %s {%s}\n", token, string);
3772    Tcl_DecrRefCount(listObjPtr);
3773    queueResponse(mesg, length, Response::VOLATILE);
3774    delete [] mesg;
3775    return TCL_OK;
3776}
3777
3778static int
3779ScreenSizeOp(ClientData clientData, Tcl_Interp *interp, int objc,
3780             Tcl_Obj *const *objv)
3781{
3782    int width, height;
3783
3784    if (Tcl_GetIntFromObj(interp, objv[2], &width) != TCL_OK ||
3785        Tcl_GetIntFromObj(interp, objv[3], &height) != TCL_OK) {
3786        return TCL_ERROR;
3787    }
3788
3789    g_renderer->setWindowSize(width, height);
3790    return TCL_OK;
3791}
3792
3793static CmdSpec screenOps[] = {
3794    {"bgcolor", 1, ScreenBgColorOp, 5, 5, "r g b"},
3795    {"coords",  1, ScreenCoordsOp, 4, 6, "token coords ?srs? ?verticalDatum?"},
3796    {"size",    1, ScreenSizeOp, 4, 4, "width height"}
3797};
3798static int nScreenOps = NumCmdSpecs(screenOps);
3799
3800static int
3801ScreenCmd(ClientData clientData, Tcl_Interp *interp, int objc,
3802          Tcl_Obj *const *objv)
3803{
3804    Tcl_ObjCmdProc *proc;
3805
3806    proc = GetOpFromObj(interp, nScreenOps, screenOps,
3807                        CMDSPEC_ARG1, objc, objv, 0);
3808    if (proc == NULL) {
3809        return TCL_ERROR;
3810    }
3811    return (*proc) (clientData, interp, objc, objv);
3812}
3813
3814static int
3815SelectAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
3816            Tcl_Obj *const *objv)
3817{
3818    int numIDs;
3819    Tcl_Obj **ids;
3820    if (Tcl_ListObjGetElements(interp, objv[2], &numIDs, &ids) != TCL_OK) {
3821        return TCL_ERROR;
3822    }
3823    if (numIDs == 0) {
3824        Tcl_AppendResult(interp, "no IDs in list", (char *)NULL);
3825        return TCL_ERROR;
3826    }
3827    std::vector<unsigned long> featureIDs;
3828    for (int i = 0; i < numIDs; i++) {
3829        long id;
3830        if (Tcl_GetLongFromObj(interp, ids[i], &id) != TCL_OK) {
3831            return TCL_ERROR;
3832        }
3833        featureIDs.push_back((unsigned long)id);
3834    }
3835    const char *layerName = Tcl_GetString(objv[3]);
3836
3837    g_renderer->selectFeatures(featureIDs, layerName, false);
3838    return TCL_OK;
3839}
3840
3841static int
3842SelectClearOp(ClientData clientData, Tcl_Interp *interp, int objc,
3843              Tcl_Obj *const *objv)
3844{
3845    g_renderer->clearSelection();
3846    return TCL_OK;
3847}
3848
3849static int
3850SelectDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
3851               Tcl_Obj *const *objv)
3852{
3853    int numIDs;
3854    Tcl_Obj **ids;
3855    if (Tcl_ListObjGetElements(interp, objv[2], &numIDs, &ids) != TCL_OK) {
3856        return TCL_ERROR;
3857    }
3858    if (numIDs == 0) {
3859        Tcl_AppendResult(interp, "no IDs in list", (char *)NULL);
3860        return TCL_ERROR;
3861    }
3862    std::vector<unsigned long> featureIDs;
3863    for (int i = 0; i < numIDs; i++) {
3864        long id;
3865        if (Tcl_GetLongFromObj(interp, ids[i], &id) != TCL_OK) {
3866            return TCL_ERROR;
3867        }
3868        featureIDs.push_back((unsigned long)id);
3869    }
3870    const char *layerName = Tcl_GetString(objv[3]);
3871
3872    g_renderer->deselectFeatures(featureIDs, layerName);
3873    return TCL_OK;
3874}
3875
3876static int
3877SelectFeatureOp(ClientData clientData, Tcl_Interp *interp, int objc,
3878                Tcl_Obj *const *objv)
3879{
3880    int numIDs;
3881    Tcl_Obj **ids;
3882    if (Tcl_ListObjGetElements(interp, objv[2], &numIDs, &ids) != TCL_OK) {
3883        return TCL_ERROR;
3884    }
3885    if (numIDs == 0) {
3886        Tcl_AppendResult(interp, "no IDs in list", (char *)NULL);
3887        return TCL_ERROR;
3888    }
3889    std::vector<unsigned long> featureIDs;
3890    for (int i = 0; i < numIDs; i++) {
3891        long id;
3892        if (Tcl_GetLongFromObj(interp, ids[i], &id) != TCL_OK) {
3893            return TCL_ERROR;
3894        }
3895        featureIDs.push_back((unsigned long)id);
3896    }
3897    const char *layerName = Tcl_GetString(objv[3]);
3898
3899    g_renderer->selectFeatures(featureIDs, layerName);
3900    return TCL_OK;
3901}
3902
3903static int
3904SelectModeOp(ClientData clientData, Tcl_Interp *interp, int objc,
3905             Tcl_Obj *const *objv)
3906{
3907    const char *modeStr = Tcl_GetString(objv[2]);
3908    // Parse mode string
3909    Renderer::SelectMode mode = Renderer::SELECT_OFF;
3910    if (modeStr[0] == 'o' && strcmp(modeStr, "off") == 0) {
3911        mode = Renderer::SELECT_OFF;
3912    } else if (modeStr[0] == 'o' && strcmp(modeStr, "on") == 0) {
3913        mode = Renderer::SELECT_ON;
3914    } else {
3915        Tcl_AppendResult(interp, "bad select mode \"", modeStr,
3916                             "\": must be 'on' or 'off'", (char*)NULL);
3917        return TCL_ERROR;
3918    }
3919    g_renderer->setSelectMode(mode);
3920    return TCL_OK;
3921}
3922
3923static CmdSpec selectOps[] = {
3924    {"clear",   1, SelectClearOp,   2, 2, ""},
3925    {"fadd",    2, SelectAddOp,     4, 4, "idlist layerName"},
3926    {"fdelete", 2, SelectDeleteOp,  4, 4, "idlist layerName"},
3927    {"feature", 2, SelectFeatureOp, 4, 4, "idlist layerName"},
3928    {"mode",    1, SelectModeOp,    3, 3, "mode"},
3929};
3930static int nSelectOps = NumCmdSpecs(selectOps);
3931
3932static int
3933SelectCmd(ClientData clientData, Tcl_Interp *interp, int objc,
3934          Tcl_Obj *const *objv)
3935{
3936    Tcl_ObjCmdProc *proc;
3937
3938    proc = GetOpFromObj(interp, nSelectOps, selectOps,
3939                        CMDSPEC_ARG1, objc, objv, 0);
3940    if (proc == NULL) {
3941        return TCL_ERROR;
3942    }
3943    return (*proc) (clientData, interp, objc, objv);
3944}
3945
3946#ifdef USE_READ_THREAD
3947int
3948GeoVis::queueCommands(Tcl_Interp *interp,
3949                      ClientData clientData,
3950                      ReadBuffer *inBufPtr)
3951{
3952    Tcl_DString commandString;
3953    Tcl_DStringInit(&commandString);
3954    fd_set readFds;
3955
3956    FD_ZERO(&readFds);
3957    FD_SET(inBufPtr->file(), &readFds);
3958    while (inBufPtr->isLineAvailable() ||
3959           (select(inBufPtr->file()+1, &readFds, NULL, NULL, NULL) > 0)) {
3960        size_t numBytes;
3961        unsigned char *buffer;
3962
3963        /* A short read is treated as an error here because we assume that we
3964         * will always get commands line by line. */
3965        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
3966            /* Terminate the server if we can't communicate with the client
3967             * anymore. */
3968            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
3969                TRACE("Exiting server on EOF from client");
3970                return -1;
3971            } else {
3972                ERROR("Exiting server, failed to read from client: %s",
3973                      strerror(errno));
3974                return -1;
3975            }
3976        }
3977        Tcl_DStringAppend(&commandString, (char *)buffer, numBytes);
3978        if (Tcl_CommandComplete(Tcl_DStringValue(&commandString))) {
3979            // Add to queue
3980            Command *command = new Command(Command::COMMAND);
3981            command->setMessage((unsigned char *)Tcl_DStringValue(&commandString),
3982                                Tcl_DStringLength(&commandString), Command::VOLATILE);
3983            g_inQueue->enqueue(command);
3984            Tcl_DStringSetLength(&commandString, 0);
3985        }
3986        FD_SET(inBufPtr->file(), &readFds);
3987    }
3988
3989    return 1;
3990}
3991#endif
3992
3993/**
3994 * \brief Execute commands from client in Tcl interpreter
3995 *
3996 * In this threaded model, the select call is for event compression.  We
3997 * want to execute render server commands as long as they keep coming. 
3998 * This lets us execute a stream of many commands but render once.  This
3999 * benefits camera movements, screen resizing, and opacity changes
4000 * (using a slider on the client).  The down side is you don't render
4001 * until there's a lull in the command stream.  If the client needs an
4002 * image, it can issue an "imgflush" command.  That breaks us out of the
4003 * read loop.
4004 */
4005int
4006GeoVis::processCommands(Tcl_Interp *interp,
4007                        ClientData clientData,
4008                        ReadBuffer *inBufPtr,
4009                        int fdOut,
4010                        struct timeval *timeout)
4011{
4012    int ret = 1;
4013    int status = TCL_OK;
4014
4015    Tcl_DString command;
4016    Tcl_DStringInit(&command);
4017    fd_set readFds;
4018    struct timeval tv, *tvPtr;
4019
4020    FD_ZERO(&readFds);
4021    FD_SET(inBufPtr->file(), &readFds);
4022
4023    bool polling = false;
4024    if (timeout->tv_sec >= 0L) {
4025        tv.tv_sec = timeout->tv_sec;
4026        tv.tv_usec = timeout->tv_usec;
4027        polling = tv.tv_sec == 0 && tv.tv_usec == 0;
4028        tvPtr = &tv;
4029    } else {
4030        // Block until data available
4031        tvPtr = NULL;
4032        TRACE("Blocking on select()");
4033    }
4034    while (inBufPtr->isLineAvailable() ||
4035           (ret = select(inBufPtr->file()+1, &readFds, NULL, NULL, tvPtr)) > 0) {
4036        size_t numBytes;
4037        unsigned char *buffer;
4038
4039        /* A short read is treated as an error here because we assume that we
4040         * will always get commands line by line. */
4041        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
4042            /* Terminate the server if we can't communicate with the client
4043             * anymore. */
4044            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
4045                TRACE("Exiting server on EOF from client");
4046                return -1;
4047            } else {
4048                ERROR("Exiting server, failed to read from client: %s",
4049                      strerror(errno));
4050                return -1;
4051            }
4052        }
4053#if 0
4054        Tcl_DString tmp;
4055        Tcl_DStringInit(&tmp);
4056        Tcl_Encoding encoding = Tcl_GetEncoding(interp, "identity");
4057        TRACE("Encoding name: %s", Tcl_GetEncodingName(encoding));
4058        Tcl_ExternalToUtfDString(encoding, (const char *)buffer, numBytes, &tmp);
4059        Tcl_FreeEncoding(encoding);
4060        Tcl_DStringAppend(&command, Tcl_DStringValue(&tmp), Tcl_DStringLength(&tmp));
4061        Tcl_DStringFree(&tmp);
4062#else
4063        Tcl_DStringAppend(&command, (char *)buffer, numBytes);
4064#endif
4065        if (Tcl_CommandComplete(Tcl_DStringValue(&command))) {
4066            struct timeval start, finish;
4067            gettimeofday(&start, NULL);
4068            g_stats.nCommands++;
4069            status = ExecuteCommand(interp, &command);
4070            gettimeofday(&finish, NULL);
4071            g_stats.cmdTime += (MSECS_ELAPSED(start, finish) / 1.0e+3);
4072            if (status == TCL_BREAK) {
4073                return 2;               /* This was caused by a "imgflush"
4074                                         * command. Break out of the read loop
4075                                         * and allow a new image to be
4076                                         * rendered. */
4077            } else { //if (status != TCL_OK) {
4078                ret = 0;
4079                if (handleError(interp, clientData, status, fdOut) < 0) {
4080                    return -1;
4081                }
4082            }
4083            if (status == TCL_OK) {
4084                ret = 3;
4085            }
4086        }
4087
4088        polling = true;
4089        tv.tv_sec = tv.tv_usec = 0L;    /* On successive reads, we break out
4090                                         * if no data is available. */
4091        FD_SET(inBufPtr->file(), &readFds);
4092        tvPtr = &tv;
4093    }
4094    if (!polling && ret == 0 && timeout->tv_sec > 0L) {
4095        // If idle timeout expired, disconnect
4096        TRACE("Exiting server after timeout waiting for client command");
4097        return -1;
4098    }
4099
4100    return ret;
4101}
4102
4103/**
4104 * \brief Send error message to client socket
4105 */
4106int
4107GeoVis::handleError(Tcl_Interp *interp,
4108                    ClientData clientData,
4109                    int status, int fdOut)
4110{
4111    const char *string;
4112    int nBytes;
4113
4114    if (status != TCL_OK) {
4115        string = Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY);
4116        nBytes = strlen(string);
4117        if (nBytes > 0) {
4118            TRACE("status=%d errorInfo=(%s)", status, string);
4119
4120            std::ostringstream oss;
4121            oss << "nv>viserror -type internal_error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
4122            std::string ostr = oss.str();
4123            nBytes = ostr.length();
4124
4125            if (queueResponse(ostr.c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
4126                return -1;
4127            }
4128        }
4129    }
4130
4131    std::string msg = getUserMessages();
4132    nBytes = msg.length();
4133    if (nBytes > 0) {
4134        string = msg.c_str();
4135        TRACE("userError=(%s)", string);
4136
4137        std::ostringstream oss;
4138        oss << "nv>viserror -type error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
4139        std::string ostr = oss.str();
4140        nBytes = ostr.length();
4141
4142        if (queueResponse(ostr.c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
4143            return -1;
4144        }
4145
4146        clearUserMessages();
4147    }
4148
4149    return 0;
4150}
4151
4152/**
4153 * \brief Make Tcl interpreter safe, set encoding and add commands
4154 */
4155void
4156GeoVis::initTcl(Tcl_Interp *interp, ClientData clientData)
4157{
4158    TRACE("LANG: %s", getenv("LANG"));
4159    Tcl_GetEncodingNames(interp);
4160    TRACE("Supported encodings: %s", Tcl_GetStringResult(interp));
4161    int result = Tcl_Eval(interp, "encoding system\n");
4162    if (result == TCL_OK) {
4163        TRACE("Current system encoding: %s", Tcl_GetStringResult(interp));
4164    } else {
4165        ERROR("Couldn't determine system encoding");
4166    }
4167    const char *encoding = "utf-8";
4168    if (Tcl_SetSystemEncoding(interp, encoding) != TCL_OK) {
4169        TRACE("Failed to set Tcl encoding to %s", encoding);
4170    } else {
4171        TRACE("Set system encoding to %s", encoding);
4172    }
4173
4174    Tcl_MakeSafe(interp);
4175
4176    Tcl_CreateObjCommand(interp, "camera",     CameraCmd,     clientData, NULL);
4177    Tcl_CreateObjCommand(interp, "clientinfo", ClientInfoCmd, clientData, NULL);
4178    Tcl_CreateObjCommand(interp, "colormap",   ColorMapCmd,   clientData, NULL);
4179    Tcl_CreateObjCommand(interp, "file",       FileCmd,       clientData, NULL);
4180    Tcl_CreateObjCommand(interp, "imgflush",   ImageFlushCmd, clientData, NULL);
4181    Tcl_CreateObjCommand(interp, "key",        KeyCmd,        clientData, NULL);
4182    Tcl_CreateObjCommand(interp, "legend",     LegendCmd,     clientData, NULL);
4183    Tcl_CreateObjCommand(interp, "map",        MapCmd,        clientData, NULL);
4184    Tcl_CreateObjCommand(interp, "mouse",      MouseCmd,      clientData, NULL);
4185    Tcl_CreateObjCommand(interp, "placard",    PlacardCmd,    clientData, NULL);
4186    Tcl_CreateObjCommand(interp, "renderer",   RendererCmd,   clientData, NULL);
4187    Tcl_CreateObjCommand(interp, "screen",     ScreenCmd,     clientData, NULL);
4188    Tcl_CreateObjCommand(interp, "select",     SelectCmd,     clientData, NULL);
4189}
4190
4191/**
4192 * \brief Delete Tcl commands and interpreter
4193 */
4194void GeoVis::exitTcl(Tcl_Interp *interp)
4195{
4196    Tcl_DeleteCommand(interp, "camera");
4197    Tcl_DeleteCommand(interp, "clientinfo");
4198    Tcl_DeleteCommand(interp, "colormap");
4199    Tcl_DeleteCommand(interp, "file");
4200    Tcl_DeleteCommand(interp, "imgflush");
4201    Tcl_DeleteCommand(interp, "key");
4202    Tcl_DeleteCommand(interp, "legend");
4203    Tcl_DeleteCommand(interp, "map");
4204    Tcl_DeleteCommand(interp, "mouse");
4205    Tcl_DeleteCommand(interp, "placard");
4206    Tcl_DeleteCommand(interp, "renderer");
4207    Tcl_DeleteCommand(interp, "screen");
4208    Tcl_DeleteCommand(interp, "select");
4209
4210    Tcl_DeleteInterp(interp);
4211}
Note: See TracBrowser for help on using the repository browser.