source: geovis/trunk/RendererCmd.cpp @ 5852

Last change on this file since 5852 was 5852, checked in by ldelgass, 9 years ago

Add initial iData client support

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