source: geovis/trunk/RendererCmd.cpp @ 4636

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

screen coords command now takes a list of coordinates

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