source: trunk/packages/vizservers/geovis/RendererCmd.cpp @ 4374

Last change on this file since 4374 was 4374, checked in by ldelgass, 10 years ago

Add support for debug driver (shows tiles with x,y,LOD indices)

File size: 60.4 KB
Line 
1/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 * Copyright (C) 2004-2013  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 <vector>
16#include <unistd.h>
17#include <sys/select.h>
18#include <sys/uio.h>
19#include <tcl.h>
20
21#include <osgEarth/Registry>
22#include <osgEarthFeatures/FeatureModelSource>
23#include <osgEarthSymbology/Color>
24#include <osgEarthSymbology/Style>
25#include <osgEarthSymbology/StyleSheet>
26#include <osgEarthSymbology/LineSymbol>
27#include <osgEarthSymbology/RenderSymbol>
28
29#include <osgEarthDrivers/debug/DebugOptions>
30#include <osgEarthDrivers/gdal/GDALOptions>
31#include <osgEarthDrivers/tms/TMSOptions>
32#include <osgEarthDrivers/wms/WMSOptions>
33#include <osgEarthDrivers/xyz/XYZOptions>
34#include <osgEarthDrivers/model_feature_geom/FeatureGeomModelOptions>
35#include <osgEarthDrivers/feature_ogr/OGRFeatureOptions>
36
37#include "Trace.h"
38#include "CmdProc.h"
39#include "ReadBuffer.h"
40#include "Types.h"
41#include "RendererCmd.h"
42#include "RenderServer.h"
43#include "Renderer.h"
44#include "Stats.h"
45#include "PPMWriter.h"
46#include "TGAWriter.h"
47#include "ResponseQueue.h"
48#ifdef USE_READ_THREAD
49#include "CommandQueue.h"
50#endif
51
52using namespace GeoVis;
53
54static int lastCmdStatus;
55
56#ifndef USE_THREADS
57static ssize_t
58SocketWrite(const void *bytes, size_t len)
59{
60    size_t ofs = 0;
61    ssize_t bytesWritten;
62    while ((bytesWritten = write(g_fdOut, (const char *)bytes + ofs, len - ofs)) > 0) {
63        ofs += bytesWritten;
64        if (ofs == len)
65            break;
66    }
67    if (bytesWritten < 0) {
68        ERROR("write: %s", strerror(errno));
69    }
70    return bytesWritten;
71}
72#endif
73
74static bool
75SocketRead(char *bytes, size_t len)
76{
77    ReadBuffer::BufferStatus status;
78    status = g_inBufPtr->followingData((unsigned char *)bytes, len);
79    TRACE("followingData status: %d", status);
80    return (status == ReadBuffer::OK);
81}
82
83ssize_t
84GeoVis::queueResponse(const void *bytes, size_t len,
85                      Response::AllocationType allocType,
86                      Response::ResponseType type)
87{
88#ifdef USE_THREADS
89    Response *response = new Response(type);
90    response->setMessage((unsigned char *)bytes, len, allocType);
91    g_outQueue->enqueue(response);
92    return (ssize_t)len;
93#else
94    return SocketWrite(bytes, len);
95#endif
96}
97
98static int
99ExecuteCommand(Tcl_Interp *interp, Tcl_DString *dsPtr)
100{
101    int result;
102#ifdef WANT_TRACE
103    char *str = Tcl_DStringValue(dsPtr);
104    std::string cmd(str);
105    cmd.erase(cmd.find_last_not_of(" \n\r\t")+1);
106    TRACE("command %lu: '%s'", g_stats.nCommands+1, cmd.c_str());
107#endif
108    lastCmdStatus = TCL_OK;
109    result = Tcl_EvalEx(interp, Tcl_DStringValue(dsPtr),
110                        Tcl_DStringLength(dsPtr),
111                        TCL_EVAL_DIRECT | TCL_EVAL_GLOBAL);
112    Tcl_DStringSetLength(dsPtr, 0);
113    if (lastCmdStatus == TCL_BREAK) {
114        return TCL_BREAK;
115    }
116    lastCmdStatus = result;
117    if (result != TCL_OK) {
118        TRACE("Error: %d", result);
119    }
120    return result;
121}
122
123static int
124GetBooleanFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, bool *boolPtr)
125{
126    int value;
127
128    if (Tcl_GetBooleanFromObj(interp, objPtr, &value) != TCL_OK) {
129        return TCL_ERROR;
130    }
131    *boolPtr = (bool)value;
132    return TCL_OK;
133}
134
135static int
136GetFloatFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, float *valuePtr)
137{
138    double value;
139
140    if (Tcl_GetDoubleFromObj(interp, objPtr, &value) != TCL_OK) {
141        return TCL_ERROR;
142    }
143    *valuePtr = (float)value;
144    return TCL_OK;
145}
146
147static int
148CameraDeleteViewpointOp(ClientData clientData, Tcl_Interp *interp, int objc,
149                        Tcl_Obj *const *objv)
150{
151    char *name = Tcl_GetString(objv[2]);
152
153    g_renderer->removeNamedViewpoint(name);
154    return TCL_OK;
155}
156
157static int
158CameraGetViewpointOp(ClientData clientData, Tcl_Interp *interp, int objc,
159                     Tcl_Obj *const *objv)
160{
161    osgEarth::Viewpoint view = g_renderer->getViewpoint();
162
163    std::ostringstream oss;
164    size_t len = 0;
165    oss << "nv>camera get "
166        << view.x() << " "
167        << view.y() << " "
168        << view.z() << " "
169        << view.getHeading() << " "
170        << view.getPitch() << " "
171        << view.getRange()
172        << " {" << ((view.getSRS() == NULL) ? "" : view.getSRS()->getHorizInitString()) << "}"
173        << " {" << ((view.getSRS() == NULL) ? "" : view.getSRS()->getVertInitString()) << "}"
174        << "\n";
175    len = oss.str().size();
176#ifdef USE_THREADS
177    queueResponse(oss.str().c_str(), len, Response::VOLATILE);
178#else
179    ssize_t bytesWritten = SocketWrite(oss.str().c_str(), len);
180
181    if (bytesWritten < 0) {
182        return TCL_ERROR;
183    }
184#endif /*USE_THREADS*/
185    return TCL_OK;
186}
187
188static int
189CameraGoOp(ClientData clientData, Tcl_Interp *interp, int objc,
190           Tcl_Obj *const *objv)
191{
192    int x, y;
193    if (Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK ||
194        Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK) {
195        return TCL_ERROR;
196    }
197    double zoom = 1.0;
198    if (objc > 4) {
199        if (Tcl_GetDoubleFromObj(interp, objv[4], &zoom) != TCL_OK) {
200            return TCL_ERROR;
201        }
202    }
203    double duration = 1.0;
204    if (objc > 5) {
205        if (Tcl_GetDoubleFromObj(interp, objv[5], &duration) != TCL_OK) {
206            return TCL_ERROR;
207        }
208    }
209
210    osgEarth::GeoPoint mapPoint;
211    if (g_renderer->mapMouseCoords(x, y, mapPoint)) {
212        osgEarth::Viewpoint vpt = g_renderer->getViewpoint();
213        vpt.x() = mapPoint.x();
214        vpt.y() = mapPoint.y();
215        vpt.z() = mapPoint.z();
216        vpt.setRange(vpt.getRange() * zoom);
217        g_renderer->setViewpoint(vpt, duration);
218    } else {
219        // Out of map bounds
220    }
221    return TCL_OK;
222}
223
224static int
225CameraOrientOp(ClientData clientData, Tcl_Interp *interp, int objc,
226               Tcl_Obj *const *objv)
227{
228    double quat[4];
229
230    if (Tcl_GetDoubleFromObj(interp, objv[2], &quat[0]) != TCL_OK ||
231        Tcl_GetDoubleFromObj(interp, objv[3], &quat[1]) != TCL_OK ||
232        Tcl_GetDoubleFromObj(interp, objv[4], &quat[2]) != TCL_OK ||
233        Tcl_GetDoubleFromObj(interp, objv[5], &quat[3]) != TCL_OK) {
234        return TCL_ERROR;
235    }
236
237    g_renderer->setCameraOrientation(quat);
238    return TCL_OK;
239}
240
241static int
242CameraPanOp(ClientData clientData, Tcl_Interp *interp, int objc,
243            Tcl_Obj *const *objv)
244{
245    double x, y;
246
247    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
248        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
249        return TCL_ERROR;
250    }
251
252    g_renderer->panCamera(x, y);
253    return TCL_OK;
254}
255
256static int
257CameraResetOp(ClientData clientData, Tcl_Interp *interp, int objc,
258              Tcl_Obj *const *objv)
259{
260    if (objc == 3) {
261        const char *string = Tcl_GetString(objv[2]);
262        char c = string[0];
263        if ((c != 'a') || (strcmp(string, "all") != 0)) {
264            Tcl_AppendResult(interp, "bad camera reset option \"", string,
265                         "\": should be all", (char*)NULL);
266            return TCL_ERROR;
267        }
268        g_renderer->resetCamera(true);
269    } else {
270        g_renderer->resetCamera(false);
271    }
272    return TCL_OK;
273}
274
275static int
276CameraRestoreViewpointOp(ClientData clientData, Tcl_Interp *interp, int objc,
277                         Tcl_Obj *const *objv)
278{
279    char *name = Tcl_GetString(objv[2]);
280
281    double duration = 0.0;
282    if (objc > 3) {
283        if (Tcl_GetDoubleFromObj(interp, objv[3], &duration) != TCL_OK) {
284            return TCL_ERROR;
285        }
286    }
287    if (!g_renderer->restoreNamedViewpoint(name, duration)) {
288        Tcl_AppendResult(interp, "camera viewpoint \"", name,
289                         "\" not found", (char*)NULL);
290        return TCL_ERROR;
291    }
292    return TCL_OK;
293}
294
295static int
296CameraRotateOp(ClientData clientData, Tcl_Interp *interp, int objc,
297               Tcl_Obj *const *objv)
298{
299    double x, y;
300
301    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
302        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
303        return TCL_ERROR;
304    }
305
306    g_renderer->rotateCamera(x, y);
307    return TCL_OK;
308}
309
310static int
311CameraSaveViewpointOp(ClientData clientData, Tcl_Interp *interp, int objc,
312                      Tcl_Obj *const *objv)
313{
314    char *name = Tcl_GetString(objv[2]);
315
316    g_renderer->saveNamedViewpoint(name);
317    return TCL_OK;
318}
319
320static int
321CameraSetDistanceOp(ClientData clientData, Tcl_Interp *interp, int objc,
322                    Tcl_Obj *const *objv)
323{
324    double dist;
325
326    if (Tcl_GetDoubleFromObj(interp, objv[2], &dist) != TCL_OK) {
327        return TCL_ERROR;
328    }
329
330    g_renderer->setCameraDistance(dist);
331    return TCL_OK;
332}
333
334static int
335CameraSetViewpointOp(ClientData clientData, Tcl_Interp *interp, int objc,
336                     Tcl_Obj *const *objv)
337{
338    double x, y, z, heading, pitch, distance;
339    double duration = 0.0;
340    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
341        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK ||
342        Tcl_GetDoubleFromObj(interp, objv[4], &z) != TCL_OK ||
343        Tcl_GetDoubleFromObj(interp, objv[5], &heading) != TCL_OK ||
344        Tcl_GetDoubleFromObj(interp, objv[6], &pitch) != TCL_OK ||
345        Tcl_GetDoubleFromObj(interp, objv[7], &distance) != TCL_OK) {
346        return TCL_ERROR;
347    }
348    if (objc > 8) {
349        if (Tcl_GetDoubleFromObj(interp, objv[8], &duration) != TCL_OK) {
350            return TCL_ERROR;
351        }
352    }
353    if (objc > 9) {
354        char *srsInit = Tcl_GetString(objv[9]);
355        if (strlen(srsInit) > 0) {
356            osgEarth::SpatialReference *srs = NULL;
357            if (objc > 10) {
358                char *vertDatum = Tcl_GetString(objv[10]);
359                srs = osgEarth::SpatialReference::get(srsInit, vertDatum);
360            } else {
361                srs = osgEarth::SpatialReference::get(srsInit);
362            }
363            if (srs == NULL) {
364                return TCL_ERROR;
365            }
366            osgEarth::Viewpoint view(x, y, z, heading, pitch, distance, srs);
367            g_renderer->setViewpoint(view, duration);
368        } else {
369            osgEarth::Viewpoint view(x, y, z, heading, pitch, distance);
370            g_renderer->setViewpoint(view, duration);
371        }
372    } else {
373        osgEarth::Viewpoint view(x, y, z, heading, pitch, distance);
374        g_renderer->setViewpoint(view, duration);
375    }
376    return TCL_OK;
377}
378
379static int
380CameraThrowOp(ClientData clientData, Tcl_Interp *interp, int objc,
381              Tcl_Obj *const *objv)
382{
383    bool state;
384
385    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
386        return TCL_ERROR;
387    }
388
389    g_renderer->setThrowingEnabled(state);
390    return TCL_OK;
391}
392
393static int
394CameraZoomOp(ClientData clientData, Tcl_Interp *interp, int objc,
395             Tcl_Obj *const *objv)
396{
397    double z;
398
399    if (Tcl_GetDoubleFromObj(interp, objv[2], &z) != TCL_OK) {
400        return TCL_ERROR;
401    }
402
403    g_renderer->zoomCamera(z);
404    return TCL_OK;
405}
406
407static CmdSpec cameraOps[] = {
408    {"delete",  2, CameraDeleteViewpointOp,  3, 3, "name"},
409    {"dist",    2, CameraSetDistanceOp,      3, 3, "distance"},
410    {"get",     2, CameraGetViewpointOp,     2, 2, ""},
411    {"go",      2, CameraGoOp,               4, 6, "x y ?zoomFactor? ?duration?"},
412    {"orient",  1, CameraOrientOp,           6, 6, "qw qx qy qz"},
413    {"pan",     1, CameraPanOp,              4, 4, "panX panY"},
414    {"reset",   4, CameraResetOp,            2, 3, "?all?"},
415    {"restore", 4, CameraRestoreViewpointOp, 3, 4, "name ?duration?"},
416    {"rotate",  2, CameraRotateOp,           4, 4, "azimuth elevation"},
417    {"save",    2, CameraSaveViewpointOp,    3, 3, "name"},
418    {"set",     2, CameraSetViewpointOp,     8, 11, "x y z heading pitch distance ?duration? ?srs? ?vertDatum?"},
419    {"throw",   1, CameraThrowOp,            3, 3, "bool"},
420    {"zoom",    1, CameraZoomOp,             3, 3, "zoomAmount"}
421};
422static int nCameraOps = NumCmdSpecs(cameraOps);
423
424static int
425CameraCmd(ClientData clientData, Tcl_Interp *interp, int objc,
426          Tcl_Obj *const *objv)
427{
428    Tcl_ObjCmdProc *proc;
429
430    proc = GetOpFromObj(interp, nCameraOps, cameraOps,
431                        CMDSPEC_ARG1, objc, objv, 0);
432    if (proc == NULL) {
433        return TCL_ERROR;
434    }
435    return (*proc) (clientData, interp, objc, objv);
436}
437
438static int
439ClientInfoCmd(ClientData clientData, Tcl_Interp *interp, int objc,
440              Tcl_Obj *const *objv)
441{
442    Tcl_DString ds;
443    Tcl_Obj *objPtr, *listObjPtr, **items;
444    int numItems;
445    char buf[BUFSIZ];
446    const char *string;
447    int length;
448    int result;
449    static bool first = true;
450
451    /* Client arguments. */
452    if (Tcl_ListObjGetElements(interp, objv[1], &numItems, &items) != TCL_OK) {
453        return TCL_ERROR;
454    }
455
456    /* First try to see if we already have an open descriptor */
457    int fd = getStatsFile();
458    if (fd < 0) {
459        /* Use the initial client key value pairs as the parts for generating
460         * a unique file name. */
461        fd = GeoVis::getStatsFile(Tcl_GetString(objv[1]));
462        if (fd < 0) {
463            Tcl_AppendResult(interp, "can't open stats file: ",
464                             Tcl_PosixError(interp), (char *)NULL);
465            return TCL_ERROR;
466        }
467    }
468    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
469    Tcl_IncrRefCount(listObjPtr);
470    if (first) {
471        first = false;
472        objPtr = Tcl_NewStringObj("render_start", 12);
473        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
474        /* renderer */
475        objPtr = Tcl_NewStringObj("renderer", 8);
476        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
477        objPtr = Tcl_NewStringObj("geovis", 6);
478        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
479        /* pid */
480        objPtr = Tcl_NewStringObj("pid", 3);
481        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
482        Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewIntObj(getpid()));
483        /* host */
484        objPtr = Tcl_NewStringObj("host", 4);
485        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
486        gethostname(buf, BUFSIZ-1);
487        buf[BUFSIZ-1] = '\0';
488        objPtr = Tcl_NewStringObj(buf, -1);
489        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
490    } else {
491        objPtr = Tcl_NewStringObj("render_info", 11);
492        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
493    }
494    /* date */
495    Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("date", 4));
496    strcpy(buf, ctime(&GeoVis::g_stats.start.tv_sec));
497    buf[strlen(buf) - 1] = '\0';
498    Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj(buf, -1));
499    /* date_secs */
500    Tcl_ListObjAppendElement(interp, listObjPtr,
501                             Tcl_NewStringObj("date_secs", 9));
502    Tcl_ListObjAppendElement(interp, listObjPtr,
503                             Tcl_NewLongObj(GeoVis::g_stats.start.tv_sec));
504    /* client items */
505    for (int i = 0; i < numItems; i++) {
506        Tcl_ListObjAppendElement(interp, listObjPtr, items[i]);
507    }
508    Tcl_DStringInit(&ds);
509    string = Tcl_GetStringFromObj(listObjPtr, &length);
510    Tcl_DStringAppend(&ds, string, length);
511    Tcl_DStringAppend(&ds, "\n", 1);
512    result = GeoVis::writeToStatsFile(fd, Tcl_DStringValue(&ds),
513                                      Tcl_DStringLength(&ds));
514    Tcl_DStringFree(&ds);
515    Tcl_DecrRefCount(listObjPtr);
516    return result;
517}
518
519static int
520ColorMapAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
521              Tcl_Obj *const *objv)
522{
523    const char *name = Tcl_GetString(objv[2]);
524    int cmapc, omapc;
525    Tcl_Obj **cmapv = NULL;
526    Tcl_Obj **omapv = NULL;
527
528    if (Tcl_ListObjGetElements(interp, objv[3], &cmapc, &cmapv) != TCL_OK) {
529        return TCL_ERROR;
530    }
531    if ((cmapc % 4) != 0) {
532        Tcl_AppendResult(interp, "wrong # elements in colormap: should be ",
533                         "{ value r g b ... }", (char*)NULL);
534        return TCL_ERROR;
535    }
536
537    osg::TransferFunction1D *colorMap = new osg::TransferFunction1D;
538    colorMap->allocate(256);
539
540    for (int i = 0; i < cmapc; i += 4) {
541        double val[4];
542        for (int j = 0; j < 4; j++) {
543            if (Tcl_GetDoubleFromObj(interp, cmapv[i+j], &val[j]) != TCL_OK) {
544                delete colorMap;
545                return TCL_ERROR;
546            }
547            if ((val[j] < 0.0) || (val[j] > 1.0)) {
548                Tcl_AppendResult(interp, "bad colormap value \"",
549                                 Tcl_GetString(cmapv[i+j]),
550                                 "\": should be in the range [0,1]", (char*)NULL);
551                delete colorMap;
552                return TCL_ERROR;
553            }
554        }
555        colorMap->setColor(val[0], osg::Vec4f(val[1], val[2], val[3], 1.0), false);
556    }
557
558    colorMap->updateImage();
559
560    if (Tcl_ListObjGetElements(interp, objv[4], &omapc, &omapv) != TCL_OK) {
561        delete colorMap;
562        return TCL_ERROR;
563    }
564    if ((omapc % 2) != 0) {
565        Tcl_AppendResult(interp, "wrong # elements in opacitymap: should be ",
566                         "{ value alpha ... }", (char*)NULL);
567        delete colorMap;
568        return TCL_ERROR;
569    }
570    for (int i = 0; i < omapc; i += 2) {
571        double val[2];
572        for (int j = 0; j < 2; j++) {
573            if (Tcl_GetDoubleFromObj(interp, omapv[i+j], &val[j]) != TCL_OK) {
574                delete colorMap;
575                return TCL_ERROR;
576            }
577            if ((val[j] < 0.0) || (val[j] > 1.0)) {
578                Tcl_AppendResult(interp, "bad opacitymap value \"",
579                                 Tcl_GetString(omapv[i+j]),
580                                 "\": should be in the range [0,1]", (char*)NULL);
581                delete colorMap;
582                return TCL_ERROR;
583            }
584        }
585#if 0
586        ColorMap::OpacityControlPoint ocp;
587        ocp.value = val[0];
588        ocp.alpha = val[1];
589        colorMap->addOpacityControlPoint(ocp);
590#endif
591    }
592
593    //colorMap->build();
594    g_renderer->addColorMap(name, colorMap);
595    return TCL_OK;
596}
597
598static int
599ColorMapDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
600                 Tcl_Obj *const *objv)
601{
602    if (objc == 3) {
603        const char *name = Tcl_GetString(objv[2]);
604        g_renderer->deleteColorMap(name);
605    } else {
606        g_renderer->deleteColorMap("all");
607    }
608
609    return TCL_OK;
610}
611
612static int
613ColorMapNumTableEntriesOp(ClientData clientData, Tcl_Interp *interp, int objc,
614                          Tcl_Obj *const *objv)
615{
616    int numEntries;
617    if (Tcl_GetIntFromObj(interp, objv[2], &numEntries) != TCL_OK) {
618        const char *str = Tcl_GetString(objv[2]);
619        if (str[0] == 'd' && strcmp(str, "default") == 0) {
620            numEntries = -1;
621        } else {
622            Tcl_AppendResult(interp, "bad colormap resolution value \"", str,
623                             "\": should be a positive integer or \"default\"", (char*)NULL);
624            return TCL_ERROR;
625        }
626    } else if (numEntries < 1) {
627        Tcl_AppendResult(interp, "bad colormap resolution value \"", Tcl_GetString(objv[2]),
628                         "\": should be a positive integer or \"default\"", (char*)NULL);
629        return TCL_ERROR;
630    }
631    if (objc == 4) {
632        const char *name = Tcl_GetString(objv[3]);
633
634        g_renderer->setColorMapNumberOfTableEntries(name, numEntries);
635    } else {
636        g_renderer->setColorMapNumberOfTableEntries("all", numEntries);
637    }
638    return TCL_OK;
639}
640
641static CmdSpec colorMapOps[] = {
642    {"add",    1, ColorMapAddOp,             5, 5, "colorMapName colormap alphamap"},
643    {"define", 3, ColorMapAddOp,             5, 5, "colorMapName colormap alphamap"},
644    {"delete", 3, ColorMapDeleteOp,          2, 3, "?colorMapName?"},
645    {"res",    1, ColorMapNumTableEntriesOp, 3, 4, "numTableEntries ?colorMapName?"}
646};
647static int nColorMapOps = NumCmdSpecs(colorMapOps);
648
649static int
650ColorMapCmd(ClientData clientData, Tcl_Interp *interp, int objc,
651            Tcl_Obj *const *objv)
652{
653    Tcl_ObjCmdProc *proc;
654
655    proc = GetOpFromObj(interp, nColorMapOps, colorMapOps,
656                        CMDSPEC_ARG1, objc, objv, 0);
657    if (proc == NULL) {
658        return TCL_ERROR;
659    }
660    return (*proc) (clientData, interp, objc, objv);
661}
662
663static int
664ImageFlushCmd(ClientData clientData, Tcl_Interp *interp, int objc,
665              Tcl_Obj *const *objv)
666{
667    lastCmdStatus = TCL_BREAK;
668    return TCL_OK;
669}
670
671static int
672KeyPressOp(ClientData clientData, Tcl_Interp *interp, int objc,
673           Tcl_Obj *const *objv)
674{
675    int key;
676    if (Tcl_GetIntFromObj(interp, objv[2], &key) != TCL_OK) {
677        return TCL_ERROR;
678    }
679
680    g_renderer->keyPress(key);
681    return TCL_OK;
682}
683
684static int
685KeyReleaseOp(ClientData clientData, Tcl_Interp *interp, int objc,
686               Tcl_Obj *const *objv)
687{
688    int key;
689    if (Tcl_GetIntFromObj(interp, objv[2], &key) != TCL_OK) {
690        return TCL_ERROR;
691    }
692
693    g_renderer->keyRelease(key);
694    return TCL_OK;
695}
696
697static CmdSpec keyOps[] = {
698    {"press",    1, KeyPressOp,       3, 3, "key"},
699    {"release",  1, KeyReleaseOp,     3, 3, "key"},
700};
701static int nKeyOps = NumCmdSpecs(keyOps);
702
703static int
704KeyCmd(ClientData clientData, Tcl_Interp *interp, int objc,
705         Tcl_Obj *const *objv)
706{
707    Tcl_ObjCmdProc *proc;
708
709    proc = GetOpFromObj(interp, nKeyOps, keyOps,
710                        CMDSPEC_ARG1, objc, objv, 0);
711    if (proc == NULL) {
712        return TCL_ERROR;
713    }
714    return (*proc) (clientData, interp, objc, objv);
715}
716
717static int
718MapCoordsOp(ClientData clientData, Tcl_Interp *interp, int objc,
719            Tcl_Obj *const *objv)
720{
721    int x, y;
722    if (Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK ||
723        Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK) {
724        return TCL_ERROR;
725    }
726
727    osgEarth::GeoPoint mapPoint;
728    size_t length;
729    char mesg[256];
730    if (g_renderer->mapMouseCoords(x, y, mapPoint)) {
731        // send coords to client
732        length = snprintf(mesg, sizeof(mesg),
733                          "nv>map coords %g %g %g %d %d\n",
734                          mapPoint.x(), mapPoint.y(), mapPoint.z(),
735                          x, y);
736
737        queueResponse(mesg, length, Response::VOLATILE);
738    } else {
739        // Out of range
740        length = snprintf(mesg, sizeof(mesg),
741                          "nv>map coords invalid %d %d\n", x, y);
742
743        queueResponse(mesg, length, Response::VOLATILE);
744    }
745    return TCL_OK;
746}
747
748static int
749MapGraticuleOp(ClientData clientData, Tcl_Interp *interp, int objc,
750               Tcl_Obj *const *objv)
751{
752    bool state;
753    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
754        return TCL_ERROR;
755    }
756    if (objc > 3) {
757        Renderer::GraticuleType type;
758        char *typeStr = Tcl_GetString(objv[3]);
759        if (typeStr[0] == 'g' && strcmp(typeStr, "geodetic") == 0) {
760            type = Renderer::GRATICULE_GEODETIC;
761        } else if (typeStr[0] == 'u' && strcmp(typeStr, "utm") == 0) {
762            type = Renderer::GRATICULE_UTM;
763        } else if (typeStr[0] == 'm' && strcmp(typeStr, "mgrs") == 0) {
764            type = Renderer::GRATICULE_MGRS;
765        } else {
766            return TCL_ERROR;
767        }
768        g_renderer->setGraticule(state, type);
769    } else {
770        g_renderer->setGraticule(state);
771    }
772    return TCL_OK;
773}
774
775static int
776MapLayerAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
777              Tcl_Obj *const *objv)
778{
779    char *type = Tcl_GetString(objv[3]);
780    if (type[0] == 'i' && strcmp(type, "image") == 0) {
781        char *driver = Tcl_GetString(objv[4]);
782        char *url = NULL;
783        if (objc > 6) {
784            url =  Tcl_GetString(objv[5]);
785        }
786        if (driver[0] == 'd' && strcmp(driver, "debug") == 0) {
787            osgEarth::Drivers::DebugOptions opts;
788            char *name = Tcl_GetString(objv[5]);
789            g_renderer->addImageLayer(name, opts);
790        } else if (driver[0] == 'g' && strcmp(driver, "gdal") == 0) {
791            osgEarth::Drivers::GDALOptions opts;
792            opts.url() = url;
793            char *name = Tcl_GetString(objv[6]);
794            g_renderer->addImageLayer(name, opts);
795        } else if (driver[0] == 't' && strcmp(driver, "tms") == 0) {
796            osgEarth::Drivers::TMSOptions opts;
797            //char *tmsType = Tcl_GetString(objv[5]);
798            //char *format = Tcl_GetString(objv[6]);
799            opts.url() = url;
800            //opts.tmsType() = tmsType;
801            //opts.format() = format;
802            char *name = Tcl_GetString(objv[6]);
803            g_renderer->addImageLayer(name, opts);
804        } else if (driver[0] == 'w' && strcmp(driver, "wms") == 0) {
805            osgEarth::Drivers::WMSOptions opts;
806            char *wmsLayers = Tcl_GetString(objv[6]);
807            char *format = Tcl_GetString(objv[7]);
808            bool transparent;
809            if (GetBooleanFromObj(interp, objv[8], &transparent) != TCL_OK) {
810                return TCL_ERROR;
811            }
812            opts.url() = url;
813            opts.layers() = wmsLayers;
814            opts.format() = format;
815            opts.transparent() = transparent;
816
817            char *name = Tcl_GetString(objv[9]);
818            g_renderer->addImageLayer(name, opts);
819        } else if (driver[0] == 'x' && strcmp(driver, "xyz") == 0) {
820            osgEarth::Drivers::XYZOptions opts;
821            opts.url() = url;
822            opts.profile() = osgEarth::ProfileOptions("global-mercator");
823            //bool invertY = false;
824            //opts.invertY() = invertY;
825            //opts.format() = Tcl_GetString(objv[6]);
826            char *name = Tcl_GetString(objv[6]);
827            g_renderer->addImageLayer(name, opts);
828        } else {
829            Tcl_AppendResult(interp, "unknown image driver \"", driver,
830                             "\": should be 'gdal', 'tms' or 'wms'", (char*)NULL);
831            return TCL_ERROR;
832        }
833    } else if (type[0] == 'e' && strcmp(type, "elevation") == 0) {
834        char *driver = Tcl_GetString(objv[4]);
835        char *url =  Tcl_GetString(objv[5]);
836
837        if (driver[0] == 'g' && strcmp(driver, "gdal") == 0) {
838            osgEarth::Drivers::GDALOptions opts;
839            opts.url() = url;
840            char *name = Tcl_GetString(objv[6]);
841            g_renderer->addElevationLayer(name, opts);
842        } else if (driver[0] == 't' && strcmp(driver, "tms") == 0) {
843            osgEarth::Drivers::TMSOptions opts;
844            //char *tmsType = Tcl_GetString(objv[6]);
845            //char *format = Tcl_GetString(objv[7]);
846            opts.url() = url;
847            //opts.tmsType() = tmsType;
848            //opts.format() = format;
849            char *name = Tcl_GetString(objv[6]);
850            g_renderer->addElevationLayer(name, opts);
851        } else {
852            Tcl_AppendResult(interp, "unknown elevation driver \"", driver,
853                             "\": should be 'gdal' or 'tms'", (char*)NULL);
854            return TCL_ERROR;
855        }
856    } else if (type[0] == 'p' && strcmp(type, "point") == 0) {
857        osgEarth::Drivers::OGRFeatureOptions opts;
858        char *url =  Tcl_GetString(objv[4]);
859        char *name = Tcl_GetString(objv[5]);
860
861        opts.url() = url;
862
863        osgEarth::Symbology::Style style;
864        osgEarth::Symbology::PointSymbol *ls = style.getOrCreateSymbol<osgEarth::Symbology::PointSymbol>();
865        ls->fill()->color() = osgEarth::Symbology::Color::Black;
866        ls->size() = 2.0f;
867
868        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
869        rs->depthOffset()->enabled() = true;
870        rs->depthOffset()->minBias() = 1000;
871
872        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
873        geomOpts.featureOptions() = opts;
874        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
875        geomOpts.styles()->addStyle(style);
876        geomOpts.enableLighting() = false;
877        g_renderer->addModelLayer(name, geomOpts);
878    } else if (type[0] == 'p' && strcmp(type, "polygon") == 0) {
879        osgEarth::Drivers::OGRFeatureOptions opts;
880        char *url =  Tcl_GetString(objv[4]);
881        char *name = Tcl_GetString(objv[5]);
882        opts.url() = url;
883
884        osgEarth::Symbology::Style style;
885        osgEarth::Symbology::PolygonSymbol *ls = style.getOrCreateSymbol<osgEarth::Symbology::PolygonSymbol>();
886        ls->fill()->color() = osgEarth::Symbology::Color::White;
887
888        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
889        rs->depthOffset()->enabled() = true;
890        rs->depthOffset()->minBias() = 1000;
891
892        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
893        geomOpts.featureOptions() = opts;
894        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
895        geomOpts.styles()->addStyle(style);
896        geomOpts.enableLighting() = false;
897        g_renderer->addModelLayer(name, geomOpts);
898    } else if (type[0] == 'l' && strcmp(type, "line") == 0) {
899        osgEarth::Drivers::OGRFeatureOptions opts;
900        char *url =  Tcl_GetString(objv[4]);
901        char *name = Tcl_GetString(objv[5]);
902        opts.url() = url;
903
904        osgEarth::Symbology::Style style;
905        osgEarth::Symbology::LineSymbol *ls = style.getOrCreateSymbol<osgEarth::Symbology::LineSymbol>();
906        ls->stroke()->color() = osgEarth::Symbology::Color::Black;
907        ls->stroke()->width() = 2.0f;
908#if 0
909        osgEarth::Symbology::AltitudeSymbol *alt = style.getOrCreateSymbol<osgEarth::Symbology::AltitudeSymbol>();
910        alt->clamping() = osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
911        //alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_DRAPE;
912        alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
913#endif
914#if 1
915        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
916        rs->depthOffset()->enabled() = true;
917        rs->depthOffset()->minBias() = 1000;
918#endif
919        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
920        geomOpts.featureOptions() = opts;
921        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
922        geomOpts.styles()->addStyle(style);
923        geomOpts.enableLighting() = false;
924        g_renderer->addModelLayer(name, geomOpts);
925   } else if (type[0] == 't' && strcmp(type, "text") == 0) {
926        osgEarth::Drivers::OGRFeatureOptions opts;
927        char *url =  Tcl_GetString(objv[4]);
928        char *content = Tcl_GetString(objv[5]);
929        char *priority = Tcl_GetString(objv[6]);
930        char *name = Tcl_GetString(objv[7]);
931
932#if 0
933        double fgR = 1.0, fgG = 1.0, fgB = 1.0;
934        double bgR = 0.0, bgG = 0.0, bgB = 0.0;
935        if (objc > 8) {
936            if (Tcl_GetDoubleFromObj(interp, objv[8], &fgR) != TCL_OK ||
937                Tcl_GetDoubleFromObj(interp, objv[9], &fgG) != TCL_OK ||
938                Tcl_GetDoubleFromObj(interp, objv[10], &fgB) != TCL_OK ||
939                Tcl_GetDoubleFromObj(interp, objv[11], &bgR) != TCL_OK ||
940                Tcl_GetDoubleFromObj(interp, objv[12], &bgG) != TCL_OK ||
941                Tcl_GetDoubleFromObj(interp, objv[13], &bgB) != TCL_OK) {
942                return TCL_ERROR;
943            }
944        }
945#endif
946        opts.url() = url;
947
948        osgEarth::Symbology::Style style;
949        osgEarth::Symbology::TextSymbol *ts = style.getOrCreateSymbol<osgEarth::Symbology::TextSymbol>();
950        ts->halo()->color() = osgEarth::Symbology::Color::Black; //::Color(bgR, bgG, bgB);
951        ts->halo()->width() = 2.0f;
952        ts->fill()->color() = osgEarth::Symbology::Color::White; //::Color(fgR, fgG, fgB);
953        ts->content() = osgEarth::Symbology::StringExpression(content);
954        ts->priority() = osgEarth::Symbology::NumericExpression(priority);
955        ts->removeDuplicateLabels() = true;
956        ts->size() = 16.0f;
957        ts->alignment() = osgEarth::Symbology::TextSymbol::ALIGN_CENTER_CENTER;
958        ts->declutter() = true;
959
960        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
961        rs->depthOffset()->enabled() = true;
962        rs->depthOffset()->minBias() = 1000;
963
964        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
965        geomOpts.featureOptions() = opts;
966        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
967        geomOpts.styles()->addStyle(style);
968        geomOpts.enableLighting() = false;
969        g_renderer->addModelLayer(name, geomOpts);
970    } else {
971        Tcl_AppendResult(interp, "unknown map layer type \"", type,
972                         "\": should be 'image', 'elevation' or 'model'", (char*)NULL);
973        return TCL_ERROR;
974    }
975    return TCL_OK;
976}
977
978static int
979MapLayerDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
980                 Tcl_Obj *const *objv)
981{
982    if (objc > 3) {
983        char *name = Tcl_GetString(objv[3]);
984        g_renderer->removeImageLayer(name);
985        g_renderer->removeElevationLayer(name);
986        g_renderer->removeModelLayer(name);
987    } else {
988        g_renderer->clearMap();
989    }
990
991    return TCL_OK;
992}
993
994static int
995MapLayerMoveOp(ClientData clientData, Tcl_Interp *interp, int objc,
996               Tcl_Obj *const *objv)
997{
998    int pos;
999    if (Tcl_GetIntFromObj(interp, objv[3], &pos) != TCL_OK) {
1000        return TCL_ERROR;
1001    }
1002    char *name = Tcl_GetString(objv[4]);
1003    if (pos < 0) {
1004        Tcl_AppendResult(interp, "bad layer pos ", pos,
1005                         ": must be positive", (char*)NULL);
1006        return TCL_ERROR;
1007    }
1008    g_renderer->moveImageLayer(name, (unsigned int)pos);
1009    g_renderer->moveElevationLayer(name, (unsigned int)pos);
1010    g_renderer->moveModelLayer(name, (unsigned int)pos);
1011
1012    return TCL_OK;
1013}
1014
1015static int
1016MapLayerOpacityOp(ClientData clientData, Tcl_Interp *interp, int objc,
1017                  Tcl_Obj *const *objv)
1018{
1019    double opacity;
1020    if (Tcl_GetDoubleFromObj(interp, objv[3], &opacity) != TCL_OK) {
1021        return TCL_ERROR;
1022    }
1023    char *name = Tcl_GetString(objv[4]);
1024    if (opacity < 0.0 || opacity > 1.0) {
1025        Tcl_AppendResult(interp, "bad layer opacity ", opacity,
1026                         ": must be [0,1]", (char*)NULL);
1027        return TCL_ERROR;
1028    }
1029    g_renderer->setImageLayerOpacity(name, opacity);
1030    g_renderer->setModelLayerOpacity(name, opacity);
1031
1032    return TCL_OK;
1033}
1034
1035static int
1036MapLayerNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
1037                Tcl_Obj *const *objv)
1038{
1039    std::vector<std::string> layers;
1040    if (objc < 4) {
1041        g_renderer->getImageLayerNames(layers);
1042        g_renderer->getElevationLayerNames(layers);
1043        g_renderer->getModelLayerNames(layers);
1044    } else {
1045        char *type = Tcl_GetString(objv[3]);
1046        if (type[0] == 'i' && strcmp(type, "image") == 0) {
1047            g_renderer->getImageLayerNames(layers);
1048        } else if (type[0] == 'e' && strcmp(type, "elevation") == 0) {
1049            g_renderer->getElevationLayerNames(layers);
1050        } else if (type[0] == 'm' && strcmp(type, "model") == 0) {
1051            g_renderer->getModelLayerNames(layers);
1052        } else {
1053            Tcl_AppendResult(interp, "uknown type \"", type,
1054                         "\": must be image, elevation or model", (char*)NULL);
1055            return TCL_ERROR;
1056        }
1057    }
1058    std::ostringstream oss;
1059    size_t len = 0;
1060    oss << "nv>map names {";
1061    len += 18;
1062    for (size_t i = 0; i < layers.size(); i++) {
1063        oss << "\"" << layers[i] << "\"";
1064        len += 2 + layers[i].length();
1065        if (i < layers.size() - 1) {
1066            oss << " ";
1067            len++;
1068        }
1069    }
1070    oss << "}\n";
1071    len += 2;
1072#ifdef USE_THREADS
1073    queueResponse(oss.str().c_str(), len, Response::VOLATILE);
1074#else
1075    ssize_t bytesWritten = SocketWrite(oss.str().c_str(), len);
1076
1077    if (bytesWritten < 0) {
1078        return TCL_ERROR;
1079    }
1080#endif /*USE_THREADS*/
1081    return TCL_OK;
1082}
1083
1084static int
1085MapLayerVisibleOp(ClientData clientData, Tcl_Interp *interp, int objc,
1086                  Tcl_Obj *const *objv)
1087{
1088    bool visible;
1089    if (GetBooleanFromObj(interp, objv[3], &visible) != TCL_OK) {
1090        return TCL_ERROR;
1091    }
1092    char *name = Tcl_GetString(objv[4]);
1093
1094    g_renderer->setImageLayerVisibility(name, visible);
1095    g_renderer->setElevationLayerVisibility(name, visible);
1096    g_renderer->setModelLayerVisibility(name, visible);
1097
1098    return TCL_OK;
1099}
1100
1101static CmdSpec mapLayerOps[] = {
1102    {"add",     1, MapLayerAddOp,       6, 0, "type driver ?url? ?args? name"},
1103    {"delete",  1, MapLayerDeleteOp,    3, 4, "?name?"},
1104    {"move",    1, MapLayerMoveOp,      5, 5, "pos name"},
1105    {"names",   1, MapLayerNamesOp,     3, 4, "?type?"},
1106    {"opacity", 1, MapLayerOpacityOp,   5, 5, "opacity ?name?"},
1107    {"visible", 1, MapLayerVisibleOp,   5, 5, "bool ?name?"},
1108};
1109static int nMapLayerOps = NumCmdSpecs(mapLayerOps);
1110
1111static int
1112MapLayerOp(ClientData clientData, Tcl_Interp *interp, int objc,
1113           Tcl_Obj *const *objv)
1114{
1115    Tcl_ObjCmdProc *proc;
1116
1117    proc = GetOpFromObj(interp, nMapLayerOps, mapLayerOps,
1118                        CMDSPEC_ARG2, objc, objv, 0);
1119    if (proc == NULL) {
1120        return TCL_ERROR;
1121    }
1122    return (*proc) (clientData, interp, objc, objv);
1123}
1124
1125static int
1126MapLoadOp(ClientData clientData, Tcl_Interp *interp, int objc,
1127          Tcl_Obj *const *objv)
1128{
1129    char *opt = Tcl_GetString(objv[2]);
1130    if (opt[0] == 'f' && strcmp(opt, "file") == 0) {
1131        g_renderer->loadEarthFile(Tcl_GetString(objv[3]));
1132    } else if (opt[0] == 'u' && strcmp(opt, "url") == 0) {
1133        std::ostringstream path;
1134        path << "server:" << Tcl_GetString(objv[3]);
1135        g_renderer->loadEarthFile(path.str().c_str());
1136    } else if (opt[0] == 'd' && strcmp(opt, "data") == 0) {
1137        opt = Tcl_GetString(objv[3]);
1138        if (opt[0] != 'f' || strcmp(opt, "follows") != 0) {
1139            return TCL_ERROR;
1140        }
1141        int len;
1142        if (Tcl_GetIntFromObj(interp, objv[4], &len) != TCL_OK) {
1143            return TCL_ERROR;
1144        }
1145        // Read Earth file from socket
1146        char *buf = (char *)malloc((size_t)len);
1147        SocketRead(buf, (size_t)len);
1148        std::ostringstream path;
1149        path << "/tmp/tmp" << getpid() << ".earth";
1150        FILE *tmpFile = fopen(path.str().c_str(), "w");
1151        fwrite(buf, len, 1, tmpFile);
1152        fclose(tmpFile);
1153        g_renderer->loadEarthFile(path.str().c_str());
1154        unlink(path.str().c_str());
1155        free(buf);
1156    } else {
1157        return TCL_ERROR;
1158    }
1159    return TCL_OK;
1160}
1161
1162static int
1163MapPositionDisplayOp(ClientData clientData, Tcl_Interp *interp, int objc,
1164                     Tcl_Obj *const *objv)
1165{
1166    bool state;
1167    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
1168        return TCL_ERROR;
1169    }
1170    Renderer::CoordinateDisplayType type = Renderer::COORDS_LATLONG_DECIMAL_DEGREES;
1171    if (state && objc > 3) {
1172        const char *str = Tcl_GetString(objv[3]);
1173        if (str[0] == 'l' && strcmp(str, "latlong_decimal_degrees") == 0) {
1174            type = Renderer::COORDS_LATLONG_DECIMAL_DEGREES;
1175        } else if (str[0] == 'l' && strcmp(str, "latlong_degrees_decimal_minutes") == 0) {
1176            type = Renderer::COORDS_LATLONG_DEGREES_DECIMAL_MINUTES;
1177        } else if (str[0] == 'l' && strcmp(str, "latlong_degrees_minutes_seconds") == 0) {
1178            type = Renderer::COORDS_LATLONG_DEGREES_MINUTES_SECONDS;
1179        } else if (str[0] == 'm' && strcmp(str, "mgrs") == 0) {
1180            type = Renderer::COORDS_MGRS;
1181        } else {
1182            Tcl_AppendResult(interp, "invalid type: \"", str,
1183                             "\": should be 'latlong_decimal_degrees', 'latlong_degrees_decimal_minutes', 'latlong_degrees_minutes_seconds', or 'mgrs'",
1184                             (char*)NULL);
1185            return TCL_ERROR;
1186        }
1187    }
1188    if (state && objc > 4) {
1189        int precision;
1190        if (Tcl_GetIntFromObj(interp, objv[4], &precision) != TCL_OK) {
1191            return TCL_ERROR;
1192        }
1193        g_renderer->setCoordinateReadout(state, type, precision);
1194    } else {
1195        g_renderer->setCoordinateReadout(state, type);
1196    }
1197
1198    return TCL_OK;
1199}
1200
1201static int
1202MapResetOp(ClientData clientData, Tcl_Interp *interp, int objc,
1203           Tcl_Obj *const *objv)
1204{
1205    char *typeStr = Tcl_GetString(objv[2]);
1206    osgEarth::MapOptions::CoordinateSystemType type;
1207    if (typeStr[0] == 'g' && strcmp(typeStr, "geocentric") == 0) {
1208        type = osgEarth::MapOptions::CSTYPE_GEOCENTRIC;
1209    } else if (typeStr[0] == 'g' && strcmp(typeStr, "geocentric_cube") == 0) {
1210        type = osgEarth::MapOptions::CSTYPE_GEOCENTRIC_CUBE;
1211    } else if (typeStr[0] == 'p' && strcmp(typeStr, "projected") == 0) {
1212        type = osgEarth::MapOptions::CSTYPE_PROJECTED;
1213    } else {
1214        Tcl_AppendResult(interp, "bad map type \"", typeStr,
1215                         "\": must be geocentric or projected", (char*)NULL);
1216        return TCL_ERROR;
1217    }
1218
1219    if (type == osgEarth::MapOptions::CSTYPE_PROJECTED) {
1220        if (objc < 4) {
1221            Tcl_AppendResult(interp, "wrong # arguments: profile required for projected maps", (char*)NULL);
1222            return TCL_ERROR;
1223        }
1224        char *profile = Tcl_GetString(objv[3]);
1225        if (objc > 4) {
1226            if (objc < 8) {
1227                Tcl_AppendResult(interp, "wrong # arguments: 4 bounds arguments required", (char*)NULL);
1228                return TCL_ERROR;
1229            }
1230            double bounds[4];
1231            for (int i = 0; i < 4; i++) {
1232                if (Tcl_GetDoubleFromObj(interp, objv[4+i], &bounds[i]) != TCL_OK) {
1233                    return TCL_ERROR;
1234                }
1235            }
1236            // Check if min > max
1237            if (bounds[0] > bounds[2] ||
1238                bounds[1] > bounds[3]) {
1239                Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
1240                return TCL_ERROR;
1241            }
1242            if (strcmp(profile, "geodetic") == 0 ||
1243                strcmp(profile, "epsg:4326") == 0 ) {
1244                if (bounds[0] < -180. || bounds[0] > 180. ||
1245                    bounds[2] < -180. || bounds[2] > 180. ||
1246                    bounds[1] < -90. || bounds[1] > 90. ||
1247                    bounds[3] < -90. || bounds[3] > 90.) {
1248                    Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
1249                    return TCL_ERROR;
1250                }
1251            } else if (strcmp(profile, "spherical-mercator") == 0 ||
1252                       strcmp(profile, "epsg:900913") == 0 ||
1253                       strcmp(profile, "epsg:3857") == 0) {
1254                for (int i = 0; i < 4; i++) {
1255                    if (bounds[i] < -20037508.34 || bounds[i] > 20037508.34) {
1256                        Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
1257                        return TCL_ERROR;
1258                    }
1259                }
1260            }
1261
1262            g_renderer->resetMap(type, profile, bounds);
1263        } else {
1264            if (osgEarth::Registry::instance()->getNamedProfile(profile) == NULL) {
1265                Tcl_AppendResult(interp, "bad named profile \"", profile,
1266                                 "\": must be e.g. 'global-geodetic', 'global-mercator'...", (char*)NULL);
1267                return TCL_ERROR;
1268            }
1269            g_renderer->resetMap(type, profile);
1270        }
1271    } else {
1272        // No profile required for geocentric (3D) maps
1273        g_renderer->resetMap(type);
1274    }
1275
1276    return TCL_OK;
1277}
1278
1279static int
1280MapScaleBarOp(ClientData clientData, Tcl_Interp *interp, int objc,
1281              Tcl_Obj *const *objv)
1282{
1283    bool state;
1284    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
1285        return TCL_ERROR;
1286    }
1287    g_renderer->setScaleBar(state);
1288    if (state && objc > 3) {
1289        const char *unitStr = Tcl_GetString(objv[3]);
1290        ScaleBarUnits units;
1291        if (unitStr[0] == 'm' && strcmp(unitStr, "meters") == 0) {
1292            units = UNITS_METERS;
1293        } else if (unitStr[0] == 'f' && strcmp(unitStr, "feet") == 0) {
1294            units = UNITS_INTL_FEET;
1295        } else if (unitStr[0] == 'u' && strcmp(unitStr, "us_survey_feet") == 0) {
1296            units = UNITS_US_SURVEY_FEET;
1297        } else if (unitStr[0] == 'n' && strcmp(unitStr, "nautical_miles") == 0) {
1298            units = UNITS_NAUTICAL_MILES;
1299        } else {
1300            Tcl_AppendResult(interp, "bad units \"", unitStr,
1301                             "\": must be 'meters', 'feet', 'us_survey_feet' or 'nautical_miles'", (char*)NULL);
1302            return TCL_ERROR;
1303        }
1304        g_renderer->setScaleBarUnits(units);
1305    }
1306    return TCL_OK;
1307}
1308
1309static int
1310MapSetPositionOp(ClientData clientData, Tcl_Interp *interp, int objc,
1311            Tcl_Obj *const *objv)
1312{
1313    if (objc < 3) {
1314        g_renderer->clearReadout();
1315    } else {
1316        int x, y;
1317        if (Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK ||
1318            Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK) {
1319            return TCL_ERROR;
1320        }
1321        g_renderer->setReadout(x, y);
1322    }
1323    return TCL_OK;
1324}
1325
1326static int
1327MapTerrainEdgesOp(ClientData clientData, Tcl_Interp *interp, int objc,
1328                  Tcl_Obj *const *objv)
1329{
1330    bool state;
1331    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
1332        return TCL_ERROR;
1333    }
1334    TRACE("Not implemented");
1335    //g_renderer->setTerrainEdges(state);
1336    return TCL_OK;
1337}
1338
1339static int
1340MapTerrainLightingOp(ClientData clientData, Tcl_Interp *interp, int objc,
1341                     Tcl_Obj *const *objv)
1342{
1343    bool state;
1344    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
1345        return TCL_ERROR;
1346    }
1347
1348    g_renderer->setTerrainLighting(state);
1349    return TCL_OK;
1350}
1351
1352static int
1353MapTerrainLineColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
1354                      Tcl_Obj *const *objv)
1355{
1356    float color[3];
1357    if (GetFloatFromObj(interp, objv[3], &color[0]) != TCL_OK ||
1358        GetFloatFromObj(interp, objv[4], &color[1]) != TCL_OK ||
1359        GetFloatFromObj(interp, objv[5], &color[2]) != TCL_OK) {
1360        return TCL_ERROR;
1361    }
1362    TRACE("Not implemented");
1363    //g_renderer->setTerrainLineColor(color);
1364    return TCL_OK;
1365}
1366
1367static int
1368MapTerrainVertScaleOp(ClientData clientData, Tcl_Interp *interp, int objc,
1369                      Tcl_Obj *const *objv)
1370{
1371    double scale;
1372    if (Tcl_GetDoubleFromObj(interp, objv[3], &scale) != TCL_OK) {
1373        return TCL_ERROR;
1374    }
1375
1376    g_renderer->setTerrainVerticalScale(scale);
1377    return TCL_OK;
1378}
1379
1380static int
1381MapTerrainWireframeOp(ClientData clientData, Tcl_Interp *interp, int objc,
1382                      Tcl_Obj *const *objv)
1383{
1384    bool state;
1385    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
1386        return TCL_ERROR;
1387    }
1388
1389    g_renderer->setTerrainWireframe(state);
1390    return TCL_OK;
1391}
1392
1393static CmdSpec mapTerrainOps[] = {
1394    {"edges",     1, MapTerrainEdgesOp,     4, 4, "bool"},
1395    {"lighting",  2, MapTerrainLightingOp,  4, 4, "bool"},
1396    {"linecolor", 2, MapTerrainLineColorOp, 6, 6, "r g b"},
1397    {"vertscale", 1, MapTerrainVertScaleOp, 4, 4, "val"},
1398    {"wireframe", 1, MapTerrainWireframeOp, 4, 4, "bool"},
1399};
1400static int nMapTerrainOps = NumCmdSpecs(mapTerrainOps);
1401
1402static int
1403MapTerrainOp(ClientData clientData, Tcl_Interp *interp, int objc,
1404           Tcl_Obj *const *objv)
1405{
1406    Tcl_ObjCmdProc *proc;
1407
1408    proc = GetOpFromObj(interp, nMapTerrainOps, mapTerrainOps,
1409                        CMDSPEC_ARG2, objc, objv, 0);
1410    if (proc == NULL) {
1411        return TCL_ERROR;
1412    }
1413    return (*proc) (clientData, interp, objc, objv);
1414}
1415
1416static CmdSpec mapOps[] = {
1417    {"coords",   1, MapCoordsOp,          4, 4, "x y"},
1418    {"grid",     1, MapGraticuleOp,       3, 4, "bool ?type?"},
1419    {"layer",    2, MapLayerOp,           3, 0, "op ?params...?"},
1420    {"load",     2, MapLoadOp,            4, 5, "options"},
1421    {"posdisp",  1, MapPositionDisplayOp, 3, 5, "bool ?format? ?precision?"},
1422    {"reset",    1, MapResetOp,           3, 8, "type ?profile xmin ymin xmax ymax?"},
1423    {"scalebar", 1, MapScaleBarOp,        3, 4, "bool ?units?"},
1424    {"setpos",   1, MapSetPositionOp,     2, 4, "x y"},
1425    {"terrain",  1, MapTerrainOp,         3, 0, "op ?params...?"},
1426};
1427static int nMapOps = NumCmdSpecs(mapOps);
1428
1429static int
1430MapCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1431       Tcl_Obj *const *objv)
1432{
1433    Tcl_ObjCmdProc *proc;
1434
1435    proc = GetOpFromObj(interp, nMapOps, mapOps,
1436                        CMDSPEC_ARG1, objc, objv, 0);
1437    if (proc == NULL) {
1438        return TCL_ERROR;
1439    }
1440    return (*proc) (clientData, interp, objc, objv);
1441}
1442
1443static int
1444MouseClickOp(ClientData clientData, Tcl_Interp *interp, int objc,
1445             Tcl_Obj *const *objv)
1446{
1447    int button;
1448    double x, y;
1449
1450    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
1451        return TCL_ERROR;
1452    }
1453    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
1454        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
1455        return TCL_ERROR;
1456    }
1457
1458    g_renderer->mouseClick(button, x, y);
1459    return TCL_OK;
1460}
1461
1462static int
1463MouseDoubleClickOp(ClientData clientData, Tcl_Interp *interp, int objc,
1464                   Tcl_Obj *const *objv)
1465{
1466    int button;
1467    double x, y;
1468
1469    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
1470        return TCL_ERROR;
1471    }
1472    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
1473        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
1474        return TCL_ERROR;
1475    }
1476
1477    g_renderer->mouseDoubleClick(button, x, y);
1478    return TCL_OK;
1479}
1480
1481static int
1482MouseDragOp(ClientData clientData, Tcl_Interp *interp, int objc,
1483            Tcl_Obj *const *objv)
1484{
1485    int button;
1486    double x, y;
1487
1488    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
1489        return TCL_ERROR;
1490    }
1491    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
1492        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
1493        return TCL_ERROR;
1494    }
1495
1496    g_renderer->mouseDrag(button, x, y);
1497    return TCL_OK;
1498}
1499
1500static int
1501MouseMotionOp(ClientData clientData, Tcl_Interp *interp, int objc,
1502              Tcl_Obj *const *objv)
1503{
1504    double x, y;
1505
1506    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
1507        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
1508        return TCL_ERROR;
1509    }
1510
1511    g_renderer->mouseMotion(x, y);
1512    return TCL_OK;
1513}
1514
1515static int
1516MouseReleaseOp(ClientData clientData, Tcl_Interp *interp, int objc,
1517               Tcl_Obj *const *objv)
1518{
1519    int button;
1520    double x, y;
1521
1522    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
1523        return TCL_ERROR;
1524    }
1525    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
1526        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
1527        return TCL_ERROR;
1528    }
1529
1530    g_renderer->mouseRelease(button, x, y);
1531    return TCL_OK;
1532}
1533
1534static int
1535MouseScrollOp(ClientData clientData, Tcl_Interp *interp, int objc,
1536              Tcl_Obj *const *objv)
1537{
1538    int direction;
1539
1540    if (Tcl_GetIntFromObj(interp, objv[2], &direction) != TCL_OK) {
1541        return TCL_ERROR;
1542    }
1543
1544    g_renderer->mouseScroll(direction);
1545    return TCL_OK;
1546}
1547
1548static CmdSpec mouseOps[] = {
1549    {"click",    1, MouseClickOp,       5, 5, "button x y"},
1550    {"dblclick", 2, MouseDoubleClickOp, 5, 5, "button x y"},
1551    {"drag",     2, MouseDragOp,        5, 5, "button x y"},
1552    {"motion",   1, MouseMotionOp,      4, 4, "x y"},
1553    {"release",  1, MouseReleaseOp,     5, 5, "button x y"},
1554    {"scroll",   1, MouseScrollOp,      3, 3, "direction"},
1555};
1556static int nMouseOps = NumCmdSpecs(mouseOps);
1557
1558static int
1559MouseCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1560         Tcl_Obj *const *objv)
1561{
1562    Tcl_ObjCmdProc *proc;
1563
1564    proc = GetOpFromObj(interp, nMouseOps, mouseOps,
1565                        CMDSPEC_ARG1, objc, objv, 0);
1566    if (proc == NULL) {
1567        return TCL_ERROR;
1568    }
1569    return (*proc) (clientData, interp, objc, objv);
1570}
1571
1572static int
1573RendererRenderOp(ClientData clientData, Tcl_Interp *interp, int objc,
1574                 Tcl_Obj *const *objv)
1575{
1576    g_renderer->eventuallyRender();
1577    return TCL_OK;
1578}
1579
1580static CmdSpec rendererOps[] = {
1581    {"render",     1, RendererRenderOp, 2, 2, ""},
1582};
1583static int nRendererOps = NumCmdSpecs(rendererOps);
1584
1585static int
1586RendererCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1587            Tcl_Obj *const *objv)
1588{
1589    Tcl_ObjCmdProc *proc;
1590
1591    proc = GetOpFromObj(interp, nRendererOps, rendererOps,
1592                        CMDSPEC_ARG1, objc, objv, 0);
1593    if (proc == NULL) {
1594        return TCL_ERROR;
1595    }
1596    return (*proc) (clientData, interp, objc, objv);
1597}
1598
1599static int
1600ScreenBgColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
1601                Tcl_Obj *const *objv)
1602{
1603    float color[3];
1604
1605    if (GetFloatFromObj(interp, objv[2], &color[0]) != TCL_OK ||
1606        GetFloatFromObj(interp, objv[3], &color[1]) != TCL_OK ||
1607        GetFloatFromObj(interp, objv[4], &color[2]) != TCL_OK) {
1608        return TCL_ERROR;
1609    }
1610
1611    g_renderer->setBackgroundColor(color);
1612    return TCL_OK;
1613}
1614
1615static int
1616ScreenSizeOp(ClientData clientData, Tcl_Interp *interp, int objc,
1617             Tcl_Obj *const *objv)
1618{
1619    int width, height;
1620
1621    if (Tcl_GetIntFromObj(interp, objv[2], &width) != TCL_OK ||
1622        Tcl_GetIntFromObj(interp, objv[3], &height) != TCL_OK) {
1623        return TCL_ERROR;
1624    }
1625
1626    g_renderer->setWindowSize(width, height);
1627    return TCL_OK;
1628}
1629
1630static CmdSpec screenOps[] = {
1631    {"bgcolor", 1, ScreenBgColorOp, 5, 5, "r g b"},
1632    {"size", 1, ScreenSizeOp, 4, 4, "width height"}
1633};
1634static int nScreenOps = NumCmdSpecs(screenOps);
1635
1636static int
1637ScreenCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1638          Tcl_Obj *const *objv)
1639{
1640    Tcl_ObjCmdProc *proc;
1641
1642    proc = GetOpFromObj(interp, nScreenOps, screenOps,
1643                        CMDSPEC_ARG1, objc, objv, 0);
1644    if (proc == NULL) {
1645        return TCL_ERROR;
1646    }
1647    return (*proc) (clientData, interp, objc, objv);
1648}
1649
1650#ifdef USE_READ_THREAD
1651int
1652GeoVis::queueCommands(Tcl_Interp *interp,
1653                      ClientData clientData,
1654                      ReadBuffer *inBufPtr)
1655{
1656    Tcl_DString commandString;
1657    Tcl_DStringInit(&commandString);
1658    fd_set readFds;
1659
1660    FD_ZERO(&readFds);
1661    FD_SET(inBufPtr->file(), &readFds);
1662    while (inBufPtr->isLineAvailable() ||
1663           (select(inBufPtr->file()+1, &readFds, NULL, NULL, NULL) > 0)) {
1664        size_t numBytes;
1665        unsigned char *buffer;
1666
1667        /* A short read is treated as an error here because we assume that we
1668         * will always get commands line by line. */
1669        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
1670            /* Terminate the server if we can't communicate with the client
1671             * anymore. */
1672            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
1673                TRACE("Exiting server on EOF from client");
1674                return -1;
1675            } else {
1676                ERROR("Exiting server, failed to read from client: %s",
1677                      strerror(errno));
1678                return -1;
1679            }
1680        }
1681        Tcl_DStringAppend(&commandString, (char *)buffer, numBytes);
1682        if (Tcl_CommandComplete(Tcl_DStringValue(&commandString))) {
1683            // Add to queue
1684            Command *command = new Command(Command::COMMAND);
1685            command->setMessage((unsigned char *)Tcl_DStringValue(&commandString),
1686                                Tcl_DStringLength(&commandString), Command::VOLATILE);
1687            g_inQueue->enqueue(command);
1688            Tcl_DStringSetLength(&commandString, 0);
1689        }
1690        FD_SET(inBufPtr->file(), &readFds);
1691    }
1692
1693    return 1;
1694}
1695#endif
1696
1697/**
1698 * \brief Execute commands from client in Tcl interpreter
1699 *
1700 * In this threaded model, the select call is for event compression.  We
1701 * want to execute render server commands as long as they keep coming. 
1702 * This lets us execute a stream of many commands but render once.  This
1703 * benefits camera movements, screen resizing, and opacity changes
1704 * (using a slider on the client).  The down side is you don't render
1705 * until there's a lull in the command stream.  If the client needs an
1706 * image, it can issue an "imgflush" command.  That breaks us out of the
1707 * read loop.
1708 */
1709int
1710GeoVis::processCommands(Tcl_Interp *interp,
1711                        ClientData clientData,
1712                        ReadBuffer *inBufPtr,
1713                        int fdOut,
1714                        long timeout)
1715{
1716    int ret = 1;
1717    int status = TCL_OK;
1718
1719    Tcl_DString command;
1720    Tcl_DStringInit(&command);
1721    fd_set readFds;
1722    struct timeval tv, *tvPtr;
1723
1724    FD_ZERO(&readFds);
1725    FD_SET(inBufPtr->file(), &readFds);
1726    tvPtr = NULL;                       /* Wait for the first read. This is so
1727                                         * that we don't spin when no data is
1728                                         * available. */
1729    if (timeout >= 0L) {
1730        tv.tv_sec = 0L;
1731        tv.tv_usec = timeout;
1732        tvPtr = &tv;
1733    } else {
1734        TRACE("Blocking on select()");
1735    }
1736    while (inBufPtr->isLineAvailable() ||
1737           (ret = select(inBufPtr->file()+1, &readFds, NULL, NULL, tvPtr)) > 0) {
1738        size_t numBytes;
1739        unsigned char *buffer;
1740
1741        /* A short read is treated as an error here because we assume that we
1742         * will always get commands line by line. */
1743        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
1744            /* Terminate the server if we can't communicate with the client
1745             * anymore. */
1746            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
1747                TRACE("Exiting server on EOF from client");
1748                return -1;
1749            } else {
1750                ERROR("Exiting server, failed to read from client: %s",
1751                      strerror(errno));
1752                return -1;
1753            }
1754        }
1755        Tcl_DStringAppend(&command, (char *)buffer, numBytes);
1756        if (Tcl_CommandComplete(Tcl_DStringValue(&command))) {
1757            struct timeval start, finish;
1758            gettimeofday(&start, NULL);
1759            status = ExecuteCommand(interp, &command);
1760            gettimeofday(&finish, NULL);
1761            g_stats.cmdTime += (MSECS_ELAPSED(start, finish) / 1.0e+3);
1762            g_stats.nCommands++;
1763            if (status == TCL_BREAK) {
1764                return 2;               /* This was caused by a "imgflush"
1765                                         * command. Break out of the read loop
1766                                         * and allow a new image to be
1767                                         * rendered. */
1768            } else { //if (status != TCL_OK) {
1769                ret = 0;
1770                if (handleError(interp, clientData, status, fdOut) < 0) {
1771                    return -1;
1772                }
1773            }
1774            if (status == TCL_OK) {
1775                ret = 3;
1776            }
1777        }
1778
1779        tv.tv_sec = tv.tv_usec = 0L;    /* On successive reads, we break out
1780                                         * if no data is available. */
1781        FD_SET(inBufPtr->file(), &readFds);
1782        tvPtr = &tv;
1783    }
1784
1785    return ret;
1786}
1787
1788/**
1789 * \brief Send error message to client socket
1790 */
1791int
1792GeoVis::handleError(Tcl_Interp *interp,
1793                    ClientData clientData,
1794                    int status, int fdOut)
1795{
1796    const char *string;
1797    int nBytes;
1798
1799    if (status != TCL_OK) {
1800        string = Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY);
1801        nBytes = strlen(string);
1802        if (nBytes > 0) {
1803            TRACE("status=%d errorInfo=(%s)", status, string);
1804
1805            std::ostringstream oss;
1806            oss << "nv>viserror -type internal_error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
1807            nBytes = oss.str().length();
1808
1809            if (queueResponse(oss.str().c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
1810                return -1;
1811            }
1812        }
1813    }
1814
1815    string = getUserMessages();
1816    nBytes = strlen(string);
1817    if (nBytes > 0) {
1818        TRACE("userError=(%s)", string);
1819
1820        std::ostringstream oss;
1821        oss << "nv>viserror -type error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
1822        nBytes = oss.str().length();
1823
1824        if (queueResponse(oss.str().c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
1825            return -1;
1826        }
1827
1828        clearUserMessages();
1829    }
1830
1831    return 0;
1832}
1833
1834/**
1835 * \brief Create Tcl interpreter and add commands
1836 *
1837 * \return The initialized Tcl interpreter
1838 */
1839void
1840GeoVis::initTcl(Tcl_Interp *interp, ClientData clientData)
1841{
1842    Tcl_MakeSafe(interp);
1843    Tcl_CreateObjCommand(interp, "camera",         CameraCmd,         clientData, NULL);
1844    Tcl_CreateObjCommand(interp, "clientinfo",     ClientInfoCmd,     clientData, NULL);
1845    Tcl_CreateObjCommand(interp, "colormap",       ColorMapCmd,       clientData, NULL);
1846    Tcl_CreateObjCommand(interp, "imgflush",       ImageFlushCmd,     clientData, NULL);
1847    Tcl_CreateObjCommand(interp, "key",            KeyCmd,            clientData, NULL);
1848    Tcl_CreateObjCommand(interp, "map",            MapCmd,            clientData, NULL);
1849    Tcl_CreateObjCommand(interp, "mouse",          MouseCmd,          clientData, NULL);
1850    Tcl_CreateObjCommand(interp, "renderer",       RendererCmd,       clientData, NULL);
1851    Tcl_CreateObjCommand(interp, "screen",         ScreenCmd,         clientData, NULL);
1852}
1853
1854/**
1855 * \brief Delete Tcl commands and interpreter
1856 */
1857void GeoVis::exitTcl(Tcl_Interp *interp)
1858{
1859    Tcl_DeleteCommand(interp, "camera");
1860    Tcl_DeleteCommand(interp, "clientinfo");
1861    Tcl_DeleteCommand(interp, "colormap");
1862    Tcl_DeleteCommand(interp, "imgflush");
1863    Tcl_DeleteCommand(interp, "key");
1864    Tcl_DeleteCommand(interp, "map");
1865    Tcl_DeleteCommand(interp, "mouse");
1866    Tcl_DeleteCommand(interp, "renderer");
1867    Tcl_DeleteCommand(interp, "screen");
1868
1869    Tcl_DeleteInterp(interp);
1870}
Note: See TracBrowser for help on using the repository browser.