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

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

Add optional coordinate conversion for 'map coords' command, add 'screen coords'
command to project map coords to screen coords.

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