source: geovis/trunk/RendererCmd.cpp @ 6369

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

Add layer position index param to add*Layer methods. Add option to transform
selection box coords to another SRS.

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