source: geovis/trunk/RendererCmd.cpp @ 4629

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

add token to map/screen coord commands

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