source: geovis/trunk/RendererCmd.cpp @ 4957

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

Add protocol option to enable/disable disk caching of image layers

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