source: geovis/trunk/RendererCmd.cpp @ 5105

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

geovis updates for NSF demo

File size: 81.9 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 <limits>
16#include <vector>
17#include <unistd.h>
18#include <sys/select.h>
19#include <sys/uio.h>
20#include <tcl.h>
21
22#include <osgDB/FileUtils>
23#include <osgDB/FileNameUtils>
24
25#include <osgEarth/Version>
26#include <osgEarth/Registry>
27#include <osgEarthFeatures/FeatureModelSource>
28#include <osgEarthSymbology/Color>
29#include <osgEarthSymbology/Style>
30#include <osgEarthSymbology/StyleSheet>
31#include <osgEarthSymbology/IconSymbol>
32#include <osgEarthSymbology/LineSymbol>
33#include <osgEarthSymbology/RenderSymbol>
34
35#if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 1)
36#include <osgEarthDrivers/osg/ColorRampOptions>
37#endif
38#include <osgEarthDrivers/debug/DebugOptions>
39#include <osgEarthDrivers/gdal/GDALOptions>
40#include <osgEarthDrivers/tms/TMSOptions>
41#include <osgEarthDrivers/wms/WMSOptions>
42#include <osgEarthDrivers/xyz/XYZOptions>
43#include <osgEarthDrivers/model_feature_geom/FeatureGeomModelOptions>
44#include <osgEarthDrivers/feature_ogr/OGRFeatureOptions>
45
46#include "Trace.h"
47#include "CmdProc.h"
48#include "ReadBuffer.h"
49#include "Types.h"
50#include "RendererCmd.h"
51#include "RenderServer.h"
52#include "Renderer.h"
53#include "Stats.h"
54#include "PPMWriter.h"
55#include "TGAWriter.h"
56#include "ResponseQueue.h"
57#ifdef USE_READ_THREAD
58#include "CommandQueue.h"
59#endif
60
61using namespace GeoVis;
62
63static int lastCmdStatus;
64
65#ifndef USE_THREADS
66static ssize_t
67SocketWrite(const void *bytes, size_t len)
68{
69    size_t ofs = 0;
70    ssize_t bytesWritten;
71    while ((bytesWritten = write(g_fdOut, (const char *)bytes + ofs, len - ofs)) > 0) {
72        ofs += bytesWritten;
73        if (ofs == len)
74            break;
75    }
76    if (bytesWritten < 0) {
77        ERROR("write: %s", strerror(errno));
78    }
79    return bytesWritten;
80}
81#endif
82
83static bool
84SocketRead(char *bytes, size_t len)
85{
86    ReadBuffer::BufferStatus status;
87    status = g_inBufPtr->followingData((unsigned char *)bytes, len);
88    TRACE("followingData status: %d", status);
89    return (status == ReadBuffer::OK);
90}
91
92ssize_t
93GeoVis::queueResponse(const void *bytes, size_t len,
94                      Response::AllocationType allocType,
95                      Response::ResponseType type)
96{
97#ifdef USE_THREADS
98    Response *response = new Response(type);
99    response->setMessage((unsigned char *)bytes, len, allocType);
100    g_outQueue->enqueue(response);
101    return (ssize_t)len;
102#else
103    return SocketWrite(bytes, len);
104#endif
105}
106
107static int
108ExecuteCommand(Tcl_Interp *interp, Tcl_DString *dsPtr)
109{
110    int result;
111#ifdef WANT_TRACE
112    char *str = Tcl_DStringValue(dsPtr);
113    std::string cmd(str);
114    cmd.erase(cmd.find_last_not_of(" \n\r\t")+1);
115    TRACE("command %lu: '%s'", g_stats.nCommands, cmd.c_str());
116#endif
117    lastCmdStatus = TCL_OK;
118    result = Tcl_EvalEx(interp, Tcl_DStringValue(dsPtr),
119                        Tcl_DStringLength(dsPtr),
120                        TCL_EVAL_DIRECT | TCL_EVAL_GLOBAL);
121    Tcl_DStringSetLength(dsPtr, 0);
122    if (lastCmdStatus == TCL_BREAK) {
123        return TCL_BREAK;
124    }
125    lastCmdStatus = result;
126    if (result != TCL_OK) {
127        TRACE("Error: %d", result);
128    }
129    return result;
130}
131
132static int
133GetBooleanFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, bool *boolPtr)
134{
135    int value;
136
137    if (Tcl_GetBooleanFromObj(interp, objPtr, &value) != TCL_OK) {
138        return TCL_ERROR;
139    }
140    *boolPtr = (bool)value;
141    return TCL_OK;
142}
143
144static int
145GetFloatFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, float *valuePtr)
146{
147    double value;
148
149    if (Tcl_GetDoubleFromObj(interp, objPtr, &value) != TCL_OK) {
150        return TCL_ERROR;
151    }
152    *valuePtr = (float)value;
153    return TCL_OK;
154}
155
156static int
157CameraDeleteViewpointOp(ClientData clientData, Tcl_Interp *interp, int objc,
158                        Tcl_Obj *const *objv)
159{
160    char *name = Tcl_GetString(objv[2]);
161
162    g_renderer->removeNamedViewpoint(name);
163    return TCL_OK;
164}
165
166static int
167CameraGetViewpointOp(ClientData clientData, Tcl_Interp *interp, int objc,
168                     Tcl_Obj *const *objv)
169{
170    osgEarth::Viewpoint view = g_renderer->getViewpoint();
171
172    std::ostringstream oss;
173    size_t len = 0;
174    oss << "nv>camera get "
175        << view.x() << " "
176        << view.y() << " "
177        << view.z() << " "
178        << view.getHeading() << " "
179        << view.getPitch() << " "
180        << view.getRange()
181        << " {" << ((view.getSRS() == NULL) ? "" : view.getSRS()->getHorizInitString()) << "}"
182        << " {" << ((view.getSRS() == NULL) ? "" : view.getSRS()->getVertInitString()) << "}"
183        << "\n";
184    std::string ostr = oss.str();
185    len = ostr.size();
186#ifdef USE_THREADS
187    queueResponse(ostr.c_str(), len, Response::VOLATILE);
188#else
189    ssize_t bytesWritten = SocketWrite(ostr.c_str(), len);
190
191    if (bytesWritten < 0) {
192        return TCL_ERROR;
193    }
194#endif /*USE_THREADS*/
195    return TCL_OK;
196}
197
198static int
199CameraGoOp(ClientData clientData, Tcl_Interp *interp, int objc,
200           Tcl_Obj *const *objv)
201{
202    int x, y;
203    if (Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK ||
204        Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK) {
205        return TCL_ERROR;
206    }
207    double zoom = 1.0;
208    if (objc > 4) {
209        if (Tcl_GetDoubleFromObj(interp, objv[4], &zoom) != TCL_OK) {
210            return TCL_ERROR;
211        }
212    }
213    double duration = 1.0;
214    if (objc > 5) {
215        if (Tcl_GetDoubleFromObj(interp, objv[5], &duration) != TCL_OK) {
216            return TCL_ERROR;
217        }
218    }
219
220    osgEarth::GeoPoint mapPoint;
221    if (g_renderer->mapMouseCoords(x, y, mapPoint)) {
222        osgEarth::Viewpoint vpt = g_renderer->getViewpoint();
223        vpt.x() = mapPoint.x();
224        vpt.y() = mapPoint.y();
225        vpt.z() = mapPoint.z();
226        vpt.setRange(vpt.getRange() * zoom);
227        g_renderer->setViewpoint(vpt, duration);
228    } else {
229        // Out of map bounds
230    }
231    return TCL_OK;
232}
233
234static int
235CameraOrientOp(ClientData clientData, Tcl_Interp *interp, int objc,
236               Tcl_Obj *const *objv)
237{
238    double quat[4];
239
240    if (Tcl_GetDoubleFromObj(interp, objv[2], &quat[0]) != TCL_OK ||
241        Tcl_GetDoubleFromObj(interp, objv[3], &quat[1]) != TCL_OK ||
242        Tcl_GetDoubleFromObj(interp, objv[4], &quat[2]) != TCL_OK ||
243        Tcl_GetDoubleFromObj(interp, objv[5], &quat[3]) != TCL_OK) {
244        return TCL_ERROR;
245    }
246
247    g_renderer->setCameraOrientation(quat);
248    return TCL_OK;
249}
250
251static int
252CameraPanOp(ClientData clientData, Tcl_Interp *interp, int objc,
253            Tcl_Obj *const *objv)
254{
255    double x, y;
256
257    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
258        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
259        return TCL_ERROR;
260    }
261
262    g_renderer->panCamera(x, y);
263    return TCL_OK;
264}
265
266static int
267CameraResetOp(ClientData clientData, Tcl_Interp *interp, int objc,
268              Tcl_Obj *const *objv)
269{
270    if (objc == 3) {
271        const char *string = Tcl_GetString(objv[2]);
272        char c = string[0];
273        if ((c != 'a') || (strcmp(string, "all") != 0)) {
274            Tcl_AppendResult(interp, "bad camera reset option \"", string,
275                         "\": should be all", (char*)NULL);
276            return TCL_ERROR;
277        }
278        g_renderer->resetCamera(true);
279    } else {
280        g_renderer->resetCamera(false);
281    }
282    return TCL_OK;
283}
284
285static int
286CameraRestoreViewpointOp(ClientData clientData, Tcl_Interp *interp, int objc,
287                         Tcl_Obj *const *objv)
288{
289    char *name = Tcl_GetString(objv[2]);
290
291    double duration = 0.0;
292    if (objc > 3) {
293        if (Tcl_GetDoubleFromObj(interp, objv[3], &duration) != TCL_OK) {
294            return TCL_ERROR;
295        }
296    }
297    if (!g_renderer->restoreNamedViewpoint(name, duration)) {
298        Tcl_AppendResult(interp, "camera viewpoint \"", name,
299                         "\" not found", (char*)NULL);
300        return TCL_ERROR;
301    }
302    return TCL_OK;
303}
304
305static int
306CameraRotateOp(ClientData clientData, Tcl_Interp *interp, int objc,
307               Tcl_Obj *const *objv)
308{
309    double x, y;
310
311    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
312        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
313        return TCL_ERROR;
314    }
315
316    g_renderer->rotateCamera(x, y);
317    return TCL_OK;
318}
319
320static int
321CameraSaveViewpointOp(ClientData clientData, Tcl_Interp *interp, int objc,
322                      Tcl_Obj *const *objv)
323{
324    char *name = Tcl_GetString(objv[2]);
325
326    g_renderer->saveNamedViewpoint(name);
327    return TCL_OK;
328}
329
330static int
331CameraSetDistanceOp(ClientData clientData, Tcl_Interp *interp, int objc,
332                    Tcl_Obj *const *objv)
333{
334    double dist;
335
336    if (Tcl_GetDoubleFromObj(interp, objv[2], &dist) != TCL_OK) {
337        return TCL_ERROR;
338    }
339
340    g_renderer->setCameraDistance(dist);
341    return TCL_OK;
342}
343
344static int
345CameraSetViewpointOp(ClientData clientData, Tcl_Interp *interp, int objc,
346                     Tcl_Obj *const *objv)
347{
348    double x, y, z, heading, pitch, distance;
349    double duration = 0.0;
350    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
351        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK ||
352        Tcl_GetDoubleFromObj(interp, objv[4], &z) != TCL_OK ||
353        Tcl_GetDoubleFromObj(interp, objv[5], &heading) != TCL_OK ||
354        Tcl_GetDoubleFromObj(interp, objv[6], &pitch) != TCL_OK ||
355        Tcl_GetDoubleFromObj(interp, objv[7], &distance) != TCL_OK) {
356        return TCL_ERROR;
357    }
358    if (objc > 8) {
359        if (Tcl_GetDoubleFromObj(interp, objv[8], &duration) != TCL_OK) {
360            return TCL_ERROR;
361        }
362    }
363    if (objc > 9) {
364        char *srsInit = Tcl_GetString(objv[9]);
365        if (strlen(srsInit) > 0) {
366            osgEarth::SpatialReference *srs = NULL;
367            if (objc > 10) {
368                char *vertDatum = Tcl_GetString(objv[10]);
369                srs = osgEarth::SpatialReference::get(srsInit, vertDatum);
370            } else {
371                srs = osgEarth::SpatialReference::get(srsInit);
372            }
373            if (srs == NULL) {
374                return TCL_ERROR;
375            }
376            osgEarth::Viewpoint view(x, y, z, heading, pitch, distance, srs);
377            g_renderer->setViewpoint(view, duration);
378        } else {
379            osgEarth::Viewpoint view(x, y, z, heading, pitch, distance);
380            g_renderer->setViewpoint(view, duration);
381        }
382    } else {
383        osgEarth::Viewpoint view(x, y, z, heading, pitch, distance);
384        g_renderer->setViewpoint(view, duration);
385    }
386    return TCL_OK;
387}
388
389static int
390CameraThrowOp(ClientData clientData, Tcl_Interp *interp, int objc,
391              Tcl_Obj *const *objv)
392{
393    bool state;
394
395    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
396        return TCL_ERROR;
397    }
398
399    g_renderer->setThrowingEnabled(state);
400    return TCL_OK;
401}
402
403static int
404CameraZoomOp(ClientData clientData, Tcl_Interp *interp, int objc,
405             Tcl_Obj *const *objv)
406{
407    double z;
408
409    if (Tcl_GetDoubleFromObj(interp, objv[2], &z) != TCL_OK) {
410        return TCL_ERROR;
411    }
412
413    g_renderer->zoomCamera(z);
414    return TCL_OK;
415}
416
417static CmdSpec cameraOps[] = {
418    {"delete",  2, CameraDeleteViewpointOp,  3, 3, "name"},
419    {"dist",    2, CameraSetDistanceOp,      3, 3, "distance"},
420    {"get",     2, CameraGetViewpointOp,     2, 2, ""},
421    {"go",      2, CameraGoOp,               4, 6, "x y ?zoomFactor? ?duration?"},
422    {"orient",  1, CameraOrientOp,           6, 6, "qw qx qy qz"},
423    {"pan",     1, CameraPanOp,              4, 4, "panX panY"},
424    {"reset",   4, CameraResetOp,            2, 3, "?all?"},
425    {"restore", 4, CameraRestoreViewpointOp, 3, 4, "name ?duration?"},
426    {"rotate",  2, CameraRotateOp,           4, 4, "azimuth elevation"},
427    {"save",    2, CameraSaveViewpointOp,    3, 3, "name"},
428    {"set",     2, CameraSetViewpointOp,     8, 11, "x y z heading pitch distance ?duration? ?srs? ?vertDatum?"},
429    {"throw",   1, CameraThrowOp,            3, 3, "bool"},
430    {"zoom",    1, CameraZoomOp,             3, 3, "zoomAmount"}
431};
432static int nCameraOps = NumCmdSpecs(cameraOps);
433
434static int
435CameraCmd(ClientData clientData, Tcl_Interp *interp, int objc,
436          Tcl_Obj *const *objv)
437{
438    Tcl_ObjCmdProc *proc;
439
440    proc = GetOpFromObj(interp, nCameraOps, cameraOps,
441                        CMDSPEC_ARG1, objc, objv, 0);
442    if (proc == NULL) {
443        return TCL_ERROR;
444    }
445    return (*proc) (clientData, interp, objc, objv);
446}
447
448static int
449ClientInfoCmd(ClientData clientData, Tcl_Interp *interp, int objc,
450              Tcl_Obj *const *objv)
451{
452    Tcl_DString ds;
453    Tcl_Obj *objPtr, *listObjPtr, **items;
454    int numItems;
455    char buf[BUFSIZ];
456    const char *string;
457    int length;
458    int result;
459    static bool first = true;
460
461    /* Client arguments. */
462    if (Tcl_ListObjGetElements(interp, objv[1], &numItems, &items) != TCL_OK) {
463        return TCL_ERROR;
464    }
465
466    /* First try to see if we already have an open descriptor */
467    int fd = getStatsFile();
468    if (fd < 0) {
469        /* Use the initial client key value pairs as the parts for generating
470         * a unique file name. */
471        fd = GeoVis::getStatsFile(Tcl_GetString(objv[1]));
472        if (fd < 0) {
473            Tcl_AppendResult(interp, "can't open stats file: ",
474                             Tcl_PosixError(interp), (char *)NULL);
475            return TCL_ERROR;
476        }
477    }
478    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
479    Tcl_IncrRefCount(listObjPtr);
480    if (first) {
481        first = false;
482        objPtr = Tcl_NewStringObj("render_start", 12);
483        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
484        /* renderer */
485        objPtr = Tcl_NewStringObj("renderer", 8);
486        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
487        objPtr = Tcl_NewStringObj("geovis", 6);
488        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
489        /* pid */
490        objPtr = Tcl_NewStringObj("pid", 3);
491        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
492        Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewIntObj(getpid()));
493        /* host */
494        objPtr = Tcl_NewStringObj("host", 4);
495        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
496        gethostname(buf, BUFSIZ-1);
497        buf[BUFSIZ-1] = '\0';
498        objPtr = Tcl_NewStringObj(buf, -1);
499        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
500    } else {
501        objPtr = Tcl_NewStringObj("render_info", 11);
502        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
503    }
504    /* date */
505    Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("date", 4));
506    strcpy(buf, ctime(&GeoVis::g_stats.start.tv_sec));
507    buf[strlen(buf) - 1] = '\0';
508    Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj(buf, -1));
509    /* date_secs */
510    Tcl_ListObjAppendElement(interp, listObjPtr,
511                             Tcl_NewStringObj("date_secs", 9));
512    Tcl_ListObjAppendElement(interp, listObjPtr,
513                             Tcl_NewLongObj(GeoVis::g_stats.start.tv_sec));
514    /* client items */
515    for (int i = 0; i < numItems; i++) {
516        Tcl_ListObjAppendElement(interp, listObjPtr, items[i]);
517    }
518    Tcl_DStringInit(&ds);
519    string = Tcl_GetStringFromObj(listObjPtr, &length);
520    Tcl_DStringAppend(&ds, string, length);
521    Tcl_DStringAppend(&ds, "\n", 1);
522    result = GeoVis::writeToStatsFile(fd, Tcl_DStringValue(&ds),
523                                      Tcl_DStringLength(&ds));
524    Tcl_DStringFree(&ds);
525    Tcl_DecrRefCount(listObjPtr);
526    return result;
527}
528
529static int
530ColorMapAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
531              Tcl_Obj *const *objv)
532{
533    const char *name = Tcl_GetString(objv[2]);
534    int cmapc;
535    Tcl_Obj **cmapv = NULL;
536
537    if (Tcl_ListObjGetElements(interp, objv[3], &cmapc, &cmapv) != TCL_OK) {
538        return TCL_ERROR;
539    }
540    if ((cmapc % 5) != 0) {
541        Tcl_AppendResult(interp, "wrong # elements in colormap: should be ",
542                         "{ value r g b a... }", (char*)NULL);
543        return TCL_ERROR;
544    }
545
546    osg::TransferFunction1D *colorMap = new osg::TransferFunction1D;
547    colorMap->allocate(256);
548
549    for (int i = 0; i < cmapc; i += 5) {
550        double val[5];
551        for (int j = 0; j < 5; j++) {
552            if (Tcl_GetDoubleFromObj(interp, cmapv[i+j], &val[j]) != TCL_OK) {
553                delete colorMap;
554                return TCL_ERROR;
555            }
556            // Need to permit un-normalized values
557            if (j > 0 && (val[j] < 0.0 || val[j] > 1.0)) {
558                Tcl_AppendResult(interp, "bad colormap value \"",
559                                 Tcl_GetString(cmapv[i+j]),
560                                 "\": should be in the range [0,1]", (char*)NULL);
561                delete colorMap;
562                return TCL_ERROR;
563            }
564        }
565        colorMap->setColor(val[0], osg::Vec4f(val[1], val[2], val[3], val[4]), false);
566    }
567
568    colorMap->updateImage();
569
570    g_renderer->addColorMap(name, colorMap);
571    return TCL_OK;
572}
573
574static int
575ColorMapDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
576                 Tcl_Obj *const *objv)
577{
578    if (objc == 3) {
579        const char *name = Tcl_GetString(objv[2]);
580        g_renderer->deleteColorMap(name);
581    } else {
582        g_renderer->deleteColorMap("all");
583    }
584
585    return TCL_OK;
586}
587
588static int
589ColorMapNumTableEntriesOp(ClientData clientData, Tcl_Interp *interp, int objc,
590                          Tcl_Obj *const *objv)
591{
592    int numEntries;
593    if (Tcl_GetIntFromObj(interp, objv[2], &numEntries) != TCL_OK) {
594        const char *str = Tcl_GetString(objv[2]);
595        if (str[0] == 'd' && strcmp(str, "default") == 0) {
596            numEntries = -1;
597        } else {
598            Tcl_AppendResult(interp, "bad colormap resolution value \"", str,
599                             "\": should be a positive integer or \"default\"", (char*)NULL);
600            return TCL_ERROR;
601        }
602    } else if (numEntries < 1) {
603        Tcl_AppendResult(interp, "bad colormap resolution value \"", Tcl_GetString(objv[2]),
604                         "\": should be a positive integer or \"default\"", (char*)NULL);
605        return TCL_ERROR;
606    }
607    if (objc == 4) {
608        const char *name = Tcl_GetString(objv[3]);
609
610        g_renderer->setColorMapNumberOfTableEntries(name, numEntries);
611    } else {
612        g_renderer->setColorMapNumberOfTableEntries("all", numEntries);
613    }
614    return TCL_OK;
615}
616
617static CmdSpec colorMapOps[] = {
618    {"add",    1, ColorMapAddOp,             4, 4, "colorMapName colormap"},
619    {"define", 3, ColorMapAddOp,             4, 4, "colorMapName colormap"},
620    {"delete", 3, ColorMapDeleteOp,          2, 3, "?colorMapName?"},
621    {"res",    1, ColorMapNumTableEntriesOp, 3, 4, "numTableEntries ?colorMapName?"}
622};
623static int nColorMapOps = NumCmdSpecs(colorMapOps);
624
625static int
626ColorMapCmd(ClientData clientData, Tcl_Interp *interp, int objc,
627            Tcl_Obj *const *objv)
628{
629    Tcl_ObjCmdProc *proc;
630
631    proc = GetOpFromObj(interp, nColorMapOps, colorMapOps,
632                        CMDSPEC_ARG1, objc, objv, 0);
633    if (proc == NULL) {
634        return TCL_ERROR;
635    }
636    return (*proc) (clientData, interp, objc, objv);
637}
638
639static int
640FilePutOp(ClientData clientData, Tcl_Interp *interp, int objc,
641          Tcl_Obj *const *objv)
642{
643    long size;
644    const char *path = Tcl_GetString(objv[2]);
645    if (Tcl_GetLongFromObj(interp, objv[4], &size) != TCL_OK ||
646        size < 1) {
647        return TCL_ERROR;
648    }
649    char *data = (char *)malloc((size_t)size);
650    if (!SocketRead(data, size)) {
651        free(data);
652        return TCL_ERROR;
653    }
654    std::ostringstream fullPath;
655    //fullPath << "/tmp/" << path;
656    fullPath << path;
657    FILE *fp = fopen(fullPath.str().c_str(), "wb");
658    if (fp == NULL) {
659        free(data);
660        return TCL_ERROR;
661    }
662    int bytesWritten = fwrite(data, 1, (size_t)size, fp);
663    fclose(fp);
664    free(data);
665    return (bytesWritten == size) ? TCL_OK : TCL_ERROR;
666}
667
668static CmdSpec fileOps[] = {
669    {"put",    1, FilePutOp, 5, 5, "name type size"}
670};
671static int nFileOps = NumCmdSpecs(fileOps);
672
673static int
674FileCmd(ClientData clientData, Tcl_Interp *interp, int objc,
675          Tcl_Obj *const *objv)
676{
677    Tcl_ObjCmdProc *proc;
678
679    proc = GetOpFromObj(interp, nFileOps, fileOps,
680                        CMDSPEC_ARG1, objc, objv, 0);
681    if (proc == NULL) {
682        return TCL_ERROR;
683    }
684    return (*proc) (clientData, interp, objc, objv);
685}
686
687static int
688ImageFlushCmd(ClientData clientData, Tcl_Interp *interp, int objc,
689              Tcl_Obj *const *objv)
690{
691    lastCmdStatus = TCL_BREAK;
692    return TCL_OK;
693}
694
695static int
696KeyPressOp(ClientData clientData, Tcl_Interp *interp, int objc,
697           Tcl_Obj *const *objv)
698{
699    int key;
700    if (Tcl_GetIntFromObj(interp, objv[2], &key) != TCL_OK) {
701        return TCL_ERROR;
702    }
703
704    g_renderer->keyPress(key);
705    return TCL_OK;
706}
707
708static int
709KeyReleaseOp(ClientData clientData, Tcl_Interp *interp, int objc,
710               Tcl_Obj *const *objv)
711{
712    int key;
713    if (Tcl_GetIntFromObj(interp, objv[2], &key) != TCL_OK) {
714        return TCL_ERROR;
715    }
716
717    g_renderer->keyRelease(key);
718    return TCL_OK;
719}
720
721static CmdSpec keyOps[] = {
722    {"press",    1, KeyPressOp,       3, 3, "key"},
723    {"release",  1, KeyReleaseOp,     3, 3, "key"},
724};
725static int nKeyOps = NumCmdSpecs(keyOps);
726
727static int
728KeyCmd(ClientData clientData, Tcl_Interp *interp, int objc,
729         Tcl_Obj *const *objv)
730{
731    Tcl_ObjCmdProc *proc;
732
733    proc = GetOpFromObj(interp, nKeyOps, keyOps,
734                        CMDSPEC_ARG1, objc, objv, 0);
735    if (proc == NULL) {
736        return TCL_ERROR;
737    }
738    return (*proc) (clientData, interp, objc, objv);
739}
740
741static int
742LegendCmd(ClientData clientData, Tcl_Interp *interp, int objc,
743          Tcl_Obj *const *objv)
744{
745    if (objc != 4) {
746        Tcl_AppendResult(interp, "wrong # args: should be \"",
747                Tcl_GetString(objv[0]), " colormapName width height\"", (char*)NULL);
748        return TCL_ERROR;
749    }
750    const char *colorMapName = Tcl_GetString(objv[1]);
751
752    int width, height;
753    if (Tcl_GetIntFromObj(interp, objv[2], &width) != TCL_OK ||
754        Tcl_GetIntFromObj(interp, objv[3], &height) != TCL_OK) {
755        return TCL_ERROR;
756    }
757
758    bool opaque = true;
759    float bgColor[3];
760    memset(bgColor, 0, sizeof(float)*3);
761    osg::ref_ptr<osg::Image> imgData = new osg::Image();
762
763#if defined(RENDER_TARGA) || defined(DEBUG)
764    if (!g_renderer->renderColorMap(colorMapName, width, height, imgData, opaque,
765                                    bgColor, true)) {
766        Tcl_AppendResult(interp, "Color map \"",
767                         colorMapName, "\" was not found", (char*)NULL);
768        return TCL_ERROR;
769    }
770#else
771    if (!g_renderer->renderColorMap(colorMapName, width, height, imgData, opaque,
772                                    bgColor)) {
773        Tcl_AppendResult(interp, "Color map \"",
774                         colorMapName, "\" was not found", (char*)NULL);
775        return TCL_ERROR;
776    }
777#endif
778
779#ifdef DEBUG
780# ifdef RENDER_TARGA
781    writeTGAFile("/tmp/legend.tga", imgData->data(), width, height,
782                 TARGA_BYTES_PER_PIXEL);
783# else
784    writeTGAFile("/tmp/legend.tga", imgData->data(), width, height,
785                 TARGA_BYTES_PER_PIXEL, true);
786# endif
787#else
788    char cmd[256];
789    float min, max;
790    g_renderer->getColorMapRange(colorMapName, &min, &max);
791    snprintf(cmd, sizeof(cmd), "nv>legend {%s} %g %g", colorMapName, min, max);
792
793# ifdef USE_THREADS
794#  ifdef RENDER_TARGA
795    queueTGA(g_outQueue, cmd, imgData->data(), width, height,
796             TARGA_BYTES_PER_PIXEL);
797#  else
798    queuePPM(g_outQueue, cmd, imgData->data(), width, height);
799#  endif
800# else
801#  ifdef RENDER_TARGA
802    writeTGA(g_fdOut, cmd, imgData->data(), width, height,
803             TARGA_BYTES_PER_PIXEL);
804#  else
805    writePPM(g_fdOut, cmd, imgData->data(), width, height);
806#  endif
807# endif // USE_THREADS
808#endif // DEBUG
809
810    return TCL_OK;
811}
812
813static int
814MapAttributionOp(ClientData clientData, Tcl_Interp *interp, int objc,
815                 Tcl_Obj *const *objv)
816{
817    g_renderer->setAttribution(Tcl_GetString(objv[2]));
818    return TCL_OK;
819}
820
821static int
822MapBoxClearOp(ClientData clientData, Tcl_Interp *interp, int objc,
823              Tcl_Obj *const *objv)
824{
825    g_renderer->clearBoxSelection();
826    return TCL_OK;
827}
828
829static int
830MapBoxInitOp(ClientData clientData, Tcl_Interp *interp, int objc,
831             Tcl_Obj *const *objv)
832{
833    int x, y;
834    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
835        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
836        return TCL_ERROR;
837    }
838    g_renderer->initBoxSelection(x, y);
839    return TCL_OK;
840}
841
842static int
843MapBoxUpdateOp(ClientData clientData, Tcl_Interp *interp, int objc,
844               Tcl_Obj *const *objv)
845{
846    int x, y;
847    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
848        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
849        return TCL_ERROR;
850    }
851    g_renderer->updateBoxSelection(x, y);
852    return TCL_OK;
853}
854
855static CmdSpec mapBoxOps[] = {
856    {"clear",   1, MapBoxClearOp,   3, 3, ""},
857    {"init",    1, MapBoxInitOp,    5, 5, "x y"},
858    {"update",  1, MapBoxUpdateOp,  5, 5, "x y"},
859};
860static int nMapBoxOps = NumCmdSpecs(mapBoxOps);
861
862static int
863MapBoxOp(ClientData clientData, Tcl_Interp *interp, int objc,
864         Tcl_Obj *const *objv)
865{
866    Tcl_ObjCmdProc *proc;
867
868    proc = GetOpFromObj(interp, nMapBoxOps, mapBoxOps,
869                        CMDSPEC_ARG2, objc, objv, 0);
870    if (proc == NULL) {
871        return TCL_ERROR;
872    }
873    return (*proc) (clientData, interp, objc, objv);
874}
875
876static int
877MapCoordsOp(ClientData clientData, Tcl_Interp *interp, int objc,
878            Tcl_Obj *const *objv)
879{
880    int tokenLength;
881    const char *token = Tcl_GetStringFromObj(objv[2], &tokenLength);
882    int numCoords;
883    Tcl_Obj **coords;
884    if (Tcl_ListObjGetElements(interp, objv[3], &numCoords, &coords) != TCL_OK) {
885        return TCL_ERROR;
886    }
887    if (numCoords == 0) {
888        Tcl_AppendResult(interp, "no x,y coordinates in list", (char *)NULL);
889        return TCL_ERROR;
890    }
891    if (numCoords & 1) {
892        Tcl_AppendResult(interp, "odd number of x, y coordinates in list",
893                         (char *)NULL);
894        return TCL_ERROR;
895    }
896    const osgEarth::SpatialReference *outSRS = NULL;
897    if (objc > 4) {
898        std::string srsInit;
899        std::string verticalDatum;
900        srsInit = Tcl_GetString(objv[4]);
901        if (objc > 5) {
902            verticalDatum = Tcl_GetString(objv[5]);
903        }
904        outSRS = osgEarth::SpatialReference::get(srsInit, verticalDatum);
905        if (outSRS == NULL) {
906            Tcl_AppendResult(interp, "bad SRS \"", srsInit.c_str(), "\"",
907                             (char*)NULL);
908            return TCL_ERROR;
909        }
910    }
911    Tcl_Obj *listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
912    std::vector<osg::Vec3d> coordVec;
913    for (int i = 0; i < numCoords; i += 2) {
914        int x, y;
915        if ((Tcl_GetIntFromObj(interp, coords[i], &x) != TCL_OK) ||
916            (Tcl_GetIntFromObj(interp, coords[i+1], &y) != TCL_OK)) {
917            return TCL_ERROR;
918        }
919        osgEarth::GeoPoint mapPoint;
920        if (g_renderer->mapMouseCoords(x, y, mapPoint)) {
921            // altmode absolute
922            coordVec.push_back(osg::Vec3d(mapPoint.x(), mapPoint.y(), mapPoint.z()));
923        } else {
924            coordVec.push_back(osg::Vec3d(std::numeric_limits<double>::quiet_NaN(),
925                                          std::numeric_limits<double>::quiet_NaN(),
926                                          std::numeric_limits<double>::quiet_NaN()));
927        }
928    }
929    if (outSRS != NULL) {
930        g_renderer->getMapSRS()->transform(coordVec, outSRS);
931    } else {
932        outSRS = g_renderer->getMapSRS();
933    }
934    for (std::vector<osg::Vec3d>::iterator itr = coordVec.begin();
935         itr != coordVec.end(); ++itr) {
936        Tcl_Obj *objPtr = Tcl_NewDoubleObj(itr->x());
937        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
938        objPtr = Tcl_NewDoubleObj(itr->y());
939        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
940        objPtr = Tcl_NewDoubleObj(itr->z());
941        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
942    }
943    // send coords to client
944    int listLength;
945    const char *string = Tcl_GetStringFromObj(listObjPtr, &listLength);
946    size_t length = listLength + tokenLength +
947        outSRS->getHorizInitString().length() +
948        outSRS->getVertInitString().length() +
949        25;
950    char *mesg = new char[length];
951    length = snprintf(mesg, length, "nv>map coords %s {%s} {%s} {%s}\n", token, string,
952                      outSRS->getHorizInitString().c_str(),
953                      outSRS->getVertInitString().c_str());
954    Tcl_DecrRefCount(listObjPtr);
955    queueResponse(mesg, length, Response::VOLATILE);
956    delete [] mesg;
957    return TCL_OK;
958}
959
960static int
961MapGraticuleOp(ClientData clientData, Tcl_Interp *interp, int objc,
962               Tcl_Obj *const *objv)
963{
964    bool state;
965    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
966        return TCL_ERROR;
967    }
968    if (objc > 3) {
969        Renderer::GraticuleType type;
970        char *typeStr = Tcl_GetString(objv[3]);
971        if (typeStr[0] == 'g' && strcmp(typeStr, "geodetic") == 0) {
972            type = Renderer::GRATICULE_GEODETIC;
973        } else if (typeStr[0] == 'u' && strcmp(typeStr, "utm") == 0) {
974            type = Renderer::GRATICULE_UTM;
975        } else if (typeStr[0] == 'm' && strcmp(typeStr, "mgrs") == 0) {
976            type = Renderer::GRATICULE_MGRS;
977        } else {
978            return TCL_ERROR;
979        }
980        g_renderer->setGraticule(state, type);
981    } else {
982        g_renderer->setGraticule(state);
983    }
984    return TCL_OK;
985}
986
987static int
988MapLayerAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
989              Tcl_Obj *const *objv)
990{
991    char *name = Tcl_GetString(objv[3]);
992    char *type = Tcl_GetString(objv[4]);
993    if (type[0] == 'i' && strcmp(type, "image") == 0) {
994        bool ret;
995        char *driver = Tcl_GetString(objv[5]);
996        std::string url;
997        if (objc > 6) {
998            char *urlIn = Tcl_GetString(objv[6]);
999            url = g_renderer->getCanonicalPath(std::string(urlIn));
1000            if (url.empty()) {
1001                Tcl_AppendResult(interp, "file not found: \"",
1002                                 urlIn, "\"", (char*)NULL);
1003                return TCL_ERROR;
1004            }
1005        }
1006        bool cache = true;
1007        if (objc > 7) {
1008            if (GetBooleanFromObj(interp, objv[7], &cache) != TCL_OK) {
1009                return TCL_ERROR;
1010            }
1011        }
1012        bool shared = false;
1013        bool visible = true;
1014        int minLOD = 0;
1015        int maxLOD = 23;
1016        float minRange = 0.f;
1017        float maxRange = FLT_MAX;
1018#if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 1)
1019        if (driver[0] == 'c' && strcmp(driver, "colorramp") == 0) {
1020            osgEarth::Drivers::ColorRampOptions colorRampOpts;
1021            osgEarth::Drivers::GDALOptions opts;
1022            opts.url() = url;
1023            char *profile = Tcl_GetString(objv[8]);
1024            char *colormap = Tcl_GetString(objv[9]);
1025            osgEarth::ElevationLayerOptions elevOpts(name, opts);
1026            if (!cache) {
1027                elevOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
1028            }
1029            if (profile != NULL) {
1030                //elevOpts.driver()->profile()->srsString() = "epsg:4326";
1031                elevOpts.driver()->profile() = osgEarth::ProfileOptions(profile);
1032            }
1033            colorRampOpts.elevationLayer() = elevOpts;
1034            colorRampOpts.ramp() = g_renderer->getColorMapFilePath(colormap);
1035            ret = g_renderer->addImageLayer(name, colorRampOpts, cache, shared, visible, minLOD, maxLOD);
1036        } else
1037#endif
1038            if (driver[0] == 'd' && strcmp(driver, "debug") == 0) {
1039            osgEarth::Drivers::DebugOptions opts;
1040            ret = g_renderer->addImageLayer(name, opts, cache, shared, visible, minLOD, maxLOD);
1041        } else if (driver[0] == 'g' && strcmp(driver, "gdal") == 0) {
1042            osgEarth::Drivers::GDALOptions opts;
1043            opts.url() = url;
1044            ret = g_renderer->addImageLayer(name, opts, cache, shared, visible, minLOD, maxLOD);
1045            g_renderer->setImageLayerVisibleRange(name, minRange, maxRange);
1046        } else if (driver[0] == 't' && strcmp(driver, "tms") == 0) {
1047            osgEarth::Drivers::TMSOptions opts;
1048            //char *tmsType = Tcl_GetString(objv[8]);
1049            //char *format = Tcl_GetString(objv[9]);
1050            opts.url() = url;
1051            //opts.tmsType() = tmsType;
1052            //opts.format() = format;
1053            ret = g_renderer->addImageLayer(name, opts, cache, shared, visible, minLOD, maxLOD);
1054            g_renderer->setImageLayerVisibleRange(name, minRange, maxRange);
1055        } else if (driver[0] == 'w' && strcmp(driver, "wms") == 0) {
1056            osgEarth::Drivers::WMSOptions opts;
1057            char *wmsLayers = Tcl_GetString(objv[8]);
1058            char *format = Tcl_GetString(objv[9]);
1059            bool transparent;
1060            if (GetBooleanFromObj(interp, objv[10], &transparent) != TCL_OK) {
1061                return TCL_ERROR;
1062            }
1063            opts.url() = url;
1064            opts.layers() = wmsLayers;
1065            opts.format() = format;
1066            opts.transparent() = transparent;
1067
1068            ret = g_renderer->addImageLayer(name, opts, cache, shared, visible, minLOD, maxLOD);
1069            g_renderer->setImageLayerVisibleRange(name, minRange, maxRange);
1070        } else if (driver[0] == 'x' && strcmp(driver, "xyz") == 0) {
1071            osgEarth::Drivers::XYZOptions opts;
1072            opts.url() = url;
1073            opts.profile() = osgEarth::ProfileOptions("global-mercator");
1074            //bool invertY = false;
1075            //opts.invertY() = invertY;
1076            //opts.format() = Tcl_GetString(objv[8]);
1077            ret = g_renderer->addImageLayer(name, opts, cache, shared, visible, minLOD, maxLOD);
1078            g_renderer->setImageLayerVisibleRange(name, minRange, maxRange);
1079        } else {
1080            Tcl_AppendResult(interp, "unknown image driver \"", driver,
1081                             "\": should be 'debug', 'gdal', 'tms', 'wms' or 'xyz'", (char*)NULL);
1082            return TCL_ERROR;
1083        }
1084        if (!ret) {
1085            Tcl_AppendResult(interp, "Failed to add image layer \"", name, "\"", (char*)NULL);
1086            return TCL_ERROR;
1087        }
1088    } else if (type[0] == 'e' && strcmp(type, "elevation") == 0) {
1089        char *driver = Tcl_GetString(objv[5]);
1090        char *urlIn = Tcl_GetString(objv[6]);
1091        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
1092        if (url.empty()) {
1093            Tcl_AppendResult(interp, "file not found: \"",
1094                             urlIn, "\"", (char*)NULL);
1095            return TCL_ERROR;
1096        }
1097        bool cache = true;
1098        bool visible = true;
1099        int minLOD = 0;
1100        int maxLOD = 23;
1101        if (driver[0] == 'g' && strcmp(driver, "gdal") == 0) {
1102            osgEarth::Drivers::GDALOptions opts;
1103            opts.url() = url;
1104            g_renderer->addElevationLayer(name, opts, cache, visible, minLOD, maxLOD);
1105        } else if (driver[0] == 't' && strcmp(driver, "tms") == 0) {
1106            osgEarth::Drivers::TMSOptions opts;
1107            //char *tmsType = Tcl_GetString(objv[7]);
1108            //char *format = Tcl_GetString(objv[8]);
1109            opts.url() = url;
1110            //opts.tmsType() = tmsType;
1111            //opts.format() = format;
1112            g_renderer->addElevationLayer(name, opts, cache, visible, minLOD, maxLOD);
1113        } else {
1114            Tcl_AppendResult(interp, "unknown elevation driver \"", driver,
1115                             "\": should be 'gdal' or 'tms'", (char*)NULL);
1116            return TCL_ERROR;
1117        }
1118    } else if (type[0] == 'p' && strcmp(type, "point") == 0) {
1119        osgEarth::Drivers::OGRFeatureOptions opts;
1120        char *urlIn = Tcl_GetString(objv[5]);
1121        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
1122        if (url.empty()) {
1123            Tcl_AppendResult(interp, "file not found: \"",
1124                             urlIn, "\"", (char*)NULL);
1125            return TCL_ERROR;
1126        }
1127        float r, g, b;
1128        if (GetFloatFromObj(interp, objv[6], &r) != TCL_OK ||
1129            GetFloatFromObj(interp, objv[7], &g) != TCL_OK ||
1130            GetFloatFromObj(interp, objv[8], &b) != TCL_OK) {
1131            return TCL_ERROR;
1132        }
1133        float ptSize;
1134        if (GetFloatFromObj(interp, objv[9], &ptSize) != TCL_OK) {
1135            return TCL_ERROR;
1136        }
1137        opts.url() = url;
1138
1139        osgEarth::Symbology::Style style;
1140        osgEarth::Symbology::PointSymbol *ps = style.getOrCreateSymbol<osgEarth::Symbology::PointSymbol>();
1141        ps->fill()->color() = osgEarth::Symbology::Color(r, g, b);
1142        ps->size() = ptSize;
1143
1144        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
1145        rs->depthOffset()->enabled() = true;
1146        rs->depthOffset()->minBias() = 1000;
1147
1148        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
1149        geomOpts.featureOptions() = opts;
1150        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
1151        geomOpts.styles()->addStyle(style);
1152        geomOpts.enableLighting() = false;
1153        geomOpts.minRange() = 0.f;
1154        geomOpts.maxRange() = FLT_MAX;
1155        //geomOpts.renderOrder = int;
1156        //geomOpts.depthTestEnabled = bool;
1157        g_renderer->addModelLayer(name, geomOpts);
1158    } else if (type[0] == 'p' && strcmp(type, "polygon") == 0) {
1159        osgEarth::Drivers::OGRFeatureOptions opts;
1160        char *urlIn = Tcl_GetString(objv[5]);
1161        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
1162        if (url.empty()) {
1163            Tcl_AppendResult(interp, "file not found: \"",
1164                             urlIn, "\"", (char*)NULL);
1165            return TCL_ERROR;
1166        }
1167        float r, g, b;
1168        if (GetFloatFromObj(interp, objv[6], &r) != TCL_OK ||
1169            GetFloatFromObj(interp, objv[7], &g) != TCL_OK ||
1170            GetFloatFromObj(interp, objv[8], &b) != TCL_OK) {
1171            return TCL_ERROR;
1172        }
1173        opts.url() = url;
1174
1175        osgEarth::Symbology::Style style;
1176        osgEarth::Symbology::PolygonSymbol *ps = style.getOrCreateSymbol<osgEarth::Symbology::PolygonSymbol>();
1177        ps->fill()->color() = osgEarth::Symbology::Color(r, g, b);
1178#if 1
1179        osgEarth::Symbology::AltitudeSymbol *alt = style.getOrCreateSymbol<osgEarth::Symbology::AltitudeSymbol>();
1180        alt->clamping() = osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
1181        alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_DRAPE;
1182        //alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
1183#endif
1184        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
1185        rs->depthOffset()->enabled() = true;
1186        rs->depthOffset()->minBias() = 1000;
1187
1188        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
1189        geomOpts.featureOptions() = opts;
1190        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
1191        geomOpts.styles()->addStyle(style);
1192        geomOpts.enableLighting() = false;
1193        geomOpts.minRange() = 0.f;
1194        geomOpts.maxRange() = FLT_MAX;
1195        g_renderer->addModelLayer(name, geomOpts);
1196    } else if (type[0] == 'l' && strcmp(type, "line") == 0) {
1197        osgEarth::Drivers::OGRFeatureOptions opts;
1198        char *urlIn = Tcl_GetString(objv[5]);
1199        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
1200        if (url.empty()) {
1201            Tcl_AppendResult(interp, "file not found: \"",
1202                             urlIn, "\"", (char*)NULL);
1203            return TCL_ERROR;
1204        }
1205        float r, g, b;
1206        if (GetFloatFromObj(interp, objv[6], &r) != TCL_OK ||
1207            GetFloatFromObj(interp, objv[7], &g) != TCL_OK ||
1208            GetFloatFromObj(interp, objv[8], &b) != TCL_OK) {
1209            return TCL_ERROR;
1210        }
1211        float lineWidth;
1212        if (GetFloatFromObj(interp, objv[9], &lineWidth) != TCL_OK) {
1213            return TCL_ERROR;
1214        }
1215        opts.url() = url;
1216
1217        osgEarth::Symbology::Style style;
1218        osgEarth::Symbology::LineSymbol *ls = style.getOrCreateSymbol<osgEarth::Symbology::LineSymbol>();
1219        ls->stroke()->color() = osgEarth::Symbology::Color(r, g, b);
1220        ls->stroke()->width() = lineWidth;
1221#if 1
1222        osgEarth::Symbology::AltitudeSymbol *alt = style.getOrCreateSymbol<osgEarth::Symbology::AltitudeSymbol>();
1223        alt->clamping() = osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
1224        //alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_DRAPE;
1225        alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
1226#endif
1227#if 1
1228        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
1229        rs->depthOffset()->enabled() = true;
1230        rs->depthOffset()->minBias() = 1000;
1231#endif
1232        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
1233        geomOpts.featureOptions() = opts;
1234        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
1235        geomOpts.styles()->addStyle(style);
1236        geomOpts.enableLighting() = false;
1237        geomOpts.minRange() = 0.f;
1238        geomOpts.maxRange() = FLT_MAX;
1239        if (objc > 10) {
1240            float min, max;
1241            if (GetFloatFromObj(interp, objv[10], &min) != TCL_OK ||
1242                GetFloatFromObj(interp, objv[11], &max) != TCL_OK) {
1243                return TCL_ERROR;
1244            }
1245            geomOpts.minRange() = min;
1246            geomOpts.maxRange() = max;
1247        }
1248        g_renderer->addModelLayer(name, geomOpts);
1249   } else if (type[0] == 't' && strcmp(type, "text") == 0) {
1250        osgEarth::Drivers::OGRFeatureOptions opts;
1251        char *urlIn = Tcl_GetString(objv[5]);
1252        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
1253        if (url.empty()) {
1254            Tcl_AppendResult(interp, "file not found: \"",
1255                             urlIn, "\"", (char*)NULL);
1256            return TCL_ERROR;
1257        }
1258        char *content = Tcl_GetString(objv[6]);
1259        char *priority = Tcl_GetString(objv[7]);
1260        float fgR, fgG, fgB;
1261        float bgR, bgG, bgB;
1262        float haloWidth, ftSize;
1263        if (GetFloatFromObj(interp, objv[8], &fgR) != TCL_OK ||
1264            GetFloatFromObj(interp, objv[9], &fgG) != TCL_OK ||
1265            GetFloatFromObj(interp, objv[10], &fgB) != TCL_OK ||
1266            GetFloatFromObj(interp, objv[11], &bgR) != TCL_OK ||
1267            GetFloatFromObj(interp, objv[12], &bgG) != TCL_OK ||
1268            GetFloatFromObj(interp, objv[13], &bgB) != TCL_OK ||
1269            GetFloatFromObj(interp, objv[14], &haloWidth) != TCL_OK ||
1270            GetFloatFromObj(interp, objv[15], &ftSize) != TCL_OK) {
1271            return TCL_ERROR;
1272        }
1273        bool removeDupes, declutter;
1274        if (GetBooleanFromObj(interp, objv[16], &removeDupes) != TCL_OK ||
1275            GetBooleanFromObj(interp, objv[17], &declutter) != TCL_OK) {
1276            return TCL_ERROR;
1277        }
1278        opts.url() = url;
1279
1280        osgEarth::Symbology::Style style;
1281        osgEarth::Symbology::TextSymbol *ts = style.getOrCreateSymbol<osgEarth::Symbology::TextSymbol>();
1282        ts->halo()->color() = osgEarth::Symbology::Color(bgR, bgG, bgB);
1283        ts->halo()->width() = haloWidth;
1284        ts->fill()->color() = osgEarth::Symbology::Color(fgR, fgG, fgB);
1285        ts->content() = osgEarth::Symbology::StringExpression(content);
1286        ts->priority() = osgEarth::Symbology::NumericExpression(priority);
1287        ts->removeDuplicateLabels() = removeDupes;
1288        //ts->font() = "Arial";
1289        ts->size() = ftSize;
1290        ts->alignment() = osgEarth::Symbology::TextSymbol::ALIGN_CENTER_CENTER;
1291        ts->declutter() = declutter;
1292        ts->encoding() = osgEarth::Symbology::TextSymbol::ENCODING_UTF8;
1293
1294        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
1295        rs->depthOffset()->enabled() = true;
1296        rs->depthOffset()->minBias() = 1000;
1297
1298        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
1299        geomOpts.featureOptions() = opts;
1300        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
1301        geomOpts.styles()->addStyle(style);
1302        geomOpts.enableLighting() = false;
1303        geomOpts.minRange() = 0.f;
1304        geomOpts.maxRange() = FLT_MAX;
1305        if (objc > 10) {
1306            float min, max;
1307            if (GetFloatFromObj(interp, objv[18], &min) != TCL_OK ||
1308                GetFloatFromObj(interp, objv[19], &max) != TCL_OK) {
1309                return TCL_ERROR;
1310            }
1311            geomOpts.minRange() = min;
1312            geomOpts.maxRange() = max;
1313        }
1314        g_renderer->addModelLayer(name, geomOpts);
1315    } else {
1316        Tcl_AppendResult(interp, "unknown map layer type \"", type,
1317                         "\": should be 'image', 'elevation' or 'model'", (char*)NULL);
1318        return TCL_ERROR;
1319    }
1320    return TCL_OK;
1321}
1322
1323static int
1324MapLayerDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
1325                 Tcl_Obj *const *objv)
1326{
1327    if (objc > 3) {
1328        char *name = Tcl_GetString(objv[3]);
1329        g_renderer->removeImageLayer(name);
1330        g_renderer->removeElevationLayer(name);
1331        g_renderer->removeModelLayer(name);
1332    } else {
1333        g_renderer->clearMap();
1334    }
1335
1336    return TCL_OK;
1337}
1338
1339static int
1340MapLayerMoveOp(ClientData clientData, Tcl_Interp *interp, int objc,
1341               Tcl_Obj *const *objv)
1342{
1343    int pos;
1344    if (Tcl_GetIntFromObj(interp, objv[3], &pos) != TCL_OK) {
1345        return TCL_ERROR;
1346    }
1347    char *name = Tcl_GetString(objv[4]);
1348    if (pos < 0) {
1349        Tcl_AppendResult(interp, "bad layer pos ", pos,
1350                         ": must be positive", (char*)NULL);
1351        return TCL_ERROR;
1352    }
1353    g_renderer->moveImageLayer(name, (unsigned int)pos);
1354    g_renderer->moveElevationLayer(name, (unsigned int)pos);
1355    g_renderer->moveModelLayer(name, (unsigned int)pos);
1356
1357    return TCL_OK;
1358}
1359
1360static int
1361MapLayerOpacityOp(ClientData clientData, Tcl_Interp *interp, int objc,
1362                  Tcl_Obj *const *objv)
1363{
1364    double opacity;
1365    if (Tcl_GetDoubleFromObj(interp, objv[3], &opacity) != TCL_OK) {
1366        return TCL_ERROR;
1367    }
1368    char *name = Tcl_GetString(objv[4]);
1369    if (opacity < 0.0 || opacity > 1.0) {
1370        Tcl_AppendResult(interp, "bad layer opacity ", opacity,
1371                         ": must be [0,1]", (char*)NULL);
1372        return TCL_ERROR;
1373    }
1374    g_renderer->setImageLayerOpacity(name, opacity);
1375    g_renderer->setModelLayerOpacity(name, opacity);
1376
1377    return TCL_OK;
1378}
1379
1380static int
1381MapLayerNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
1382                Tcl_Obj *const *objv)
1383{
1384    std::vector<std::string> layers;
1385    if (objc < 4) {
1386        g_renderer->getImageLayerNames(layers);
1387        g_renderer->getElevationLayerNames(layers);
1388        g_renderer->getModelLayerNames(layers);
1389    } else {
1390        char *type = Tcl_GetString(objv[3]);
1391        if (type[0] == 'i' && strcmp(type, "image") == 0) {
1392            g_renderer->getImageLayerNames(layers);
1393        } else if (type[0] == 'e' && strcmp(type, "elevation") == 0) {
1394            g_renderer->getElevationLayerNames(layers);
1395        } else if (type[0] == 'm' && strcmp(type, "model") == 0) {
1396            g_renderer->getModelLayerNames(layers);
1397        } else {
1398            Tcl_AppendResult(interp, "uknown type \"", type,
1399                         "\": must be image, elevation or model", (char*)NULL);
1400            return TCL_ERROR;
1401        }
1402    }
1403    std::ostringstream oss;
1404    size_t len = 0;
1405    oss << "nv>map names {";
1406    len += 18;
1407    for (size_t i = 0; i < layers.size(); i++) {
1408        oss << "\"" << layers[i] << "\"";
1409        len += 2 + layers[i].length();
1410        if (i < layers.size() - 1) {
1411            oss << " ";
1412            len++;
1413        }
1414    }
1415    oss << "}\n";
1416    len += 2;
1417#ifdef USE_THREADS
1418    queueResponse(oss.str().c_str(), len, Response::VOLATILE);
1419#else
1420    ssize_t bytesWritten = SocketWrite(oss.str().c_str(), len);
1421
1422    if (bytesWritten < 0) {
1423        return TCL_ERROR;
1424    }
1425#endif /*USE_THREADS*/
1426    return TCL_OK;
1427}
1428
1429static int
1430MapLayerVisibleOp(ClientData clientData, Tcl_Interp *interp, int objc,
1431                  Tcl_Obj *const *objv)
1432{
1433    bool visible;
1434    if (GetBooleanFromObj(interp, objv[3], &visible) != TCL_OK) {
1435        return TCL_ERROR;
1436    }
1437    char *name = Tcl_GetString(objv[4]);
1438
1439    g_renderer->setImageLayerVisibility(name, visible);
1440    g_renderer->setElevationLayerVisibility(name, visible);
1441    g_renderer->setModelLayerVisibility(name, visible);
1442
1443    return TCL_OK;
1444}
1445
1446static CmdSpec mapLayerOps[] = {
1447    {"add",     1, MapLayerAddOp,       6, 0, "name type driver ?url? ?args?"},
1448    {"delete",  1, MapLayerDeleteOp,    3, 4, "?name?"},
1449    {"move",    1, MapLayerMoveOp,      5, 5, "pos name"},
1450    {"names",   1, MapLayerNamesOp,     3, 4, "?type?"},
1451    {"opacity", 1, MapLayerOpacityOp,   5, 5, "opacity ?name?"},
1452    {"visible", 1, MapLayerVisibleOp,   5, 5, "bool ?name?"},
1453};
1454static int nMapLayerOps = NumCmdSpecs(mapLayerOps);
1455
1456static int
1457MapLayerOp(ClientData clientData, Tcl_Interp *interp, int objc,
1458           Tcl_Obj *const *objv)
1459{
1460    Tcl_ObjCmdProc *proc;
1461
1462    proc = GetOpFromObj(interp, nMapLayerOps, mapLayerOps,
1463                        CMDSPEC_ARG2, objc, objv, 0);
1464    if (proc == NULL) {
1465        return TCL_ERROR;
1466    }
1467    return (*proc) (clientData, interp, objc, objv);
1468}
1469
1470static int
1471MapLoadOp(ClientData clientData, Tcl_Interp *interp, int objc,
1472          Tcl_Obj *const *objv)
1473{
1474    char *opt = Tcl_GetString(objv[2]);
1475    if (opt[0] == 'f' && strcmp(opt, "file") == 0) {
1476        g_renderer->loadEarthFile(Tcl_GetString(objv[3]));
1477    } else if (opt[0] == 'u' && strcmp(opt, "url") == 0) {
1478        std::ostringstream path;
1479        path << "server:" << Tcl_GetString(objv[3]);
1480        g_renderer->loadEarthFile(path.str().c_str());
1481    } else if (opt[0] == 'd' && strcmp(opt, "data") == 0) {
1482        opt = Tcl_GetString(objv[3]);
1483        if (opt[0] != 'f' || strcmp(opt, "follows") != 0) {
1484            return TCL_ERROR;
1485        }
1486        int len;
1487        if (Tcl_GetIntFromObj(interp, objv[4], &len) != TCL_OK) {
1488            return TCL_ERROR;
1489        }
1490        // Read Earth file from socket
1491        char *buf = (char *)malloc((size_t)len);
1492        SocketRead(buf, (size_t)len);
1493        std::ostringstream path;
1494        path << "/tmp/tmp" << getpid() << ".earth";
1495        const char *pathStr = path.str().c_str();
1496        FILE *tmpFile = fopen(pathStr, "w");
1497        fwrite(buf, len, 1, tmpFile);
1498        fclose(tmpFile);
1499        g_renderer->loadEarthFile(pathStr);
1500        unlink(pathStr);
1501        free(buf);
1502    } else {
1503        return TCL_ERROR;
1504    }
1505    return TCL_OK;
1506}
1507
1508static int
1509MapPinAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
1510            Tcl_Obj *const *objv)
1511{
1512    int x, y;
1513    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
1514        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
1515        return TCL_ERROR;
1516    }
1517    char *label = NULL;
1518    if (objc > 5) {
1519        label = Tcl_GetString(objv[5]);
1520    }
1521
1522    if (g_renderer->getMapSRS() == NULL) {
1523        Tcl_AppendResult(interp, "Could not get map SRS", (char*)NULL);
1524        return TCL_ERROR;
1525    }
1526
1527    // Get lat/long
1528    double latitude, longitude;
1529    if (!g_renderer->mouseToLatLong(x, y, &latitude, &longitude)) {
1530        USER_ERROR("Can't add pin here");
1531        return TCL_OK;
1532    }
1533
1534    g_renderer->addPlaceNode(latitude, longitude, label);
1535    return TCL_OK;
1536}
1537
1538static int
1539MapPinDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
1540               Tcl_Obj *const *objv)
1541{
1542    int x, y;
1543    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
1544        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
1545        return TCL_ERROR;
1546    }
1547
1548    g_renderer->deletePlaceNode(x, y);
1549    return TCL_OK;
1550}
1551
1552static int
1553MapPinHoverOp(ClientData clientData, Tcl_Interp *interp, int objc,
1554              Tcl_Obj *const *objv)
1555{
1556    int x, y;
1557    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
1558        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
1559        return TCL_ERROR;
1560    }
1561
1562    g_renderer->hoverPlaceNode(x, y);
1563    return TCL_OK;
1564}
1565
1566static CmdSpec mapPinOps[] = {
1567    {"add",     1, MapPinAddOp,     5, 6, "x y ?label?"},
1568    {"delete",  1, MapPinDeleteOp,  5, 5, "x y"},
1569    {"hover",   1, MapPinHoverOp,   5, 5, "x y"},
1570};
1571static int nMapPinOps = NumCmdSpecs(mapPinOps);
1572
1573static int
1574MapPinOp(ClientData clientData, Tcl_Interp *interp, int objc,
1575           Tcl_Obj *const *objv)
1576{
1577    Tcl_ObjCmdProc *proc;
1578
1579    proc = GetOpFromObj(interp, nMapPinOps, mapPinOps,
1580                        CMDSPEC_ARG2, objc, objv, 0);
1581    if (proc == NULL) {
1582        return TCL_ERROR;
1583    }
1584    return (*proc) (clientData, interp, objc, objv);
1585}
1586
1587static int
1588MapPositionDisplayOp(ClientData clientData, Tcl_Interp *interp, int objc,
1589                     Tcl_Obj *const *objv)
1590{
1591    bool state;
1592    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
1593        return TCL_ERROR;
1594    }
1595    Renderer::CoordinateDisplayType type = Renderer::COORDS_LATLONG_DECIMAL_DEGREES;
1596    if (state && objc > 3) {
1597        const char *str = Tcl_GetString(objv[3]);
1598        if (str[0] == 'l' && strcmp(str, "latlong_decimal_degrees") == 0) {
1599            type = Renderer::COORDS_LATLONG_DECIMAL_DEGREES;
1600        } else if (str[0] == 'l' && strcmp(str, "latlong_degrees_decimal_minutes") == 0) {
1601            type = Renderer::COORDS_LATLONG_DEGREES_DECIMAL_MINUTES;
1602        } else if (str[0] == 'l' && strcmp(str, "latlong_degrees_minutes_seconds") == 0) {
1603            type = Renderer::COORDS_LATLONG_DEGREES_MINUTES_SECONDS;
1604        } else if (str[0] == 'm' && strcmp(str, "mgrs") == 0) {
1605            type = Renderer::COORDS_MGRS;
1606        } else {
1607            Tcl_AppendResult(interp, "invalid type: \"", str,
1608                             "\": should be 'latlong_decimal_degrees', 'latlong_degrees_decimal_minutes', 'latlong_degrees_minutes_seconds', or 'mgrs'",
1609                             (char*)NULL);
1610            return TCL_ERROR;
1611        }
1612    }
1613    if (state && objc > 4) {
1614        int precision;
1615        if (Tcl_GetIntFromObj(interp, objv[4], &precision) != TCL_OK) {
1616            return TCL_ERROR;
1617        }
1618        g_renderer->setCoordinateReadout(state, type, precision);
1619    } else {
1620        g_renderer->setCoordinateReadout(state, type);
1621    }
1622
1623    return TCL_OK;
1624}
1625
1626static int
1627MapResetOp(ClientData clientData, Tcl_Interp *interp, int objc,
1628           Tcl_Obj *const *objv)
1629{
1630    char *typeStr = Tcl_GetString(objv[2]);
1631    osgEarth::MapOptions::CoordinateSystemType type;
1632    if (typeStr[0] == 'g' && strcmp(typeStr, "geocentric") == 0) {
1633        type = osgEarth::MapOptions::CSTYPE_GEOCENTRIC;
1634    } else if (typeStr[0] == 'g' && strcmp(typeStr, "geocentric_cube") == 0) {
1635        type = osgEarth::MapOptions::CSTYPE_GEOCENTRIC_CUBE;
1636    } else if (typeStr[0] == 'p' && strcmp(typeStr, "projected") == 0) {
1637        type = osgEarth::MapOptions::CSTYPE_PROJECTED;
1638    } else {
1639        Tcl_AppendResult(interp, "bad map type \"", typeStr,
1640                         "\": must be geocentric or projected", (char*)NULL);
1641        return TCL_ERROR;
1642    }
1643    float color[3];
1644    if (GetFloatFromObj(interp, objv[3], &color[0]) != TCL_OK ||
1645        GetFloatFromObj(interp, objv[4], &color[1]) != TCL_OK ||
1646        GetFloatFromObj(interp, objv[5], &color[2]) != TCL_OK) {
1647        return TCL_ERROR;
1648    }
1649
1650    osg::Vec4f bgColor(color[0],color[1],color[2],1);
1651    if (type == osgEarth::MapOptions::CSTYPE_PROJECTED) {
1652        if (objc < 7) {
1653            Tcl_AppendResult(interp, "wrong # arguments: profile required for projected maps", (char*)NULL);
1654            return TCL_ERROR;
1655        }
1656        char *profile = Tcl_GetString(objv[6]);
1657        if (objc > 7) {
1658            if (objc < 11) {
1659                Tcl_AppendResult(interp, "wrong # arguments: 4 bounds arguments required", (char*)NULL);
1660                return TCL_ERROR;
1661            }
1662            double bounds[4];
1663            for (int i = 0; i < 4; i++) {
1664                if (Tcl_GetDoubleFromObj(interp, objv[7+i], &bounds[i]) != TCL_OK) {
1665                    return TCL_ERROR;
1666                }
1667            }
1668#if 1
1669            // Check if min > max
1670            if (bounds[0] > bounds[2] ||
1671                bounds[1] > bounds[3]) {
1672                Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
1673                return TCL_ERROR;
1674            }
1675            // Note: plate-carre generates same SRS as others, but with
1676            // _is_plate_carre flag set
1677            // In map profile, _is_plate_carre is forced on for
1678            // geographic+projected SRS
1679            if (strcmp(profile, "geodetic") == 0 ||
1680                strcmp(profile, "epsg:4326") == 0 ||
1681                strcmp(profile, "wgs84") == 0 ||
1682                strcmp(profile, "plate-carre") == 0) {
1683                if (bounds[0] < -180. || bounds[0] > 180. ||
1684                    bounds[2] < -180. || bounds[2] > 180. ||
1685                    bounds[1] < -90. || bounds[1] > 90. ||
1686                    bounds[3] < -90. || bounds[3] > 90.) {
1687                    Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
1688                    return TCL_ERROR;
1689                }
1690            } else if (strcmp(profile, "spherical-mercator") == 0 ||
1691                       strcmp(profile, "epsg:900913") == 0 ||
1692                       strcmp(profile, "epsg:3785") == 0 ||
1693                       strcmp(profile, "epsg:3857") == 0 ||
1694                       strcmp(profile, "epsg:102113") == 0) {
1695                for (int i = 0; i < 4; i++) {
1696                    if (bounds[i] < -20037508.34 || bounds[i] > 20037508.34) {
1697                        Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
1698                        return TCL_ERROR;
1699                    }
1700                }
1701            } else if (strcmp(profile, "epsg:32662") == 0 ||
1702                       strcmp(profile, "epsg:32663") == 0) {
1703                if (bounds[0] < -20037508.3428 || bounds[0] > 20037508.3428 ||
1704                    bounds[2] < -20037508.3428 || bounds[2] > 20037508.3428 ||
1705                    bounds[1] < -10018754.1714 || bounds[1] > 10018754.1714 ||
1706                    bounds[3] < -10018754.1714 || bounds[3] > 10018754.1714) {
1707                    Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
1708                    return TCL_ERROR;
1709                }
1710            }
1711#endif
1712            g_renderer->resetMap(type, bgColor, profile, bounds);
1713        } else {
1714            if (osgEarth::Registry::instance()->getNamedProfile(profile) == NULL) {
1715                Tcl_AppendResult(interp, "bad named profile \"", profile,
1716                                 "\": must be e.g. 'global-geodetic', 'global-mercator'...", (char*)NULL);
1717                return TCL_ERROR;
1718            }
1719            g_renderer->resetMap(type, bgColor, profile);
1720        }
1721    } else {
1722        // No profile required for geocentric (3D) maps
1723        g_renderer->resetMap(type, bgColor);
1724    }
1725
1726    return TCL_OK;
1727}
1728
1729static int
1730MapScaleBarOp(ClientData clientData, Tcl_Interp *interp, int objc,
1731              Tcl_Obj *const *objv)
1732{
1733    bool state;
1734    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
1735        return TCL_ERROR;
1736    }
1737    g_renderer->setScaleBar(state);
1738    if (state && objc > 3) {
1739        const char *unitStr = Tcl_GetString(objv[3]);
1740        ScaleBarUnits units;
1741        if (unitStr[0] == 'm' && strcmp(unitStr, "meters") == 0) {
1742            units = UNITS_METERS;
1743        } else if (unitStr[0] == 'f' && strcmp(unitStr, "feet") == 0) {
1744            units = UNITS_INTL_FEET;
1745        } else if (unitStr[0] == 'u' && strcmp(unitStr, "us_survey_feet") == 0) {
1746            units = UNITS_US_SURVEY_FEET;
1747        } else if (unitStr[0] == 'n' && strcmp(unitStr, "nautical_miles") == 0) {
1748            units = UNITS_NAUTICAL_MILES;
1749        } else {
1750            Tcl_AppendResult(interp, "bad units \"", unitStr,
1751                             "\": must be 'meters', 'feet', 'us_survey_feet' or 'nautical_miles'", (char*)NULL);
1752            return TCL_ERROR;
1753        }
1754        g_renderer->setScaleBarUnits(units);
1755    }
1756    return TCL_OK;
1757}
1758
1759static int
1760MapSetPositionOp(ClientData clientData, Tcl_Interp *interp, int objc,
1761            Tcl_Obj *const *objv)
1762{
1763    if (objc < 3) {
1764        g_renderer->clearReadout();
1765    } else {
1766        int x, y;
1767        if (Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK ||
1768            Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK) {
1769            return TCL_ERROR;
1770        }
1771        g_renderer->setReadout(x, y);
1772    }
1773    return TCL_OK;
1774}
1775
1776static int
1777MapTerrainColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
1778                  Tcl_Obj *const *objv)
1779{
1780    float color[3];
1781    if (GetFloatFromObj(interp, objv[3], &color[0]) != TCL_OK ||
1782        GetFloatFromObj(interp, objv[4], &color[1]) != TCL_OK ||
1783        GetFloatFromObj(interp, objv[5], &color[2]) != TCL_OK) {
1784        return TCL_ERROR;
1785    }
1786    g_renderer->setTerrainColor(osg::Vec4f(color[0],color[1],color[2],1));
1787    return TCL_OK;
1788}
1789
1790static int
1791MapTerrainEdgesOp(ClientData clientData, Tcl_Interp *interp, int objc,
1792                  Tcl_Obj *const *objv)
1793{
1794    bool state;
1795    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
1796        return TCL_ERROR;
1797    }
1798    g_renderer->setTerrainEdges(state);
1799    return TCL_OK;
1800}
1801
1802static int
1803MapTerrainLightingOp(ClientData clientData, Tcl_Interp *interp, int objc,
1804                     Tcl_Obj *const *objv)
1805{
1806    bool state;
1807    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
1808        return TCL_ERROR;
1809    }
1810
1811    g_renderer->setTerrainLighting(state);
1812    return TCL_OK;
1813}
1814
1815static int
1816MapTerrainLineColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
1817                      Tcl_Obj *const *objv)
1818{
1819    float color[3];
1820    if (GetFloatFromObj(interp, objv[3], &color[0]) != TCL_OK ||
1821        GetFloatFromObj(interp, objv[4], &color[1]) != TCL_OK ||
1822        GetFloatFromObj(interp, objv[5], &color[2]) != TCL_OK) {
1823        return TCL_ERROR;
1824    }
1825    g_renderer->setTerrainLineColor(osg::Vec4f(color[0], color[1], color[2],1));
1826    return TCL_OK;
1827}
1828
1829static int
1830MapTerrainLineWidthOp(ClientData clientData, Tcl_Interp *interp, int objc,
1831                      Tcl_Obj *const *objv)
1832{
1833    float width;
1834    if (GetFloatFromObj(interp, objv[3], &width) != TCL_OK) {
1835        return TCL_ERROR;
1836    }
1837    g_renderer->setTerrainLineWidth(width);
1838    return TCL_OK;
1839}
1840
1841static int
1842MapTerrainVertScaleOp(ClientData clientData, Tcl_Interp *interp, int objc,
1843                      Tcl_Obj *const *objv)
1844{
1845    double scale;
1846    if (Tcl_GetDoubleFromObj(interp, objv[3], &scale) != TCL_OK) {
1847        return TCL_ERROR;
1848    }
1849
1850    g_renderer->setTerrainVerticalScale(scale);
1851    return TCL_OK;
1852}
1853
1854static int
1855MapTerrainWireframeOp(ClientData clientData, Tcl_Interp *interp, int objc,
1856                      Tcl_Obj *const *objv)
1857{
1858    bool state;
1859    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
1860        return TCL_ERROR;
1861    }
1862
1863    g_renderer->setTerrainWireframe(state);
1864    return TCL_OK;
1865}
1866
1867static CmdSpec mapTerrainOps[] = {
1868    {"color",     1, MapTerrainColorOp,     6, 6, "r g b"},
1869    {"edges",     1, MapTerrainEdgesOp,     4, 4, "bool"},
1870    {"lighting",  2, MapTerrainLightingOp,  4, 4, "bool"},
1871    {"linecolor", 5, MapTerrainLineColorOp, 6, 6, "r g b"},
1872    {"linewidth", 5, MapTerrainLineWidthOp, 4, 4, "val"},
1873    {"vertscale", 1, MapTerrainVertScaleOp, 4, 4, "val"},
1874    {"wireframe", 1, MapTerrainWireframeOp, 4, 4, "bool"},
1875};
1876static int nMapTerrainOps = NumCmdSpecs(mapTerrainOps);
1877
1878static int
1879MapTerrainOp(ClientData clientData, Tcl_Interp *interp, int objc,
1880           Tcl_Obj *const *objv)
1881{
1882    Tcl_ObjCmdProc *proc;
1883
1884    proc = GetOpFromObj(interp, nMapTerrainOps, mapTerrainOps,
1885                        CMDSPEC_ARG2, objc, objv, 0);
1886    if (proc == NULL) {
1887        return TCL_ERROR;
1888    }
1889    return (*proc) (clientData, interp, objc, objv);
1890}
1891
1892static int
1893MapTimeOp(ClientData clientData, Tcl_Interp *interp, int objc,
1894          Tcl_Obj *const *objv)
1895{
1896    osgEarth::DateTime now;
1897    int year, month, day;
1898    double hours;
1899    year = now.year();
1900    month = now.month();
1901    day = now.day();
1902    hours = now.hours();
1903    if (objc > 2) {
1904        if (Tcl_GetDoubleFromObj(interp, objv[2], &hours) != TCL_OK) {
1905            return TCL_ERROR;
1906        }
1907    }
1908    if (objc > 3) {
1909        if (Tcl_GetIntFromObj(interp, objv[3], &day) != TCL_OK) {
1910            return TCL_ERROR;
1911        }
1912    }
1913    if (objc > 4) {
1914        if (Tcl_GetIntFromObj(interp, objv[4], &month) != TCL_OK) {
1915            return TCL_ERROR;
1916        }
1917    }
1918    if (objc > 5) {
1919        if (Tcl_GetIntFromObj(interp, objv[5], &year) != TCL_OK) {
1920            return TCL_ERROR;
1921        }
1922    }
1923
1924    g_renderer->setEphemerisTime(year, month, day, hours);
1925    return TCL_OK;
1926}
1927
1928static CmdSpec mapOps[] = {
1929    {"attrib",   1, MapAttributionOp,     3, 3, "string"},
1930    {"box",      1, MapBoxOp,             3, 0, "op ?params..."},
1931    {"coords",   1, MapCoordsOp,          4, 6, "token coords ?srs? ?verticalDatum?"},
1932    {"grid",     1, MapGraticuleOp,       3, 4, "bool ?type?"},
1933    {"layer",    2, MapLayerOp,           3, 0, "op ?params...?"},
1934    {"load",     2, MapLoadOp,            4, 5, "options"},
1935    {"pin",      2, MapPinOp,             3, 0, "op ?params...?"},
1936    {"posdisp",  2, MapPositionDisplayOp, 3, 5, "bool ?format? ?precision?"},
1937    {"reset",    1, MapResetOp,           6, 11, "type r g b ?profile xmin ymin xmax ymax?"},
1938    {"scalebar", 1, MapScaleBarOp,        3, 4, "bool ?units?"},
1939    {"setpos",   1, MapSetPositionOp,     2, 4, "x y"},
1940    {"terrain",  1, MapTerrainOp,         3, 0, "op ?params...?"},
1941    {"time",     1, MapTimeOp,            2, 6, "?hours? ?day? ?month? ?year?"},
1942};
1943static int nMapOps = NumCmdSpecs(mapOps);
1944
1945static int
1946MapCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1947       Tcl_Obj *const *objv)
1948{
1949    Tcl_ObjCmdProc *proc;
1950
1951    proc = GetOpFromObj(interp, nMapOps, mapOps,
1952                        CMDSPEC_ARG1, objc, objv, 0);
1953    if (proc == NULL) {
1954        return TCL_ERROR;
1955    }
1956    return (*proc) (clientData, interp, objc, objv);
1957}
1958
1959static int
1960MouseClickOp(ClientData clientData, Tcl_Interp *interp, int objc,
1961             Tcl_Obj *const *objv)
1962{
1963    int button;
1964    double x, y;
1965
1966    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
1967        return TCL_ERROR;
1968    }
1969    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
1970        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
1971        return TCL_ERROR;
1972    }
1973
1974    g_renderer->mouseClick(button, x, y);
1975    return TCL_OK;
1976}
1977
1978static int
1979MouseDoubleClickOp(ClientData clientData, Tcl_Interp *interp, int objc,
1980                   Tcl_Obj *const *objv)
1981{
1982    int button;
1983    double x, y;
1984
1985    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
1986        return TCL_ERROR;
1987    }
1988    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
1989        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
1990        return TCL_ERROR;
1991    }
1992
1993    g_renderer->mouseDoubleClick(button, x, y);
1994    return TCL_OK;
1995}
1996
1997static int
1998MouseDragOp(ClientData clientData, Tcl_Interp *interp, int objc,
1999            Tcl_Obj *const *objv)
2000{
2001    int button;
2002    double x, y;
2003
2004    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
2005        return TCL_ERROR;
2006    }
2007    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
2008        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
2009        return TCL_ERROR;
2010    }
2011
2012    g_renderer->mouseDrag(button, x, y);
2013    return TCL_OK;
2014}
2015
2016static int
2017MouseMotionOp(ClientData clientData, Tcl_Interp *interp, int objc,
2018              Tcl_Obj *const *objv)
2019{
2020    double x, y;
2021
2022    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
2023        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
2024        return TCL_ERROR;
2025    }
2026
2027    g_renderer->mouseMotion(x, y);
2028    return TCL_OK;
2029}
2030
2031static int
2032MouseReleaseOp(ClientData clientData, Tcl_Interp *interp, int objc,
2033               Tcl_Obj *const *objv)
2034{
2035    int button;
2036    double x, y;
2037
2038    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
2039        return TCL_ERROR;
2040    }
2041    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
2042        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
2043        return TCL_ERROR;
2044    }
2045
2046    g_renderer->mouseRelease(button, x, y);
2047    return TCL_OK;
2048}
2049
2050static int
2051MouseScrollOp(ClientData clientData, Tcl_Interp *interp, int objc,
2052              Tcl_Obj *const *objv)
2053{
2054    int direction;
2055
2056    if (Tcl_GetIntFromObj(interp, objv[2], &direction) != TCL_OK) {
2057        return TCL_ERROR;
2058    }
2059
2060    g_renderer->mouseScroll(direction);
2061    return TCL_OK;
2062}
2063
2064static CmdSpec mouseOps[] = {
2065    {"click",    1, MouseClickOp,       5, 5, "button x y"},
2066    {"dblclick", 2, MouseDoubleClickOp, 5, 5, "button x y"},
2067    {"drag",     2, MouseDragOp,        5, 5, "button x y"},
2068    {"motion",   1, MouseMotionOp,      4, 4, "x y"},
2069    {"release",  1, MouseReleaseOp,     5, 5, "button x y"},
2070    {"scroll",   1, MouseScrollOp,      3, 3, "direction"},
2071};
2072static int nMouseOps = NumCmdSpecs(mouseOps);
2073
2074static int
2075MouseCmd(ClientData clientData, Tcl_Interp *interp, int objc,
2076         Tcl_Obj *const *objv)
2077{
2078    Tcl_ObjCmdProc *proc;
2079
2080    proc = GetOpFromObj(interp, nMouseOps, mouseOps,
2081                        CMDSPEC_ARG1, objc, objv, 0);
2082    if (proc == NULL) {
2083        return TCL_ERROR;
2084    }
2085    return (*proc) (clientData, interp, objc, objv);
2086}
2087
2088static int
2089RendererRenderOp(ClientData clientData, Tcl_Interp *interp, int objc,
2090                 Tcl_Obj *const *objv)
2091{
2092    g_renderer->eventuallyRender();
2093    return TCL_OK;
2094}
2095
2096static CmdSpec rendererOps[] = {
2097    {"render",     1, RendererRenderOp, 2, 2, ""},
2098};
2099static int nRendererOps = NumCmdSpecs(rendererOps);
2100
2101static int
2102RendererCmd(ClientData clientData, Tcl_Interp *interp, int objc,
2103            Tcl_Obj *const *objv)
2104{
2105    Tcl_ObjCmdProc *proc;
2106
2107    proc = GetOpFromObj(interp, nRendererOps, rendererOps,
2108                        CMDSPEC_ARG1, objc, objv, 0);
2109    if (proc == NULL) {
2110        return TCL_ERROR;
2111    }
2112    return (*proc) (clientData, interp, objc, objv);
2113}
2114
2115static int
2116ScreenBgColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
2117                Tcl_Obj *const *objv)
2118{
2119    float color[3];
2120
2121    if (GetFloatFromObj(interp, objv[2], &color[0]) != TCL_OK ||
2122        GetFloatFromObj(interp, objv[3], &color[1]) != TCL_OK ||
2123        GetFloatFromObj(interp, objv[4], &color[2]) != TCL_OK) {
2124        return TCL_ERROR;
2125    }
2126
2127    g_renderer->setBackgroundColor(color);
2128    return TCL_OK;
2129}
2130
2131static int
2132ScreenCoordsOp(ClientData clientData, Tcl_Interp *interp, int objc,
2133               Tcl_Obj *const *objv)
2134{
2135    int tokenLength;
2136    const char *token = Tcl_GetStringFromObj(objv[2], &tokenLength);
2137    int numCoords;
2138    Tcl_Obj **coords;
2139    if (Tcl_ListObjGetElements(interp, objv[3], &numCoords, &coords) != TCL_OK) {
2140        return TCL_ERROR;
2141    }
2142    if (numCoords == 0) {
2143        Tcl_AppendResult(interp, "no x,y,z coordinates in list", (char *)NULL);
2144        return TCL_ERROR;
2145    }
2146    if (numCoords % 3 != 0) {
2147        Tcl_AppendResult(interp, "invalid number of coordinates in list",
2148                         (char *)NULL);
2149        return TCL_ERROR;
2150    }
2151
2152    const osgEarth::SpatialReference *srs = NULL;
2153    if (objc < 5) {
2154        srs = g_renderer->getMapSRS();
2155        if (srs == NULL) {
2156            Tcl_AppendResult(interp, "Could not determine map SRS", (char*)NULL);
2157            return TCL_ERROR;
2158        }
2159    } else {
2160        std::string srsInit(Tcl_GetString(objv[4]));
2161        std::string verticalDatum;
2162        if (objc > 5) {
2163            verticalDatum = Tcl_GetString(objv[5]);
2164        }
2165        srs = osgEarth::SpatialReference::get(srsInit, verticalDatum);
2166        if (srs == NULL) {
2167            Tcl_AppendResult(interp, "bad SRS \"", srsInit.c_str(), "\"", (char*)NULL);
2168            return TCL_ERROR;
2169        }
2170    }
2171    Tcl_Obj *listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
2172    std::vector<osg::Vec3d> coordVec;
2173    for (int i = 0; i < numCoords; i += 3) {
2174        double x, y, z;
2175        if (Tcl_GetDoubleFromObj(interp, coords[i], &x) != TCL_OK ||
2176            Tcl_GetDoubleFromObj(interp, coords[i+1], &y) != TCL_OK ||
2177            Tcl_GetDoubleFromObj(interp, coords[i+2], &z) != TCL_OK) {
2178            return TCL_ERROR;
2179        }
2180        // ALTMODE_RELATIVE is height above terrain, ALTMODE_ABSOLUTE means
2181        // relative to the vertical datum
2182        osgEarth::GeoPoint mapPoint(srs, x, y, z, osgEarth::ALTMODE_ABSOLUTE);
2183        osg::Vec3d world;
2184        if (g_renderer->getWorldCoords(mapPoint, &world)) {
2185            coordVec.push_back(world);
2186        } else {
2187            coordVec.push_back(osg::Vec3d(std::numeric_limits<double>::quiet_NaN(),
2188                                          std::numeric_limits<double>::quiet_NaN(),
2189                                          std::numeric_limits<double>::quiet_NaN()));
2190        }
2191    }
2192    g_renderer->worldToScreen(coordVec);
2193    for (std::vector<osg::Vec3d>::iterator itr = coordVec.begin();
2194         itr != coordVec.end(); ++itr) {
2195        Tcl_Obj *objPtr = Tcl_NewDoubleObj(itr->x());
2196        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
2197        objPtr = Tcl_NewDoubleObj(itr->y());
2198        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
2199        objPtr = Tcl_NewDoubleObj(itr->z());
2200        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
2201    }
2202    // send coords to client
2203    int listLength;
2204    const char *string = Tcl_GetStringFromObj(listObjPtr, &listLength);
2205    size_t length = listLength + tokenLength + 22;
2206    char *mesg = new char[length];
2207    length = snprintf(mesg, length, "nv>screen coords %s {%s}\n", token, string);
2208    Tcl_DecrRefCount(listObjPtr);
2209    queueResponse(mesg, length, Response::VOLATILE);
2210    delete [] mesg;
2211    return TCL_OK;
2212}
2213
2214static int
2215ScreenSizeOp(ClientData clientData, Tcl_Interp *interp, int objc,
2216             Tcl_Obj *const *objv)
2217{
2218    int width, height;
2219
2220    if (Tcl_GetIntFromObj(interp, objv[2], &width) != TCL_OK ||
2221        Tcl_GetIntFromObj(interp, objv[3], &height) != TCL_OK) {
2222        return TCL_ERROR;
2223    }
2224
2225    g_renderer->setWindowSize(width, height);
2226    return TCL_OK;
2227}
2228
2229static CmdSpec screenOps[] = {
2230    {"bgcolor", 1, ScreenBgColorOp, 5, 5, "r g b"},
2231    {"coords",  1, ScreenCoordsOp, 4, 6, "token coords ?srs? ?verticalDatum?"},
2232    {"size",    1, ScreenSizeOp, 4, 4, "width height"}
2233};
2234static int nScreenOps = NumCmdSpecs(screenOps);
2235
2236static int
2237ScreenCmd(ClientData clientData, Tcl_Interp *interp, int objc,
2238          Tcl_Obj *const *objv)
2239{
2240    Tcl_ObjCmdProc *proc;
2241
2242    proc = GetOpFromObj(interp, nScreenOps, screenOps,
2243                        CMDSPEC_ARG1, objc, objv, 0);
2244    if (proc == NULL) {
2245        return TCL_ERROR;
2246    }
2247    return (*proc) (clientData, interp, objc, objv);
2248}
2249
2250#ifdef USE_READ_THREAD
2251int
2252GeoVis::queueCommands(Tcl_Interp *interp,
2253                      ClientData clientData,
2254                      ReadBuffer *inBufPtr)
2255{
2256    Tcl_DString commandString;
2257    Tcl_DStringInit(&commandString);
2258    fd_set readFds;
2259
2260    FD_ZERO(&readFds);
2261    FD_SET(inBufPtr->file(), &readFds);
2262    while (inBufPtr->isLineAvailable() ||
2263           (select(inBufPtr->file()+1, &readFds, NULL, NULL, NULL) > 0)) {
2264        size_t numBytes;
2265        unsigned char *buffer;
2266
2267        /* A short read is treated as an error here because we assume that we
2268         * will always get commands line by line. */
2269        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
2270            /* Terminate the server if we can't communicate with the client
2271             * anymore. */
2272            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
2273                TRACE("Exiting server on EOF from client");
2274                return -1;
2275            } else {
2276                ERROR("Exiting server, failed to read from client: %s",
2277                      strerror(errno));
2278                return -1;
2279            }
2280        }
2281        Tcl_DStringAppend(&commandString, (char *)buffer, numBytes);
2282        if (Tcl_CommandComplete(Tcl_DStringValue(&commandString))) {
2283            // Add to queue
2284            Command *command = new Command(Command::COMMAND);
2285            command->setMessage((unsigned char *)Tcl_DStringValue(&commandString),
2286                                Tcl_DStringLength(&commandString), Command::VOLATILE);
2287            g_inQueue->enqueue(command);
2288            Tcl_DStringSetLength(&commandString, 0);
2289        }
2290        FD_SET(inBufPtr->file(), &readFds);
2291    }
2292
2293    return 1;
2294}
2295#endif
2296
2297/**
2298 * \brief Execute commands from client in Tcl interpreter
2299 *
2300 * In this threaded model, the select call is for event compression.  We
2301 * want to execute render server commands as long as they keep coming. 
2302 * This lets us execute a stream of many commands but render once.  This
2303 * benefits camera movements, screen resizing, and opacity changes
2304 * (using a slider on the client).  The down side is you don't render
2305 * until there's a lull in the command stream.  If the client needs an
2306 * image, it can issue an "imgflush" command.  That breaks us out of the
2307 * read loop.
2308 */
2309int
2310GeoVis::processCommands(Tcl_Interp *interp,
2311                        ClientData clientData,
2312                        ReadBuffer *inBufPtr,
2313                        int fdOut,
2314                        long timeout)
2315{
2316    int ret = 1;
2317    int status = TCL_OK;
2318
2319    Tcl_DString command;
2320    Tcl_DStringInit(&command);
2321    fd_set readFds;
2322    struct timeval tv, *tvPtr;
2323
2324    FD_ZERO(&readFds);
2325    FD_SET(inBufPtr->file(), &readFds);
2326    tvPtr = NULL;                       /* Wait for the first read. This is so
2327                                         * that we don't spin when no data is
2328                                         * available. */
2329    if (timeout >= 0L) {
2330        tv.tv_sec = 0L;
2331        tv.tv_usec = timeout;
2332        tvPtr = &tv;
2333    } else {
2334        TRACE("Blocking on select()");
2335    }
2336    while (inBufPtr->isLineAvailable() ||
2337           (ret = select(inBufPtr->file()+1, &readFds, NULL, NULL, tvPtr)) > 0) {
2338        size_t numBytes;
2339        unsigned char *buffer;
2340
2341        /* A short read is treated as an error here because we assume that we
2342         * will always get commands line by line. */
2343        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
2344            /* Terminate the server if we can't communicate with the client
2345             * anymore. */
2346            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
2347                TRACE("Exiting server on EOF from client");
2348                return -1;
2349            } else {
2350                ERROR("Exiting server, failed to read from client: %s",
2351                      strerror(errno));
2352                return -1;
2353            }
2354        }
2355        Tcl_DStringAppend(&command, (char *)buffer, numBytes);
2356        if (Tcl_CommandComplete(Tcl_DStringValue(&command))) {
2357            struct timeval start, finish;
2358            gettimeofday(&start, NULL);
2359            g_stats.nCommands++;
2360            status = ExecuteCommand(interp, &command);
2361            gettimeofday(&finish, NULL);
2362            g_stats.cmdTime += (MSECS_ELAPSED(start, finish) / 1.0e+3);
2363            if (status == TCL_BREAK) {
2364                return 2;               /* This was caused by a "imgflush"
2365                                         * command. Break out of the read loop
2366                                         * and allow a new image to be
2367                                         * rendered. */
2368            } else { //if (status != TCL_OK) {
2369                ret = 0;
2370                if (handleError(interp, clientData, status, fdOut) < 0) {
2371                    return -1;
2372                }
2373            }
2374            if (status == TCL_OK) {
2375                ret = 3;
2376            }
2377        }
2378
2379        tv.tv_sec = tv.tv_usec = 0L;    /* On successive reads, we break out
2380                                         * if no data is available. */
2381        FD_SET(inBufPtr->file(), &readFds);
2382        tvPtr = &tv;
2383    }
2384
2385    return ret;
2386}
2387
2388/**
2389 * \brief Send error message to client socket
2390 */
2391int
2392GeoVis::handleError(Tcl_Interp *interp,
2393                    ClientData clientData,
2394                    int status, int fdOut)
2395{
2396    const char *string;
2397    int nBytes;
2398
2399    if (status != TCL_OK) {
2400        string = Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY);
2401        nBytes = strlen(string);
2402        if (nBytes > 0) {
2403            TRACE("status=%d errorInfo=(%s)", status, string);
2404
2405            std::ostringstream oss;
2406            oss << "nv>viserror -type internal_error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
2407            std::string ostr = oss.str();
2408            nBytes = ostr.length();
2409
2410            if (queueResponse(ostr.c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
2411                return -1;
2412            }
2413        }
2414    }
2415
2416    std::string msg = getUserMessages();
2417    nBytes = msg.length();
2418    if (nBytes > 0) {
2419        string = msg.c_str();
2420        TRACE("userError=(%s)", string);
2421
2422        std::ostringstream oss;
2423        oss << "nv>viserror -type error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
2424        std::string ostr = oss.str();
2425        nBytes = ostr.length();
2426
2427        if (queueResponse(ostr.c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
2428            return -1;
2429        }
2430
2431        clearUserMessages();
2432    }
2433
2434    return 0;
2435}
2436
2437/**
2438 * \brief Create Tcl interpreter and add commands
2439 *
2440 * \return The initialized Tcl interpreter
2441 */
2442void
2443GeoVis::initTcl(Tcl_Interp *interp, ClientData clientData)
2444{
2445    Tcl_MakeSafe(interp);
2446    Tcl_CreateObjCommand(interp, "camera",         CameraCmd,         clientData, NULL);
2447    Tcl_CreateObjCommand(interp, "clientinfo",     ClientInfoCmd,     clientData, NULL);
2448    Tcl_CreateObjCommand(interp, "colormap",       ColorMapCmd,       clientData, NULL);
2449    Tcl_CreateObjCommand(interp, "file",           FileCmd,           clientData, NULL);
2450    Tcl_CreateObjCommand(interp, "imgflush",       ImageFlushCmd,     clientData, NULL);
2451    Tcl_CreateObjCommand(interp, "key",            KeyCmd,            clientData, NULL);
2452    Tcl_CreateObjCommand(interp, "legend",         LegendCmd,         clientData, NULL);
2453    Tcl_CreateObjCommand(interp, "map",            MapCmd,            clientData, NULL);
2454    Tcl_CreateObjCommand(interp, "mouse",          MouseCmd,          clientData, NULL);
2455    Tcl_CreateObjCommand(interp, "renderer",       RendererCmd,       clientData, NULL);
2456    Tcl_CreateObjCommand(interp, "screen",         ScreenCmd,         clientData, NULL);
2457}
2458
2459/**
2460 * \brief Delete Tcl commands and interpreter
2461 */
2462void GeoVis::exitTcl(Tcl_Interp *interp)
2463{
2464    Tcl_DeleteCommand(interp, "camera");
2465    Tcl_DeleteCommand(interp, "clientinfo");
2466    Tcl_DeleteCommand(interp, "colormap");
2467    Tcl_DeleteCommand(interp, "file");
2468    Tcl_DeleteCommand(interp, "imgflush");
2469    Tcl_DeleteCommand(interp, "key");
2470    Tcl_DeleteCommand(interp, "legend");
2471    Tcl_DeleteCommand(interp, "map");
2472    Tcl_DeleteCommand(interp, "mouse");
2473    Tcl_DeleteCommand(interp, "renderer");
2474    Tcl_DeleteCommand(interp, "screen");
2475
2476    Tcl_DeleteInterp(interp);
2477}
Note: See TracBrowser for help on using the repository browser.