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

Last change on this file since 4422 was 4422, checked in by ldelgass, 7 years ago

Fix crash bug in user errors: was returning pointer to temporary. Return a
copy of string object from getUserMessages instead of pointer.

File size: 62.1 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            char *urlIn = Tcl_GetString(objv[5]);
788            url = g_renderer->getCanonicalPath(std::string(urlIn));
789            if (url.empty()) {
790                Tcl_AppendResult(interp, "file not found: \"",
791                                 urlIn, "\"", (char*)NULL);
792                return TCL_ERROR;
793            }
794        }
795        if (driver[0] == 'd' && strcmp(driver, "debug") == 0) {
796            osgEarth::Drivers::DebugOptions opts;
797            char *name = Tcl_GetString(objv[5]);
798            g_renderer->addImageLayer(name, opts);
799        } else if (driver[0] == 'g' && strcmp(driver, "gdal") == 0) {
800            osgEarth::Drivers::GDALOptions opts;
801            opts.url() = url;
802            char *name = Tcl_GetString(objv[6]);
803            g_renderer->addImageLayer(name, opts);
804        } else if (driver[0] == 't' && strcmp(driver, "tms") == 0) {
805            osgEarth::Drivers::TMSOptions opts;
806            //char *tmsType = Tcl_GetString(objv[5]);
807            //char *format = Tcl_GetString(objv[6]);
808            opts.url() = url;
809            //opts.tmsType() = tmsType;
810            //opts.format() = format;
811            char *name = Tcl_GetString(objv[6]);
812            g_renderer->addImageLayer(name, opts);
813        } else if (driver[0] == 'w' && strcmp(driver, "wms") == 0) {
814            osgEarth::Drivers::WMSOptions opts;
815            char *wmsLayers = Tcl_GetString(objv[6]);
816            char *format = Tcl_GetString(objv[7]);
817            bool transparent;
818            if (GetBooleanFromObj(interp, objv[8], &transparent) != TCL_OK) {
819                return TCL_ERROR;
820            }
821            opts.url() = url;
822            opts.layers() = wmsLayers;
823            opts.format() = format;
824            opts.transparent() = transparent;
825
826            char *name = Tcl_GetString(objv[9]);
827            g_renderer->addImageLayer(name, opts);
828        } else if (driver[0] == 'x' && strcmp(driver, "xyz") == 0) {
829            osgEarth::Drivers::XYZOptions opts;
830            opts.url() = url;
831            opts.profile() = osgEarth::ProfileOptions("global-mercator");
832            //bool invertY = false;
833            //opts.invertY() = invertY;
834            //opts.format() = Tcl_GetString(objv[6]);
835            char *name = Tcl_GetString(objv[6]);
836            g_renderer->addImageLayer(name, opts);
837        } else {
838            Tcl_AppendResult(interp, "unknown image driver \"", driver,
839                             "\": should be 'debug', 'gdal', 'tms', 'wms' or 'xyz'", (char*)NULL);
840            return TCL_ERROR;
841        }
842    } else if (type[0] == 'e' && strcmp(type, "elevation") == 0) {
843        char *driver = Tcl_GetString(objv[4]);
844        char *urlIn = Tcl_GetString(objv[5]);
845        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
846        if (url.empty()) {
847            Tcl_AppendResult(interp, "file not found: \"",
848                             urlIn, "\"", (char*)NULL);
849            return TCL_ERROR;
850        }
851
852        if (driver[0] == 'g' && strcmp(driver, "gdal") == 0) {
853            osgEarth::Drivers::GDALOptions opts;
854            opts.url() = url;
855            char *name = Tcl_GetString(objv[6]);
856            g_renderer->addElevationLayer(name, opts);
857        } else if (driver[0] == 't' && strcmp(driver, "tms") == 0) {
858            osgEarth::Drivers::TMSOptions opts;
859            //char *tmsType = Tcl_GetString(objv[6]);
860            //char *format = Tcl_GetString(objv[7]);
861            opts.url() = url;
862            //opts.tmsType() = tmsType;
863            //opts.format() = format;
864            char *name = Tcl_GetString(objv[6]);
865            g_renderer->addElevationLayer(name, opts);
866        } else {
867            Tcl_AppendResult(interp, "unknown elevation driver \"", driver,
868                             "\": should be 'gdal' or 'tms'", (char*)NULL);
869            return TCL_ERROR;
870        }
871    } else if (type[0] == 'p' && strcmp(type, "point") == 0) {
872        osgEarth::Drivers::OGRFeatureOptions opts;
873        char *urlIn = Tcl_GetString(objv[4]);
874        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
875        if (url.empty()) {
876            Tcl_AppendResult(interp, "file not found: \"",
877                             urlIn, "\"", (char*)NULL);
878            return TCL_ERROR;
879        }
880        char *name = Tcl_GetString(objv[5]);
881
882        opts.url() = url;
883
884        osgEarth::Symbology::Style style;
885        osgEarth::Symbology::PointSymbol *ls = style.getOrCreateSymbol<osgEarth::Symbology::PointSymbol>();
886        ls->fill()->color() = osgEarth::Symbology::Color::Black;
887        ls->size() = 2.0f;
888
889        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
890        rs->depthOffset()->enabled() = true;
891        rs->depthOffset()->minBias() = 1000;
892
893        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
894        geomOpts.featureOptions() = opts;
895        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
896        geomOpts.styles()->addStyle(style);
897        geomOpts.enableLighting() = false;
898        g_renderer->addModelLayer(name, geomOpts);
899    } else if (type[0] == 'p' && strcmp(type, "polygon") == 0) {
900        osgEarth::Drivers::OGRFeatureOptions opts;
901        char *urlIn = Tcl_GetString(objv[4]);
902        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
903        if (url.empty()) {
904            Tcl_AppendResult(interp, "file not found: \"",
905                             urlIn, "\"", (char*)NULL);
906            return TCL_ERROR;
907        }
908        char *name = Tcl_GetString(objv[5]);
909        opts.url() = url;
910
911        osgEarth::Symbology::Style style;
912        osgEarth::Symbology::PolygonSymbol *ls = style.getOrCreateSymbol<osgEarth::Symbology::PolygonSymbol>();
913        ls->fill()->color() = osgEarth::Symbology::Color::White;
914
915        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
916        rs->depthOffset()->enabled() = true;
917        rs->depthOffset()->minBias() = 1000;
918
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] == 'l' && strcmp(type, "line") == 0) {
926        osgEarth::Drivers::OGRFeatureOptions opts;
927        char *urlIn = Tcl_GetString(objv[4]);
928        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
929        if (url.empty()) {
930            Tcl_AppendResult(interp, "file not found: \"",
931                             urlIn, "\"", (char*)NULL);
932            return TCL_ERROR;
933        }
934        char *name = Tcl_GetString(objv[5]);
935        opts.url() = url;
936
937        osgEarth::Symbology::Style style;
938        osgEarth::Symbology::LineSymbol *ls = style.getOrCreateSymbol<osgEarth::Symbology::LineSymbol>();
939        ls->stroke()->color() = osgEarth::Symbology::Color::Black;
940        ls->stroke()->width() = 2.0f;
941#if 0
942        osgEarth::Symbology::AltitudeSymbol *alt = style.getOrCreateSymbol<osgEarth::Symbology::AltitudeSymbol>();
943        alt->clamping() = osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
944        //alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_DRAPE;
945        alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
946#endif
947#if 1
948        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
949        rs->depthOffset()->enabled() = true;
950        rs->depthOffset()->minBias() = 1000;
951#endif
952        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
953        geomOpts.featureOptions() = opts;
954        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
955        geomOpts.styles()->addStyle(style);
956        geomOpts.enableLighting() = false;
957        g_renderer->addModelLayer(name, geomOpts);
958   } else if (type[0] == 't' && strcmp(type, "text") == 0) {
959        osgEarth::Drivers::OGRFeatureOptions opts;
960        char *urlIn = Tcl_GetString(objv[4]);
961        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
962        if (url.empty()) {
963            Tcl_AppendResult(interp, "file not found: \"",
964                             urlIn, "\"", (char*)NULL);
965            return TCL_ERROR;
966        }
967        char *content = Tcl_GetString(objv[5]);
968        char *priority = Tcl_GetString(objv[6]);
969        char *name = Tcl_GetString(objv[7]);
970
971#if 0
972        double fgR = 1.0, fgG = 1.0, fgB = 1.0;
973        double bgR = 0.0, bgG = 0.0, bgB = 0.0;
974        if (objc > 8) {
975            if (Tcl_GetDoubleFromObj(interp, objv[8], &fgR) != TCL_OK ||
976                Tcl_GetDoubleFromObj(interp, objv[9], &fgG) != TCL_OK ||
977                Tcl_GetDoubleFromObj(interp, objv[10], &fgB) != TCL_OK ||
978                Tcl_GetDoubleFromObj(interp, objv[11], &bgR) != TCL_OK ||
979                Tcl_GetDoubleFromObj(interp, objv[12], &bgG) != TCL_OK ||
980                Tcl_GetDoubleFromObj(interp, objv[13], &bgB) != TCL_OK) {
981                return TCL_ERROR;
982            }
983        }
984#endif
985        opts.url() = url;
986
987        osgEarth::Symbology::Style style;
988        osgEarth::Symbology::TextSymbol *ts = style.getOrCreateSymbol<osgEarth::Symbology::TextSymbol>();
989        ts->halo()->color() = osgEarth::Symbology::Color::Black; //::Color(bgR, bgG, bgB);
990        ts->halo()->width() = 2.0f;
991        ts->fill()->color() = osgEarth::Symbology::Color::White; //::Color(fgR, fgG, fgB);
992        ts->content() = osgEarth::Symbology::StringExpression(content);
993        ts->priority() = osgEarth::Symbology::NumericExpression(priority);
994        ts->removeDuplicateLabels() = true;
995        ts->size() = 16.0f;
996        ts->alignment() = osgEarth::Symbology::TextSymbol::ALIGN_CENTER_CENTER;
997        ts->declutter() = true;
998
999        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
1000        rs->depthOffset()->enabled() = true;
1001        rs->depthOffset()->minBias() = 1000;
1002
1003        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
1004        geomOpts.featureOptions() = opts;
1005        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
1006        geomOpts.styles()->addStyle(style);
1007        geomOpts.enableLighting() = false;
1008        g_renderer->addModelLayer(name, geomOpts);
1009    } else {
1010        Tcl_AppendResult(interp, "unknown map layer type \"", type,
1011                         "\": should be 'image', 'elevation' or 'model'", (char*)NULL);
1012        return TCL_ERROR;
1013    }
1014    return TCL_OK;
1015}
1016
1017static int
1018MapLayerDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
1019                 Tcl_Obj *const *objv)
1020{
1021    if (objc > 3) {
1022        char *name = Tcl_GetString(objv[3]);
1023        g_renderer->removeImageLayer(name);
1024        g_renderer->removeElevationLayer(name);
1025        g_renderer->removeModelLayer(name);
1026    } else {
1027        g_renderer->clearMap();
1028    }
1029
1030    return TCL_OK;
1031}
1032
1033static int
1034MapLayerMoveOp(ClientData clientData, Tcl_Interp *interp, int objc,
1035               Tcl_Obj *const *objv)
1036{
1037    int pos;
1038    if (Tcl_GetIntFromObj(interp, objv[3], &pos) != TCL_OK) {
1039        return TCL_ERROR;
1040    }
1041    char *name = Tcl_GetString(objv[4]);
1042    if (pos < 0) {
1043        Tcl_AppendResult(interp, "bad layer pos ", pos,
1044                         ": must be positive", (char*)NULL);
1045        return TCL_ERROR;
1046    }
1047    g_renderer->moveImageLayer(name, (unsigned int)pos);
1048    g_renderer->moveElevationLayer(name, (unsigned int)pos);
1049    g_renderer->moveModelLayer(name, (unsigned int)pos);
1050
1051    return TCL_OK;
1052}
1053
1054static int
1055MapLayerOpacityOp(ClientData clientData, Tcl_Interp *interp, int objc,
1056                  Tcl_Obj *const *objv)
1057{
1058    double opacity;
1059    if (Tcl_GetDoubleFromObj(interp, objv[3], &opacity) != TCL_OK) {
1060        return TCL_ERROR;
1061    }
1062    char *name = Tcl_GetString(objv[4]);
1063    if (opacity < 0.0 || opacity > 1.0) {
1064        Tcl_AppendResult(interp, "bad layer opacity ", opacity,
1065                         ": must be [0,1]", (char*)NULL);
1066        return TCL_ERROR;
1067    }
1068    g_renderer->setImageLayerOpacity(name, opacity);
1069    g_renderer->setModelLayerOpacity(name, opacity);
1070
1071    return TCL_OK;
1072}
1073
1074static int
1075MapLayerNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
1076                Tcl_Obj *const *objv)
1077{
1078    std::vector<std::string> layers;
1079    if (objc < 4) {
1080        g_renderer->getImageLayerNames(layers);
1081        g_renderer->getElevationLayerNames(layers);
1082        g_renderer->getModelLayerNames(layers);
1083    } else {
1084        char *type = Tcl_GetString(objv[3]);
1085        if (type[0] == 'i' && strcmp(type, "image") == 0) {
1086            g_renderer->getImageLayerNames(layers);
1087        } else if (type[0] == 'e' && strcmp(type, "elevation") == 0) {
1088            g_renderer->getElevationLayerNames(layers);
1089        } else if (type[0] == 'm' && strcmp(type, "model") == 0) {
1090            g_renderer->getModelLayerNames(layers);
1091        } else {
1092            Tcl_AppendResult(interp, "uknown type \"", type,
1093                         "\": must be image, elevation or model", (char*)NULL);
1094            return TCL_ERROR;
1095        }
1096    }
1097    std::ostringstream oss;
1098    size_t len = 0;
1099    oss << "nv>map names {";
1100    len += 18;
1101    for (size_t i = 0; i < layers.size(); i++) {
1102        oss << "\"" << layers[i] << "\"";
1103        len += 2 + layers[i].length();
1104        if (i < layers.size() - 1) {
1105            oss << " ";
1106            len++;
1107        }
1108    }
1109    oss << "}\n";
1110    len += 2;
1111#ifdef USE_THREADS
1112    queueResponse(oss.str().c_str(), len, Response::VOLATILE);
1113#else
1114    ssize_t bytesWritten = SocketWrite(oss.str().c_str(), len);
1115
1116    if (bytesWritten < 0) {
1117        return TCL_ERROR;
1118    }
1119#endif /*USE_THREADS*/
1120    return TCL_OK;
1121}
1122
1123static int
1124MapLayerVisibleOp(ClientData clientData, Tcl_Interp *interp, int objc,
1125                  Tcl_Obj *const *objv)
1126{
1127    bool visible;
1128    if (GetBooleanFromObj(interp, objv[3], &visible) != TCL_OK) {
1129        return TCL_ERROR;
1130    }
1131    char *name = Tcl_GetString(objv[4]);
1132
1133    g_renderer->setImageLayerVisibility(name, visible);
1134    g_renderer->setElevationLayerVisibility(name, visible);
1135    g_renderer->setModelLayerVisibility(name, visible);
1136
1137    return TCL_OK;
1138}
1139
1140static CmdSpec mapLayerOps[] = {
1141    {"add",     1, MapLayerAddOp,       6, 0, "type driver ?url? ?args? name"},
1142    {"delete",  1, MapLayerDeleteOp,    3, 4, "?name?"},
1143    {"move",    1, MapLayerMoveOp,      5, 5, "pos name"},
1144    {"names",   1, MapLayerNamesOp,     3, 4, "?type?"},
1145    {"opacity", 1, MapLayerOpacityOp,   5, 5, "opacity ?name?"},
1146    {"visible", 1, MapLayerVisibleOp,   5, 5, "bool ?name?"},
1147};
1148static int nMapLayerOps = NumCmdSpecs(mapLayerOps);
1149
1150static int
1151MapLayerOp(ClientData clientData, Tcl_Interp *interp, int objc,
1152           Tcl_Obj *const *objv)
1153{
1154    Tcl_ObjCmdProc *proc;
1155
1156    proc = GetOpFromObj(interp, nMapLayerOps, mapLayerOps,
1157                        CMDSPEC_ARG2, objc, objv, 0);
1158    if (proc == NULL) {
1159        return TCL_ERROR;
1160    }
1161    return (*proc) (clientData, interp, objc, objv);
1162}
1163
1164static int
1165MapLoadOp(ClientData clientData, Tcl_Interp *interp, int objc,
1166          Tcl_Obj *const *objv)
1167{
1168    char *opt = Tcl_GetString(objv[2]);
1169    if (opt[0] == 'f' && strcmp(opt, "file") == 0) {
1170        g_renderer->loadEarthFile(Tcl_GetString(objv[3]));
1171    } else if (opt[0] == 'u' && strcmp(opt, "url") == 0) {
1172        std::ostringstream path;
1173        path << "server:" << Tcl_GetString(objv[3]);
1174        g_renderer->loadEarthFile(path.str().c_str());
1175    } else if (opt[0] == 'd' && strcmp(opt, "data") == 0) {
1176        opt = Tcl_GetString(objv[3]);
1177        if (opt[0] != 'f' || strcmp(opt, "follows") != 0) {
1178            return TCL_ERROR;
1179        }
1180        int len;
1181        if (Tcl_GetIntFromObj(interp, objv[4], &len) != TCL_OK) {
1182            return TCL_ERROR;
1183        }
1184        // Read Earth file from socket
1185        char *buf = (char *)malloc((size_t)len);
1186        SocketRead(buf, (size_t)len);
1187        std::ostringstream path;
1188        path << "/tmp/tmp" << getpid() << ".earth";
1189        FILE *tmpFile = fopen(path.str().c_str(), "w");
1190        fwrite(buf, len, 1, tmpFile);
1191        fclose(tmpFile);
1192        g_renderer->loadEarthFile(path.str().c_str());
1193        unlink(path.str().c_str());
1194        free(buf);
1195    } else {
1196        return TCL_ERROR;
1197    }
1198    return TCL_OK;
1199}
1200
1201static int
1202MapPositionDisplayOp(ClientData clientData, Tcl_Interp *interp, int objc,
1203                     Tcl_Obj *const *objv)
1204{
1205    bool state;
1206    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
1207        return TCL_ERROR;
1208    }
1209    Renderer::CoordinateDisplayType type = Renderer::COORDS_LATLONG_DECIMAL_DEGREES;
1210    if (state && objc > 3) {
1211        const char *str = Tcl_GetString(objv[3]);
1212        if (str[0] == 'l' && strcmp(str, "latlong_decimal_degrees") == 0) {
1213            type = Renderer::COORDS_LATLONG_DECIMAL_DEGREES;
1214        } else if (str[0] == 'l' && strcmp(str, "latlong_degrees_decimal_minutes") == 0) {
1215            type = Renderer::COORDS_LATLONG_DEGREES_DECIMAL_MINUTES;
1216        } else if (str[0] == 'l' && strcmp(str, "latlong_degrees_minutes_seconds") == 0) {
1217            type = Renderer::COORDS_LATLONG_DEGREES_MINUTES_SECONDS;
1218        } else if (str[0] == 'm' && strcmp(str, "mgrs") == 0) {
1219            type = Renderer::COORDS_MGRS;
1220        } else {
1221            Tcl_AppendResult(interp, "invalid type: \"", str,
1222                             "\": should be 'latlong_decimal_degrees', 'latlong_degrees_decimal_minutes', 'latlong_degrees_minutes_seconds', or 'mgrs'",
1223                             (char*)NULL);
1224            return TCL_ERROR;
1225        }
1226    }
1227    if (state && objc > 4) {
1228        int precision;
1229        if (Tcl_GetIntFromObj(interp, objv[4], &precision) != TCL_OK) {
1230            return TCL_ERROR;
1231        }
1232        g_renderer->setCoordinateReadout(state, type, precision);
1233    } else {
1234        g_renderer->setCoordinateReadout(state, type);
1235    }
1236
1237    return TCL_OK;
1238}
1239
1240static int
1241MapResetOp(ClientData clientData, Tcl_Interp *interp, int objc,
1242           Tcl_Obj *const *objv)
1243{
1244    char *typeStr = Tcl_GetString(objv[2]);
1245    osgEarth::MapOptions::CoordinateSystemType type;
1246    if (typeStr[0] == 'g' && strcmp(typeStr, "geocentric") == 0) {
1247        type = osgEarth::MapOptions::CSTYPE_GEOCENTRIC;
1248    } else if (typeStr[0] == 'g' && strcmp(typeStr, "geocentric_cube") == 0) {
1249        type = osgEarth::MapOptions::CSTYPE_GEOCENTRIC_CUBE;
1250    } else if (typeStr[0] == 'p' && strcmp(typeStr, "projected") == 0) {
1251        type = osgEarth::MapOptions::CSTYPE_PROJECTED;
1252    } else {
1253        Tcl_AppendResult(interp, "bad map type \"", typeStr,
1254                         "\": must be geocentric or projected", (char*)NULL);
1255        return TCL_ERROR;
1256    }
1257
1258    if (type == osgEarth::MapOptions::CSTYPE_PROJECTED) {
1259        if (objc < 4) {
1260            Tcl_AppendResult(interp, "wrong # arguments: profile required for projected maps", (char*)NULL);
1261            return TCL_ERROR;
1262        }
1263        char *profile = Tcl_GetString(objv[3]);
1264        if (objc > 4) {
1265            if (objc < 8) {
1266                Tcl_AppendResult(interp, "wrong # arguments: 4 bounds arguments required", (char*)NULL);
1267                return TCL_ERROR;
1268            }
1269            double bounds[4];
1270            for (int i = 0; i < 4; i++) {
1271                if (Tcl_GetDoubleFromObj(interp, objv[4+i], &bounds[i]) != TCL_OK) {
1272                    return TCL_ERROR;
1273                }
1274            }
1275            // Check if min > max
1276            if (bounds[0] > bounds[2] ||
1277                bounds[1] > bounds[3]) {
1278                Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
1279                return TCL_ERROR;
1280            }
1281            if (strcmp(profile, "geodetic") == 0 ||
1282                strcmp(profile, "epsg:4326") == 0 ) {
1283                if (bounds[0] < -180. || bounds[0] > 180. ||
1284                    bounds[2] < -180. || bounds[2] > 180. ||
1285                    bounds[1] < -90. || bounds[1] > 90. ||
1286                    bounds[3] < -90. || bounds[3] > 90.) {
1287                    Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
1288                    return TCL_ERROR;
1289                }
1290            } else if (strcmp(profile, "spherical-mercator") == 0 ||
1291                       strcmp(profile, "epsg:900913") == 0 ||
1292                       strcmp(profile, "epsg:3857") == 0) {
1293                for (int i = 0; i < 4; i++) {
1294                    if (bounds[i] < -20037508.34 || bounds[i] > 20037508.34) {
1295                        Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
1296                        return TCL_ERROR;
1297                    }
1298                }
1299            }
1300
1301            g_renderer->resetMap(type, profile, bounds);
1302        } else {
1303            if (osgEarth::Registry::instance()->getNamedProfile(profile) == NULL) {
1304                Tcl_AppendResult(interp, "bad named profile \"", profile,
1305                                 "\": must be e.g. 'global-geodetic', 'global-mercator'...", (char*)NULL);
1306                return TCL_ERROR;
1307            }
1308            g_renderer->resetMap(type, profile);
1309        }
1310    } else {
1311        // No profile required for geocentric (3D) maps
1312        g_renderer->resetMap(type);
1313    }
1314
1315    return TCL_OK;
1316}
1317
1318static int
1319MapScaleBarOp(ClientData clientData, Tcl_Interp *interp, int objc,
1320              Tcl_Obj *const *objv)
1321{
1322    bool state;
1323    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
1324        return TCL_ERROR;
1325    }
1326    g_renderer->setScaleBar(state);
1327    if (state && objc > 3) {
1328        const char *unitStr = Tcl_GetString(objv[3]);
1329        ScaleBarUnits units;
1330        if (unitStr[0] == 'm' && strcmp(unitStr, "meters") == 0) {
1331            units = UNITS_METERS;
1332        } else if (unitStr[0] == 'f' && strcmp(unitStr, "feet") == 0) {
1333            units = UNITS_INTL_FEET;
1334        } else if (unitStr[0] == 'u' && strcmp(unitStr, "us_survey_feet") == 0) {
1335            units = UNITS_US_SURVEY_FEET;
1336        } else if (unitStr[0] == 'n' && strcmp(unitStr, "nautical_miles") == 0) {
1337            units = UNITS_NAUTICAL_MILES;
1338        } else {
1339            Tcl_AppendResult(interp, "bad units \"", unitStr,
1340                             "\": must be 'meters', 'feet', 'us_survey_feet' or 'nautical_miles'", (char*)NULL);
1341            return TCL_ERROR;
1342        }
1343        g_renderer->setScaleBarUnits(units);
1344    }
1345    return TCL_OK;
1346}
1347
1348static int
1349MapSetPositionOp(ClientData clientData, Tcl_Interp *interp, int objc,
1350            Tcl_Obj *const *objv)
1351{
1352    if (objc < 3) {
1353        g_renderer->clearReadout();
1354    } else {
1355        int x, y;
1356        if (Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK ||
1357            Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK) {
1358            return TCL_ERROR;
1359        }
1360        g_renderer->setReadout(x, y);
1361    }
1362    return TCL_OK;
1363}
1364
1365static int
1366MapTerrainEdgesOp(ClientData clientData, Tcl_Interp *interp, int objc,
1367                  Tcl_Obj *const *objv)
1368{
1369    bool state;
1370    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
1371        return TCL_ERROR;
1372    }
1373    TRACE("Not implemented");
1374    //g_renderer->setTerrainEdges(state);
1375    return TCL_OK;
1376}
1377
1378static int
1379MapTerrainLightingOp(ClientData clientData, Tcl_Interp *interp, int objc,
1380                     Tcl_Obj *const *objv)
1381{
1382    bool state;
1383    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
1384        return TCL_ERROR;
1385    }
1386
1387    g_renderer->setTerrainLighting(state);
1388    return TCL_OK;
1389}
1390
1391static int
1392MapTerrainLineColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
1393                      Tcl_Obj *const *objv)
1394{
1395    float color[3];
1396    if (GetFloatFromObj(interp, objv[3], &color[0]) != TCL_OK ||
1397        GetFloatFromObj(interp, objv[4], &color[1]) != TCL_OK ||
1398        GetFloatFromObj(interp, objv[5], &color[2]) != TCL_OK) {
1399        return TCL_ERROR;
1400    }
1401    TRACE("Not implemented");
1402    //g_renderer->setTerrainLineColor(color);
1403    return TCL_OK;
1404}
1405
1406static int
1407MapTerrainVertScaleOp(ClientData clientData, Tcl_Interp *interp, int objc,
1408                      Tcl_Obj *const *objv)
1409{
1410    double scale;
1411    if (Tcl_GetDoubleFromObj(interp, objv[3], &scale) != TCL_OK) {
1412        return TCL_ERROR;
1413    }
1414
1415    g_renderer->setTerrainVerticalScale(scale);
1416    return TCL_OK;
1417}
1418
1419static int
1420MapTerrainWireframeOp(ClientData clientData, Tcl_Interp *interp, int objc,
1421                      Tcl_Obj *const *objv)
1422{
1423    bool state;
1424    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
1425        return TCL_ERROR;
1426    }
1427
1428    g_renderer->setTerrainWireframe(state);
1429    return TCL_OK;
1430}
1431
1432static CmdSpec mapTerrainOps[] = {
1433    {"edges",     1, MapTerrainEdgesOp,     4, 4, "bool"},
1434    {"lighting",  2, MapTerrainLightingOp,  4, 4, "bool"},
1435    {"linecolor", 2, MapTerrainLineColorOp, 6, 6, "r g b"},
1436    {"vertscale", 1, MapTerrainVertScaleOp, 4, 4, "val"},
1437    {"wireframe", 1, MapTerrainWireframeOp, 4, 4, "bool"},
1438};
1439static int nMapTerrainOps = NumCmdSpecs(mapTerrainOps);
1440
1441static int
1442MapTerrainOp(ClientData clientData, Tcl_Interp *interp, int objc,
1443           Tcl_Obj *const *objv)
1444{
1445    Tcl_ObjCmdProc *proc;
1446
1447    proc = GetOpFromObj(interp, nMapTerrainOps, mapTerrainOps,
1448                        CMDSPEC_ARG2, objc, objv, 0);
1449    if (proc == NULL) {
1450        return TCL_ERROR;
1451    }
1452    return (*proc) (clientData, interp, objc, objv);
1453}
1454
1455static CmdSpec mapOps[] = {
1456    {"coords",   1, MapCoordsOp,          4, 4, "x y"},
1457    {"grid",     1, MapGraticuleOp,       3, 4, "bool ?type?"},
1458    {"layer",    2, MapLayerOp,           3, 0, "op ?params...?"},
1459    {"load",     2, MapLoadOp,            4, 5, "options"},
1460    {"posdisp",  1, MapPositionDisplayOp, 3, 5, "bool ?format? ?precision?"},
1461    {"reset",    1, MapResetOp,           3, 8, "type ?profile xmin ymin xmax ymax?"},
1462    {"scalebar", 1, MapScaleBarOp,        3, 4, "bool ?units?"},
1463    {"setpos",   1, MapSetPositionOp,     2, 4, "x y"},
1464    {"terrain",  1, MapTerrainOp,         3, 0, "op ?params...?"},
1465};
1466static int nMapOps = NumCmdSpecs(mapOps);
1467
1468static int
1469MapCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1470       Tcl_Obj *const *objv)
1471{
1472    Tcl_ObjCmdProc *proc;
1473
1474    proc = GetOpFromObj(interp, nMapOps, mapOps,
1475                        CMDSPEC_ARG1, objc, objv, 0);
1476    if (proc == NULL) {
1477        return TCL_ERROR;
1478    }
1479    return (*proc) (clientData, interp, objc, objv);
1480}
1481
1482static int
1483MouseClickOp(ClientData clientData, Tcl_Interp *interp, int objc,
1484             Tcl_Obj *const *objv)
1485{
1486    int button;
1487    double x, y;
1488
1489    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
1490        return TCL_ERROR;
1491    }
1492    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
1493        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
1494        return TCL_ERROR;
1495    }
1496
1497    g_renderer->mouseClick(button, x, y);
1498    return TCL_OK;
1499}
1500
1501static int
1502MouseDoubleClickOp(ClientData clientData, Tcl_Interp *interp, int objc,
1503                   Tcl_Obj *const *objv)
1504{
1505    int button;
1506    double x, y;
1507
1508    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
1509        return TCL_ERROR;
1510    }
1511    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
1512        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
1513        return TCL_ERROR;
1514    }
1515
1516    g_renderer->mouseDoubleClick(button, x, y);
1517    return TCL_OK;
1518}
1519
1520static int
1521MouseDragOp(ClientData clientData, Tcl_Interp *interp, int objc,
1522            Tcl_Obj *const *objv)
1523{
1524    int button;
1525    double x, y;
1526
1527    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
1528        return TCL_ERROR;
1529    }
1530    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
1531        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
1532        return TCL_ERROR;
1533    }
1534
1535    g_renderer->mouseDrag(button, x, y);
1536    return TCL_OK;
1537}
1538
1539static int
1540MouseMotionOp(ClientData clientData, Tcl_Interp *interp, int objc,
1541              Tcl_Obj *const *objv)
1542{
1543    double x, y;
1544
1545    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
1546        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
1547        return TCL_ERROR;
1548    }
1549
1550    g_renderer->mouseMotion(x, y);
1551    return TCL_OK;
1552}
1553
1554static int
1555MouseReleaseOp(ClientData clientData, Tcl_Interp *interp, int objc,
1556               Tcl_Obj *const *objv)
1557{
1558    int button;
1559    double x, y;
1560
1561    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
1562        return TCL_ERROR;
1563    }
1564    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
1565        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
1566        return TCL_ERROR;
1567    }
1568
1569    g_renderer->mouseRelease(button, x, y);
1570    return TCL_OK;
1571}
1572
1573static int
1574MouseScrollOp(ClientData clientData, Tcl_Interp *interp, int objc,
1575              Tcl_Obj *const *objv)
1576{
1577    int direction;
1578
1579    if (Tcl_GetIntFromObj(interp, objv[2], &direction) != TCL_OK) {
1580        return TCL_ERROR;
1581    }
1582
1583    g_renderer->mouseScroll(direction);
1584    return TCL_OK;
1585}
1586
1587static CmdSpec mouseOps[] = {
1588    {"click",    1, MouseClickOp,       5, 5, "button x y"},
1589    {"dblclick", 2, MouseDoubleClickOp, 5, 5, "button x y"},
1590    {"drag",     2, MouseDragOp,        5, 5, "button x y"},
1591    {"motion",   1, MouseMotionOp,      4, 4, "x y"},
1592    {"release",  1, MouseReleaseOp,     5, 5, "button x y"},
1593    {"scroll",   1, MouseScrollOp,      3, 3, "direction"},
1594};
1595static int nMouseOps = NumCmdSpecs(mouseOps);
1596
1597static int
1598MouseCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1599         Tcl_Obj *const *objv)
1600{
1601    Tcl_ObjCmdProc *proc;
1602
1603    proc = GetOpFromObj(interp, nMouseOps, mouseOps,
1604                        CMDSPEC_ARG1, objc, objv, 0);
1605    if (proc == NULL) {
1606        return TCL_ERROR;
1607    }
1608    return (*proc) (clientData, interp, objc, objv);
1609}
1610
1611static int
1612RendererRenderOp(ClientData clientData, Tcl_Interp *interp, int objc,
1613                 Tcl_Obj *const *objv)
1614{
1615    g_renderer->eventuallyRender();
1616    return TCL_OK;
1617}
1618
1619static CmdSpec rendererOps[] = {
1620    {"render",     1, RendererRenderOp, 2, 2, ""},
1621};
1622static int nRendererOps = NumCmdSpecs(rendererOps);
1623
1624static int
1625RendererCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1626            Tcl_Obj *const *objv)
1627{
1628    Tcl_ObjCmdProc *proc;
1629
1630    proc = GetOpFromObj(interp, nRendererOps, rendererOps,
1631                        CMDSPEC_ARG1, objc, objv, 0);
1632    if (proc == NULL) {
1633        return TCL_ERROR;
1634    }
1635    return (*proc) (clientData, interp, objc, objv);
1636}
1637
1638static int
1639ScreenBgColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
1640                Tcl_Obj *const *objv)
1641{
1642    float color[3];
1643
1644    if (GetFloatFromObj(interp, objv[2], &color[0]) != TCL_OK ||
1645        GetFloatFromObj(interp, objv[3], &color[1]) != TCL_OK ||
1646        GetFloatFromObj(interp, objv[4], &color[2]) != TCL_OK) {
1647        return TCL_ERROR;
1648    }
1649
1650    g_renderer->setBackgroundColor(color);
1651    return TCL_OK;
1652}
1653
1654static int
1655ScreenSizeOp(ClientData clientData, Tcl_Interp *interp, int objc,
1656             Tcl_Obj *const *objv)
1657{
1658    int width, height;
1659
1660    if (Tcl_GetIntFromObj(interp, objv[2], &width) != TCL_OK ||
1661        Tcl_GetIntFromObj(interp, objv[3], &height) != TCL_OK) {
1662        return TCL_ERROR;
1663    }
1664
1665    g_renderer->setWindowSize(width, height);
1666    return TCL_OK;
1667}
1668
1669static CmdSpec screenOps[] = {
1670    {"bgcolor", 1, ScreenBgColorOp, 5, 5, "r g b"},
1671    {"size", 1, ScreenSizeOp, 4, 4, "width height"}
1672};
1673static int nScreenOps = NumCmdSpecs(screenOps);
1674
1675static int
1676ScreenCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1677          Tcl_Obj *const *objv)
1678{
1679    Tcl_ObjCmdProc *proc;
1680
1681    proc = GetOpFromObj(interp, nScreenOps, screenOps,
1682                        CMDSPEC_ARG1, objc, objv, 0);
1683    if (proc == NULL) {
1684        return TCL_ERROR;
1685    }
1686    return (*proc) (clientData, interp, objc, objv);
1687}
1688
1689#ifdef USE_READ_THREAD
1690int
1691GeoVis::queueCommands(Tcl_Interp *interp,
1692                      ClientData clientData,
1693                      ReadBuffer *inBufPtr)
1694{
1695    Tcl_DString commandString;
1696    Tcl_DStringInit(&commandString);
1697    fd_set readFds;
1698
1699    FD_ZERO(&readFds);
1700    FD_SET(inBufPtr->file(), &readFds);
1701    while (inBufPtr->isLineAvailable() ||
1702           (select(inBufPtr->file()+1, &readFds, NULL, NULL, NULL) > 0)) {
1703        size_t numBytes;
1704        unsigned char *buffer;
1705
1706        /* A short read is treated as an error here because we assume that we
1707         * will always get commands line by line. */
1708        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
1709            /* Terminate the server if we can't communicate with the client
1710             * anymore. */
1711            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
1712                TRACE("Exiting server on EOF from client");
1713                return -1;
1714            } else {
1715                ERROR("Exiting server, failed to read from client: %s",
1716                      strerror(errno));
1717                return -1;
1718            }
1719        }
1720        Tcl_DStringAppend(&commandString, (char *)buffer, numBytes);
1721        if (Tcl_CommandComplete(Tcl_DStringValue(&commandString))) {
1722            // Add to queue
1723            Command *command = new Command(Command::COMMAND);
1724            command->setMessage((unsigned char *)Tcl_DStringValue(&commandString),
1725                                Tcl_DStringLength(&commandString), Command::VOLATILE);
1726            g_inQueue->enqueue(command);
1727            Tcl_DStringSetLength(&commandString, 0);
1728        }
1729        FD_SET(inBufPtr->file(), &readFds);
1730    }
1731
1732    return 1;
1733}
1734#endif
1735
1736/**
1737 * \brief Execute commands from client in Tcl interpreter
1738 *
1739 * In this threaded model, the select call is for event compression.  We
1740 * want to execute render server commands as long as they keep coming. 
1741 * This lets us execute a stream of many commands but render once.  This
1742 * benefits camera movements, screen resizing, and opacity changes
1743 * (using a slider on the client).  The down side is you don't render
1744 * until there's a lull in the command stream.  If the client needs an
1745 * image, it can issue an "imgflush" command.  That breaks us out of the
1746 * read loop.
1747 */
1748int
1749GeoVis::processCommands(Tcl_Interp *interp,
1750                        ClientData clientData,
1751                        ReadBuffer *inBufPtr,
1752                        int fdOut,
1753                        long timeout)
1754{
1755    int ret = 1;
1756    int status = TCL_OK;
1757
1758    Tcl_DString command;
1759    Tcl_DStringInit(&command);
1760    fd_set readFds;
1761    struct timeval tv, *tvPtr;
1762
1763    FD_ZERO(&readFds);
1764    FD_SET(inBufPtr->file(), &readFds);
1765    tvPtr = NULL;                       /* Wait for the first read. This is so
1766                                         * that we don't spin when no data is
1767                                         * available. */
1768    if (timeout >= 0L) {
1769        tv.tv_sec = 0L;
1770        tv.tv_usec = timeout;
1771        tvPtr = &tv;
1772    } else {
1773        TRACE("Blocking on select()");
1774    }
1775    while (inBufPtr->isLineAvailable() ||
1776           (ret = select(inBufPtr->file()+1, &readFds, NULL, NULL, tvPtr)) > 0) {
1777        size_t numBytes;
1778        unsigned char *buffer;
1779
1780        /* A short read is treated as an error here because we assume that we
1781         * will always get commands line by line. */
1782        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
1783            /* Terminate the server if we can't communicate with the client
1784             * anymore. */
1785            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
1786                TRACE("Exiting server on EOF from client");
1787                return -1;
1788            } else {
1789                ERROR("Exiting server, failed to read from client: %s",
1790                      strerror(errno));
1791                return -1;
1792            }
1793        }
1794        Tcl_DStringAppend(&command, (char *)buffer, numBytes);
1795        if (Tcl_CommandComplete(Tcl_DStringValue(&command))) {
1796            struct timeval start, finish;
1797            gettimeofday(&start, NULL);
1798            status = ExecuteCommand(interp, &command);
1799            gettimeofday(&finish, NULL);
1800            g_stats.cmdTime += (MSECS_ELAPSED(start, finish) / 1.0e+3);
1801            g_stats.nCommands++;
1802            if (status == TCL_BREAK) {
1803                return 2;               /* This was caused by a "imgflush"
1804                                         * command. Break out of the read loop
1805                                         * and allow a new image to be
1806                                         * rendered. */
1807            } else { //if (status != TCL_OK) {
1808                ret = 0;
1809                if (handleError(interp, clientData, status, fdOut) < 0) {
1810                    return -1;
1811                }
1812            }
1813            if (status == TCL_OK) {
1814                ret = 3;
1815            }
1816        }
1817
1818        tv.tv_sec = tv.tv_usec = 0L;    /* On successive reads, we break out
1819                                         * if no data is available. */
1820        FD_SET(inBufPtr->file(), &readFds);
1821        tvPtr = &tv;
1822    }
1823
1824    return ret;
1825}
1826
1827/**
1828 * \brief Send error message to client socket
1829 */
1830int
1831GeoVis::handleError(Tcl_Interp *interp,
1832                    ClientData clientData,
1833                    int status, int fdOut)
1834{
1835    const char *string;
1836    int nBytes;
1837
1838    if (status != TCL_OK) {
1839        string = Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY);
1840        nBytes = strlen(string);
1841        if (nBytes > 0) {
1842            TRACE("status=%d errorInfo=(%s)", status, string);
1843
1844            std::ostringstream oss;
1845            oss << "nv>viserror -type internal_error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
1846            nBytes = oss.str().length();
1847
1848            if (queueResponse(oss.str().c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
1849                return -1;
1850            }
1851        }
1852    }
1853
1854    std::string msg = getUserMessages();
1855    nBytes = msg.length();
1856    if (nBytes > 0) {
1857        string = msg.c_str();
1858        TRACE("userError=(%s)", string);
1859
1860        std::ostringstream oss;
1861        oss << "nv>viserror -type error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
1862        std::string ostr = oss.str();
1863        nBytes = ostr.length();
1864
1865        if (queueResponse(ostr.c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
1866            return -1;
1867        }
1868
1869        clearUserMessages();
1870    }
1871
1872    return 0;
1873}
1874
1875/**
1876 * \brief Create Tcl interpreter and add commands
1877 *
1878 * \return The initialized Tcl interpreter
1879 */
1880void
1881GeoVis::initTcl(Tcl_Interp *interp, ClientData clientData)
1882{
1883    Tcl_MakeSafe(interp);
1884    Tcl_CreateObjCommand(interp, "camera",         CameraCmd,         clientData, NULL);
1885    Tcl_CreateObjCommand(interp, "clientinfo",     ClientInfoCmd,     clientData, NULL);
1886    Tcl_CreateObjCommand(interp, "colormap",       ColorMapCmd,       clientData, NULL);
1887    Tcl_CreateObjCommand(interp, "imgflush",       ImageFlushCmd,     clientData, NULL);
1888    Tcl_CreateObjCommand(interp, "key",            KeyCmd,            clientData, NULL);
1889    Tcl_CreateObjCommand(interp, "map",            MapCmd,            clientData, NULL);
1890    Tcl_CreateObjCommand(interp, "mouse",          MouseCmd,          clientData, NULL);
1891    Tcl_CreateObjCommand(interp, "renderer",       RendererCmd,       clientData, NULL);
1892    Tcl_CreateObjCommand(interp, "screen",         ScreenCmd,         clientData, NULL);
1893}
1894
1895/**
1896 * \brief Delete Tcl commands and interpreter
1897 */
1898void GeoVis::exitTcl(Tcl_Interp *interp)
1899{
1900    Tcl_DeleteCommand(interp, "camera");
1901    Tcl_DeleteCommand(interp, "clientinfo");
1902    Tcl_DeleteCommand(interp, "colormap");
1903    Tcl_DeleteCommand(interp, "imgflush");
1904    Tcl_DeleteCommand(interp, "key");
1905    Tcl_DeleteCommand(interp, "map");
1906    Tcl_DeleteCommand(interp, "mouse");
1907    Tcl_DeleteCommand(interp, "renderer");
1908    Tcl_DeleteCommand(interp, "screen");
1909
1910    Tcl_DeleteInterp(interp);
1911}
Note: See TracBrowser for help on using the repository browser.