source: geovis/trunk/RendererCmd.cpp @ 4635

Last change on this file since 4635 was 4635, checked in by ldelgass, 6 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.