source: geovis/trunk/RendererCmd.cpp @ 4973

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

First pass at protocol to set ephemeris time (currently uses server local time)

File size: 75.4 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 int
1730MapTimeOp(ClientData clientData, Tcl_Interp *interp, int objc,
1731          Tcl_Obj *const *objv)
1732{
1733    osgEarth::DateTime now;
1734    int year, month, day;
1735    double hours;
1736    year = now.year();
1737    month = now.month();
1738    day = now.day();
1739    hours = now.hours();
1740    if (objc > 2) {
1741        if (Tcl_GetDoubleFromObj(interp, objv[2], &hours) != TCL_OK) {
1742            return TCL_ERROR;
1743        }
1744    }
1745    if (objc > 3) {
1746        if (Tcl_GetIntFromObj(interp, objv[3], &day) != TCL_OK) {
1747            return TCL_ERROR;
1748        }
1749    }
1750    if (objc > 4) {
1751        if (Tcl_GetIntFromObj(interp, objv[4], &month) != TCL_OK) {
1752            return TCL_ERROR;
1753        }
1754    }
1755    if (objc > 5) {
1756        if (Tcl_GetIntFromObj(interp, objv[5], &year) != TCL_OK) {
1757            return TCL_ERROR;
1758        }
1759    }
1760
1761    g_renderer->setEphemerisTime(year, month, day, hours);
1762    return TCL_OK;
1763}
1764
1765static CmdSpec mapOps[] = {
1766    {"box",      1, MapBoxOp,             3, 0, "op ?params..."},
1767    {"coords",   1, MapCoordsOp,          4, 6, "token coords ?srs? ?verticalDatum?"},
1768    {"grid",     1, MapGraticuleOp,       3, 4, "bool ?type?"},
1769    {"layer",    2, MapLayerOp,           3, 0, "op ?params...?"},
1770    {"load",     2, MapLoadOp,            4, 5, "options"},
1771    {"pin",      2, MapPinOp,             3, 0, "op ?params...?"},
1772    {"posdisp",  2, MapPositionDisplayOp, 3, 5, "bool ?format? ?precision?"},
1773    {"reset",    1, MapResetOp,           3, 8, "type ?profile xmin ymin xmax ymax?"},
1774    {"scalebar", 1, MapScaleBarOp,        3, 4, "bool ?units?"},
1775    {"setpos",   1, MapSetPositionOp,     2, 4, "x y"},
1776    {"terrain",  1, MapTerrainOp,         3, 0, "op ?params...?"},
1777    {"time",     1, MapTimeOp,            2, 6, "?hours? ?day? ?month? ?year?"},
1778};
1779static int nMapOps = NumCmdSpecs(mapOps);
1780
1781static int
1782MapCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1783       Tcl_Obj *const *objv)
1784{
1785    Tcl_ObjCmdProc *proc;
1786
1787    proc = GetOpFromObj(interp, nMapOps, mapOps,
1788                        CMDSPEC_ARG1, objc, objv, 0);
1789    if (proc == NULL) {
1790        return TCL_ERROR;
1791    }
1792    return (*proc) (clientData, interp, objc, objv);
1793}
1794
1795static int
1796MouseClickOp(ClientData clientData, Tcl_Interp *interp, int objc,
1797             Tcl_Obj *const *objv)
1798{
1799    int button;
1800    double x, y;
1801
1802    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
1803        return TCL_ERROR;
1804    }
1805    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
1806        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
1807        return TCL_ERROR;
1808    }
1809
1810    g_renderer->mouseClick(button, x, y);
1811    return TCL_OK;
1812}
1813
1814static int
1815MouseDoubleClickOp(ClientData clientData, Tcl_Interp *interp, int objc,
1816                   Tcl_Obj *const *objv)
1817{
1818    int button;
1819    double x, y;
1820
1821    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
1822        return TCL_ERROR;
1823    }
1824    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
1825        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
1826        return TCL_ERROR;
1827    }
1828
1829    g_renderer->mouseDoubleClick(button, x, y);
1830    return TCL_OK;
1831}
1832
1833static int
1834MouseDragOp(ClientData clientData, Tcl_Interp *interp, int objc,
1835            Tcl_Obj *const *objv)
1836{
1837    int button;
1838    double x, y;
1839
1840    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
1841        return TCL_ERROR;
1842    }
1843    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
1844        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
1845        return TCL_ERROR;
1846    }
1847
1848    g_renderer->mouseDrag(button, x, y);
1849    return TCL_OK;
1850}
1851
1852static int
1853MouseMotionOp(ClientData clientData, Tcl_Interp *interp, int objc,
1854              Tcl_Obj *const *objv)
1855{
1856    double x, y;
1857
1858    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
1859        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
1860        return TCL_ERROR;
1861    }
1862
1863    g_renderer->mouseMotion(x, y);
1864    return TCL_OK;
1865}
1866
1867static int
1868MouseReleaseOp(ClientData clientData, Tcl_Interp *interp, int objc,
1869               Tcl_Obj *const *objv)
1870{
1871    int button;
1872    double x, y;
1873
1874    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
1875        return TCL_ERROR;
1876    }
1877    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
1878        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
1879        return TCL_ERROR;
1880    }
1881
1882    g_renderer->mouseRelease(button, x, y);
1883    return TCL_OK;
1884}
1885
1886static int
1887MouseScrollOp(ClientData clientData, Tcl_Interp *interp, int objc,
1888              Tcl_Obj *const *objv)
1889{
1890    int direction;
1891
1892    if (Tcl_GetIntFromObj(interp, objv[2], &direction) != TCL_OK) {
1893        return TCL_ERROR;
1894    }
1895
1896    g_renderer->mouseScroll(direction);
1897    return TCL_OK;
1898}
1899
1900static CmdSpec mouseOps[] = {
1901    {"click",    1, MouseClickOp,       5, 5, "button x y"},
1902    {"dblclick", 2, MouseDoubleClickOp, 5, 5, "button x y"},
1903    {"drag",     2, MouseDragOp,        5, 5, "button x y"},
1904    {"motion",   1, MouseMotionOp,      4, 4, "x y"},
1905    {"release",  1, MouseReleaseOp,     5, 5, "button x y"},
1906    {"scroll",   1, MouseScrollOp,      3, 3, "direction"},
1907};
1908static int nMouseOps = NumCmdSpecs(mouseOps);
1909
1910static int
1911MouseCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1912         Tcl_Obj *const *objv)
1913{
1914    Tcl_ObjCmdProc *proc;
1915
1916    proc = GetOpFromObj(interp, nMouseOps, mouseOps,
1917                        CMDSPEC_ARG1, objc, objv, 0);
1918    if (proc == NULL) {
1919        return TCL_ERROR;
1920    }
1921    return (*proc) (clientData, interp, objc, objv);
1922}
1923
1924static int
1925RendererRenderOp(ClientData clientData, Tcl_Interp *interp, int objc,
1926                 Tcl_Obj *const *objv)
1927{
1928    g_renderer->eventuallyRender();
1929    return TCL_OK;
1930}
1931
1932static CmdSpec rendererOps[] = {
1933    {"render",     1, RendererRenderOp, 2, 2, ""},
1934};
1935static int nRendererOps = NumCmdSpecs(rendererOps);
1936
1937static int
1938RendererCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1939            Tcl_Obj *const *objv)
1940{
1941    Tcl_ObjCmdProc *proc;
1942
1943    proc = GetOpFromObj(interp, nRendererOps, rendererOps,
1944                        CMDSPEC_ARG1, objc, objv, 0);
1945    if (proc == NULL) {
1946        return TCL_ERROR;
1947    }
1948    return (*proc) (clientData, interp, objc, objv);
1949}
1950
1951static int
1952ScreenBgColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
1953                Tcl_Obj *const *objv)
1954{
1955    float color[3];
1956
1957    if (GetFloatFromObj(interp, objv[2], &color[0]) != TCL_OK ||
1958        GetFloatFromObj(interp, objv[3], &color[1]) != TCL_OK ||
1959        GetFloatFromObj(interp, objv[4], &color[2]) != TCL_OK) {
1960        return TCL_ERROR;
1961    }
1962
1963    g_renderer->setBackgroundColor(color);
1964    return TCL_OK;
1965}
1966
1967static int
1968ScreenCoordsOp(ClientData clientData, Tcl_Interp *interp, int objc,
1969               Tcl_Obj *const *objv)
1970{
1971    int tokenLength;
1972    const char *token = Tcl_GetStringFromObj(objv[2], &tokenLength);
1973    int numCoords;
1974    Tcl_Obj **coords;
1975    if (Tcl_ListObjGetElements(interp, objv[3], &numCoords, &coords) != TCL_OK) {
1976        return TCL_ERROR;
1977    }
1978    if (numCoords == 0) {
1979        Tcl_AppendResult(interp, "no x,y,z coordinates in list", (char *)NULL);
1980        return TCL_ERROR;
1981    }
1982    if (numCoords % 3 != 0) {
1983        Tcl_AppendResult(interp, "invalid number of coordinates in list",
1984                         (char *)NULL);
1985        return TCL_ERROR;
1986    }
1987
1988    const osgEarth::SpatialReference *srs = NULL;
1989    if (objc < 5) {
1990        srs = g_renderer->getMapSRS();
1991        if (srs == NULL) {
1992            Tcl_AppendResult(interp, "Could not determine map SRS", (char*)NULL);
1993            return TCL_ERROR;
1994        }
1995    } else {
1996        std::string srsInit(Tcl_GetString(objv[4]));
1997        std::string verticalDatum;
1998        if (objc > 5) {
1999            verticalDatum = Tcl_GetString(objv[5]);
2000        }
2001        srs = osgEarth::SpatialReference::get(srsInit, verticalDatum);
2002        if (srs == NULL) {
2003            Tcl_AppendResult(interp, "bad SRS \"", srsInit.c_str(), "\"", (char*)NULL);
2004            return TCL_ERROR;
2005        }
2006    }
2007    Tcl_Obj *listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
2008    std::vector<osg::Vec3d> coordVec;
2009    for (int i = 0; i < numCoords; i += 3) {
2010        double x, y, z;
2011        if (Tcl_GetDoubleFromObj(interp, coords[i], &x) != TCL_OK ||
2012            Tcl_GetDoubleFromObj(interp, coords[i+1], &y) != TCL_OK ||
2013            Tcl_GetDoubleFromObj(interp, coords[i+2], &z) != TCL_OK) {
2014            return TCL_ERROR;
2015        }
2016        // ALTMODE_RELATIVE is height above terrain, ALTMODE_ABSOLUTE means
2017        // relative to the vertical datum
2018        osgEarth::GeoPoint mapPoint(srs, x, y, z, osgEarth::ALTMODE_ABSOLUTE);
2019        osg::Vec3d world;
2020        if (g_renderer->getWorldCoords(mapPoint, &world)) {
2021            coordVec.push_back(world);
2022        } else {
2023            coordVec.push_back(osg::Vec3d(std::numeric_limits<double>::quiet_NaN(),
2024                                          std::numeric_limits<double>::quiet_NaN(),
2025                                          std::numeric_limits<double>::quiet_NaN()));
2026        }
2027    }
2028    g_renderer->worldToScreen(coordVec);
2029    for (std::vector<osg::Vec3d>::iterator itr = coordVec.begin();
2030         itr != coordVec.end(); ++itr) {
2031        Tcl_Obj *objPtr = Tcl_NewDoubleObj(itr->x());
2032        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
2033        objPtr = Tcl_NewDoubleObj(itr->y());
2034        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
2035        objPtr = Tcl_NewDoubleObj(itr->z());
2036        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
2037    }
2038    // send coords to client
2039    int listLength;
2040    const char *string = Tcl_GetStringFromObj(listObjPtr, &listLength);
2041    size_t length = listLength + tokenLength + 22;
2042    char *mesg = new char[length];
2043    length = snprintf(mesg, length, "nv>screen coords %s {%s}\n", token, string);
2044    Tcl_DecrRefCount(listObjPtr);
2045    queueResponse(mesg, length, Response::VOLATILE);
2046    delete [] mesg;
2047    return TCL_OK;
2048}
2049
2050static int
2051ScreenSizeOp(ClientData clientData, Tcl_Interp *interp, int objc,
2052             Tcl_Obj *const *objv)
2053{
2054    int width, height;
2055
2056    if (Tcl_GetIntFromObj(interp, objv[2], &width) != TCL_OK ||
2057        Tcl_GetIntFromObj(interp, objv[3], &height) != TCL_OK) {
2058        return TCL_ERROR;
2059    }
2060
2061    g_renderer->setWindowSize(width, height);
2062    return TCL_OK;
2063}
2064
2065static CmdSpec screenOps[] = {
2066    {"bgcolor", 1, ScreenBgColorOp, 5, 5, "r g b"},
2067    {"coords",  1, ScreenCoordsOp, 4, 6, "token coords ?srs? ?verticalDatum?"},
2068    {"size",    1, ScreenSizeOp, 4, 4, "width height"}
2069};
2070static int nScreenOps = NumCmdSpecs(screenOps);
2071
2072static int
2073ScreenCmd(ClientData clientData, Tcl_Interp *interp, int objc,
2074          Tcl_Obj *const *objv)
2075{
2076    Tcl_ObjCmdProc *proc;
2077
2078    proc = GetOpFromObj(interp, nScreenOps, screenOps,
2079                        CMDSPEC_ARG1, objc, objv, 0);
2080    if (proc == NULL) {
2081        return TCL_ERROR;
2082    }
2083    return (*proc) (clientData, interp, objc, objv);
2084}
2085
2086#ifdef USE_READ_THREAD
2087int
2088GeoVis::queueCommands(Tcl_Interp *interp,
2089                      ClientData clientData,
2090                      ReadBuffer *inBufPtr)
2091{
2092    Tcl_DString commandString;
2093    Tcl_DStringInit(&commandString);
2094    fd_set readFds;
2095
2096    FD_ZERO(&readFds);
2097    FD_SET(inBufPtr->file(), &readFds);
2098    while (inBufPtr->isLineAvailable() ||
2099           (select(inBufPtr->file()+1, &readFds, NULL, NULL, NULL) > 0)) {
2100        size_t numBytes;
2101        unsigned char *buffer;
2102
2103        /* A short read is treated as an error here because we assume that we
2104         * will always get commands line by line. */
2105        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
2106            /* Terminate the server if we can't communicate with the client
2107             * anymore. */
2108            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
2109                TRACE("Exiting server on EOF from client");
2110                return -1;
2111            } else {
2112                ERROR("Exiting server, failed to read from client: %s",
2113                      strerror(errno));
2114                return -1;
2115            }
2116        }
2117        Tcl_DStringAppend(&commandString, (char *)buffer, numBytes);
2118        if (Tcl_CommandComplete(Tcl_DStringValue(&commandString))) {
2119            // Add to queue
2120            Command *command = new Command(Command::COMMAND);
2121            command->setMessage((unsigned char *)Tcl_DStringValue(&commandString),
2122                                Tcl_DStringLength(&commandString), Command::VOLATILE);
2123            g_inQueue->enqueue(command);
2124            Tcl_DStringSetLength(&commandString, 0);
2125        }
2126        FD_SET(inBufPtr->file(), &readFds);
2127    }
2128
2129    return 1;
2130}
2131#endif
2132
2133/**
2134 * \brief Execute commands from client in Tcl interpreter
2135 *
2136 * In this threaded model, the select call is for event compression.  We
2137 * want to execute render server commands as long as they keep coming. 
2138 * This lets us execute a stream of many commands but render once.  This
2139 * benefits camera movements, screen resizing, and opacity changes
2140 * (using a slider on the client).  The down side is you don't render
2141 * until there's a lull in the command stream.  If the client needs an
2142 * image, it can issue an "imgflush" command.  That breaks us out of the
2143 * read loop.
2144 */
2145int
2146GeoVis::processCommands(Tcl_Interp *interp,
2147                        ClientData clientData,
2148                        ReadBuffer *inBufPtr,
2149                        int fdOut,
2150                        long timeout)
2151{
2152    int ret = 1;
2153    int status = TCL_OK;
2154
2155    Tcl_DString command;
2156    Tcl_DStringInit(&command);
2157    fd_set readFds;
2158    struct timeval tv, *tvPtr;
2159
2160    FD_ZERO(&readFds);
2161    FD_SET(inBufPtr->file(), &readFds);
2162    tvPtr = NULL;                       /* Wait for the first read. This is so
2163                                         * that we don't spin when no data is
2164                                         * available. */
2165    if (timeout >= 0L) {
2166        tv.tv_sec = 0L;
2167        tv.tv_usec = timeout;
2168        tvPtr = &tv;
2169    } else {
2170        TRACE("Blocking on select()");
2171    }
2172    while (inBufPtr->isLineAvailable() ||
2173           (ret = select(inBufPtr->file()+1, &readFds, NULL, NULL, tvPtr)) > 0) {
2174        size_t numBytes;
2175        unsigned char *buffer;
2176
2177        /* A short read is treated as an error here because we assume that we
2178         * will always get commands line by line. */
2179        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
2180            /* Terminate the server if we can't communicate with the client
2181             * anymore. */
2182            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
2183                TRACE("Exiting server on EOF from client");
2184                return -1;
2185            } else {
2186                ERROR("Exiting server, failed to read from client: %s",
2187                      strerror(errno));
2188                return -1;
2189            }
2190        }
2191        Tcl_DStringAppend(&command, (char *)buffer, numBytes);
2192        if (Tcl_CommandComplete(Tcl_DStringValue(&command))) {
2193            struct timeval start, finish;
2194            gettimeofday(&start, NULL);
2195            g_stats.nCommands++;
2196            status = ExecuteCommand(interp, &command);
2197            gettimeofday(&finish, NULL);
2198            g_stats.cmdTime += (MSECS_ELAPSED(start, finish) / 1.0e+3);
2199            if (status == TCL_BREAK) {
2200                return 2;               /* This was caused by a "imgflush"
2201                                         * command. Break out of the read loop
2202                                         * and allow a new image to be
2203                                         * rendered. */
2204            } else { //if (status != TCL_OK) {
2205                ret = 0;
2206                if (handleError(interp, clientData, status, fdOut) < 0) {
2207                    return -1;
2208                }
2209            }
2210            if (status == TCL_OK) {
2211                ret = 3;
2212            }
2213        }
2214
2215        tv.tv_sec = tv.tv_usec = 0L;    /* On successive reads, we break out
2216                                         * if no data is available. */
2217        FD_SET(inBufPtr->file(), &readFds);
2218        tvPtr = &tv;
2219    }
2220
2221    return ret;
2222}
2223
2224/**
2225 * \brief Send error message to client socket
2226 */
2227int
2228GeoVis::handleError(Tcl_Interp *interp,
2229                    ClientData clientData,
2230                    int status, int fdOut)
2231{
2232    const char *string;
2233    int nBytes;
2234
2235    if (status != TCL_OK) {
2236        string = Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY);
2237        nBytes = strlen(string);
2238        if (nBytes > 0) {
2239            TRACE("status=%d errorInfo=(%s)", status, string);
2240
2241            std::ostringstream oss;
2242            oss << "nv>viserror -type internal_error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
2243            std::string ostr = oss.str();
2244            nBytes = ostr.length();
2245
2246            if (queueResponse(ostr.c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
2247                return -1;
2248            }
2249        }
2250    }
2251
2252    std::string msg = getUserMessages();
2253    nBytes = msg.length();
2254    if (nBytes > 0) {
2255        string = msg.c_str();
2256        TRACE("userError=(%s)", string);
2257
2258        std::ostringstream oss;
2259        oss << "nv>viserror -type error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
2260        std::string ostr = oss.str();
2261        nBytes = ostr.length();
2262
2263        if (queueResponse(ostr.c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
2264            return -1;
2265        }
2266
2267        clearUserMessages();
2268    }
2269
2270    return 0;
2271}
2272
2273/**
2274 * \brief Create Tcl interpreter and add commands
2275 *
2276 * \return The initialized Tcl interpreter
2277 */
2278void
2279GeoVis::initTcl(Tcl_Interp *interp, ClientData clientData)
2280{
2281    Tcl_MakeSafe(interp);
2282    Tcl_CreateObjCommand(interp, "camera",         CameraCmd,         clientData, NULL);
2283    Tcl_CreateObjCommand(interp, "clientinfo",     ClientInfoCmd,     clientData, NULL);
2284    Tcl_CreateObjCommand(interp, "colormap",       ColorMapCmd,       clientData, NULL);
2285    Tcl_CreateObjCommand(interp, "file",           FileCmd,           clientData, NULL);
2286    Tcl_CreateObjCommand(interp, "imgflush",       ImageFlushCmd,     clientData, NULL);
2287    Tcl_CreateObjCommand(interp, "key",            KeyCmd,            clientData, NULL);
2288    Tcl_CreateObjCommand(interp, "map",            MapCmd,            clientData, NULL);
2289    Tcl_CreateObjCommand(interp, "mouse",          MouseCmd,          clientData, NULL);
2290    Tcl_CreateObjCommand(interp, "renderer",       RendererCmd,       clientData, NULL);
2291    Tcl_CreateObjCommand(interp, "screen",         ScreenCmd,         clientData, NULL);
2292}
2293
2294/**
2295 * \brief Delete Tcl commands and interpreter
2296 */
2297void GeoVis::exitTcl(Tcl_Interp *interp)
2298{
2299    Tcl_DeleteCommand(interp, "camera");
2300    Tcl_DeleteCommand(interp, "clientinfo");
2301    Tcl_DeleteCommand(interp, "colormap");
2302    Tcl_DeleteCommand(interp, "file");
2303    Tcl_DeleteCommand(interp, "imgflush");
2304    Tcl_DeleteCommand(interp, "key");
2305    Tcl_DeleteCommand(interp, "map");
2306    Tcl_DeleteCommand(interp, "mouse");
2307    Tcl_DeleteCommand(interp, "renderer");
2308    Tcl_DeleteCommand(interp, "screen");
2309
2310    Tcl_DeleteInterp(interp);
2311}
Note: See TracBrowser for help on using the repository browser.