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

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

Error on missing file, set per-process cache directory, sky node fixes

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