source: geovis/trunk/RendererCmd.cpp @ 5107

Last change on this file since 5107 was 5107, checked in by ldelgass, 5 years ago

add colormap driver option

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