source: geovis/trunk/RendererCmd.cpp @ 4790

Last change on this file since 4790 was 4655, checked in by ldelgass, 10 years ago

More selection testing

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