source: geovis/trunk/RendererCmd.cpp @ 4632

Last change on this file since 4632 was 4632, checked in by ldelgass, 6 years ago

Add pin annotations for testing

File size: 70.2 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
1282MapPinAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
1283            Tcl_Obj *const *objv)
1284{
1285    int x, y;
1286    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
1287        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
1288        return TCL_ERROR;
1289    }
1290    char *label = NULL;
1291    if (objc > 5) {
1292        label = Tcl_GetString(objv[5]);
1293    }
1294
1295    if (g_renderer->getMapSRS() == NULL) {
1296        Tcl_AppendResult(interp, "Could not get map SRS", (char*)NULL);
1297        return TCL_ERROR;
1298    }
1299
1300    // Get lat/long
1301    double latitude, longitude;
1302    const osgEarth::SpatialReference *outSRS =
1303        g_renderer->getMapSRS()->getGeographicSRS();
1304    osgEarth::GeoPoint mapPoint;
1305    if (g_renderer->mapMouseCoords(x, y, mapPoint)) {
1306        mapPoint = mapPoint.transform(outSRS);
1307        longitude = mapPoint.x();
1308        latitude = mapPoint.y();
1309    } else {
1310        USER_ERROR("Can't add pin here");
1311        return TCL_OK;
1312    }
1313
1314    g_renderer->addPlaceNode(latitude, longitude, label);
1315    return TCL_OK;
1316}
1317
1318static int
1319MapPinDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
1320               Tcl_Obj *const *objv)
1321{
1322    int x, y;
1323    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
1324        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
1325        return TCL_ERROR;
1326    }
1327
1328    g_renderer->deletePlaceNode(x, y);
1329    return TCL_OK;
1330}
1331
1332static int
1333MapPinHoverOp(ClientData clientData, Tcl_Interp *interp, int objc,
1334              Tcl_Obj *const *objv)
1335{
1336    int x, y;
1337    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
1338        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
1339        return TCL_ERROR;
1340    }
1341
1342    g_renderer->hoverPlaceNode(x, y);
1343    return TCL_OK;
1344}
1345
1346static CmdSpec mapPinOps[] = {
1347    {"add",     1, MapPinAddOp,     5, 6, "x y ?label?"},
1348    {"delete",  1, MapPinDeleteOp,  5, 5, "x y"},
1349    {"hover",   1, MapPinHoverOp,   5, 5, "x y"},
1350};
1351static int nMapPinOps = NumCmdSpecs(mapPinOps);
1352
1353static int
1354MapPinOp(ClientData clientData, Tcl_Interp *interp, int objc,
1355           Tcl_Obj *const *objv)
1356{
1357    Tcl_ObjCmdProc *proc;
1358
1359    proc = GetOpFromObj(interp, nMapPinOps, mapPinOps,
1360                        CMDSPEC_ARG2, objc, objv, 0);
1361    if (proc == NULL) {
1362        return TCL_ERROR;
1363    }
1364    return (*proc) (clientData, interp, objc, objv);
1365}
1366
1367static int
1368MapPositionDisplayOp(ClientData clientData, Tcl_Interp *interp, int objc,
1369                     Tcl_Obj *const *objv)
1370{
1371    bool state;
1372    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
1373        return TCL_ERROR;
1374    }
1375    Renderer::CoordinateDisplayType type = Renderer::COORDS_LATLONG_DECIMAL_DEGREES;
1376    if (state && objc > 3) {
1377        const char *str = Tcl_GetString(objv[3]);
1378        if (str[0] == 'l' && strcmp(str, "latlong_decimal_degrees") == 0) {
1379            type = Renderer::COORDS_LATLONG_DECIMAL_DEGREES;
1380        } else if (str[0] == 'l' && strcmp(str, "latlong_degrees_decimal_minutes") == 0) {
1381            type = Renderer::COORDS_LATLONG_DEGREES_DECIMAL_MINUTES;
1382        } else if (str[0] == 'l' && strcmp(str, "latlong_degrees_minutes_seconds") == 0) {
1383            type = Renderer::COORDS_LATLONG_DEGREES_MINUTES_SECONDS;
1384        } else if (str[0] == 'm' && strcmp(str, "mgrs") == 0) {
1385            type = Renderer::COORDS_MGRS;
1386        } else {
1387            Tcl_AppendResult(interp, "invalid type: \"", str,
1388                             "\": should be 'latlong_decimal_degrees', 'latlong_degrees_decimal_minutes', 'latlong_degrees_minutes_seconds', or 'mgrs'",
1389                             (char*)NULL);
1390            return TCL_ERROR;
1391        }
1392    }
1393    if (state && objc > 4) {
1394        int precision;
1395        if (Tcl_GetIntFromObj(interp, objv[4], &precision) != TCL_OK) {
1396            return TCL_ERROR;
1397        }
1398        g_renderer->setCoordinateReadout(state, type, precision);
1399    } else {
1400        g_renderer->setCoordinateReadout(state, type);
1401    }
1402
1403    return TCL_OK;
1404}
1405
1406static int
1407MapResetOp(ClientData clientData, Tcl_Interp *interp, int objc,
1408           Tcl_Obj *const *objv)
1409{
1410    char *typeStr = Tcl_GetString(objv[2]);
1411    osgEarth::MapOptions::CoordinateSystemType type;
1412    if (typeStr[0] == 'g' && strcmp(typeStr, "geocentric") == 0) {
1413        type = osgEarth::MapOptions::CSTYPE_GEOCENTRIC;
1414    } else if (typeStr[0] == 'g' && strcmp(typeStr, "geocentric_cube") == 0) {
1415        type = osgEarth::MapOptions::CSTYPE_GEOCENTRIC_CUBE;
1416    } else if (typeStr[0] == 'p' && strcmp(typeStr, "projected") == 0) {
1417        type = osgEarth::MapOptions::CSTYPE_PROJECTED;
1418    } else {
1419        Tcl_AppendResult(interp, "bad map type \"", typeStr,
1420                         "\": must be geocentric or projected", (char*)NULL);
1421        return TCL_ERROR;
1422    }
1423
1424    if (type == osgEarth::MapOptions::CSTYPE_PROJECTED) {
1425        if (objc < 4) {
1426            Tcl_AppendResult(interp, "wrong # arguments: profile required for projected maps", (char*)NULL);
1427            return TCL_ERROR;
1428        }
1429        char *profile = Tcl_GetString(objv[3]);
1430        if (objc > 4) {
1431            if (objc < 8) {
1432                Tcl_AppendResult(interp, "wrong # arguments: 4 bounds arguments required", (char*)NULL);
1433                return TCL_ERROR;
1434            }
1435            double bounds[4];
1436            for (int i = 0; i < 4; i++) {
1437                if (Tcl_GetDoubleFromObj(interp, objv[4+i], &bounds[i]) != TCL_OK) {
1438                    return TCL_ERROR;
1439                }
1440            }
1441            // Check if min > max
1442            if (bounds[0] > bounds[2] ||
1443                bounds[1] > bounds[3]) {
1444                Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
1445                return TCL_ERROR;
1446            }
1447            // Note: plate-carre generates same SRS as others, but with
1448            // _is_plate_carre flag set
1449            // In map profile, _is_plate_carre is forced on for
1450            // geographic+projected SRS
1451            if (strcmp(profile, "geodetic") == 0 ||
1452                strcmp(profile, "epsg:4326") == 0 ||
1453                strcmp(profile, "wgs84") == 0 ||
1454                strcmp(profile, "plate-carre") == 0) {
1455                if (bounds[0] < -180. || bounds[0] > 180. ||
1456                    bounds[2] < -180. || bounds[2] > 180. ||
1457                    bounds[1] < -90. || bounds[1] > 90. ||
1458                    bounds[3] < -90. || bounds[3] > 90.) {
1459                    Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
1460                    return TCL_ERROR;
1461                }
1462            } else if (strcmp(profile, "spherical-mercator") == 0 ||
1463                       strcmp(profile, "epsg:900913") == 0 ||
1464                       strcmp(profile, "epsg:3785") == 0 ||
1465                       strcmp(profile, "epsg:3857") == 0 ||
1466                       strcmp(profile, "epsg:102113") == 0) {
1467                for (int i = 0; i < 4; i++) {
1468                    if (bounds[i] < -20037508.34 || bounds[i] > 20037508.34) {
1469                        Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
1470                        return TCL_ERROR;
1471                    }
1472                }
1473            } else if (strcmp(profile, "epsg:32662") == 0 ||
1474                       strcmp(profile, "epsg:32663") == 0) {
1475                if (bounds[0] < -20037508.3428 || bounds[0] > 20037508.3428 ||
1476                    bounds[2] < -20037508.3428 || bounds[2] > 20037508.3428 ||
1477                    bounds[1] < -10018754.1714 || bounds[1] > 10018754.1714 ||
1478                    bounds[3] < -10018754.1714 || bounds[3] > 10018754.1714) {
1479                    Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
1480                    return TCL_ERROR;
1481                }
1482            }
1483            g_renderer->resetMap(type, profile, bounds);
1484        } else {
1485            if (osgEarth::Registry::instance()->getNamedProfile(profile) == NULL) {
1486                Tcl_AppendResult(interp, "bad named profile \"", profile,
1487                                 "\": must be e.g. 'global-geodetic', 'global-mercator'...", (char*)NULL);
1488                return TCL_ERROR;
1489            }
1490            g_renderer->resetMap(type, profile);
1491        }
1492    } else {
1493        // No profile required for geocentric (3D) maps
1494        g_renderer->resetMap(type);
1495    }
1496
1497    return TCL_OK;
1498}
1499
1500static int
1501MapScaleBarOp(ClientData clientData, Tcl_Interp *interp, int objc,
1502              Tcl_Obj *const *objv)
1503{
1504    bool state;
1505    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
1506        return TCL_ERROR;
1507    }
1508    g_renderer->setScaleBar(state);
1509    if (state && objc > 3) {
1510        const char *unitStr = Tcl_GetString(objv[3]);
1511        ScaleBarUnits units;
1512        if (unitStr[0] == 'm' && strcmp(unitStr, "meters") == 0) {
1513            units = UNITS_METERS;
1514        } else if (unitStr[0] == 'f' && strcmp(unitStr, "feet") == 0) {
1515            units = UNITS_INTL_FEET;
1516        } else if (unitStr[0] == 'u' && strcmp(unitStr, "us_survey_feet") == 0) {
1517            units = UNITS_US_SURVEY_FEET;
1518        } else if (unitStr[0] == 'n' && strcmp(unitStr, "nautical_miles") == 0) {
1519            units = UNITS_NAUTICAL_MILES;
1520        } else {
1521            Tcl_AppendResult(interp, "bad units \"", unitStr,
1522                             "\": must be 'meters', 'feet', 'us_survey_feet' or 'nautical_miles'", (char*)NULL);
1523            return TCL_ERROR;
1524        }
1525        g_renderer->setScaleBarUnits(units);
1526    }
1527    return TCL_OK;
1528}
1529
1530static int
1531MapSetPositionOp(ClientData clientData, Tcl_Interp *interp, int objc,
1532            Tcl_Obj *const *objv)
1533{
1534    if (objc < 3) {
1535        g_renderer->clearReadout();
1536    } else {
1537        int x, y;
1538        if (Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK ||
1539            Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK) {
1540            return TCL_ERROR;
1541        }
1542        g_renderer->setReadout(x, y);
1543    }
1544    return TCL_OK;
1545}
1546
1547static int
1548MapTerrainEdgesOp(ClientData clientData, Tcl_Interp *interp, int objc,
1549                  Tcl_Obj *const *objv)
1550{
1551    bool state;
1552    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
1553        return TCL_ERROR;
1554    }
1555    TRACE("Not implemented");
1556    //g_renderer->setTerrainEdges(state);
1557    return TCL_OK;
1558}
1559
1560static int
1561MapTerrainLightingOp(ClientData clientData, Tcl_Interp *interp, int objc,
1562                     Tcl_Obj *const *objv)
1563{
1564    bool state;
1565    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
1566        return TCL_ERROR;
1567    }
1568
1569    g_renderer->setTerrainLighting(state);
1570    return TCL_OK;
1571}
1572
1573static int
1574MapTerrainLineColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
1575                      Tcl_Obj *const *objv)
1576{
1577    float color[3];
1578    if (GetFloatFromObj(interp, objv[3], &color[0]) != TCL_OK ||
1579        GetFloatFromObj(interp, objv[4], &color[1]) != TCL_OK ||
1580        GetFloatFromObj(interp, objv[5], &color[2]) != TCL_OK) {
1581        return TCL_ERROR;
1582    }
1583    TRACE("Not implemented");
1584    //g_renderer->setTerrainLineColor(color);
1585    return TCL_OK;
1586}
1587
1588static int
1589MapTerrainVertScaleOp(ClientData clientData, Tcl_Interp *interp, int objc,
1590                      Tcl_Obj *const *objv)
1591{
1592    double scale;
1593    if (Tcl_GetDoubleFromObj(interp, objv[3], &scale) != TCL_OK) {
1594        return TCL_ERROR;
1595    }
1596
1597    g_renderer->setTerrainVerticalScale(scale);
1598    return TCL_OK;
1599}
1600
1601static int
1602MapTerrainWireframeOp(ClientData clientData, Tcl_Interp *interp, int objc,
1603                      Tcl_Obj *const *objv)
1604{
1605    bool state;
1606    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
1607        return TCL_ERROR;
1608    }
1609
1610    g_renderer->setTerrainWireframe(state);
1611    return TCL_OK;
1612}
1613
1614static CmdSpec mapTerrainOps[] = {
1615    {"edges",     1, MapTerrainEdgesOp,     4, 4, "bool"},
1616    {"lighting",  2, MapTerrainLightingOp,  4, 4, "bool"},
1617    {"linecolor", 2, MapTerrainLineColorOp, 6, 6, "r g b"},
1618    {"vertscale", 1, MapTerrainVertScaleOp, 4, 4, "val"},
1619    {"wireframe", 1, MapTerrainWireframeOp, 4, 4, "bool"},
1620};
1621static int nMapTerrainOps = NumCmdSpecs(mapTerrainOps);
1622
1623static int
1624MapTerrainOp(ClientData clientData, Tcl_Interp *interp, int objc,
1625           Tcl_Obj *const *objv)
1626{
1627    Tcl_ObjCmdProc *proc;
1628
1629    proc = GetOpFromObj(interp, nMapTerrainOps, mapTerrainOps,
1630                        CMDSPEC_ARG2, objc, objv, 0);
1631    if (proc == NULL) {
1632        return TCL_ERROR;
1633    }
1634    return (*proc) (clientData, interp, objc, objv);
1635}
1636
1637static CmdSpec mapOps[] = {
1638    {"coords",   1, MapCoordsOp,          5, 7, "token x y ?srs? ?verticalDatum?"},
1639    {"grid",     1, MapGraticuleOp,       3, 4, "bool ?type?"},
1640    {"layer",    2, MapLayerOp,           3, 0, "op ?params...?"},
1641    {"load",     2, MapLoadOp,            4, 5, "options"},
1642    {"pin",      2, MapPinOp,             3, 0, "op ?params...?"},
1643    {"posdisp",  2, MapPositionDisplayOp, 3, 5, "bool ?format? ?precision?"},
1644    {"reset",    1, MapResetOp,           3, 8, "type ?profile xmin ymin xmax ymax?"},
1645    {"scalebar", 1, MapScaleBarOp,        3, 4, "bool ?units?"},
1646    {"setpos",   1, MapSetPositionOp,     2, 4, "x y"},
1647    {"terrain",  1, MapTerrainOp,         3, 0, "op ?params...?"},
1648};
1649static int nMapOps = NumCmdSpecs(mapOps);
1650
1651static int
1652MapCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1653       Tcl_Obj *const *objv)
1654{
1655    Tcl_ObjCmdProc *proc;
1656
1657    proc = GetOpFromObj(interp, nMapOps, mapOps,
1658                        CMDSPEC_ARG1, objc, objv, 0);
1659    if (proc == NULL) {
1660        return TCL_ERROR;
1661    }
1662    return (*proc) (clientData, interp, objc, objv);
1663}
1664
1665static int
1666MouseClickOp(ClientData clientData, Tcl_Interp *interp, int objc,
1667             Tcl_Obj *const *objv)
1668{
1669    int button;
1670    double x, y;
1671
1672    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
1673        return TCL_ERROR;
1674    }
1675    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
1676        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
1677        return TCL_ERROR;
1678    }
1679
1680    g_renderer->mouseClick(button, x, y);
1681    return TCL_OK;
1682}
1683
1684static int
1685MouseDoubleClickOp(ClientData clientData, Tcl_Interp *interp, int objc,
1686                   Tcl_Obj *const *objv)
1687{
1688    int button;
1689    double x, y;
1690
1691    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
1692        return TCL_ERROR;
1693    }
1694    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
1695        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
1696        return TCL_ERROR;
1697    }
1698
1699    g_renderer->mouseDoubleClick(button, x, y);
1700    return TCL_OK;
1701}
1702
1703static int
1704MouseDragOp(ClientData clientData, Tcl_Interp *interp, int objc,
1705            Tcl_Obj *const *objv)
1706{
1707    int button;
1708    double x, y;
1709
1710    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
1711        return TCL_ERROR;
1712    }
1713    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
1714        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
1715        return TCL_ERROR;
1716    }
1717
1718    g_renderer->mouseDrag(button, x, y);
1719    return TCL_OK;
1720}
1721
1722static int
1723MouseMotionOp(ClientData clientData, Tcl_Interp *interp, int objc,
1724              Tcl_Obj *const *objv)
1725{
1726    double x, y;
1727
1728    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
1729        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
1730        return TCL_ERROR;
1731    }
1732
1733    g_renderer->mouseMotion(x, y);
1734    return TCL_OK;
1735}
1736
1737static int
1738MouseReleaseOp(ClientData clientData, Tcl_Interp *interp, int objc,
1739               Tcl_Obj *const *objv)
1740{
1741    int button;
1742    double x, y;
1743
1744    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
1745        return TCL_ERROR;
1746    }
1747    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
1748        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
1749        return TCL_ERROR;
1750    }
1751
1752    g_renderer->mouseRelease(button, x, y);
1753    return TCL_OK;
1754}
1755
1756static int
1757MouseScrollOp(ClientData clientData, Tcl_Interp *interp, int objc,
1758              Tcl_Obj *const *objv)
1759{
1760    int direction;
1761
1762    if (Tcl_GetIntFromObj(interp, objv[2], &direction) != TCL_OK) {
1763        return TCL_ERROR;
1764    }
1765
1766    g_renderer->mouseScroll(direction);
1767    return TCL_OK;
1768}
1769
1770static CmdSpec mouseOps[] = {
1771    {"click",    1, MouseClickOp,       5, 5, "button x y"},
1772    {"dblclick", 2, MouseDoubleClickOp, 5, 5, "button x y"},
1773    {"drag",     2, MouseDragOp,        5, 5, "button x y"},
1774    {"motion",   1, MouseMotionOp,      4, 4, "x y"},
1775    {"release",  1, MouseReleaseOp,     5, 5, "button x y"},
1776    {"scroll",   1, MouseScrollOp,      3, 3, "direction"},
1777};
1778static int nMouseOps = NumCmdSpecs(mouseOps);
1779
1780static int
1781MouseCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1782         Tcl_Obj *const *objv)
1783{
1784    Tcl_ObjCmdProc *proc;
1785
1786    proc = GetOpFromObj(interp, nMouseOps, mouseOps,
1787                        CMDSPEC_ARG1, objc, objv, 0);
1788    if (proc == NULL) {
1789        return TCL_ERROR;
1790    }
1791    return (*proc) (clientData, interp, objc, objv);
1792}
1793
1794static int
1795RendererRenderOp(ClientData clientData, Tcl_Interp *interp, int objc,
1796                 Tcl_Obj *const *objv)
1797{
1798    g_renderer->eventuallyRender();
1799    return TCL_OK;
1800}
1801
1802static CmdSpec rendererOps[] = {
1803    {"render",     1, RendererRenderOp, 2, 2, ""},
1804};
1805static int nRendererOps = NumCmdSpecs(rendererOps);
1806
1807static int
1808RendererCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1809            Tcl_Obj *const *objv)
1810{
1811    Tcl_ObjCmdProc *proc;
1812
1813    proc = GetOpFromObj(interp, nRendererOps, rendererOps,
1814                        CMDSPEC_ARG1, objc, objv, 0);
1815    if (proc == NULL) {
1816        return TCL_ERROR;
1817    }
1818    return (*proc) (clientData, interp, objc, objv);
1819}
1820
1821static int
1822ScreenBgColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
1823                Tcl_Obj *const *objv)
1824{
1825    float color[3];
1826
1827    if (GetFloatFromObj(interp, objv[2], &color[0]) != TCL_OK ||
1828        GetFloatFromObj(interp, objv[3], &color[1]) != TCL_OK ||
1829        GetFloatFromObj(interp, objv[4], &color[2]) != TCL_OK) {
1830        return TCL_ERROR;
1831    }
1832
1833    g_renderer->setBackgroundColor(color);
1834    return TCL_OK;
1835}
1836
1837static int
1838ScreenCoordsOp(ClientData clientData, Tcl_Interp *interp, int objc,
1839               Tcl_Obj *const *objv)
1840{
1841    double x, y, z;
1842    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
1843        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK ||
1844        Tcl_GetDoubleFromObj(interp, objv[5], &z) != TCL_OK) {
1845        return TCL_ERROR;
1846    }
1847    const osgEarth::SpatialReference *srs = NULL;
1848    if (objc < 7) {
1849        srs = g_renderer->getMapSRS();
1850        if (srs == NULL) {
1851            Tcl_AppendResult(interp, "Could not determine map SRS", (char*)NULL);
1852            return TCL_ERROR;
1853        }
1854    } else {
1855        std::string srsInit(Tcl_GetString(objv[6]));
1856        std::string verticalDatum;
1857        if (objc > 7) {
1858            verticalDatum = Tcl_GetString(objv[7]);
1859        }
1860        srs = osgEarth::SpatialReference::get(srsInit, verticalDatum);
1861        if (srs == NULL) {
1862            Tcl_AppendResult(interp, "bad SRS \"", srsInit.c_str(), "\"", (char*)NULL);
1863            return TCL_ERROR;
1864        }
1865    }
1866    // ALTMODE_RELATIVE is height above terrain, ALTMODE_ABSOLUTE means
1867    // relative to the vertical datum
1868    osgEarth::GeoPoint mapPoint(srs, x, y, z, osgEarth::ALTMODE_ABSOLUTE);
1869    size_t length;
1870    char mesg[256];
1871    osg::Vec3d world;
1872    osg::Vec3d screen;
1873    if (g_renderer->getWorldCoords(mapPoint, &world) &&
1874        g_renderer->worldToScreen(world, &screen)) {
1875        // send coords to client
1876        length = snprintf(mesg, sizeof(mesg),
1877                          "nv>screen coords %s %g %g %g %g %g %g\n",
1878                          Tcl_GetString(objv[2]),
1879                          screen.x(), screen.y(), screen.z(),
1880                          x, y, z);
1881
1882        queueResponse(mesg, length, Response::VOLATILE);
1883    } else {
1884        // Out of range
1885        length = snprintf(mesg, sizeof(mesg),
1886                          "nv>screen coords %s invalid %g %g %g\n",
1887                          Tcl_GetString(objv[2]),
1888                          x, y, z);
1889
1890        queueResponse(mesg, length, Response::VOLATILE);
1891    }
1892    return TCL_OK;
1893}
1894
1895static int
1896ScreenSizeOp(ClientData clientData, Tcl_Interp *interp, int objc,
1897             Tcl_Obj *const *objv)
1898{
1899    int width, height;
1900
1901    if (Tcl_GetIntFromObj(interp, objv[2], &width) != TCL_OK ||
1902        Tcl_GetIntFromObj(interp, objv[3], &height) != TCL_OK) {
1903        return TCL_ERROR;
1904    }
1905
1906    g_renderer->setWindowSize(width, height);
1907    return TCL_OK;
1908}
1909
1910static CmdSpec screenOps[] = {
1911    {"bgcolor", 1, ScreenBgColorOp, 5, 5, "r g b"},
1912    {"coords",  1, ScreenCoordsOp, 6, 8, "token x y z ?srs? ?verticalDatum?"},
1913    {"size",    1, ScreenSizeOp, 4, 4, "width height"}
1914};
1915static int nScreenOps = NumCmdSpecs(screenOps);
1916
1917static int
1918ScreenCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1919          Tcl_Obj *const *objv)
1920{
1921    Tcl_ObjCmdProc *proc;
1922
1923    proc = GetOpFromObj(interp, nScreenOps, screenOps,
1924                        CMDSPEC_ARG1, objc, objv, 0);
1925    if (proc == NULL) {
1926        return TCL_ERROR;
1927    }
1928    return (*proc) (clientData, interp, objc, objv);
1929}
1930
1931#ifdef USE_READ_THREAD
1932int
1933GeoVis::queueCommands(Tcl_Interp *interp,
1934                      ClientData clientData,
1935                      ReadBuffer *inBufPtr)
1936{
1937    Tcl_DString commandString;
1938    Tcl_DStringInit(&commandString);
1939    fd_set readFds;
1940
1941    FD_ZERO(&readFds);
1942    FD_SET(inBufPtr->file(), &readFds);
1943    while (inBufPtr->isLineAvailable() ||
1944           (select(inBufPtr->file()+1, &readFds, NULL, NULL, NULL) > 0)) {
1945        size_t numBytes;
1946        unsigned char *buffer;
1947
1948        /* A short read is treated as an error here because we assume that we
1949         * will always get commands line by line. */
1950        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
1951            /* Terminate the server if we can't communicate with the client
1952             * anymore. */
1953            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
1954                TRACE("Exiting server on EOF from client");
1955                return -1;
1956            } else {
1957                ERROR("Exiting server, failed to read from client: %s",
1958                      strerror(errno));
1959                return -1;
1960            }
1961        }
1962        Tcl_DStringAppend(&commandString, (char *)buffer, numBytes);
1963        if (Tcl_CommandComplete(Tcl_DStringValue(&commandString))) {
1964            // Add to queue
1965            Command *command = new Command(Command::COMMAND);
1966            command->setMessage((unsigned char *)Tcl_DStringValue(&commandString),
1967                                Tcl_DStringLength(&commandString), Command::VOLATILE);
1968            g_inQueue->enqueue(command);
1969            Tcl_DStringSetLength(&commandString, 0);
1970        }
1971        FD_SET(inBufPtr->file(), &readFds);
1972    }
1973
1974    return 1;
1975}
1976#endif
1977
1978/**
1979 * \brief Execute commands from client in Tcl interpreter
1980 *
1981 * In this threaded model, the select call is for event compression.  We
1982 * want to execute render server commands as long as they keep coming. 
1983 * This lets us execute a stream of many commands but render once.  This
1984 * benefits camera movements, screen resizing, and opacity changes
1985 * (using a slider on the client).  The down side is you don't render
1986 * until there's a lull in the command stream.  If the client needs an
1987 * image, it can issue an "imgflush" command.  That breaks us out of the
1988 * read loop.
1989 */
1990int
1991GeoVis::processCommands(Tcl_Interp *interp,
1992                        ClientData clientData,
1993                        ReadBuffer *inBufPtr,
1994                        int fdOut,
1995                        long timeout)
1996{
1997    int ret = 1;
1998    int status = TCL_OK;
1999
2000    Tcl_DString command;
2001    Tcl_DStringInit(&command);
2002    fd_set readFds;
2003    struct timeval tv, *tvPtr;
2004
2005    FD_ZERO(&readFds);
2006    FD_SET(inBufPtr->file(), &readFds);
2007    tvPtr = NULL;                       /* Wait for the first read. This is so
2008                                         * that we don't spin when no data is
2009                                         * available. */
2010    if (timeout >= 0L) {
2011        tv.tv_sec = 0L;
2012        tv.tv_usec = timeout;
2013        tvPtr = &tv;
2014    } else {
2015        TRACE("Blocking on select()");
2016    }
2017    while (inBufPtr->isLineAvailable() ||
2018           (ret = select(inBufPtr->file()+1, &readFds, NULL, NULL, tvPtr)) > 0) {
2019        size_t numBytes;
2020        unsigned char *buffer;
2021
2022        /* A short read is treated as an error here because we assume that we
2023         * will always get commands line by line. */
2024        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
2025            /* Terminate the server if we can't communicate with the client
2026             * anymore. */
2027            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
2028                TRACE("Exiting server on EOF from client");
2029                return -1;
2030            } else {
2031                ERROR("Exiting server, failed to read from client: %s",
2032                      strerror(errno));
2033                return -1;
2034            }
2035        }
2036        Tcl_DStringAppend(&command, (char *)buffer, numBytes);
2037        if (Tcl_CommandComplete(Tcl_DStringValue(&command))) {
2038            struct timeval start, finish;
2039            gettimeofday(&start, NULL);
2040            g_stats.nCommands++;
2041            status = ExecuteCommand(interp, &command);
2042            gettimeofday(&finish, NULL);
2043            g_stats.cmdTime += (MSECS_ELAPSED(start, finish) / 1.0e+3);
2044            if (status == TCL_BREAK) {
2045                return 2;               /* This was caused by a "imgflush"
2046                                         * command. Break out of the read loop
2047                                         * and allow a new image to be
2048                                         * rendered. */
2049            } else { //if (status != TCL_OK) {
2050                ret = 0;
2051                if (handleError(interp, clientData, status, fdOut) < 0) {
2052                    return -1;
2053                }
2054            }
2055            if (status == TCL_OK) {
2056                ret = 3;
2057            }
2058        }
2059
2060        tv.tv_sec = tv.tv_usec = 0L;    /* On successive reads, we break out
2061                                         * if no data is available. */
2062        FD_SET(inBufPtr->file(), &readFds);
2063        tvPtr = &tv;
2064    }
2065
2066    return ret;
2067}
2068
2069/**
2070 * \brief Send error message to client socket
2071 */
2072int
2073GeoVis::handleError(Tcl_Interp *interp,
2074                    ClientData clientData,
2075                    int status, int fdOut)
2076{
2077    const char *string;
2078    int nBytes;
2079
2080    if (status != TCL_OK) {
2081        string = Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY);
2082        nBytes = strlen(string);
2083        if (nBytes > 0) {
2084            TRACE("status=%d errorInfo=(%s)", status, string);
2085
2086            std::ostringstream oss;
2087            oss << "nv>viserror -type internal_error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
2088            std::string ostr = oss.str();
2089            nBytes = ostr.length();
2090
2091            if (queueResponse(ostr.c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
2092                return -1;
2093            }
2094        }
2095    }
2096
2097    std::string msg = getUserMessages();
2098    nBytes = msg.length();
2099    if (nBytes > 0) {
2100        string = msg.c_str();
2101        TRACE("userError=(%s)", string);
2102
2103        std::ostringstream oss;
2104        oss << "nv>viserror -type error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
2105        std::string ostr = oss.str();
2106        nBytes = ostr.length();
2107
2108        if (queueResponse(ostr.c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
2109            return -1;
2110        }
2111
2112        clearUserMessages();
2113    }
2114
2115    return 0;
2116}
2117
2118/**
2119 * \brief Create Tcl interpreter and add commands
2120 *
2121 * \return The initialized Tcl interpreter
2122 */
2123void
2124GeoVis::initTcl(Tcl_Interp *interp, ClientData clientData)
2125{
2126    Tcl_MakeSafe(interp);
2127    Tcl_CreateObjCommand(interp, "camera",         CameraCmd,         clientData, NULL);
2128    Tcl_CreateObjCommand(interp, "clientinfo",     ClientInfoCmd,     clientData, NULL);
2129    Tcl_CreateObjCommand(interp, "colormap",       ColorMapCmd,       clientData, NULL);
2130    Tcl_CreateObjCommand(interp, "file",           FileCmd,           clientData, NULL);
2131    Tcl_CreateObjCommand(interp, "imgflush",       ImageFlushCmd,     clientData, NULL);
2132    Tcl_CreateObjCommand(interp, "key",            KeyCmd,            clientData, NULL);
2133    Tcl_CreateObjCommand(interp, "map",            MapCmd,            clientData, NULL);
2134    Tcl_CreateObjCommand(interp, "mouse",          MouseCmd,          clientData, NULL);
2135    Tcl_CreateObjCommand(interp, "renderer",       RendererCmd,       clientData, NULL);
2136    Tcl_CreateObjCommand(interp, "screen",         ScreenCmd,         clientData, NULL);
2137}
2138
2139/**
2140 * \brief Delete Tcl commands and interpreter
2141 */
2142void GeoVis::exitTcl(Tcl_Interp *interp)
2143{
2144    Tcl_DeleteCommand(interp, "camera");
2145    Tcl_DeleteCommand(interp, "clientinfo");
2146    Tcl_DeleteCommand(interp, "colormap");
2147    Tcl_DeleteCommand(interp, "file");
2148    Tcl_DeleteCommand(interp, "imgflush");
2149    Tcl_DeleteCommand(interp, "key");
2150    Tcl_DeleteCommand(interp, "map");
2151    Tcl_DeleteCommand(interp, "mouse");
2152    Tcl_DeleteCommand(interp, "renderer");
2153    Tcl_DeleteCommand(interp, "screen");
2154
2155    Tcl_DeleteInterp(interp);
2156}
Note: See TracBrowser for help on using the repository browser.