source: geovis/trunk/RendererCmd.cpp @ 4635

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

Add resources directory command line option, preliminary testing of new map
coords syntax

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