source: geovis/trunk/RendererCmd.cpp @ 4628

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

Add some Makefile flags for sleep throttling settings

File size: 67.6 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
668FilePutOp(ClientData clientData, Tcl_Interp *interp, int objc,
669          Tcl_Obj *const *objv)
670{
671    long size;
672    const char *path = Tcl_GetString(objv[2]);
673    if (Tcl_GetLongFromObj(interp, objv[4], &size) != TCL_OK ||
674        size < 1) {
675        return TCL_ERROR;
676    }
677    char *data = (char *)malloc((size_t)size);
678    if (!SocketRead(data, size)) {
679        free(data);
680        return TCL_ERROR;
681    }
682    std::ostringstream fullPath;
683    //fullPath << "/tmp/" << path;
684    fullPath << path;
685    FILE *fp = fopen(fullPath.str().c_str(), "wb");
686    if (fp == NULL) {
687        free(data);
688        return TCL_ERROR;
689    }
690    int bytesWritten = fwrite(data, 1, (size_t)size, fp);
691    fclose(fp);
692    free(data);
693    return (bytesWritten == size) ? TCL_OK : TCL_ERROR;
694}
695
696static CmdSpec fileOps[] = {
697    {"put",    1, FilePutOp, 5, 5, "name type size"}
698};
699static int nFileOps = NumCmdSpecs(fileOps);
700
701static int
702FileCmd(ClientData clientData, Tcl_Interp *interp, int objc,
703          Tcl_Obj *const *objv)
704{
705    Tcl_ObjCmdProc *proc;
706
707    proc = GetOpFromObj(interp, nFileOps, fileOps,
708                        CMDSPEC_ARG1, objc, objv, 0);
709    if (proc == NULL) {
710        return TCL_ERROR;
711    }
712    return (*proc) (clientData, interp, objc, objv);
713}
714
715static int
716ImageFlushCmd(ClientData clientData, Tcl_Interp *interp, int objc,
717              Tcl_Obj *const *objv)
718{
719    lastCmdStatus = TCL_BREAK;
720    return TCL_OK;
721}
722
723static int
724KeyPressOp(ClientData clientData, Tcl_Interp *interp, int objc,
725           Tcl_Obj *const *objv)
726{
727    int key;
728    if (Tcl_GetIntFromObj(interp, objv[2], &key) != TCL_OK) {
729        return TCL_ERROR;
730    }
731
732    g_renderer->keyPress(key);
733    return TCL_OK;
734}
735
736static int
737KeyReleaseOp(ClientData clientData, Tcl_Interp *interp, int objc,
738               Tcl_Obj *const *objv)
739{
740    int key;
741    if (Tcl_GetIntFromObj(interp, objv[2], &key) != TCL_OK) {
742        return TCL_ERROR;
743    }
744
745    g_renderer->keyRelease(key);
746    return TCL_OK;
747}
748
749static CmdSpec keyOps[] = {
750    {"press",    1, KeyPressOp,       3, 3, "key"},
751    {"release",  1, KeyReleaseOp,     3, 3, "key"},
752};
753static int nKeyOps = NumCmdSpecs(keyOps);
754
755static int
756KeyCmd(ClientData clientData, Tcl_Interp *interp, int objc,
757         Tcl_Obj *const *objv)
758{
759    Tcl_ObjCmdProc *proc;
760
761    proc = GetOpFromObj(interp, nKeyOps, keyOps,
762                        CMDSPEC_ARG1, objc, objv, 0);
763    if (proc == NULL) {
764        return TCL_ERROR;
765    }
766    return (*proc) (clientData, interp, objc, objv);
767}
768
769static int
770MapCoordsOp(ClientData clientData, Tcl_Interp *interp, int objc,
771            Tcl_Obj *const *objv)
772{
773    int x, y;
774    if (Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK ||
775        Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK) {
776        return TCL_ERROR;
777    }
778
779    osgEarth::GeoPoint mapPoint;
780    size_t length;
781    char mesg[256];
782    if (g_renderer->mapMouseCoords(x, y, mapPoint)) {
783        std::string srsInit;
784        std::string verticalDatum;
785        if (objc > 4) {
786            srsInit = Tcl_GetString(objv[4]);
787            if (objc > 5) {
788                verticalDatum = Tcl_GetString(objv[5]);
789            }
790            osgEarth::SpatialReference *outSRS =
791                osgEarth::SpatialReference::get(srsInit, verticalDatum);
792            if (outSRS == NULL) {
793                Tcl_AppendResult(interp, "bad SRS \"", srsInit.c_str(), "\"", (char*)NULL);
794                return TCL_ERROR;
795            }
796            mapPoint = mapPoint.transform(outSRS);
797            if (!mapPoint.isValid()) {
798                Tcl_AppendResult(interp, "Could not transform map point to requested SRS", (char*)NULL);
799                return TCL_ERROR;
800            }
801        }
802        // send coords to client
803        length = snprintf(mesg, sizeof(mesg),
804                          "nv>map coords %g %g %g %d %d {%s} {%s}\n",
805                          mapPoint.x(), mapPoint.y(), mapPoint.z(),
806                          x, y,
807                          mapPoint.getSRS()->getHorizInitString().c_str(),
808                          mapPoint.getSRS()->getVertInitString().c_str());
809
810        queueResponse(mesg, length, Response::VOLATILE);
811    } else {
812        // Out of range
813        length = snprintf(mesg, sizeof(mesg),
814                          "nv>map coords invalid %d %d\n", x, y);
815
816        queueResponse(mesg, length, Response::VOLATILE);
817    }
818    return TCL_OK;
819}
820
821static int
822MapGraticuleOp(ClientData clientData, Tcl_Interp *interp, int objc,
823               Tcl_Obj *const *objv)
824{
825    bool state;
826    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
827        return TCL_ERROR;
828    }
829    if (objc > 3) {
830        Renderer::GraticuleType type;
831        char *typeStr = Tcl_GetString(objv[3]);
832        if (typeStr[0] == 'g' && strcmp(typeStr, "geodetic") == 0) {
833            type = Renderer::GRATICULE_GEODETIC;
834        } else if (typeStr[0] == 'u' && strcmp(typeStr, "utm") == 0) {
835            type = Renderer::GRATICULE_UTM;
836        } else if (typeStr[0] == 'm' && strcmp(typeStr, "mgrs") == 0) {
837            type = Renderer::GRATICULE_MGRS;
838        } else {
839            return TCL_ERROR;
840        }
841        g_renderer->setGraticule(state, type);
842    } else {
843        g_renderer->setGraticule(state);
844    }
845    return TCL_OK;
846}
847
848static int
849MapLayerAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
850              Tcl_Obj *const *objv)
851{
852    char *type = Tcl_GetString(objv[3]);
853    if (type[0] == 'i' && strcmp(type, "image") == 0) {
854        bool ret;
855        char *name;
856        char *driver = Tcl_GetString(objv[4]);
857        std::string url;
858        if (objc > 6) {
859            char *urlIn = Tcl_GetString(objv[5]);
860            url = g_renderer->getCanonicalPath(std::string(urlIn));
861            if (url.empty()) {
862                Tcl_AppendResult(interp, "file not found: \"",
863                                 urlIn, "\"", (char*)NULL);
864                return TCL_ERROR;
865            }
866        }
867        if (driver[0] == 'd' && strcmp(driver, "debug") == 0) {
868            osgEarth::Drivers::DebugOptions opts;
869            name = Tcl_GetString(objv[5]);
870            ret = g_renderer->addImageLayer(name, opts);
871        } else if (driver[0] == 'g' && strcmp(driver, "gdal") == 0) {
872            osgEarth::Drivers::GDALOptions opts;
873            opts.url() = url;
874            name = Tcl_GetString(objv[6]);
875            ret = g_renderer->addImageLayer(name, opts);
876        } else if (driver[0] == 't' && strcmp(driver, "tms") == 0) {
877            osgEarth::Drivers::TMSOptions opts;
878            //char *tmsType = Tcl_GetString(objv[5]);
879            //char *format = Tcl_GetString(objv[6]);
880            opts.url() = url;
881            //opts.tmsType() = tmsType;
882            //opts.format() = format;
883            name = Tcl_GetString(objv[6]);
884            ret = g_renderer->addImageLayer(name, opts);
885        } else if (driver[0] == 'w' && strcmp(driver, "wms") == 0) {
886            osgEarth::Drivers::WMSOptions opts;
887            char *wmsLayers = Tcl_GetString(objv[6]);
888            char *format = Tcl_GetString(objv[7]);
889            bool transparent;
890            if (GetBooleanFromObj(interp, objv[8], &transparent) != TCL_OK) {
891                return TCL_ERROR;
892            }
893            opts.url() = url;
894            opts.layers() = wmsLayers;
895            opts.format() = format;
896            opts.transparent() = transparent;
897
898            name = Tcl_GetString(objv[9]);
899            ret = g_renderer->addImageLayer(name, opts);
900        } else if (driver[0] == 'x' && strcmp(driver, "xyz") == 0) {
901            osgEarth::Drivers::XYZOptions opts;
902            opts.url() = url;
903            opts.profile() = osgEarth::ProfileOptions("global-mercator");
904            //bool invertY = false;
905            //opts.invertY() = invertY;
906            //opts.format() = Tcl_GetString(objv[6]);
907            name = Tcl_GetString(objv[6]);
908            ret = g_renderer->addImageLayer(name, opts);
909        } else {
910            Tcl_AppendResult(interp, "unknown image driver \"", driver,
911                             "\": should be 'debug', 'gdal', 'tms', 'wms' or 'xyz'", (char*)NULL);
912            return TCL_ERROR;
913        }
914        if (!ret) {
915            Tcl_AppendResult(interp, "Failed to add image layer \"", name, "\"", (char*)NULL);
916            return TCL_ERROR;
917        }
918    } else if (type[0] == 'e' && strcmp(type, "elevation") == 0) {
919        char *driver = Tcl_GetString(objv[4]);
920        char *urlIn = Tcl_GetString(objv[5]);
921        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
922        if (url.empty()) {
923            Tcl_AppendResult(interp, "file not found: \"",
924                             urlIn, "\"", (char*)NULL);
925            return TCL_ERROR;
926        }
927
928        if (driver[0] == 'g' && strcmp(driver, "gdal") == 0) {
929            osgEarth::Drivers::GDALOptions opts;
930            opts.url() = url;
931            char *name = Tcl_GetString(objv[6]);
932            g_renderer->addElevationLayer(name, opts);
933        } else if (driver[0] == 't' && strcmp(driver, "tms") == 0) {
934            osgEarth::Drivers::TMSOptions opts;
935            //char *tmsType = Tcl_GetString(objv[6]);
936            //char *format = Tcl_GetString(objv[7]);
937            opts.url() = url;
938            //opts.tmsType() = tmsType;
939            //opts.format() = format;
940            char *name = Tcl_GetString(objv[6]);
941            g_renderer->addElevationLayer(name, opts);
942        } else {
943            Tcl_AppendResult(interp, "unknown elevation driver \"", driver,
944                             "\": should be 'gdal' or 'tms'", (char*)NULL);
945            return TCL_ERROR;
946        }
947    } else if (type[0] == 'p' && strcmp(type, "point") == 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
958        opts.url() = url;
959
960        osgEarth::Symbology::Style style;
961        osgEarth::Symbology::PointSymbol *ls = style.getOrCreateSymbol<osgEarth::Symbology::PointSymbol>();
962        ls->fill()->color() = osgEarth::Symbology::Color::Black;
963        ls->size() = 2.0f;
964
965        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
966        rs->depthOffset()->enabled() = true;
967        rs->depthOffset()->minBias() = 1000;
968
969        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
970        geomOpts.featureOptions() = opts;
971        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
972        geomOpts.styles()->addStyle(style);
973        geomOpts.enableLighting() = false;
974        g_renderer->addModelLayer(name, geomOpts);
975    } else if (type[0] == 'p' && strcmp(type, "polygon") == 0) {
976        osgEarth::Drivers::OGRFeatureOptions opts;
977        char *urlIn = Tcl_GetString(objv[4]);
978        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
979        if (url.empty()) {
980            Tcl_AppendResult(interp, "file not found: \"",
981                             urlIn, "\"", (char*)NULL);
982            return TCL_ERROR;
983        }
984        char *name = Tcl_GetString(objv[5]);
985        opts.url() = url;
986
987        osgEarth::Symbology::Style style;
988        osgEarth::Symbology::PolygonSymbol *ls = style.getOrCreateSymbol<osgEarth::Symbology::PolygonSymbol>();
989        ls->fill()->color() = osgEarth::Symbology::Color::White;
990
991        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
992        rs->depthOffset()->enabled() = true;
993        rs->depthOffset()->minBias() = 1000;
994
995        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
996        geomOpts.featureOptions() = opts;
997        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
998        geomOpts.styles()->addStyle(style);
999        geomOpts.enableLighting() = false;
1000        g_renderer->addModelLayer(name, geomOpts);
1001    } else if (type[0] == 'l' && strcmp(type, "line") == 0) {
1002        osgEarth::Drivers::OGRFeatureOptions opts;
1003        char *urlIn = Tcl_GetString(objv[4]);
1004        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
1005        if (url.empty()) {
1006            Tcl_AppendResult(interp, "file not found: \"",
1007                             urlIn, "\"", (char*)NULL);
1008            return TCL_ERROR;
1009        }
1010        char *name = Tcl_GetString(objv[5]);
1011        opts.url() = url;
1012
1013        osgEarth::Symbology::Style style;
1014        osgEarth::Symbology::LineSymbol *ls = style.getOrCreateSymbol<osgEarth::Symbology::LineSymbol>();
1015        ls->stroke()->color() = osgEarth::Symbology::Color::Black;
1016        ls->stroke()->width() = 2.0f;
1017#if 0
1018        osgEarth::Symbology::AltitudeSymbol *alt = style.getOrCreateSymbol<osgEarth::Symbology::AltitudeSymbol>();
1019        alt->clamping() = osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
1020        //alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_DRAPE;
1021        alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
1022#endif
1023#if 1
1024        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
1025        rs->depthOffset()->enabled() = true;
1026        rs->depthOffset()->minBias() = 1000;
1027#endif
1028        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
1029        geomOpts.featureOptions() = opts;
1030        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
1031        geomOpts.styles()->addStyle(style);
1032        geomOpts.enableLighting() = false;
1033        g_renderer->addModelLayer(name, geomOpts);
1034   } else if (type[0] == 't' && strcmp(type, "text") == 0) {
1035        osgEarth::Drivers::OGRFeatureOptions opts;
1036        char *urlIn = Tcl_GetString(objv[4]);
1037        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
1038        if (url.empty()) {
1039            Tcl_AppendResult(interp, "file not found: \"",
1040                             urlIn, "\"", (char*)NULL);
1041            return TCL_ERROR;
1042        }
1043        char *content = Tcl_GetString(objv[5]);
1044        char *priority = Tcl_GetString(objv[6]);
1045        char *name = Tcl_GetString(objv[7]);
1046
1047#if 0
1048        double fgR = 1.0, fgG = 1.0, fgB = 1.0;
1049        double bgR = 0.0, bgG = 0.0, bgB = 0.0;
1050        if (objc > 8) {
1051            if (Tcl_GetDoubleFromObj(interp, objv[8], &fgR) != TCL_OK ||
1052                Tcl_GetDoubleFromObj(interp, objv[9], &fgG) != TCL_OK ||
1053                Tcl_GetDoubleFromObj(interp, objv[10], &fgB) != TCL_OK ||
1054                Tcl_GetDoubleFromObj(interp, objv[11], &bgR) != TCL_OK ||
1055                Tcl_GetDoubleFromObj(interp, objv[12], &bgG) != TCL_OK ||
1056                Tcl_GetDoubleFromObj(interp, objv[13], &bgB) != TCL_OK) {
1057                return TCL_ERROR;
1058            }
1059        }
1060#endif
1061        opts.url() = url;
1062
1063        osgEarth::Symbology::Style style;
1064        osgEarth::Symbology::TextSymbol *ts = style.getOrCreateSymbol<osgEarth::Symbology::TextSymbol>();
1065        ts->halo()->color() = osgEarth::Symbology::Color::Black; //::Color(bgR, bgG, bgB);
1066        ts->halo()->width() = 2.0f;
1067        ts->fill()->color() = osgEarth::Symbology::Color::White; //::Color(fgR, fgG, fgB);
1068        ts->content() = osgEarth::Symbology::StringExpression(content);
1069        ts->priority() = osgEarth::Symbology::NumericExpression(priority);
1070        ts->removeDuplicateLabels() = true;
1071        ts->size() = 16.0f;
1072        ts->alignment() = osgEarth::Symbology::TextSymbol::ALIGN_CENTER_CENTER;
1073        ts->declutter() = true;
1074
1075        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
1076        rs->depthOffset()->enabled() = true;
1077        rs->depthOffset()->minBias() = 1000;
1078
1079        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
1080        geomOpts.featureOptions() = opts;
1081        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
1082        geomOpts.styles()->addStyle(style);
1083        geomOpts.enableLighting() = false;
1084        g_renderer->addModelLayer(name, geomOpts);
1085    } else {
1086        Tcl_AppendResult(interp, "unknown map layer type \"", type,
1087                         "\": should be 'image', 'elevation' or 'model'", (char*)NULL);
1088        return TCL_ERROR;
1089    }
1090    return TCL_OK;
1091}
1092
1093static int
1094MapLayerDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
1095                 Tcl_Obj *const *objv)
1096{
1097    if (objc > 3) {
1098        char *name = Tcl_GetString(objv[3]);
1099        g_renderer->removeImageLayer(name);
1100        g_renderer->removeElevationLayer(name);
1101        g_renderer->removeModelLayer(name);
1102    } else {
1103        g_renderer->clearMap();
1104    }
1105
1106    return TCL_OK;
1107}
1108
1109static int
1110MapLayerMoveOp(ClientData clientData, Tcl_Interp *interp, int objc,
1111               Tcl_Obj *const *objv)
1112{
1113    int pos;
1114    if (Tcl_GetIntFromObj(interp, objv[3], &pos) != TCL_OK) {
1115        return TCL_ERROR;
1116    }
1117    char *name = Tcl_GetString(objv[4]);
1118    if (pos < 0) {
1119        Tcl_AppendResult(interp, "bad layer pos ", pos,
1120                         ": must be positive", (char*)NULL);
1121        return TCL_ERROR;
1122    }
1123    g_renderer->moveImageLayer(name, (unsigned int)pos);
1124    g_renderer->moveElevationLayer(name, (unsigned int)pos);
1125    g_renderer->moveModelLayer(name, (unsigned int)pos);
1126
1127    return TCL_OK;
1128}
1129
1130static int
1131MapLayerOpacityOp(ClientData clientData, Tcl_Interp *interp, int objc,
1132                  Tcl_Obj *const *objv)
1133{
1134    double opacity;
1135    if (Tcl_GetDoubleFromObj(interp, objv[3], &opacity) != TCL_OK) {
1136        return TCL_ERROR;
1137    }
1138    char *name = Tcl_GetString(objv[4]);
1139    if (opacity < 0.0 || opacity > 1.0) {
1140        Tcl_AppendResult(interp, "bad layer opacity ", opacity,
1141                         ": must be [0,1]", (char*)NULL);
1142        return TCL_ERROR;
1143    }
1144    g_renderer->setImageLayerOpacity(name, opacity);
1145    g_renderer->setModelLayerOpacity(name, opacity);
1146
1147    return TCL_OK;
1148}
1149
1150static int
1151MapLayerNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
1152                Tcl_Obj *const *objv)
1153{
1154    std::vector<std::string> layers;
1155    if (objc < 4) {
1156        g_renderer->getImageLayerNames(layers);
1157        g_renderer->getElevationLayerNames(layers);
1158        g_renderer->getModelLayerNames(layers);
1159    } else {
1160        char *type = Tcl_GetString(objv[3]);
1161        if (type[0] == 'i' && strcmp(type, "image") == 0) {
1162            g_renderer->getImageLayerNames(layers);
1163        } else if (type[0] == 'e' && strcmp(type, "elevation") == 0) {
1164            g_renderer->getElevationLayerNames(layers);
1165        } else if (type[0] == 'm' && strcmp(type, "model") == 0) {
1166            g_renderer->getModelLayerNames(layers);
1167        } else {
1168            Tcl_AppendResult(interp, "uknown type \"", type,
1169                         "\": must be image, elevation or model", (char*)NULL);
1170            return TCL_ERROR;
1171        }
1172    }
1173    std::ostringstream oss;
1174    size_t len = 0;
1175    oss << "nv>map names {";
1176    len += 18;
1177    for (size_t i = 0; i < layers.size(); i++) {
1178        oss << "\"" << layers[i] << "\"";
1179        len += 2 + layers[i].length();
1180        if (i < layers.size() - 1) {
1181            oss << " ";
1182            len++;
1183        }
1184    }
1185    oss << "}\n";
1186    len += 2;
1187#ifdef USE_THREADS
1188    queueResponse(oss.str().c_str(), len, Response::VOLATILE);
1189#else
1190    ssize_t bytesWritten = SocketWrite(oss.str().c_str(), len);
1191
1192    if (bytesWritten < 0) {
1193        return TCL_ERROR;
1194    }
1195#endif /*USE_THREADS*/
1196    return TCL_OK;
1197}
1198
1199static int
1200MapLayerVisibleOp(ClientData clientData, Tcl_Interp *interp, int objc,
1201                  Tcl_Obj *const *objv)
1202{
1203    bool visible;
1204    if (GetBooleanFromObj(interp, objv[3], &visible) != TCL_OK) {
1205        return TCL_ERROR;
1206    }
1207    char *name = Tcl_GetString(objv[4]);
1208
1209    g_renderer->setImageLayerVisibility(name, visible);
1210    g_renderer->setElevationLayerVisibility(name, visible);
1211    g_renderer->setModelLayerVisibility(name, visible);
1212
1213    return TCL_OK;
1214}
1215
1216static CmdSpec mapLayerOps[] = {
1217    {"add",     1, MapLayerAddOp,       6, 0, "type driver ?url? ?args? name"},
1218    {"delete",  1, MapLayerDeleteOp,    3, 4, "?name?"},
1219    {"move",    1, MapLayerMoveOp,      5, 5, "pos name"},
1220    {"names",   1, MapLayerNamesOp,     3, 4, "?type?"},
1221    {"opacity", 1, MapLayerOpacityOp,   5, 5, "opacity ?name?"},
1222    {"visible", 1, MapLayerVisibleOp,   5, 5, "bool ?name?"},
1223};
1224static int nMapLayerOps = NumCmdSpecs(mapLayerOps);
1225
1226static int
1227MapLayerOp(ClientData clientData, Tcl_Interp *interp, int objc,
1228           Tcl_Obj *const *objv)
1229{
1230    Tcl_ObjCmdProc *proc;
1231
1232    proc = GetOpFromObj(interp, nMapLayerOps, mapLayerOps,
1233                        CMDSPEC_ARG2, objc, objv, 0);
1234    if (proc == NULL) {
1235        return TCL_ERROR;
1236    }
1237    return (*proc) (clientData, interp, objc, objv);
1238}
1239
1240static int
1241MapLoadOp(ClientData clientData, Tcl_Interp *interp, int objc,
1242          Tcl_Obj *const *objv)
1243{
1244    char *opt = Tcl_GetString(objv[2]);
1245    if (opt[0] == 'f' && strcmp(opt, "file") == 0) {
1246        g_renderer->loadEarthFile(Tcl_GetString(objv[3]));
1247    } else if (opt[0] == 'u' && strcmp(opt, "url") == 0) {
1248        std::ostringstream path;
1249        path << "server:" << Tcl_GetString(objv[3]);
1250        g_renderer->loadEarthFile(path.str().c_str());
1251    } else if (opt[0] == 'd' && strcmp(opt, "data") == 0) {
1252        opt = Tcl_GetString(objv[3]);
1253        if (opt[0] != 'f' || strcmp(opt, "follows") != 0) {
1254            return TCL_ERROR;
1255        }
1256        int len;
1257        if (Tcl_GetIntFromObj(interp, objv[4], &len) != TCL_OK) {
1258            return TCL_ERROR;
1259        }
1260        // Read Earth file from socket
1261        char *buf = (char *)malloc((size_t)len);
1262        SocketRead(buf, (size_t)len);
1263        std::ostringstream path;
1264        path << "/tmp/tmp" << getpid() << ".earth";
1265        const char *pathStr = path.str().c_str();
1266        FILE *tmpFile = fopen(pathStr, "w");
1267        fwrite(buf, len, 1, tmpFile);
1268        fclose(tmpFile);
1269        g_renderer->loadEarthFile(pathStr);
1270        unlink(pathStr);
1271        free(buf);
1272    } else {
1273        return TCL_ERROR;
1274    }
1275    return TCL_OK;
1276}
1277
1278static int
1279MapPositionDisplayOp(ClientData clientData, Tcl_Interp *interp, int objc,
1280                     Tcl_Obj *const *objv)
1281{
1282    bool state;
1283    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
1284        return TCL_ERROR;
1285    }
1286    Renderer::CoordinateDisplayType type = Renderer::COORDS_LATLONG_DECIMAL_DEGREES;
1287    if (state && objc > 3) {
1288        const char *str = Tcl_GetString(objv[3]);
1289        if (str[0] == 'l' && strcmp(str, "latlong_decimal_degrees") == 0) {
1290            type = Renderer::COORDS_LATLONG_DECIMAL_DEGREES;
1291        } else if (str[0] == 'l' && strcmp(str, "latlong_degrees_decimal_minutes") == 0) {
1292            type = Renderer::COORDS_LATLONG_DEGREES_DECIMAL_MINUTES;
1293        } else if (str[0] == 'l' && strcmp(str, "latlong_degrees_minutes_seconds") == 0) {
1294            type = Renderer::COORDS_LATLONG_DEGREES_MINUTES_SECONDS;
1295        } else if (str[0] == 'm' && strcmp(str, "mgrs") == 0) {
1296            type = Renderer::COORDS_MGRS;
1297        } else {
1298            Tcl_AppendResult(interp, "invalid type: \"", str,
1299                             "\": should be 'latlong_decimal_degrees', 'latlong_degrees_decimal_minutes', 'latlong_degrees_minutes_seconds', or 'mgrs'",
1300                             (char*)NULL);
1301            return TCL_ERROR;
1302        }
1303    }
1304    if (state && objc > 4) {
1305        int precision;
1306        if (Tcl_GetIntFromObj(interp, objv[4], &precision) != TCL_OK) {
1307            return TCL_ERROR;
1308        }
1309        g_renderer->setCoordinateReadout(state, type, precision);
1310    } else {
1311        g_renderer->setCoordinateReadout(state, type);
1312    }
1313
1314    return TCL_OK;
1315}
1316
1317static int
1318MapResetOp(ClientData clientData, Tcl_Interp *interp, int objc,
1319           Tcl_Obj *const *objv)
1320{
1321    char *typeStr = Tcl_GetString(objv[2]);
1322    osgEarth::MapOptions::CoordinateSystemType type;
1323    if (typeStr[0] == 'g' && strcmp(typeStr, "geocentric") == 0) {
1324        type = osgEarth::MapOptions::CSTYPE_GEOCENTRIC;
1325    } else if (typeStr[0] == 'g' && strcmp(typeStr, "geocentric_cube") == 0) {
1326        type = osgEarth::MapOptions::CSTYPE_GEOCENTRIC_CUBE;
1327    } else if (typeStr[0] == 'p' && strcmp(typeStr, "projected") == 0) {
1328        type = osgEarth::MapOptions::CSTYPE_PROJECTED;
1329    } else {
1330        Tcl_AppendResult(interp, "bad map type \"", typeStr,
1331                         "\": must be geocentric or projected", (char*)NULL);
1332        return TCL_ERROR;
1333    }
1334
1335    if (type == osgEarth::MapOptions::CSTYPE_PROJECTED) {
1336        if (objc < 4) {
1337            Tcl_AppendResult(interp, "wrong # arguments: profile required for projected maps", (char*)NULL);
1338            return TCL_ERROR;
1339        }
1340        char *profile = Tcl_GetString(objv[3]);
1341        if (objc > 4) {
1342            if (objc < 8) {
1343                Tcl_AppendResult(interp, "wrong # arguments: 4 bounds arguments required", (char*)NULL);
1344                return TCL_ERROR;
1345            }
1346            double bounds[4];
1347            for (int i = 0; i < 4; i++) {
1348                if (Tcl_GetDoubleFromObj(interp, objv[4+i], &bounds[i]) != TCL_OK) {
1349                    return TCL_ERROR;
1350                }
1351            }
1352            // Check if min > max
1353            if (bounds[0] > bounds[2] ||
1354                bounds[1] > bounds[3]) {
1355                Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
1356                return TCL_ERROR;
1357            }
1358            // Note: plate-carre generates same SRS as others, but with
1359            // _is_plate_carre flag set
1360            // In map profile, _is_plate_carre is forced on for
1361            // geographic+projected SRS
1362            if (strcmp(profile, "geodetic") == 0 ||
1363                strcmp(profile, "epsg:4326") == 0 ||
1364                strcmp(profile, "wgs84") == 0 ||
1365                strcmp(profile, "plate-carre") == 0) {
1366                if (bounds[0] < -180. || bounds[0] > 180. ||
1367                    bounds[2] < -180. || bounds[2] > 180. ||
1368                    bounds[1] < -90. || bounds[1] > 90. ||
1369                    bounds[3] < -90. || bounds[3] > 90.) {
1370                    Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
1371                    return TCL_ERROR;
1372                }
1373            } else if (strcmp(profile, "spherical-mercator") == 0 ||
1374                       strcmp(profile, "epsg:900913") == 0 ||
1375                       strcmp(profile, "epsg:3785") == 0 ||
1376                       strcmp(profile, "epsg:3857") == 0 ||
1377                       strcmp(profile, "epsg:102113") == 0) {
1378                for (int i = 0; i < 4; i++) {
1379                    if (bounds[i] < -20037508.34 || bounds[i] > 20037508.34) {
1380                        Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
1381                        return TCL_ERROR;
1382                    }
1383                }
1384            } else if (strcmp(profile, "epsg:32662") == 0 ||
1385                       strcmp(profile, "epsg:32663") == 0) {
1386                if (bounds[0] < -20037508.3428 || bounds[0] > 20037508.3428 ||
1387                    bounds[2] < -20037508.3428 || bounds[2] > 20037508.3428 ||
1388                    bounds[1] < -10018754.1714 || bounds[1] > 10018754.1714 ||
1389                    bounds[3] < -10018754.1714 || bounds[3] > 10018754.1714) {
1390                    Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
1391                    return TCL_ERROR;
1392                }
1393            }
1394            g_renderer->resetMap(type, profile, bounds);
1395        } else {
1396            if (osgEarth::Registry::instance()->getNamedProfile(profile) == NULL) {
1397                Tcl_AppendResult(interp, "bad named profile \"", profile,
1398                                 "\": must be e.g. 'global-geodetic', 'global-mercator'...", (char*)NULL);
1399                return TCL_ERROR;
1400            }
1401            g_renderer->resetMap(type, profile);
1402        }
1403    } else {
1404        // No profile required for geocentric (3D) maps
1405        g_renderer->resetMap(type);
1406    }
1407
1408    return TCL_OK;
1409}
1410
1411static int
1412MapScaleBarOp(ClientData clientData, Tcl_Interp *interp, int objc,
1413              Tcl_Obj *const *objv)
1414{
1415    bool state;
1416    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
1417        return TCL_ERROR;
1418    }
1419    g_renderer->setScaleBar(state);
1420    if (state && objc > 3) {
1421        const char *unitStr = Tcl_GetString(objv[3]);
1422        ScaleBarUnits units;
1423        if (unitStr[0] == 'm' && strcmp(unitStr, "meters") == 0) {
1424            units = UNITS_METERS;
1425        } else if (unitStr[0] == 'f' && strcmp(unitStr, "feet") == 0) {
1426            units = UNITS_INTL_FEET;
1427        } else if (unitStr[0] == 'u' && strcmp(unitStr, "us_survey_feet") == 0) {
1428            units = UNITS_US_SURVEY_FEET;
1429        } else if (unitStr[0] == 'n' && strcmp(unitStr, "nautical_miles") == 0) {
1430            units = UNITS_NAUTICAL_MILES;
1431        } else {
1432            Tcl_AppendResult(interp, "bad units \"", unitStr,
1433                             "\": must be 'meters', 'feet', 'us_survey_feet' or 'nautical_miles'", (char*)NULL);
1434            return TCL_ERROR;
1435        }
1436        g_renderer->setScaleBarUnits(units);
1437    }
1438    return TCL_OK;
1439}
1440
1441static int
1442MapSetPositionOp(ClientData clientData, Tcl_Interp *interp, int objc,
1443            Tcl_Obj *const *objv)
1444{
1445    if (objc < 3) {
1446        g_renderer->clearReadout();
1447    } else {
1448        int x, y;
1449        if (Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK ||
1450            Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK) {
1451            return TCL_ERROR;
1452        }
1453        g_renderer->setReadout(x, y);
1454    }
1455    return TCL_OK;
1456}
1457
1458static int
1459MapTerrainEdgesOp(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    TRACE("Not implemented");
1467    //g_renderer->setTerrainEdges(state);
1468    return TCL_OK;
1469}
1470
1471static int
1472MapTerrainLightingOp(ClientData clientData, Tcl_Interp *interp, int objc,
1473                     Tcl_Obj *const *objv)
1474{
1475    bool state;
1476    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
1477        return TCL_ERROR;
1478    }
1479
1480    g_renderer->setTerrainLighting(state);
1481    return TCL_OK;
1482}
1483
1484static int
1485MapTerrainLineColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
1486                      Tcl_Obj *const *objv)
1487{
1488    float color[3];
1489    if (GetFloatFromObj(interp, objv[3], &color[0]) != TCL_OK ||
1490        GetFloatFromObj(interp, objv[4], &color[1]) != TCL_OK ||
1491        GetFloatFromObj(interp, objv[5], &color[2]) != TCL_OK) {
1492        return TCL_ERROR;
1493    }
1494    TRACE("Not implemented");
1495    //g_renderer->setTerrainLineColor(color);
1496    return TCL_OK;
1497}
1498
1499static int
1500MapTerrainVertScaleOp(ClientData clientData, Tcl_Interp *interp, int objc,
1501                      Tcl_Obj *const *objv)
1502{
1503    double scale;
1504    if (Tcl_GetDoubleFromObj(interp, objv[3], &scale) != TCL_OK) {
1505        return TCL_ERROR;
1506    }
1507
1508    g_renderer->setTerrainVerticalScale(scale);
1509    return TCL_OK;
1510}
1511
1512static int
1513MapTerrainWireframeOp(ClientData clientData, Tcl_Interp *interp, int objc,
1514                      Tcl_Obj *const *objv)
1515{
1516    bool state;
1517    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
1518        return TCL_ERROR;
1519    }
1520
1521    g_renderer->setTerrainWireframe(state);
1522    return TCL_OK;
1523}
1524
1525static CmdSpec mapTerrainOps[] = {
1526    {"edges",     1, MapTerrainEdgesOp,     4, 4, "bool"},
1527    {"lighting",  2, MapTerrainLightingOp,  4, 4, "bool"},
1528    {"linecolor", 2, MapTerrainLineColorOp, 6, 6, "r g b"},
1529    {"vertscale", 1, MapTerrainVertScaleOp, 4, 4, "val"},
1530    {"wireframe", 1, MapTerrainWireframeOp, 4, 4, "bool"},
1531};
1532static int nMapTerrainOps = NumCmdSpecs(mapTerrainOps);
1533
1534static int
1535MapTerrainOp(ClientData clientData, Tcl_Interp *interp, int objc,
1536           Tcl_Obj *const *objv)
1537{
1538    Tcl_ObjCmdProc *proc;
1539
1540    proc = GetOpFromObj(interp, nMapTerrainOps, mapTerrainOps,
1541                        CMDSPEC_ARG2, objc, objv, 0);
1542    if (proc == NULL) {
1543        return TCL_ERROR;
1544    }
1545    return (*proc) (clientData, interp, objc, objv);
1546}
1547
1548static CmdSpec mapOps[] = {
1549    {"coords",   1, MapCoordsOp,          4, 6, "x y ?srs? ?verticalDatum?"},
1550    {"grid",     1, MapGraticuleOp,       3, 4, "bool ?type?"},
1551    {"layer",    2, MapLayerOp,           3, 0, "op ?params...?"},
1552    {"load",     2, MapLoadOp,            4, 5, "options"},
1553    {"posdisp",  1, MapPositionDisplayOp, 3, 5, "bool ?format? ?precision?"},
1554    {"reset",    1, MapResetOp,           3, 8, "type ?profile xmin ymin xmax ymax?"},
1555    {"scalebar", 1, MapScaleBarOp,        3, 4, "bool ?units?"},
1556    {"setpos",   1, MapSetPositionOp,     2, 4, "x y"},
1557    {"terrain",  1, MapTerrainOp,         3, 0, "op ?params...?"},
1558};
1559static int nMapOps = NumCmdSpecs(mapOps);
1560
1561static int
1562MapCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1563       Tcl_Obj *const *objv)
1564{
1565    Tcl_ObjCmdProc *proc;
1566
1567    proc = GetOpFromObj(interp, nMapOps, mapOps,
1568                        CMDSPEC_ARG1, objc, objv, 0);
1569    if (proc == NULL) {
1570        return TCL_ERROR;
1571    }
1572    return (*proc) (clientData, interp, objc, objv);
1573}
1574
1575static int
1576MouseClickOp(ClientData clientData, Tcl_Interp *interp, int objc,
1577             Tcl_Obj *const *objv)
1578{
1579    int button;
1580    double x, y;
1581
1582    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
1583        return TCL_ERROR;
1584    }
1585    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
1586        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
1587        return TCL_ERROR;
1588    }
1589
1590    g_renderer->mouseClick(button, x, y);
1591    return TCL_OK;
1592}
1593
1594static int
1595MouseDoubleClickOp(ClientData clientData, Tcl_Interp *interp, int objc,
1596                   Tcl_Obj *const *objv)
1597{
1598    int button;
1599    double x, y;
1600
1601    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
1602        return TCL_ERROR;
1603    }
1604    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
1605        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
1606        return TCL_ERROR;
1607    }
1608
1609    g_renderer->mouseDoubleClick(button, x, y);
1610    return TCL_OK;
1611}
1612
1613static int
1614MouseDragOp(ClientData clientData, Tcl_Interp *interp, int objc,
1615            Tcl_Obj *const *objv)
1616{
1617    int button;
1618    double x, y;
1619
1620    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
1621        return TCL_ERROR;
1622    }
1623    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
1624        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
1625        return TCL_ERROR;
1626    }
1627
1628    g_renderer->mouseDrag(button, x, y);
1629    return TCL_OK;
1630}
1631
1632static int
1633MouseMotionOp(ClientData clientData, Tcl_Interp *interp, int objc,
1634              Tcl_Obj *const *objv)
1635{
1636    double x, y;
1637
1638    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
1639        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
1640        return TCL_ERROR;
1641    }
1642
1643    g_renderer->mouseMotion(x, y);
1644    return TCL_OK;
1645}
1646
1647static int
1648MouseReleaseOp(ClientData clientData, Tcl_Interp *interp, int objc,
1649               Tcl_Obj *const *objv)
1650{
1651    int button;
1652    double x, y;
1653
1654    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
1655        return TCL_ERROR;
1656    }
1657    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
1658        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
1659        return TCL_ERROR;
1660    }
1661
1662    g_renderer->mouseRelease(button, x, y);
1663    return TCL_OK;
1664}
1665
1666static int
1667MouseScrollOp(ClientData clientData, Tcl_Interp *interp, int objc,
1668              Tcl_Obj *const *objv)
1669{
1670    int direction;
1671
1672    if (Tcl_GetIntFromObj(interp, objv[2], &direction) != TCL_OK) {
1673        return TCL_ERROR;
1674    }
1675
1676    g_renderer->mouseScroll(direction);
1677    return TCL_OK;
1678}
1679
1680static CmdSpec mouseOps[] = {
1681    {"click",    1, MouseClickOp,       5, 5, "button x y"},
1682    {"dblclick", 2, MouseDoubleClickOp, 5, 5, "button x y"},
1683    {"drag",     2, MouseDragOp,        5, 5, "button x y"},
1684    {"motion",   1, MouseMotionOp,      4, 4, "x y"},
1685    {"release",  1, MouseReleaseOp,     5, 5, "button x y"},
1686    {"scroll",   1, MouseScrollOp,      3, 3, "direction"},
1687};
1688static int nMouseOps = NumCmdSpecs(mouseOps);
1689
1690static int
1691MouseCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1692         Tcl_Obj *const *objv)
1693{
1694    Tcl_ObjCmdProc *proc;
1695
1696    proc = GetOpFromObj(interp, nMouseOps, mouseOps,
1697                        CMDSPEC_ARG1, objc, objv, 0);
1698    if (proc == NULL) {
1699        return TCL_ERROR;
1700    }
1701    return (*proc) (clientData, interp, objc, objv);
1702}
1703
1704static int
1705RendererRenderOp(ClientData clientData, Tcl_Interp *interp, int objc,
1706                 Tcl_Obj *const *objv)
1707{
1708    g_renderer->eventuallyRender();
1709    return TCL_OK;
1710}
1711
1712static CmdSpec rendererOps[] = {
1713    {"render",     1, RendererRenderOp, 2, 2, ""},
1714};
1715static int nRendererOps = NumCmdSpecs(rendererOps);
1716
1717static int
1718RendererCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1719            Tcl_Obj *const *objv)
1720{
1721    Tcl_ObjCmdProc *proc;
1722
1723    proc = GetOpFromObj(interp, nRendererOps, rendererOps,
1724                        CMDSPEC_ARG1, objc, objv, 0);
1725    if (proc == NULL) {
1726        return TCL_ERROR;
1727    }
1728    return (*proc) (clientData, interp, objc, objv);
1729}
1730
1731static int
1732ScreenBgColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
1733                Tcl_Obj *const *objv)
1734{
1735    float color[3];
1736
1737    if (GetFloatFromObj(interp, objv[2], &color[0]) != TCL_OK ||
1738        GetFloatFromObj(interp, objv[3], &color[1]) != TCL_OK ||
1739        GetFloatFromObj(interp, objv[4], &color[2]) != TCL_OK) {
1740        return TCL_ERROR;
1741    }
1742
1743    g_renderer->setBackgroundColor(color);
1744    return TCL_OK;
1745}
1746
1747static int
1748ScreenCoordsOp(ClientData clientData, Tcl_Interp *interp, int objc,
1749               Tcl_Obj *const *objv)
1750{
1751    double x, y, z;
1752    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
1753        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK ||
1754        Tcl_GetDoubleFromObj(interp, objv[4], &z) != TCL_OK) {
1755        return TCL_ERROR;
1756    }
1757    const osgEarth::SpatialReference *srs = NULL;
1758    if (objc < 6) {
1759        srs = g_renderer->getMapSRS();
1760        if (srs == NULL) {
1761            Tcl_AppendResult(interp, "Could not determine map SRS", (char*)NULL);
1762            return TCL_ERROR;
1763        }
1764    } else {
1765        std::string srsInit(Tcl_GetString(objv[5]));
1766        std::string verticalDatum;
1767        if (objc > 6) {
1768            verticalDatum = Tcl_GetString(objv[6]);
1769        }
1770        srs = osgEarth::SpatialReference::get(srsInit, verticalDatum);
1771        if (srs == NULL) {
1772            Tcl_AppendResult(interp, "bad SRS \"", srsInit.c_str(), "\"", (char*)NULL);
1773            return TCL_ERROR;
1774        }
1775    }
1776    // ALTMODE_RELATIVE is height above terrain, ALTMODE_ABSOLUTE means
1777    // relative to the vertical datum
1778    osgEarth::GeoPoint mapPoint(srs, x, y, z, osgEarth::ALTMODE_ABSOLUTE);
1779    size_t length;
1780    char mesg[256];
1781    osg::Vec3d world;
1782    osg::Vec3d screen;
1783    if (g_renderer->getWorldCoords(mapPoint, &world) &&
1784        g_renderer->worldToScreen(world, &screen)) {
1785        // send coords to client
1786        length = snprintf(mesg, sizeof(mesg),
1787                          "nv>screen coords %g %g %g %g %g %g\n",
1788                          screen.x(), screen.y(), screen.z(),
1789                          x, y, z);
1790
1791        queueResponse(mesg, length, Response::VOLATILE);
1792    } else {
1793        // Out of range
1794        length = snprintf(mesg, sizeof(mesg),
1795                          "nv>screen coords invalid %g %g %g\n", x, y, z);
1796
1797        queueResponse(mesg, length, Response::VOLATILE);
1798    }
1799    return TCL_OK;
1800}
1801
1802static int
1803ScreenSizeOp(ClientData clientData, Tcl_Interp *interp, int objc,
1804             Tcl_Obj *const *objv)
1805{
1806    int width, height;
1807
1808    if (Tcl_GetIntFromObj(interp, objv[2], &width) != TCL_OK ||
1809        Tcl_GetIntFromObj(interp, objv[3], &height) != TCL_OK) {
1810        return TCL_ERROR;
1811    }
1812
1813    g_renderer->setWindowSize(width, height);
1814    return TCL_OK;
1815}
1816
1817static CmdSpec screenOps[] = {
1818    {"bgcolor", 1, ScreenBgColorOp, 5, 5, "r g b"},
1819    {"coords",  1, ScreenCoordsOp, 5, 7, "x y z ?srs? ?verticalDatum?"},
1820    {"size",    1, ScreenSizeOp, 4, 4, "width height"}
1821};
1822static int nScreenOps = NumCmdSpecs(screenOps);
1823
1824static int
1825ScreenCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1826          Tcl_Obj *const *objv)
1827{
1828    Tcl_ObjCmdProc *proc;
1829
1830    proc = GetOpFromObj(interp, nScreenOps, screenOps,
1831                        CMDSPEC_ARG1, objc, objv, 0);
1832    if (proc == NULL) {
1833        return TCL_ERROR;
1834    }
1835    return (*proc) (clientData, interp, objc, objv);
1836}
1837
1838#ifdef USE_READ_THREAD
1839int
1840GeoVis::queueCommands(Tcl_Interp *interp,
1841                      ClientData clientData,
1842                      ReadBuffer *inBufPtr)
1843{
1844    Tcl_DString commandString;
1845    Tcl_DStringInit(&commandString);
1846    fd_set readFds;
1847
1848    FD_ZERO(&readFds);
1849    FD_SET(inBufPtr->file(), &readFds);
1850    while (inBufPtr->isLineAvailable() ||
1851           (select(inBufPtr->file()+1, &readFds, NULL, NULL, NULL) > 0)) {
1852        size_t numBytes;
1853        unsigned char *buffer;
1854
1855        /* A short read is treated as an error here because we assume that we
1856         * will always get commands line by line. */
1857        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
1858            /* Terminate the server if we can't communicate with the client
1859             * anymore. */
1860            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
1861                TRACE("Exiting server on EOF from client");
1862                return -1;
1863            } else {
1864                ERROR("Exiting server, failed to read from client: %s",
1865                      strerror(errno));
1866                return -1;
1867            }
1868        }
1869        Tcl_DStringAppend(&commandString, (char *)buffer, numBytes);
1870        if (Tcl_CommandComplete(Tcl_DStringValue(&commandString))) {
1871            // Add to queue
1872            Command *command = new Command(Command::COMMAND);
1873            command->setMessage((unsigned char *)Tcl_DStringValue(&commandString),
1874                                Tcl_DStringLength(&commandString), Command::VOLATILE);
1875            g_inQueue->enqueue(command);
1876            Tcl_DStringSetLength(&commandString, 0);
1877        }
1878        FD_SET(inBufPtr->file(), &readFds);
1879    }
1880
1881    return 1;
1882}
1883#endif
1884
1885/**
1886 * \brief Execute commands from client in Tcl interpreter
1887 *
1888 * In this threaded model, the select call is for event compression.  We
1889 * want to execute render server commands as long as they keep coming. 
1890 * This lets us execute a stream of many commands but render once.  This
1891 * benefits camera movements, screen resizing, and opacity changes
1892 * (using a slider on the client).  The down side is you don't render
1893 * until there's a lull in the command stream.  If the client needs an
1894 * image, it can issue an "imgflush" command.  That breaks us out of the
1895 * read loop.
1896 */
1897int
1898GeoVis::processCommands(Tcl_Interp *interp,
1899                        ClientData clientData,
1900                        ReadBuffer *inBufPtr,
1901                        int fdOut,
1902                        long timeout)
1903{
1904    int ret = 1;
1905    int status = TCL_OK;
1906
1907    Tcl_DString command;
1908    Tcl_DStringInit(&command);
1909    fd_set readFds;
1910    struct timeval tv, *tvPtr;
1911
1912    FD_ZERO(&readFds);
1913    FD_SET(inBufPtr->file(), &readFds);
1914    tvPtr = NULL;                       /* Wait for the first read. This is so
1915                                         * that we don't spin when no data is
1916                                         * available. */
1917    if (timeout >= 0L) {
1918        tv.tv_sec = 0L;
1919        tv.tv_usec = timeout;
1920        tvPtr = &tv;
1921    } else {
1922        TRACE("Blocking on select()");
1923    }
1924    while (inBufPtr->isLineAvailable() ||
1925           (ret = select(inBufPtr->file()+1, &readFds, NULL, NULL, tvPtr)) > 0) {
1926        size_t numBytes;
1927        unsigned char *buffer;
1928
1929        /* A short read is treated as an error here because we assume that we
1930         * will always get commands line by line. */
1931        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
1932            /* Terminate the server if we can't communicate with the client
1933             * anymore. */
1934            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
1935                TRACE("Exiting server on EOF from client");
1936                return -1;
1937            } else {
1938                ERROR("Exiting server, failed to read from client: %s",
1939                      strerror(errno));
1940                return -1;
1941            }
1942        }
1943        Tcl_DStringAppend(&command, (char *)buffer, numBytes);
1944        if (Tcl_CommandComplete(Tcl_DStringValue(&command))) {
1945            struct timeval start, finish;
1946            gettimeofday(&start, NULL);
1947            status = ExecuteCommand(interp, &command);
1948            gettimeofday(&finish, NULL);
1949            g_stats.cmdTime += (MSECS_ELAPSED(start, finish) / 1.0e+3);
1950            g_stats.nCommands++;
1951            if (status == TCL_BREAK) {
1952                return 2;               /* This was caused by a "imgflush"
1953                                         * command. Break out of the read loop
1954                                         * and allow a new image to be
1955                                         * rendered. */
1956            } else { //if (status != TCL_OK) {
1957                ret = 0;
1958                if (handleError(interp, clientData, status, fdOut) < 0) {
1959                    return -1;
1960                }
1961            }
1962            if (status == TCL_OK) {
1963                ret = 3;
1964            }
1965        }
1966
1967        tv.tv_sec = tv.tv_usec = 0L;    /* On successive reads, we break out
1968                                         * if no data is available. */
1969        FD_SET(inBufPtr->file(), &readFds);
1970        tvPtr = &tv;
1971    }
1972
1973    return ret;
1974}
1975
1976/**
1977 * \brief Send error message to client socket
1978 */
1979int
1980GeoVis::handleError(Tcl_Interp *interp,
1981                    ClientData clientData,
1982                    int status, int fdOut)
1983{
1984    const char *string;
1985    int nBytes;
1986
1987    if (status != TCL_OK) {
1988        string = Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY);
1989        nBytes = strlen(string);
1990        if (nBytes > 0) {
1991            TRACE("status=%d errorInfo=(%s)", status, string);
1992
1993            std::ostringstream oss;
1994            oss << "nv>viserror -type internal_error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
1995            std::string ostr = oss.str();
1996            nBytes = ostr.length();
1997
1998            if (queueResponse(ostr.c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
1999                return -1;
2000            }
2001        }
2002    }
2003
2004    std::string msg = getUserMessages();
2005    nBytes = msg.length();
2006    if (nBytes > 0) {
2007        string = msg.c_str();
2008        TRACE("userError=(%s)", string);
2009
2010        std::ostringstream oss;
2011        oss << "nv>viserror -type error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
2012        std::string ostr = oss.str();
2013        nBytes = ostr.length();
2014
2015        if (queueResponse(ostr.c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
2016            return -1;
2017        }
2018
2019        clearUserMessages();
2020    }
2021
2022    return 0;
2023}
2024
2025/**
2026 * \brief Create Tcl interpreter and add commands
2027 *
2028 * \return The initialized Tcl interpreter
2029 */
2030void
2031GeoVis::initTcl(Tcl_Interp *interp, ClientData clientData)
2032{
2033    Tcl_MakeSafe(interp);
2034    Tcl_CreateObjCommand(interp, "camera",         CameraCmd,         clientData, NULL);
2035    Tcl_CreateObjCommand(interp, "clientinfo",     ClientInfoCmd,     clientData, NULL);
2036    Tcl_CreateObjCommand(interp, "colormap",       ColorMapCmd,       clientData, NULL);
2037    Tcl_CreateObjCommand(interp, "file",           FileCmd,           clientData, NULL);
2038    Tcl_CreateObjCommand(interp, "imgflush",       ImageFlushCmd,     clientData, NULL);
2039    Tcl_CreateObjCommand(interp, "key",            KeyCmd,            clientData, NULL);
2040    Tcl_CreateObjCommand(interp, "map",            MapCmd,            clientData, NULL);
2041    Tcl_CreateObjCommand(interp, "mouse",          MouseCmd,          clientData, NULL);
2042    Tcl_CreateObjCommand(interp, "renderer",       RendererCmd,       clientData, NULL);
2043    Tcl_CreateObjCommand(interp, "screen",         ScreenCmd,         clientData, NULL);
2044}
2045
2046/**
2047 * \brief Delete Tcl commands and interpreter
2048 */
2049void GeoVis::exitTcl(Tcl_Interp *interp)
2050{
2051    Tcl_DeleteCommand(interp, "camera");
2052    Tcl_DeleteCommand(interp, "clientinfo");
2053    Tcl_DeleteCommand(interp, "colormap");
2054    Tcl_DeleteCommand(interp, "file");
2055    Tcl_DeleteCommand(interp, "imgflush");
2056    Tcl_DeleteCommand(interp, "key");
2057    Tcl_DeleteCommand(interp, "map");
2058    Tcl_DeleteCommand(interp, "mouse");
2059    Tcl_DeleteCommand(interp, "renderer");
2060    Tcl_DeleteCommand(interp, "screen");
2061
2062    Tcl_DeleteInterp(interp);
2063}
Note: See TracBrowser for help on using the repository browser.