source: trunk/packages/vizservers/geovis/RendererCmd.cpp @ 4346

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

Add xyz image driver support: used by e.g. MapQuest? OSM

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