source: geovis/trunk/RendererCmd.cpp @ 4589

Last change on this file since 4589 was 4575, checked in by ldelgass, 10 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.