source: geovis/trunk/RendererCmd.cpp @ 5119

Last change on this file since 5119 was 5119, checked in by ldelgass, 9 years ago

Add legend option for blending against a background color

File size: 83.2 KB
Line 
1/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 * Copyright (C) 2004-2015  HUBzero Foundation, LLC
4 *
5 * Author: Leif Delgass <ldelgass@purdue.edu>
6 */
7
8#include <cstdlib>
9#include <cstdio>
10#include <cstring>
11#include <cfloat>
12#include <cerrno>
13#include <string>
14#include <sstream>
15#include <limits>
16#include <vector>
17#include <unistd.h>
18#include <sys/select.h>
19#include <sys/uio.h>
20#include <tcl.h>
21
22#include <osgDB/FileUtils>
23#include <osgDB/FileNameUtils>
24
25#include <osgEarth/Version>
26#include <osgEarth/Registry>
27#include <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    bool opaque = true;
758    if (objc > 4) {
759        if (GetBooleanFromObj(interp, objv[4], &opaque) != TCL_OK) {
760            return TCL_ERROR;
761        }
762    }
763    float bgColor[3];
764    if (objc > 5) {
765        if (GetFloatFromObj(interp, objv[5], &bgColor[0]) != TCL_OK ||
766            GetFloatFromObj(interp, objv[6], &bgColor[1]) != TCL_OK ||
767            GetFloatFromObj(interp, objv[7], &bgColor[2]) != TCL_OK) {
768            return TCL_ERROR;
769        }
770    } else {
771        memset(bgColor, 0, sizeof(float)*3);
772    }
773    osg::ref_ptr<osg::Image> imgData = new osg::Image();
774
775#if defined(RENDER_TARGA) || defined(DEBUG)
776    if (!g_renderer->renderColorMap(colorMapName, width, height, imgData, opaque,
777                                    bgColor, true)) {
778        Tcl_AppendResult(interp, "Color map \"",
779                         colorMapName, "\" was not found", (char*)NULL);
780        return TCL_ERROR;
781    }
782#else
783    if (!g_renderer->renderColorMap(colorMapName, width, height, imgData, opaque,
784                                    bgColor)) {
785        Tcl_AppendResult(interp, "Color map \"",
786                         colorMapName, "\" was not found", (char*)NULL);
787        return TCL_ERROR;
788    }
789#endif
790
791#ifdef DEBUG
792# ifdef RENDER_TARGA
793    writeTGAFile("/tmp/legend.tga", imgData->data(), width, height,
794                 TARGA_BYTES_PER_PIXEL);
795# else
796    writeTGAFile("/tmp/legend.tga", imgData->data(), width, height,
797                 TARGA_BYTES_PER_PIXEL, true);
798# endif
799#else
800    char cmd[256];
801    float min, max;
802    g_renderer->getColorMapRange(colorMapName, &min, &max);
803    snprintf(cmd, sizeof(cmd), "nv>legend {%s} %g %g", colorMapName, min, max);
804
805# ifdef USE_THREADS
806#  ifdef RENDER_TARGA
807    queueTGA(g_outQueue, cmd, imgData->data(), width, height,
808             TARGA_BYTES_PER_PIXEL);
809#  else
810    queuePPM(g_outQueue, cmd, imgData->data(), width, height);
811#  endif
812# else
813#  ifdef RENDER_TARGA
814    writeTGA(g_fdOut, cmd, imgData->data(), width, height,
815             TARGA_BYTES_PER_PIXEL);
816#  else
817    writePPM(g_fdOut, cmd, imgData->data(), width, height);
818#  endif
819# endif // USE_THREADS
820#endif // DEBUG
821
822    return TCL_OK;
823}
824
825static int
826MapAttributionOp(ClientData clientData, Tcl_Interp *interp, int objc,
827                 Tcl_Obj *const *objv)
828{
829    g_renderer->setAttribution(Tcl_GetString(objv[2]));
830    return TCL_OK;
831}
832
833static int
834MapBoxClearOp(ClientData clientData, Tcl_Interp *interp, int objc,
835              Tcl_Obj *const *objv)
836{
837    g_renderer->clearBoxSelection();
838    return TCL_OK;
839}
840
841static int
842MapBoxInitOp(ClientData clientData, Tcl_Interp *interp, int objc,
843             Tcl_Obj *const *objv)
844{
845    int x, y;
846    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
847        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
848        return TCL_ERROR;
849    }
850    g_renderer->initBoxSelection(x, y);
851    return TCL_OK;
852}
853
854static int
855MapBoxUpdateOp(ClientData clientData, Tcl_Interp *interp, int objc,
856               Tcl_Obj *const *objv)
857{
858    int x, y;
859    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
860        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
861        return TCL_ERROR;
862    }
863    g_renderer->updateBoxSelection(x, y);
864    return TCL_OK;
865}
866
867static CmdSpec mapBoxOps[] = {
868    {"clear",   1, MapBoxClearOp,   3, 3, ""},
869    {"init",    1, MapBoxInitOp,    5, 5, "x y"},
870    {"update",  1, MapBoxUpdateOp,  5, 5, "x y"},
871};
872static int nMapBoxOps = NumCmdSpecs(mapBoxOps);
873
874static int
875MapBoxOp(ClientData clientData, Tcl_Interp *interp, int objc,
876         Tcl_Obj *const *objv)
877{
878    Tcl_ObjCmdProc *proc;
879
880    proc = GetOpFromObj(interp, nMapBoxOps, mapBoxOps,
881                        CMDSPEC_ARG2, objc, objv, 0);
882    if (proc == NULL) {
883        return TCL_ERROR;
884    }
885    return (*proc) (clientData, interp, objc, objv);
886}
887
888static int
889MapCoordsOp(ClientData clientData, Tcl_Interp *interp, int objc,
890            Tcl_Obj *const *objv)
891{
892    int tokenLength;
893    const char *token = Tcl_GetStringFromObj(objv[2], &tokenLength);
894    int numCoords;
895    Tcl_Obj **coords;
896    if (Tcl_ListObjGetElements(interp, objv[3], &numCoords, &coords) != TCL_OK) {
897        return TCL_ERROR;
898    }
899    if (numCoords == 0) {
900        Tcl_AppendResult(interp, "no x,y coordinates in list", (char *)NULL);
901        return TCL_ERROR;
902    }
903    if (numCoords & 1) {
904        Tcl_AppendResult(interp, "odd number of x, y coordinates in list",
905                         (char *)NULL);
906        return TCL_ERROR;
907    }
908    const osgEarth::SpatialReference *outSRS = NULL;
909    if (objc > 4) {
910        std::string srsInit;
911        std::string verticalDatum;
912        srsInit = Tcl_GetString(objv[4]);
913        if (objc > 5) {
914            verticalDatum = Tcl_GetString(objv[5]);
915        }
916        outSRS = osgEarth::SpatialReference::get(srsInit, verticalDatum);
917        if (outSRS == NULL) {
918            Tcl_AppendResult(interp, "bad SRS \"", srsInit.c_str(), "\"",
919                             (char*)NULL);
920            return TCL_ERROR;
921        }
922    }
923    Tcl_Obj *listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
924    std::vector<osg::Vec3d> coordVec;
925    for (int i = 0; i < numCoords; i += 2) {
926        int x, y;
927        if ((Tcl_GetIntFromObj(interp, coords[i], &x) != TCL_OK) ||
928            (Tcl_GetIntFromObj(interp, coords[i+1], &y) != TCL_OK)) {
929            return TCL_ERROR;
930        }
931        osgEarth::GeoPoint mapPoint;
932        if (g_renderer->mapMouseCoords(x, y, mapPoint)) {
933            // altmode absolute
934            coordVec.push_back(osg::Vec3d(mapPoint.x(), mapPoint.y(), mapPoint.z()));
935        } else {
936            coordVec.push_back(osg::Vec3d(std::numeric_limits<double>::quiet_NaN(),
937                                          std::numeric_limits<double>::quiet_NaN(),
938                                          std::numeric_limits<double>::quiet_NaN()));
939        }
940    }
941    if (outSRS != NULL) {
942        g_renderer->getMapSRS()->transform(coordVec, outSRS);
943    } else {
944        outSRS = g_renderer->getMapSRS();
945    }
946    for (std::vector<osg::Vec3d>::iterator itr = coordVec.begin();
947         itr != coordVec.end(); ++itr) {
948        Tcl_Obj *objPtr = Tcl_NewDoubleObj(itr->x());
949        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
950        objPtr = Tcl_NewDoubleObj(itr->y());
951        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
952        objPtr = Tcl_NewDoubleObj(itr->z());
953        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
954    }
955    // send coords to client
956    int listLength;
957    const char *string = Tcl_GetStringFromObj(listObjPtr, &listLength);
958    size_t length = listLength + tokenLength +
959        outSRS->getHorizInitString().length() +
960        outSRS->getVertInitString().length() +
961        25;
962    char *mesg = new char[length];
963    length = snprintf(mesg, length, "nv>map coords %s {%s} {%s} {%s}\n", token, string,
964                      outSRS->getHorizInitString().c_str(),
965                      outSRS->getVertInitString().c_str());
966    Tcl_DecrRefCount(listObjPtr);
967    queueResponse(mesg, length, Response::VOLATILE);
968    delete [] mesg;
969    return TCL_OK;
970}
971
972static int
973MapGraticuleOp(ClientData clientData, Tcl_Interp *interp, int objc,
974               Tcl_Obj *const *objv)
975{
976    bool state;
977    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
978        return TCL_ERROR;
979    }
980    if (objc > 3) {
981        Renderer::GraticuleType type;
982        char *typeStr = Tcl_GetString(objv[3]);
983        if (typeStr[0] == 'g' && strcmp(typeStr, "geodetic") == 0) {
984            type = Renderer::GRATICULE_GEODETIC;
985        } else if (typeStr[0] == 'u' && strcmp(typeStr, "utm") == 0) {
986            type = Renderer::GRATICULE_UTM;
987        } else if (typeStr[0] == 'm' && strcmp(typeStr, "mgrs") == 0) {
988            type = Renderer::GRATICULE_MGRS;
989        } else {
990            return TCL_ERROR;
991        }
992        g_renderer->setGraticule(state, type);
993    } else {
994        g_renderer->setGraticule(state);
995    }
996    return TCL_OK;
997}
998
999static int
1000MapLayerAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
1001              Tcl_Obj *const *objv)
1002{
1003    char *name = Tcl_GetString(objv[3]);
1004    char *type = Tcl_GetString(objv[4]);
1005    if (type[0] == 'i' && strcmp(type, "image") == 0) {
1006        bool ret;
1007        char *driver = Tcl_GetString(objv[5]);
1008        std::string url;
1009        if (objc > 6) {
1010            char *urlIn = Tcl_GetString(objv[6]);
1011            url = g_renderer->getCanonicalPath(std::string(urlIn));
1012            if (url.empty()) {
1013                Tcl_AppendResult(interp, "file not found: \"",
1014                                 urlIn, "\"", (char*)NULL);
1015                return TCL_ERROR;
1016            }
1017        }
1018        bool cache = true;
1019        if (objc > 7) {
1020            if (GetBooleanFromObj(interp, objv[7], &cache) != TCL_OK) {
1021                return TCL_ERROR;
1022            }
1023        }
1024        bool shared = false;
1025        bool visible = true;
1026        int minLOD = 0;
1027        int maxLOD = 23;
1028        float minRange = 0.f;
1029        float maxRange = FLT_MAX;
1030#if OSGEARTH_MIN_VERSION_REQUIRED(2, 5, 1)
1031        if (driver[0] == 'c' && strcmp(driver, "colorramp") == 0) {
1032            osgEarth::Drivers::ColorRampOptions colorRampOpts;
1033            char *edriver = Tcl_GetString(objv[8]);
1034            char *profile = Tcl_GetString(objv[9]);
1035            char *colormap = Tcl_GetString(objv[10]);
1036            if (edriver[0] == 'g' && strcmp(edriver, "gdal") == 0) {
1037                osgEarth::Drivers::GDALOptions opts;
1038                opts.url() = url;
1039                osgEarth::ElevationLayerOptions elevOpts(name, opts);
1040                if (!cache) {
1041                    elevOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
1042                }
1043                if (profile != NULL) {
1044                    elevOpts.driver()->profile() = osgEarth::ProfileOptions(profile);
1045                }
1046                colorRampOpts.elevationLayer() = elevOpts;
1047            } else if (edriver[0] == 't' && strcmp(edriver, "tms") == 0) {
1048                osgEarth::Drivers::TMSOptions opts;
1049                //char *tmsType = Tcl_GetString(objv[8]);
1050                //char *format = Tcl_GetString(objv[9]);
1051                opts.url() = url;
1052                //opts.tmsType() = tmsType;
1053                //opts.format() = format;
1054                osgEarth::ElevationLayerOptions elevOpts(name, opts);
1055                if (!cache) {
1056                    elevOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
1057                }
1058                if (profile != NULL) {
1059                    elevOpts.driver()->profile() = osgEarth::ProfileOptions(profile);
1060                }
1061                colorRampOpts.elevationLayer() = elevOpts;
1062            }
1063            colorRampOpts.ramp() = g_renderer->getColorMapFilePath(colormap);
1064            ret = g_renderer->addImageLayer(name, colorRampOpts, cache, shared, visible, minLOD, maxLOD);
1065        } else
1066#endif
1067            if (driver[0] == 'd' && strcmp(driver, "debug") == 0) {
1068            osgEarth::Drivers::DebugOptions opts;
1069            ret = g_renderer->addImageLayer(name, opts, cache, shared, visible, minLOD, maxLOD);
1070        } else if (driver[0] == 'g' && strcmp(driver, "gdal") == 0) {
1071            osgEarth::Drivers::GDALOptions opts;
1072            opts.url() = url;
1073            ret = g_renderer->addImageLayer(name, opts, cache, shared, visible, minLOD, maxLOD);
1074            g_renderer->setImageLayerVisibleRange(name, minRange, maxRange);
1075        } else if (driver[0] == 't' && strcmp(driver, "tms") == 0) {
1076            osgEarth::Drivers::TMSOptions opts;
1077            //char *tmsType = Tcl_GetString(objv[8]);
1078            //char *format = Tcl_GetString(objv[9]);
1079            opts.url() = url;
1080            //opts.tmsType() = tmsType;
1081            //opts.format() = format;
1082            ret = g_renderer->addImageLayer(name, opts, cache, shared, visible, minLOD, maxLOD);
1083            g_renderer->setImageLayerVisibleRange(name, minRange, maxRange);
1084        } else if (driver[0] == 'w' && strcmp(driver, "wms") == 0) {
1085            osgEarth::Drivers::WMSOptions opts;
1086            char *wmsLayers = Tcl_GetString(objv[8]);
1087            char *format = Tcl_GetString(objv[9]);
1088            bool transparent;
1089            if (GetBooleanFromObj(interp, objv[10], &transparent) != TCL_OK) {
1090                return TCL_ERROR;
1091            }
1092            opts.url() = url;
1093            opts.layers() = wmsLayers;
1094            opts.format() = format;
1095            opts.transparent() = transparent;
1096
1097            ret = g_renderer->addImageLayer(name, opts, cache, shared, visible, minLOD, maxLOD);
1098            g_renderer->setImageLayerVisibleRange(name, minRange, maxRange);
1099        } else if (driver[0] == 'x' && strcmp(driver, "xyz") == 0) {
1100            osgEarth::Drivers::XYZOptions opts;
1101            opts.url() = url;
1102            opts.profile() = osgEarth::ProfileOptions("global-mercator");
1103            //bool invertY = false;
1104            //opts.invertY() = invertY;
1105            //opts.format() = Tcl_GetString(objv[8]);
1106            ret = g_renderer->addImageLayer(name, opts, cache, shared, visible, minLOD, maxLOD);
1107            g_renderer->setImageLayerVisibleRange(name, minRange, maxRange);
1108        } else {
1109            Tcl_AppendResult(interp, "unknown image driver \"", driver,
1110                             "\": should be 'debug', 'gdal', 'tms', 'wms' or 'xyz'", (char*)NULL);
1111            return TCL_ERROR;
1112        }
1113        if (!ret) {
1114            Tcl_AppendResult(interp, "Failed to add image layer \"", name, "\"", (char*)NULL);
1115            return TCL_ERROR;
1116        }
1117    } else if (type[0] == 'e' && strcmp(type, "elevation") == 0) {
1118        char *driver = Tcl_GetString(objv[5]);
1119        char *urlIn = Tcl_GetString(objv[6]);
1120        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
1121        if (url.empty()) {
1122            Tcl_AppendResult(interp, "file not found: \"",
1123                             urlIn, "\"", (char*)NULL);
1124            return TCL_ERROR;
1125        }
1126        bool cache = true;
1127        bool visible = true;
1128        int minLOD = 0;
1129        int maxLOD = 23;
1130        if (driver[0] == 'g' && strcmp(driver, "gdal") == 0) {
1131            osgEarth::Drivers::GDALOptions opts;
1132            opts.url() = url;
1133            g_renderer->addElevationLayer(name, opts, cache, visible, minLOD, maxLOD);
1134        } else if (driver[0] == 't' && strcmp(driver, "tms") == 0) {
1135            osgEarth::Drivers::TMSOptions opts;
1136            //char *tmsType = Tcl_GetString(objv[7]);
1137            //char *format = Tcl_GetString(objv[8]);
1138            opts.url() = url;
1139            //opts.tmsType() = tmsType;
1140            //opts.format() = format;
1141            g_renderer->addElevationLayer(name, opts, cache, visible, minLOD, maxLOD);
1142        } else {
1143            Tcl_AppendResult(interp, "unknown elevation driver \"", driver,
1144                             "\": should be 'gdal' or 'tms'", (char*)NULL);
1145            return TCL_ERROR;
1146        }
1147    } else if (type[0] == 'p' && strcmp(type, "point") == 0) {
1148        osgEarth::Drivers::OGRFeatureOptions opts;
1149        char *urlIn = Tcl_GetString(objv[5]);
1150        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
1151        if (url.empty()) {
1152            Tcl_AppendResult(interp, "file not found: \"",
1153                             urlIn, "\"", (char*)NULL);
1154            return TCL_ERROR;
1155        }
1156        float r, g, b;
1157        if (GetFloatFromObj(interp, objv[6], &r) != TCL_OK ||
1158            GetFloatFromObj(interp, objv[7], &g) != TCL_OK ||
1159            GetFloatFromObj(interp, objv[8], &b) != TCL_OK) {
1160            return TCL_ERROR;
1161        }
1162        float ptSize;
1163        if (GetFloatFromObj(interp, objv[9], &ptSize) != TCL_OK) {
1164            return TCL_ERROR;
1165        }
1166        opts.url() = url;
1167
1168        osgEarth::Symbology::Style style;
1169        osgEarth::Symbology::PointSymbol *ps = style.getOrCreateSymbol<osgEarth::Symbology::PointSymbol>();
1170        ps->fill()->color() = osgEarth::Symbology::Color(r, g, b);
1171        ps->size() = ptSize;
1172
1173        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
1174        rs->depthOffset()->enabled() = true;
1175        rs->depthOffset()->minBias() = 1000;
1176
1177        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
1178        geomOpts.featureOptions() = opts;
1179        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
1180        geomOpts.styles()->addStyle(style);
1181        geomOpts.enableLighting() = false;
1182        geomOpts.minRange() = 0.f;
1183        geomOpts.maxRange() = FLT_MAX;
1184        //geomOpts.renderOrder = int;
1185        //geomOpts.depthTestEnabled = bool;
1186        g_renderer->addModelLayer(name, geomOpts);
1187    } else if (type[0] == 'p' && strcmp(type, "polygon") == 0) {
1188        osgEarth::Drivers::OGRFeatureOptions opts;
1189        char *urlIn = Tcl_GetString(objv[5]);
1190        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
1191        if (url.empty()) {
1192            Tcl_AppendResult(interp, "file not found: \"",
1193                             urlIn, "\"", (char*)NULL);
1194            return TCL_ERROR;
1195        }
1196        float r, g, b;
1197        if (GetFloatFromObj(interp, objv[6], &r) != TCL_OK ||
1198            GetFloatFromObj(interp, objv[7], &g) != TCL_OK ||
1199            GetFloatFromObj(interp, objv[8], &b) != TCL_OK) {
1200            return TCL_ERROR;
1201        }
1202        opts.url() = url;
1203
1204        osgEarth::Symbology::Style style;
1205        osgEarth::Symbology::PolygonSymbol *ps = style.getOrCreateSymbol<osgEarth::Symbology::PolygonSymbol>();
1206        ps->fill()->color() = osgEarth::Symbology::Color(r, g, b);
1207#if 1
1208        osgEarth::Symbology::AltitudeSymbol *alt = style.getOrCreateSymbol<osgEarth::Symbology::AltitudeSymbol>();
1209        alt->clamping() = osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
1210        alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_DRAPE;
1211        //alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
1212#endif
1213        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
1214        rs->depthOffset()->enabled() = true;
1215        rs->depthOffset()->minBias() = 1000;
1216
1217        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
1218        geomOpts.featureOptions() = opts;
1219        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
1220        geomOpts.styles()->addStyle(style);
1221        geomOpts.enableLighting() = false;
1222        geomOpts.minRange() = 0.f;
1223        geomOpts.maxRange() = FLT_MAX;
1224        g_renderer->addModelLayer(name, geomOpts);
1225    } else if (type[0] == 'l' && strcmp(type, "line") == 0) {
1226        osgEarth::Drivers::OGRFeatureOptions opts;
1227        char *urlIn = Tcl_GetString(objv[5]);
1228        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
1229        if (url.empty()) {
1230            Tcl_AppendResult(interp, "file not found: \"",
1231                             urlIn, "\"", (char*)NULL);
1232            return TCL_ERROR;
1233        }
1234        float r, g, b;
1235        if (GetFloatFromObj(interp, objv[6], &r) != TCL_OK ||
1236            GetFloatFromObj(interp, objv[7], &g) != TCL_OK ||
1237            GetFloatFromObj(interp, objv[8], &b) != TCL_OK) {
1238            return TCL_ERROR;
1239        }
1240        float lineWidth;
1241        if (GetFloatFromObj(interp, objv[9], &lineWidth) != TCL_OK) {
1242            return TCL_ERROR;
1243        }
1244        opts.url() = url;
1245
1246        osgEarth::Symbology::Style style;
1247        osgEarth::Symbology::LineSymbol *ls = style.getOrCreateSymbol<osgEarth::Symbology::LineSymbol>();
1248        ls->stroke()->color() = osgEarth::Symbology::Color(r, g, b);
1249        ls->stroke()->width() = lineWidth;
1250#if 1
1251        osgEarth::Symbology::AltitudeSymbol *alt = style.getOrCreateSymbol<osgEarth::Symbology::AltitudeSymbol>();
1252        alt->clamping() = osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
1253        //alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_DRAPE;
1254        alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
1255#endif
1256#if 1
1257        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
1258        rs->depthOffset()->enabled() = true;
1259        rs->depthOffset()->minBias() = 1000;
1260#endif
1261        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
1262        geomOpts.featureOptions() = opts;
1263        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
1264        geomOpts.styles()->addStyle(style);
1265        geomOpts.enableLighting() = false;
1266        geomOpts.minRange() = 0.f;
1267        geomOpts.maxRange() = FLT_MAX;
1268        if (objc > 10) {
1269            float min, max;
1270            if (GetFloatFromObj(interp, objv[10], &min) != TCL_OK ||
1271                GetFloatFromObj(interp, objv[11], &max) != TCL_OK) {
1272                return TCL_ERROR;
1273            }
1274            geomOpts.minRange() = min;
1275            geomOpts.maxRange() = max;
1276        }
1277        g_renderer->addModelLayer(name, geomOpts);
1278   } else if (type[0] == 't' && strcmp(type, "text") == 0) {
1279        osgEarth::Drivers::OGRFeatureOptions opts;
1280        char *urlIn = Tcl_GetString(objv[5]);
1281        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
1282        if (url.empty()) {
1283            Tcl_AppendResult(interp, "file not found: \"",
1284                             urlIn, "\"", (char*)NULL);
1285            return TCL_ERROR;
1286        }
1287        char *content = Tcl_GetString(objv[6]);
1288        char *priority = Tcl_GetString(objv[7]);
1289        float fgR, fgG, fgB;
1290        float bgR, bgG, bgB;
1291        float haloWidth, ftSize;
1292        if (GetFloatFromObj(interp, objv[8], &fgR) != TCL_OK ||
1293            GetFloatFromObj(interp, objv[9], &fgG) != TCL_OK ||
1294            GetFloatFromObj(interp, objv[10], &fgB) != TCL_OK ||
1295            GetFloatFromObj(interp, objv[11], &bgR) != TCL_OK ||
1296            GetFloatFromObj(interp, objv[12], &bgG) != TCL_OK ||
1297            GetFloatFromObj(interp, objv[13], &bgB) != TCL_OK ||
1298            GetFloatFromObj(interp, objv[14], &haloWidth) != TCL_OK ||
1299            GetFloatFromObj(interp, objv[15], &ftSize) != TCL_OK) {
1300            return TCL_ERROR;
1301        }
1302        bool removeDupes, declutter;
1303        if (GetBooleanFromObj(interp, objv[16], &removeDupes) != TCL_OK ||
1304            GetBooleanFromObj(interp, objv[17], &declutter) != TCL_OK) {
1305            return TCL_ERROR;
1306        }
1307        opts.url() = url;
1308
1309        osgEarth::Symbology::Style style;
1310        osgEarth::Symbology::TextSymbol *ts = style.getOrCreateSymbol<osgEarth::Symbology::TextSymbol>();
1311        ts->halo()->color() = osgEarth::Symbology::Color(bgR, bgG, bgB);
1312        ts->halo()->width() = haloWidth;
1313        ts->fill()->color() = osgEarth::Symbology::Color(fgR, fgG, fgB);
1314        ts->content() = osgEarth::Symbology::StringExpression(content);
1315        ts->priority() = osgEarth::Symbology::NumericExpression(priority);
1316        ts->removeDuplicateLabels() = removeDupes;
1317        //ts->font() = "Arial";
1318        ts->size() = ftSize;
1319        ts->alignment() = osgEarth::Symbology::TextSymbol::ALIGN_CENTER_CENTER;
1320        ts->declutter() = declutter;
1321        ts->encoding() = osgEarth::Symbology::TextSymbol::ENCODING_UTF8;
1322
1323        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
1324        rs->depthOffset()->enabled() = true;
1325        rs->depthOffset()->minBias() = 1000;
1326
1327        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
1328        geomOpts.featureOptions() = opts;
1329        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
1330        geomOpts.styles()->addStyle(style);
1331        geomOpts.enableLighting() = false;
1332        geomOpts.minRange() = 0.f;
1333        geomOpts.maxRange() = FLT_MAX;
1334        if (objc > 10) {
1335            float min, max;
1336            if (GetFloatFromObj(interp, objv[18], &min) != TCL_OK ||
1337                GetFloatFromObj(interp, objv[19], &max) != TCL_OK) {
1338                return TCL_ERROR;
1339            }
1340            geomOpts.minRange() = min;
1341            geomOpts.maxRange() = max;
1342        }
1343        g_renderer->addModelLayer(name, geomOpts);
1344    } else {
1345        Tcl_AppendResult(interp, "unknown map layer type \"", type,
1346                         "\": should be 'image', 'elevation' or 'model'", (char*)NULL);
1347        return TCL_ERROR;
1348    }
1349    return TCL_OK;
1350}
1351
1352static int
1353MapLayerDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
1354                 Tcl_Obj *const *objv)
1355{
1356    if (objc > 3) {
1357        char *name = Tcl_GetString(objv[3]);
1358        g_renderer->removeImageLayer(name);
1359        g_renderer->removeElevationLayer(name);
1360        g_renderer->removeModelLayer(name);
1361    } else {
1362        g_renderer->clearMap();
1363    }
1364
1365    return TCL_OK;
1366}
1367
1368static int
1369MapLayerMoveOp(ClientData clientData, Tcl_Interp *interp, int objc,
1370               Tcl_Obj *const *objv)
1371{
1372    int pos;
1373    if (Tcl_GetIntFromObj(interp, objv[3], &pos) != TCL_OK) {
1374        return TCL_ERROR;
1375    }
1376    char *name = Tcl_GetString(objv[4]);
1377    if (pos < 0) {
1378        Tcl_AppendResult(interp, "bad layer pos ", pos,
1379                         ": must be positive", (char*)NULL);
1380        return TCL_ERROR;
1381    }
1382    g_renderer->moveImageLayer(name, (unsigned int)pos);
1383    g_renderer->moveElevationLayer(name, (unsigned int)pos);
1384    g_renderer->moveModelLayer(name, (unsigned int)pos);
1385
1386    return TCL_OK;
1387}
1388
1389static int
1390MapLayerOpacityOp(ClientData clientData, Tcl_Interp *interp, int objc,
1391                  Tcl_Obj *const *objv)
1392{
1393    double opacity;
1394    if (Tcl_GetDoubleFromObj(interp, objv[3], &opacity) != TCL_OK) {
1395        return TCL_ERROR;
1396    }
1397    char *name = Tcl_GetString(objv[4]);
1398    if (opacity < 0.0 || opacity > 1.0) {
1399        Tcl_AppendResult(interp, "bad layer opacity ", opacity,
1400                         ": must be [0,1]", (char*)NULL);
1401        return TCL_ERROR;
1402    }
1403    g_renderer->setImageLayerOpacity(name, opacity);
1404    g_renderer->setModelLayerOpacity(name, opacity);
1405
1406    return TCL_OK;
1407}
1408
1409static int
1410MapLayerNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
1411                Tcl_Obj *const *objv)
1412{
1413    std::vector<std::string> layers;
1414    if (objc < 4) {
1415        g_renderer->getImageLayerNames(layers);
1416        g_renderer->getElevationLayerNames(layers);
1417        g_renderer->getModelLayerNames(layers);
1418    } else {
1419        char *type = Tcl_GetString(objv[3]);
1420        if (type[0] == 'i' && strcmp(type, "image") == 0) {
1421            g_renderer->getImageLayerNames(layers);
1422        } else if (type[0] == 'e' && strcmp(type, "elevation") == 0) {
1423            g_renderer->getElevationLayerNames(layers);
1424        } else if (type[0] == 'm' && strcmp(type, "model") == 0) {
1425            g_renderer->getModelLayerNames(layers);
1426        } else {
1427            Tcl_AppendResult(interp, "uknown type \"", type,
1428                         "\": must be image, elevation or model", (char*)NULL);
1429            return TCL_ERROR;
1430        }
1431    }
1432    std::ostringstream oss;
1433    size_t len = 0;
1434    oss << "nv>map names {";
1435    len += 18;
1436    for (size_t i = 0; i < layers.size(); i++) {
1437        oss << "\"" << layers[i] << "\"";
1438        len += 2 + layers[i].length();
1439        if (i < layers.size() - 1) {
1440            oss << " ";
1441            len++;
1442        }
1443    }
1444    oss << "}\n";
1445    len += 2;
1446#ifdef USE_THREADS
1447    queueResponse(oss.str().c_str(), len, Response::VOLATILE);
1448#else
1449    ssize_t bytesWritten = SocketWrite(oss.str().c_str(), len);
1450
1451    if (bytesWritten < 0) {
1452        return TCL_ERROR;
1453    }
1454#endif /*USE_THREADS*/
1455    return TCL_OK;
1456}
1457
1458static int
1459MapLayerVisibleOp(ClientData clientData, Tcl_Interp *interp, int objc,
1460                  Tcl_Obj *const *objv)
1461{
1462    bool visible;
1463    if (GetBooleanFromObj(interp, objv[3], &visible) != TCL_OK) {
1464        return TCL_ERROR;
1465    }
1466    char *name = Tcl_GetString(objv[4]);
1467
1468    g_renderer->setImageLayerVisibility(name, visible);
1469    g_renderer->setElevationLayerVisibility(name, visible);
1470    g_renderer->setModelLayerVisibility(name, visible);
1471
1472    return TCL_OK;
1473}
1474
1475static CmdSpec mapLayerOps[] = {
1476    {"add",     1, MapLayerAddOp,       6, 0, "name type driver ?url? ?args?"},
1477    {"delete",  1, MapLayerDeleteOp,    3, 4, "?name?"},
1478    {"move",    1, MapLayerMoveOp,      5, 5, "pos name"},
1479    {"names",   1, MapLayerNamesOp,     3, 4, "?type?"},
1480    {"opacity", 1, MapLayerOpacityOp,   5, 5, "opacity ?name?"},
1481    {"visible", 1, MapLayerVisibleOp,   5, 5, "bool ?name?"},
1482};
1483static int nMapLayerOps = NumCmdSpecs(mapLayerOps);
1484
1485static int
1486MapLayerOp(ClientData clientData, Tcl_Interp *interp, int objc,
1487           Tcl_Obj *const *objv)
1488{
1489    Tcl_ObjCmdProc *proc;
1490
1491    proc = GetOpFromObj(interp, nMapLayerOps, mapLayerOps,
1492                        CMDSPEC_ARG2, objc, objv, 0);
1493    if (proc == NULL) {
1494        return TCL_ERROR;
1495    }
1496    return (*proc) (clientData, interp, objc, objv);
1497}
1498
1499static int
1500MapLoadOp(ClientData clientData, Tcl_Interp *interp, int objc,
1501          Tcl_Obj *const *objv)
1502{
1503    char *opt = Tcl_GetString(objv[2]);
1504    if (opt[0] == 'f' && strcmp(opt, "file") == 0) {
1505        g_renderer->loadEarthFile(Tcl_GetString(objv[3]));
1506    } else if (opt[0] == 'u' && strcmp(opt, "url") == 0) {
1507        std::ostringstream path;
1508        path << "server:" << Tcl_GetString(objv[3]);
1509        g_renderer->loadEarthFile(path.str().c_str());
1510    } else if (opt[0] == 'd' && strcmp(opt, "data") == 0) {
1511        opt = Tcl_GetString(objv[3]);
1512        if (opt[0] != 'f' || strcmp(opt, "follows") != 0) {
1513            return TCL_ERROR;
1514        }
1515        int len;
1516        if (Tcl_GetIntFromObj(interp, objv[4], &len) != TCL_OK) {
1517            return TCL_ERROR;
1518        }
1519        // Read Earth file from socket
1520        char *buf = (char *)malloc((size_t)len);
1521        SocketRead(buf, (size_t)len);
1522        std::ostringstream path;
1523        path << "/tmp/tmp" << getpid() << ".earth";
1524        const char *pathStr = path.str().c_str();
1525        FILE *tmpFile = fopen(pathStr, "w");
1526        fwrite(buf, len, 1, tmpFile);
1527        fclose(tmpFile);
1528        g_renderer->loadEarthFile(pathStr);
1529        unlink(pathStr);
1530        free(buf);
1531    } else {
1532        return TCL_ERROR;
1533    }
1534    return TCL_OK;
1535}
1536
1537static int
1538MapPinAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
1539            Tcl_Obj *const *objv)
1540{
1541    int x, y;
1542    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
1543        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
1544        return TCL_ERROR;
1545    }
1546    char *label = NULL;
1547    if (objc > 5) {
1548        label = Tcl_GetString(objv[5]);
1549    }
1550
1551    if (g_renderer->getMapSRS() == NULL) {
1552        Tcl_AppendResult(interp, "Could not get map SRS", (char*)NULL);
1553        return TCL_ERROR;
1554    }
1555
1556    // Get lat/long
1557    double latitude, longitude;
1558    if (!g_renderer->mouseToLatLong(x, y, &latitude, &longitude)) {
1559        USER_ERROR("Can't add pin here");
1560        return TCL_OK;
1561    }
1562
1563    g_renderer->addPlaceNode(latitude, longitude, label);
1564    return TCL_OK;
1565}
1566
1567static int
1568MapPinDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
1569               Tcl_Obj *const *objv)
1570{
1571    int x, y;
1572    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
1573        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
1574        return TCL_ERROR;
1575    }
1576
1577    g_renderer->deletePlaceNode(x, y);
1578    return TCL_OK;
1579}
1580
1581static int
1582MapPinHoverOp(ClientData clientData, Tcl_Interp *interp, int objc,
1583              Tcl_Obj *const *objv)
1584{
1585    int x, y;
1586    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
1587        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
1588        return TCL_ERROR;
1589    }
1590
1591    g_renderer->hoverPlaceNode(x, y);
1592    return TCL_OK;
1593}
1594
1595static CmdSpec mapPinOps[] = {
1596    {"add",     1, MapPinAddOp,     5, 6, "x y ?label?"},
1597    {"delete",  1, MapPinDeleteOp,  5, 5, "x y"},
1598    {"hover",   1, MapPinHoverOp,   5, 5, "x y"},
1599};
1600static int nMapPinOps = NumCmdSpecs(mapPinOps);
1601
1602static int
1603MapPinOp(ClientData clientData, Tcl_Interp *interp, int objc,
1604           Tcl_Obj *const *objv)
1605{
1606    Tcl_ObjCmdProc *proc;
1607
1608    proc = GetOpFromObj(interp, nMapPinOps, mapPinOps,
1609                        CMDSPEC_ARG2, objc, objv, 0);
1610    if (proc == NULL) {
1611        return TCL_ERROR;
1612    }
1613    return (*proc) (clientData, interp, objc, objv);
1614}
1615
1616static int
1617MapPositionDisplayOp(ClientData clientData, Tcl_Interp *interp, int objc,
1618                     Tcl_Obj *const *objv)
1619{
1620    bool state;
1621    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
1622        return TCL_ERROR;
1623    }
1624    Renderer::CoordinateDisplayType type = Renderer::COORDS_LATLONG_DECIMAL_DEGREES;
1625    if (state && objc > 3) {
1626        const char *str = Tcl_GetString(objv[3]);
1627        if (str[0] == 'l' && strcmp(str, "latlong_decimal_degrees") == 0) {
1628            type = Renderer::COORDS_LATLONG_DECIMAL_DEGREES;
1629        } else if (str[0] == 'l' && strcmp(str, "latlong_degrees_decimal_minutes") == 0) {
1630            type = Renderer::COORDS_LATLONG_DEGREES_DECIMAL_MINUTES;
1631        } else if (str[0] == 'l' && strcmp(str, "latlong_degrees_minutes_seconds") == 0) {
1632            type = Renderer::COORDS_LATLONG_DEGREES_MINUTES_SECONDS;
1633        } else if (str[0] == 'm' && strcmp(str, "mgrs") == 0) {
1634            type = Renderer::COORDS_MGRS;
1635        } else {
1636            Tcl_AppendResult(interp, "invalid type: \"", str,
1637                             "\": should be 'latlong_decimal_degrees', 'latlong_degrees_decimal_minutes', 'latlong_degrees_minutes_seconds', or 'mgrs'",
1638                             (char*)NULL);
1639            return TCL_ERROR;
1640        }
1641    }
1642    if (state && objc > 4) {
1643        int precision;
1644        if (Tcl_GetIntFromObj(interp, objv[4], &precision) != TCL_OK) {
1645            return TCL_ERROR;
1646        }
1647        g_renderer->setCoordinateReadout(state, type, precision);
1648    } else {
1649        g_renderer->setCoordinateReadout(state, type);
1650    }
1651
1652    return TCL_OK;
1653}
1654
1655static int
1656MapResetOp(ClientData clientData, Tcl_Interp *interp, int objc,
1657           Tcl_Obj *const *objv)
1658{
1659    char *typeStr = Tcl_GetString(objv[2]);
1660    osgEarth::MapOptions::CoordinateSystemType type;
1661    if (typeStr[0] == 'g' && strcmp(typeStr, "geocentric") == 0) {
1662        type = osgEarth::MapOptions::CSTYPE_GEOCENTRIC;
1663    } else if (typeStr[0] == 'g' && strcmp(typeStr, "geocentric_cube") == 0) {
1664        type = osgEarth::MapOptions::CSTYPE_GEOCENTRIC_CUBE;
1665    } else if (typeStr[0] == 'p' && strcmp(typeStr, "projected") == 0) {
1666        type = osgEarth::MapOptions::CSTYPE_PROJECTED;
1667    } else {
1668        Tcl_AppendResult(interp, "bad map type \"", typeStr,
1669                         "\": must be geocentric or projected", (char*)NULL);
1670        return TCL_ERROR;
1671    }
1672    float color[3];
1673    if (GetFloatFromObj(interp, objv[3], &color[0]) != TCL_OK ||
1674        GetFloatFromObj(interp, objv[4], &color[1]) != TCL_OK ||
1675        GetFloatFromObj(interp, objv[5], &color[2]) != TCL_OK) {
1676        return TCL_ERROR;
1677    }
1678
1679    osg::Vec4f bgColor(color[0],color[1],color[2],1);
1680    if (type == osgEarth::MapOptions::CSTYPE_PROJECTED) {
1681        if (objc < 7) {
1682            Tcl_AppendResult(interp, "wrong # arguments: profile required for projected maps", (char*)NULL);
1683            return TCL_ERROR;
1684        }
1685        char *profile = Tcl_GetString(objv[6]);
1686        if (objc > 7) {
1687            if (objc < 11) {
1688                Tcl_AppendResult(interp, "wrong # arguments: 4 bounds arguments required", (char*)NULL);
1689                return TCL_ERROR;
1690            }
1691            double bounds[4];
1692            for (int i = 0; i < 4; i++) {
1693                if (Tcl_GetDoubleFromObj(interp, objv[7+i], &bounds[i]) != TCL_OK) {
1694                    return TCL_ERROR;
1695                }
1696            }
1697#if 1
1698            // Check if min > max
1699            if (bounds[0] > bounds[2] ||
1700                bounds[1] > bounds[3]) {
1701                Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
1702                return TCL_ERROR;
1703            }
1704            // Note: plate-carre generates same SRS as others, but with
1705            // _is_plate_carre flag set
1706            // In map profile, _is_plate_carre is forced on for
1707            // geographic+projected SRS
1708            if (strcmp(profile, "geodetic") == 0 ||
1709                strcmp(profile, "epsg:4326") == 0 ||
1710                strcmp(profile, "wgs84") == 0 ||
1711                strcmp(profile, "plate-carre") == 0) {
1712                if (bounds[0] < -180. || bounds[0] > 180. ||
1713                    bounds[2] < -180. || bounds[2] > 180. ||
1714                    bounds[1] < -90. || bounds[1] > 90. ||
1715                    bounds[3] < -90. || bounds[3] > 90.) {
1716                    Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
1717                    return TCL_ERROR;
1718                }
1719            } else if (strcmp(profile, "spherical-mercator") == 0 ||
1720                       strcmp(profile, "epsg:900913") == 0 ||
1721                       strcmp(profile, "epsg:3785") == 0 ||
1722                       strcmp(profile, "epsg:3857") == 0 ||
1723                       strcmp(profile, "epsg:102113") == 0) {
1724                for (int i = 0; i < 4; i++) {
1725                    if (bounds[i] < -20037508.34 || bounds[i] > 20037508.34) {
1726                        Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
1727                        return TCL_ERROR;
1728                    }
1729                }
1730            } else if (strcmp(profile, "epsg:32662") == 0 ||
1731                       strcmp(profile, "epsg:32663") == 0) {
1732                if (bounds[0] < -20037508.3428 || bounds[0] > 20037508.3428 ||
1733                    bounds[2] < -20037508.3428 || bounds[2] > 20037508.3428 ||
1734                    bounds[1] < -10018754.1714 || bounds[1] > 10018754.1714 ||
1735                    bounds[3] < -10018754.1714 || bounds[3] > 10018754.1714) {
1736                    Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
1737                    return TCL_ERROR;
1738                }
1739            }
1740#endif
1741            g_renderer->resetMap(type, bgColor, profile, bounds);
1742        } else {
1743            if (osgEarth::Registry::instance()->getNamedProfile(profile) == NULL) {
1744                Tcl_AppendResult(interp, "bad named profile \"", profile,
1745                                 "\": must be e.g. 'global-geodetic', 'global-mercator'...", (char*)NULL);
1746                return TCL_ERROR;
1747            }
1748            g_renderer->resetMap(type, bgColor, profile);
1749        }
1750    } else {
1751        // No profile required for geocentric (3D) maps
1752        g_renderer->resetMap(type, bgColor);
1753    }
1754
1755    return TCL_OK;
1756}
1757
1758static int
1759MapScaleBarOp(ClientData clientData, Tcl_Interp *interp, int objc,
1760              Tcl_Obj *const *objv)
1761{
1762    bool state;
1763    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
1764        return TCL_ERROR;
1765    }
1766    g_renderer->setScaleBar(state);
1767    if (state && objc > 3) {
1768        const char *unitStr = Tcl_GetString(objv[3]);
1769        ScaleBarUnits units;
1770        if (unitStr[0] == 'm' && strcmp(unitStr, "meters") == 0) {
1771            units = UNITS_METERS;
1772        } else if (unitStr[0] == 'f' && strcmp(unitStr, "feet") == 0) {
1773            units = UNITS_INTL_FEET;
1774        } else if (unitStr[0] == 'u' && strcmp(unitStr, "us_survey_feet") == 0) {
1775            units = UNITS_US_SURVEY_FEET;
1776        } else if (unitStr[0] == 'n' && strcmp(unitStr, "nautical_miles") == 0) {
1777            units = UNITS_NAUTICAL_MILES;
1778        } else {
1779            Tcl_AppendResult(interp, "bad units \"", unitStr,
1780                             "\": must be 'meters', 'feet', 'us_survey_feet' or 'nautical_miles'", (char*)NULL);
1781            return TCL_ERROR;
1782        }
1783        g_renderer->setScaleBarUnits(units);
1784    }
1785    return TCL_OK;
1786}
1787
1788static int
1789MapSetPositionOp(ClientData clientData, Tcl_Interp *interp, int objc,
1790            Tcl_Obj *const *objv)
1791{
1792    if (objc < 3) {
1793        g_renderer->clearReadout();
1794    } else {
1795        int x, y;
1796        if (Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK ||
1797            Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK) {
1798            return TCL_ERROR;
1799        }
1800        g_renderer->setReadout(x, y);
1801    }
1802    return TCL_OK;
1803}
1804
1805static int
1806MapTerrainColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
1807                  Tcl_Obj *const *objv)
1808{
1809    float color[3];
1810    if (GetFloatFromObj(interp, objv[3], &color[0]) != TCL_OK ||
1811        GetFloatFromObj(interp, objv[4], &color[1]) != TCL_OK ||
1812        GetFloatFromObj(interp, objv[5], &color[2]) != TCL_OK) {
1813        return TCL_ERROR;
1814    }
1815    g_renderer->setTerrainColor(osg::Vec4f(color[0],color[1],color[2],1));
1816    return TCL_OK;
1817}
1818
1819static int
1820MapTerrainEdgesOp(ClientData clientData, Tcl_Interp *interp, int objc,
1821                  Tcl_Obj *const *objv)
1822{
1823    bool state;
1824    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
1825        return TCL_ERROR;
1826    }
1827    g_renderer->setTerrainEdges(state);
1828    return TCL_OK;
1829}
1830
1831static int
1832MapTerrainLightingOp(ClientData clientData, Tcl_Interp *interp, int objc,
1833                     Tcl_Obj *const *objv)
1834{
1835    bool state;
1836    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
1837        return TCL_ERROR;
1838    }
1839
1840    g_renderer->setTerrainLighting(state);
1841    return TCL_OK;
1842}
1843
1844static int
1845MapTerrainLineColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
1846                      Tcl_Obj *const *objv)
1847{
1848    float color[3];
1849    if (GetFloatFromObj(interp, objv[3], &color[0]) != TCL_OK ||
1850        GetFloatFromObj(interp, objv[4], &color[1]) != TCL_OK ||
1851        GetFloatFromObj(interp, objv[5], &color[2]) != TCL_OK) {
1852        return TCL_ERROR;
1853    }
1854    g_renderer->setTerrainLineColor(osg::Vec4f(color[0], color[1], color[2],1));
1855    return TCL_OK;
1856}
1857
1858static int
1859MapTerrainLineWidthOp(ClientData clientData, Tcl_Interp *interp, int objc,
1860                      Tcl_Obj *const *objv)
1861{
1862    float width;
1863    if (GetFloatFromObj(interp, objv[3], &width) != TCL_OK) {
1864        return TCL_ERROR;
1865    }
1866    g_renderer->setTerrainLineWidth(width);
1867    return TCL_OK;
1868}
1869
1870static int
1871MapTerrainVertScaleOp(ClientData clientData, Tcl_Interp *interp, int objc,
1872                      Tcl_Obj *const *objv)
1873{
1874    double scale;
1875    if (Tcl_GetDoubleFromObj(interp, objv[3], &scale) != TCL_OK) {
1876        return TCL_ERROR;
1877    }
1878
1879    g_renderer->setTerrainVerticalScale(scale);
1880    return TCL_OK;
1881}
1882
1883static int
1884MapTerrainWireframeOp(ClientData clientData, Tcl_Interp *interp, int objc,
1885                      Tcl_Obj *const *objv)
1886{
1887    bool state;
1888    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
1889        return TCL_ERROR;
1890    }
1891
1892    g_renderer->setTerrainWireframe(state);
1893    return TCL_OK;
1894}
1895
1896static CmdSpec mapTerrainOps[] = {
1897    {"color",     1, MapTerrainColorOp,     6, 6, "r g b"},
1898    {"edges",     1, MapTerrainEdgesOp,     4, 4, "bool"},
1899    {"lighting",  2, MapTerrainLightingOp,  4, 4, "bool"},
1900    {"linecolor", 5, MapTerrainLineColorOp, 6, 6, "r g b"},
1901    {"linewidth", 5, MapTerrainLineWidthOp, 4, 4, "val"},
1902    {"vertscale", 1, MapTerrainVertScaleOp, 4, 4, "val"},
1903    {"wireframe", 1, MapTerrainWireframeOp, 4, 4, "bool"},
1904};
1905static int nMapTerrainOps = NumCmdSpecs(mapTerrainOps);
1906
1907static int
1908MapTerrainOp(ClientData clientData, Tcl_Interp *interp, int objc,
1909           Tcl_Obj *const *objv)
1910{
1911    Tcl_ObjCmdProc *proc;
1912
1913    proc = GetOpFromObj(interp, nMapTerrainOps, mapTerrainOps,
1914                        CMDSPEC_ARG2, objc, objv, 0);
1915    if (proc == NULL) {
1916        return TCL_ERROR;
1917    }
1918    return (*proc) (clientData, interp, objc, objv);
1919}
1920
1921static int
1922MapTimeOp(ClientData clientData, Tcl_Interp *interp, int objc,
1923          Tcl_Obj *const *objv)
1924{
1925    osgEarth::DateTime now;
1926    int year, month, day;
1927    double hours;
1928    year = now.year();
1929    month = now.month();
1930    day = now.day();
1931    hours = now.hours();
1932    if (objc > 2) {
1933        if (Tcl_GetDoubleFromObj(interp, objv[2], &hours) != TCL_OK) {
1934            return TCL_ERROR;
1935        }
1936    }
1937    if (objc > 3) {
1938        if (Tcl_GetIntFromObj(interp, objv[3], &day) != TCL_OK) {
1939            return TCL_ERROR;
1940        }
1941    }
1942    if (objc > 4) {
1943        if (Tcl_GetIntFromObj(interp, objv[4], &month) != TCL_OK) {
1944            return TCL_ERROR;
1945        }
1946    }
1947    if (objc > 5) {
1948        if (Tcl_GetIntFromObj(interp, objv[5], &year) != TCL_OK) {
1949            return TCL_ERROR;
1950        }
1951    }
1952
1953    g_renderer->setEphemerisTime(year, month, day, hours);
1954    return TCL_OK;
1955}
1956
1957static CmdSpec mapOps[] = {
1958    {"attrib",   1, MapAttributionOp,     3, 3, "string"},
1959    {"box",      1, MapBoxOp,             3, 0, "op ?params..."},
1960    {"coords",   1, MapCoordsOp,          4, 6, "token coords ?srs? ?verticalDatum?"},
1961    {"grid",     1, MapGraticuleOp,       3, 4, "bool ?type?"},
1962    {"layer",    2, MapLayerOp,           3, 0, "op ?params...?"},
1963    {"load",     2, MapLoadOp,            4, 5, "options"},
1964    {"pin",      2, MapPinOp,             3, 0, "op ?params...?"},
1965    {"posdisp",  2, MapPositionDisplayOp, 3, 5, "bool ?format? ?precision?"},
1966    {"reset",    1, MapResetOp,           6, 11, "type r g b ?profile xmin ymin xmax ymax?"},
1967    {"scalebar", 1, MapScaleBarOp,        3, 4, "bool ?units?"},
1968    {"setpos",   1, MapSetPositionOp,     2, 4, "x y"},
1969    {"terrain",  1, MapTerrainOp,         3, 0, "op ?params...?"},
1970    {"time",     1, MapTimeOp,            2, 6, "?hours? ?day? ?month? ?year?"},
1971};
1972static int nMapOps = NumCmdSpecs(mapOps);
1973
1974static int
1975MapCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1976       Tcl_Obj *const *objv)
1977{
1978    Tcl_ObjCmdProc *proc;
1979
1980    proc = GetOpFromObj(interp, nMapOps, mapOps,
1981                        CMDSPEC_ARG1, objc, objv, 0);
1982    if (proc == NULL) {
1983        return TCL_ERROR;
1984    }
1985    return (*proc) (clientData, interp, objc, objv);
1986}
1987
1988static int
1989MouseClickOp(ClientData clientData, Tcl_Interp *interp, int objc,
1990             Tcl_Obj *const *objv)
1991{
1992    int button;
1993    double x, y;
1994
1995    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
1996        return TCL_ERROR;
1997    }
1998    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
1999        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
2000        return TCL_ERROR;
2001    }
2002
2003    g_renderer->mouseClick(button, x, y);
2004    return TCL_OK;
2005}
2006
2007static int
2008MouseDoubleClickOp(ClientData clientData, Tcl_Interp *interp, int objc,
2009                   Tcl_Obj *const *objv)
2010{
2011    int button;
2012    double x, y;
2013
2014    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
2015        return TCL_ERROR;
2016    }
2017    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
2018        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
2019        return TCL_ERROR;
2020    }
2021
2022    g_renderer->mouseDoubleClick(button, x, y);
2023    return TCL_OK;
2024}
2025
2026static int
2027MouseDragOp(ClientData clientData, Tcl_Interp *interp, int objc,
2028            Tcl_Obj *const *objv)
2029{
2030    int button;
2031    double x, y;
2032
2033    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
2034        return TCL_ERROR;
2035    }
2036    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
2037        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
2038        return TCL_ERROR;
2039    }
2040
2041    g_renderer->mouseDrag(button, x, y);
2042    return TCL_OK;
2043}
2044
2045static int
2046MouseMotionOp(ClientData clientData, Tcl_Interp *interp, int objc,
2047              Tcl_Obj *const *objv)
2048{
2049    double x, y;
2050
2051    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
2052        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
2053        return TCL_ERROR;
2054    }
2055
2056    g_renderer->mouseMotion(x, y);
2057    return TCL_OK;
2058}
2059
2060static int
2061MouseReleaseOp(ClientData clientData, Tcl_Interp *interp, int objc,
2062               Tcl_Obj *const *objv)
2063{
2064    int button;
2065    double x, y;
2066
2067    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
2068        return TCL_ERROR;
2069    }
2070    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
2071        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
2072        return TCL_ERROR;
2073    }
2074
2075    g_renderer->mouseRelease(button, x, y);
2076    return TCL_OK;
2077}
2078
2079static int
2080MouseScrollOp(ClientData clientData, Tcl_Interp *interp, int objc,
2081              Tcl_Obj *const *objv)
2082{
2083    int direction;
2084
2085    if (Tcl_GetIntFromObj(interp, objv[2], &direction) != TCL_OK) {
2086        return TCL_ERROR;
2087    }
2088
2089    g_renderer->mouseScroll(direction);
2090    return TCL_OK;
2091}
2092
2093static CmdSpec mouseOps[] = {
2094    {"click",    1, MouseClickOp,       5, 5, "button x y"},
2095    {"dblclick", 2, MouseDoubleClickOp, 5, 5, "button x y"},
2096    {"drag",     2, MouseDragOp,        5, 5, "button x y"},
2097    {"motion",   1, MouseMotionOp,      4, 4, "x y"},
2098    {"release",  1, MouseReleaseOp,     5, 5, "button x y"},
2099    {"scroll",   1, MouseScrollOp,      3, 3, "direction"},
2100};
2101static int nMouseOps = NumCmdSpecs(mouseOps);
2102
2103static int
2104MouseCmd(ClientData clientData, Tcl_Interp *interp, int objc,
2105         Tcl_Obj *const *objv)
2106{
2107    Tcl_ObjCmdProc *proc;
2108
2109    proc = GetOpFromObj(interp, nMouseOps, mouseOps,
2110                        CMDSPEC_ARG1, objc, objv, 0);
2111    if (proc == NULL) {
2112        return TCL_ERROR;
2113    }
2114    return (*proc) (clientData, interp, objc, objv);
2115}
2116
2117static int
2118RendererRenderOp(ClientData clientData, Tcl_Interp *interp, int objc,
2119                 Tcl_Obj *const *objv)
2120{
2121    g_renderer->eventuallyRender();
2122    return TCL_OK;
2123}
2124
2125static CmdSpec rendererOps[] = {
2126    {"render",     1, RendererRenderOp, 2, 2, ""},
2127};
2128static int nRendererOps = NumCmdSpecs(rendererOps);
2129
2130static int
2131RendererCmd(ClientData clientData, Tcl_Interp *interp, int objc,
2132            Tcl_Obj *const *objv)
2133{
2134    Tcl_ObjCmdProc *proc;
2135
2136    proc = GetOpFromObj(interp, nRendererOps, rendererOps,
2137                        CMDSPEC_ARG1, objc, objv, 0);
2138    if (proc == NULL) {
2139        return TCL_ERROR;
2140    }
2141    return (*proc) (clientData, interp, objc, objv);
2142}
2143
2144static int
2145ScreenBgColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
2146                Tcl_Obj *const *objv)
2147{
2148    float color[3];
2149
2150    if (GetFloatFromObj(interp, objv[2], &color[0]) != TCL_OK ||
2151        GetFloatFromObj(interp, objv[3], &color[1]) != TCL_OK ||
2152        GetFloatFromObj(interp, objv[4], &color[2]) != TCL_OK) {
2153        return TCL_ERROR;
2154    }
2155
2156    g_renderer->setBackgroundColor(color);
2157    return TCL_OK;
2158}
2159
2160static int
2161ScreenCoordsOp(ClientData clientData, Tcl_Interp *interp, int objc,
2162               Tcl_Obj *const *objv)
2163{
2164    int tokenLength;
2165    const char *token = Tcl_GetStringFromObj(objv[2], &tokenLength);
2166    int numCoords;
2167    Tcl_Obj **coords;
2168    if (Tcl_ListObjGetElements(interp, objv[3], &numCoords, &coords) != TCL_OK) {
2169        return TCL_ERROR;
2170    }
2171    if (numCoords == 0) {
2172        Tcl_AppendResult(interp, "no x,y,z coordinates in list", (char *)NULL);
2173        return TCL_ERROR;
2174    }
2175    if (numCoords % 3 != 0) {
2176        Tcl_AppendResult(interp, "invalid number of coordinates in list",
2177                         (char *)NULL);
2178        return TCL_ERROR;
2179    }
2180
2181    const osgEarth::SpatialReference *srs = NULL;
2182    if (objc < 5) {
2183        srs = g_renderer->getMapSRS();
2184        if (srs == NULL) {
2185            Tcl_AppendResult(interp, "Could not determine map SRS", (char*)NULL);
2186            return TCL_ERROR;
2187        }
2188    } else {
2189        std::string srsInit(Tcl_GetString(objv[4]));
2190        std::string verticalDatum;
2191        if (objc > 5) {
2192            verticalDatum = Tcl_GetString(objv[5]);
2193        }
2194        srs = osgEarth::SpatialReference::get(srsInit, verticalDatum);
2195        if (srs == NULL) {
2196            Tcl_AppendResult(interp, "bad SRS \"", srsInit.c_str(), "\"", (char*)NULL);
2197            return TCL_ERROR;
2198        }
2199    }
2200    Tcl_Obj *listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
2201    std::vector<osg::Vec3d> coordVec;
2202    for (int i = 0; i < numCoords; i += 3) {
2203        double x, y, z;
2204        if (Tcl_GetDoubleFromObj(interp, coords[i], &x) != TCL_OK ||
2205            Tcl_GetDoubleFromObj(interp, coords[i+1], &y) != TCL_OK ||
2206            Tcl_GetDoubleFromObj(interp, coords[i+2], &z) != TCL_OK) {
2207            return TCL_ERROR;
2208        }
2209        // ALTMODE_RELATIVE is height above terrain, ALTMODE_ABSOLUTE means
2210        // relative to the vertical datum
2211        osgEarth::GeoPoint mapPoint(srs, x, y, z, osgEarth::ALTMODE_ABSOLUTE);
2212        osg::Vec3d world;
2213        if (g_renderer->getWorldCoords(mapPoint, &world)) {
2214            coordVec.push_back(world);
2215        } else {
2216            coordVec.push_back(osg::Vec3d(std::numeric_limits<double>::quiet_NaN(),
2217                                          std::numeric_limits<double>::quiet_NaN(),
2218                                          std::numeric_limits<double>::quiet_NaN()));
2219        }
2220    }
2221    g_renderer->worldToScreen(coordVec);
2222    for (std::vector<osg::Vec3d>::iterator itr = coordVec.begin();
2223         itr != coordVec.end(); ++itr) {
2224        Tcl_Obj *objPtr = Tcl_NewDoubleObj(itr->x());
2225        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
2226        objPtr = Tcl_NewDoubleObj(itr->y());
2227        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
2228        objPtr = Tcl_NewDoubleObj(itr->z());
2229        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
2230    }
2231    // send coords to client
2232    int listLength;
2233    const char *string = Tcl_GetStringFromObj(listObjPtr, &listLength);
2234    size_t length = listLength + tokenLength + 22;
2235    char *mesg = new char[length];
2236    length = snprintf(mesg, length, "nv>screen coords %s {%s}\n", token, string);
2237    Tcl_DecrRefCount(listObjPtr);
2238    queueResponse(mesg, length, Response::VOLATILE);
2239    delete [] mesg;
2240    return TCL_OK;
2241}
2242
2243static int
2244ScreenSizeOp(ClientData clientData, Tcl_Interp *interp, int objc,
2245             Tcl_Obj *const *objv)
2246{
2247    int width, height;
2248
2249    if (Tcl_GetIntFromObj(interp, objv[2], &width) != TCL_OK ||
2250        Tcl_GetIntFromObj(interp, objv[3], &height) != TCL_OK) {
2251        return TCL_ERROR;
2252    }
2253
2254    g_renderer->setWindowSize(width, height);
2255    return TCL_OK;
2256}
2257
2258static CmdSpec screenOps[] = {
2259    {"bgcolor", 1, ScreenBgColorOp, 5, 5, "r g b"},
2260    {"coords",  1, ScreenCoordsOp, 4, 6, "token coords ?srs? ?verticalDatum?"},
2261    {"size",    1, ScreenSizeOp, 4, 4, "width height"}
2262};
2263static int nScreenOps = NumCmdSpecs(screenOps);
2264
2265static int
2266ScreenCmd(ClientData clientData, Tcl_Interp *interp, int objc,
2267          Tcl_Obj *const *objv)
2268{
2269    Tcl_ObjCmdProc *proc;
2270
2271    proc = GetOpFromObj(interp, nScreenOps, screenOps,
2272                        CMDSPEC_ARG1, objc, objv, 0);
2273    if (proc == NULL) {
2274        return TCL_ERROR;
2275    }
2276    return (*proc) (clientData, interp, objc, objv);
2277}
2278
2279#ifdef USE_READ_THREAD
2280int
2281GeoVis::queueCommands(Tcl_Interp *interp,
2282                      ClientData clientData,
2283                      ReadBuffer *inBufPtr)
2284{
2285    Tcl_DString commandString;
2286    Tcl_DStringInit(&commandString);
2287    fd_set readFds;
2288
2289    FD_ZERO(&readFds);
2290    FD_SET(inBufPtr->file(), &readFds);
2291    while (inBufPtr->isLineAvailable() ||
2292           (select(inBufPtr->file()+1, &readFds, NULL, NULL, NULL) > 0)) {
2293        size_t numBytes;
2294        unsigned char *buffer;
2295
2296        /* A short read is treated as an error here because we assume that we
2297         * will always get commands line by line. */
2298        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
2299            /* Terminate the server if we can't communicate with the client
2300             * anymore. */
2301            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
2302                TRACE("Exiting server on EOF from client");
2303                return -1;
2304            } else {
2305                ERROR("Exiting server, failed to read from client: %s",
2306                      strerror(errno));
2307                return -1;
2308            }
2309        }
2310        Tcl_DStringAppend(&commandString, (char *)buffer, numBytes);
2311        if (Tcl_CommandComplete(Tcl_DStringValue(&commandString))) {
2312            // Add to queue
2313            Command *command = new Command(Command::COMMAND);
2314            command->setMessage((unsigned char *)Tcl_DStringValue(&commandString),
2315                                Tcl_DStringLength(&commandString), Command::VOLATILE);
2316            g_inQueue->enqueue(command);
2317            Tcl_DStringSetLength(&commandString, 0);
2318        }
2319        FD_SET(inBufPtr->file(), &readFds);
2320    }
2321
2322    return 1;
2323}
2324#endif
2325
2326/**
2327 * \brief Execute commands from client in Tcl interpreter
2328 *
2329 * In this threaded model, the select call is for event compression.  We
2330 * want to execute render server commands as long as they keep coming. 
2331 * This lets us execute a stream of many commands but render once.  This
2332 * benefits camera movements, screen resizing, and opacity changes
2333 * (using a slider on the client).  The down side is you don't render
2334 * until there's a lull in the command stream.  If the client needs an
2335 * image, it can issue an "imgflush" command.  That breaks us out of the
2336 * read loop.
2337 */
2338int
2339GeoVis::processCommands(Tcl_Interp *interp,
2340                        ClientData clientData,
2341                        ReadBuffer *inBufPtr,
2342                        int fdOut,
2343                        long timeout)
2344{
2345    int ret = 1;
2346    int status = TCL_OK;
2347
2348    Tcl_DString command;
2349    Tcl_DStringInit(&command);
2350    fd_set readFds;
2351    struct timeval tv, *tvPtr;
2352
2353    FD_ZERO(&readFds);
2354    FD_SET(inBufPtr->file(), &readFds);
2355    tvPtr = NULL;                       /* Wait for the first read. This is so
2356                                         * that we don't spin when no data is
2357                                         * available. */
2358    if (timeout >= 0L) {
2359        tv.tv_sec = 0L;
2360        tv.tv_usec = timeout;
2361        tvPtr = &tv;
2362    } else {
2363        TRACE("Blocking on select()");
2364    }
2365    while (inBufPtr->isLineAvailable() ||
2366           (ret = select(inBufPtr->file()+1, &readFds, NULL, NULL, tvPtr)) > 0) {
2367        size_t numBytes;
2368        unsigned char *buffer;
2369
2370        /* A short read is treated as an error here because we assume that we
2371         * will always get commands line by line. */
2372        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
2373            /* Terminate the server if we can't communicate with the client
2374             * anymore. */
2375            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
2376                TRACE("Exiting server on EOF from client");
2377                return -1;
2378            } else {
2379                ERROR("Exiting server, failed to read from client: %s",
2380                      strerror(errno));
2381                return -1;
2382            }
2383        }
2384        Tcl_DStringAppend(&command, (char *)buffer, numBytes);
2385        if (Tcl_CommandComplete(Tcl_DStringValue(&command))) {
2386            struct timeval start, finish;
2387            gettimeofday(&start, NULL);
2388            g_stats.nCommands++;
2389            status = ExecuteCommand(interp, &command);
2390            gettimeofday(&finish, NULL);
2391            g_stats.cmdTime += (MSECS_ELAPSED(start, finish) / 1.0e+3);
2392            if (status == TCL_BREAK) {
2393                return 2;               /* This was caused by a "imgflush"
2394                                         * command. Break out of the read loop
2395                                         * and allow a new image to be
2396                                         * rendered. */
2397            } else { //if (status != TCL_OK) {
2398                ret = 0;
2399                if (handleError(interp, clientData, status, fdOut) < 0) {
2400                    return -1;
2401                }
2402            }
2403            if (status == TCL_OK) {
2404                ret = 3;
2405            }
2406        }
2407
2408        tv.tv_sec = tv.tv_usec = 0L;    /* On successive reads, we break out
2409                                         * if no data is available. */
2410        FD_SET(inBufPtr->file(), &readFds);
2411        tvPtr = &tv;
2412    }
2413
2414    return ret;
2415}
2416
2417/**
2418 * \brief Send error message to client socket
2419 */
2420int
2421GeoVis::handleError(Tcl_Interp *interp,
2422                    ClientData clientData,
2423                    int status, int fdOut)
2424{
2425    const char *string;
2426    int nBytes;
2427
2428    if (status != TCL_OK) {
2429        string = Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY);
2430        nBytes = strlen(string);
2431        if (nBytes > 0) {
2432            TRACE("status=%d errorInfo=(%s)", status, string);
2433
2434            std::ostringstream oss;
2435            oss << "nv>viserror -type internal_error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
2436            std::string ostr = oss.str();
2437            nBytes = ostr.length();
2438
2439            if (queueResponse(ostr.c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
2440                return -1;
2441            }
2442        }
2443    }
2444
2445    std::string msg = getUserMessages();
2446    nBytes = msg.length();
2447    if (nBytes > 0) {
2448        string = msg.c_str();
2449        TRACE("userError=(%s)", string);
2450
2451        std::ostringstream oss;
2452        oss << "nv>viserror -type error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
2453        std::string ostr = oss.str();
2454        nBytes = ostr.length();
2455
2456        if (queueResponse(ostr.c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
2457            return -1;
2458        }
2459
2460        clearUserMessages();
2461    }
2462
2463    return 0;
2464}
2465
2466/**
2467 * \brief Create Tcl interpreter and add commands
2468 *
2469 * \return The initialized Tcl interpreter
2470 */
2471void
2472GeoVis::initTcl(Tcl_Interp *interp, ClientData clientData)
2473{
2474    Tcl_MakeSafe(interp);
2475    Tcl_CreateObjCommand(interp, "camera",         CameraCmd,         clientData, NULL);
2476    Tcl_CreateObjCommand(interp, "clientinfo",     ClientInfoCmd,     clientData, NULL);
2477    Tcl_CreateObjCommand(interp, "colormap",       ColorMapCmd,       clientData, NULL);
2478    Tcl_CreateObjCommand(interp, "file",           FileCmd,           clientData, NULL);
2479    Tcl_CreateObjCommand(interp, "imgflush",       ImageFlushCmd,     clientData, NULL);
2480    Tcl_CreateObjCommand(interp, "key",            KeyCmd,            clientData, NULL);
2481    Tcl_CreateObjCommand(interp, "legend",         LegendCmd,         clientData, NULL);
2482    Tcl_CreateObjCommand(interp, "map",            MapCmd,            clientData, NULL);
2483    Tcl_CreateObjCommand(interp, "mouse",          MouseCmd,          clientData, NULL);
2484    Tcl_CreateObjCommand(interp, "renderer",       RendererCmd,       clientData, NULL);
2485    Tcl_CreateObjCommand(interp, "screen",         ScreenCmd,         clientData, NULL);
2486}
2487
2488/**
2489 * \brief Delete Tcl commands and interpreter
2490 */
2491void GeoVis::exitTcl(Tcl_Interp *interp)
2492{
2493    Tcl_DeleteCommand(interp, "camera");
2494    Tcl_DeleteCommand(interp, "clientinfo");
2495    Tcl_DeleteCommand(interp, "colormap");
2496    Tcl_DeleteCommand(interp, "file");
2497    Tcl_DeleteCommand(interp, "imgflush");
2498    Tcl_DeleteCommand(interp, "key");
2499    Tcl_DeleteCommand(interp, "legend");
2500    Tcl_DeleteCommand(interp, "map");
2501    Tcl_DeleteCommand(interp, "mouse");
2502    Tcl_DeleteCommand(interp, "renderer");
2503    Tcl_DeleteCommand(interp, "screen");
2504
2505    Tcl_DeleteInterp(interp);
2506}
Note: See TracBrowser for help on using the repository browser.