source: geovis/trunk/RendererCmd.cpp @ 6319

Last change on this file since 6319 was 6319, checked in by ldelgass, 8 years ago

fix response length for map layer names

File size: 140.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 <osgEarth/Units>
28#include <osgEarth/Config>
29#include <osgEarthFeatures/FeatureModelSource>
30#include <osgEarthSymbology/Color>
31#include <osgEarthSymbology/Style>
32#include <osgEarthSymbology/StyleSheet>
33#include <osgEarthSymbology/CssUtils>
34#include <osgEarthSymbology/IconSymbol>
35#include <osgEarthSymbology/ModelSymbol>
36#include <osgEarthSymbology/LineSymbol>
37#include <osgEarthSymbology/RenderSymbol>
38
39#include <osgEarthDrivers/agglite/AGGLiteOptions>
40#include <osgEarthDrivers/osg/ColorRampOptions>
41#include <osgEarthDrivers/debug/DebugOptions>
42#include <osgEarthDrivers/arcgis/ArcGISOptions>
43#include <osgEarthDrivers/gdal/GDALOptions>
44#include <osgEarthDrivers/tms/TMSOptions>
45#include <osgEarthDrivers/wcs/WCSOptions>
46#include <osgEarthDrivers/wms/WMSOptions>
47#include <osgEarthDrivers/xyz/XYZOptions>
48#include <osgEarthDrivers/model_feature_geom/FeatureGeomModelOptions>
49#include <osgEarthDrivers/feature_ogr/OGRFeatureOptions>
50#include <osgEarthDrivers/feature_tfs/TFSFeatureOptions>
51#include <osgEarthDrivers/feature_wfs/WFSFeatureOptions>
52
53#include "Trace.h"
54#include "CmdProc.h"
55#include "ReadBuffer.h"
56#include "Types.h"
57#include "RendererCmd.h"
58#include "RenderServer.h"
59#include "Renderer.h"
60#include "IData.h"
61#include "Stats.h"
62#include "PPMWriter.h"
63#include "TGAWriter.h"
64#include "ResponseQueue.h"
65#ifdef USE_READ_THREAD
66#include "CommandQueue.h"
67#endif
68#include "Util.h"
69
70using namespace GeoVis;
71
72static int lastCmdStatus;
73
74#ifndef USE_THREADS
75static ssize_t
76SocketWrite(const void *bytes, size_t len)
77{
78    size_t ofs = 0;
79    ssize_t bytesWritten;
80    while ((bytesWritten = write(g_fdOut, (const char *)bytes + ofs, len - ofs)) > 0) {
81        ofs += bytesWritten;
82        if (ofs == len)
83            break;
84    }
85    if (bytesWritten < 0) {
86        ERROR("write: %s", strerror(errno));
87    }
88    return bytesWritten;
89}
90#endif
91
92static bool
93SocketRead(char *bytes, size_t len)
94{
95    ReadBuffer::BufferStatus status;
96    status = g_inBufPtr->followingData((unsigned char *)bytes, len);
97    TRACE("followingData status: %d", status);
98    return (status == ReadBuffer::OK);
99}
100
101ssize_t
102GeoVis::queueResponse(const void *bytes, size_t len,
103                      Response::AllocationType allocType,
104                      Response::ResponseType type)
105{
106#ifdef USE_THREADS
107    Response *response = new Response(type);
108    response->setMessage((unsigned char *)bytes, len, allocType);
109    g_outQueue->enqueue(response);
110    return (ssize_t)len;
111#else
112    return SocketWrite(bytes, len);
113#endif
114}
115
116static int
117ExecuteCommand(Tcl_Interp *interp, Tcl_DString *dsPtr)
118{
119    int result;
120#ifdef WANT_TRACE
121    char *str = Tcl_DStringValue(dsPtr);
122    std::string cmd(str);
123    cmd.erase(cmd.find_last_not_of(" \n\r\t")+1);
124    TRACE("command %lu: '%s'", g_stats.nCommands, cmd.c_str());
125#endif
126    lastCmdStatus = TCL_OK;
127    result = Tcl_EvalEx(interp, Tcl_DStringValue(dsPtr),
128                        Tcl_DStringLength(dsPtr),
129                        TCL_EVAL_DIRECT | TCL_EVAL_GLOBAL);
130    Tcl_DStringSetLength(dsPtr, 0);
131    if (lastCmdStatus == TCL_BREAK) {
132        return TCL_BREAK;
133    }
134    lastCmdStatus = result;
135    if (result != TCL_OK) {
136        TRACE("Error: %d", result);
137    }
138    return result;
139}
140
141static int
142GetBooleanFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, bool *boolPtr)
143{
144    int value;
145
146    if (Tcl_GetBooleanFromObj(interp, objPtr, &value) != TCL_OK) {
147        return TCL_ERROR;
148    }
149    *boolPtr = (bool)value;
150    return TCL_OK;
151}
152#if 0
153static short
154GetShortFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, short *valuePtr)
155{
156    int value;
157
158    if (Tcl_GetIntFromObj(interp, objPtr, &value) != TCL_OK) {
159        return TCL_ERROR;
160    }
161    *valuePtr = (short)value;
162    return TCL_OK;
163}
164#endif
165static unsigned short
166GetUShortFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, unsigned short *valuePtr)
167{
168    int value;
169
170    if (Tcl_GetIntFromObj(interp, objPtr, &value) != TCL_OK) {
171        return TCL_ERROR;
172    }
173    *valuePtr = (unsigned short)value;
174    return TCL_OK;
175}
176
177static unsigned int
178GetUIntFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, unsigned int *valuePtr)
179{
180    long value;
181
182    if (Tcl_GetLongFromObj(interp, objPtr, &value) != TCL_OK) {
183        return TCL_ERROR;
184    }
185    *valuePtr = (unsigned int)value;
186    return TCL_OK;
187}
188
189static int
190GetFloatFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, float *valuePtr)
191{
192    double value;
193
194    if (Tcl_GetDoubleFromObj(interp, objPtr, &value) != TCL_OK) {
195        return TCL_ERROR;
196    }
197    *valuePtr = (float)value;
198    return TCL_OK;
199}
200
201static osgEarth::Symbology::TextSymbol::Alignment
202ParseTextAlignment(const char *str,
203                   osgEarth::Symbology::TextSymbol::Alignment defAlign =
204                   osgEarth::Symbology::TextSymbol::ALIGN_LEFT_BASE_LINE)
205{
206    osgEarth::Symbology::TextSymbol::Alignment alignment = defAlign;
207    if (str[0] == 'l' && strcmp(str, "left_top") == 0) {
208        alignment = osgEarth::Symbology::TextSymbol::ALIGN_LEFT_TOP;
209    } else if (str[0] == 'l' && strcmp(str, "left_center") == 0) {
210        alignment = osgEarth::Symbology::TextSymbol::ALIGN_LEFT_CENTER;
211    } else if (str[0] == 'l' && strcmp(str, "left_bottom") == 0) {
212        alignment = osgEarth::Symbology::TextSymbol::ALIGN_LEFT_BOTTOM;
213    } else if (str[0] == 'c' && strcmp(str, "center_top") == 0) {
214        alignment = osgEarth::Symbology::TextSymbol::ALIGN_CENTER_TOP;
215    } else if (str[0] == 'c' && strcmp(str, "center_center") == 0) {
216        alignment = osgEarth::Symbology::TextSymbol::ALIGN_CENTER_CENTER;
217    } else if (str[0] == 'c' && strcmp(str, "center_bottom") == 0) {
218        alignment = osgEarth::Symbology::TextSymbol::ALIGN_CENTER_BOTTOM;
219    } else if (str[0] == 'r' && strcmp(str, "right_top") == 0) {
220        alignment = osgEarth::Symbology::TextSymbol::ALIGN_RIGHT_TOP;
221    } else if (str[0] == 'r' && strcmp(str, "right_center") == 0) {
222        alignment = osgEarth::Symbology::TextSymbol::ALIGN_RIGHT_CENTER;
223    } else if (str[0] == 'r' && strcmp(str, "right_bottom") == 0) {
224        alignment = osgEarth::Symbology::TextSymbol::ALIGN_RIGHT_BOTTOM;
225    } else if (str[0] == 'l' && strcmp(str, "left_baseline") == 0) {
226        alignment = osgEarth::Symbology::TextSymbol::ALIGN_LEFT_BASE_LINE;
227    } else if (str[0] == 'c' && strcmp(str, "center_baseline") == 0) {
228        alignment = osgEarth::Symbology::TextSymbol::ALIGN_CENTER_BASE_LINE;
229    } else if (str[0] == 'r' && strcmp(str, "right_baseline") == 0) {
230        alignment = osgEarth::Symbology::TextSymbol::ALIGN_RIGHT_BASE_LINE;
231    } else  if (str[0] == 'l' && strcmp(str, "left_bottom_baseline") == 0) {
232        alignment = osgEarth::Symbology::TextSymbol::ALIGN_LEFT_BOTTOM_BASE_LINE;
233    } else if (str[0] == 'c' && strcmp(str, "center_bottom_baseline") == 0) {
234        alignment = osgEarth::Symbology::TextSymbol::ALIGN_CENTER_BOTTOM_BASE_LINE;
235    } else if (str[0] == 'r' && strcmp(str, "right_bottom_baseline") == 0) {
236        alignment = osgEarth::Symbology::TextSymbol::ALIGN_RIGHT_BOTTOM_BASE_LINE;
237    }
238    return alignment;
239}
240
241static osgEarth::Symbology::IconSymbol::Alignment
242ParseIconAlignment(const char *str,
243                   osgEarth::Symbology::IconSymbol::Alignment defAlign =
244                   osgEarth::Symbology::IconSymbol::ALIGN_CENTER_BOTTOM)
245{
246    osgEarth::Symbology::IconSymbol::Alignment alignment = defAlign;
247    if (str[0] == 'l' && strcmp(str, "left_top") == 0) {
248        alignment = osgEarth::Symbology::IconSymbol::ALIGN_LEFT_TOP;
249    } else if (str[0] == 'l' && strcmp(str, "left_center") == 0) {
250        alignment = osgEarth::Symbology::IconSymbol::ALIGN_LEFT_CENTER;
251    } else if (str[0] == 'l' && strcmp(str, "left_bottom") == 0) {
252        alignment = osgEarth::Symbology::IconSymbol::ALIGN_LEFT_BOTTOM;
253    } else if (str[0] == 'c' && strcmp(str, "center_top") == 0) {
254        alignment = osgEarth::Symbology::IconSymbol::ALIGN_CENTER_TOP;
255    } else if (str[0] == 'c' && strcmp(str, "center_center") == 0) {
256        alignment = osgEarth::Symbology::IconSymbol::ALIGN_CENTER_CENTER;
257    } else if (str[0] == 'c' && strcmp(str, "center_bottom") == 0) {
258        alignment = osgEarth::Symbology::IconSymbol::ALIGN_CENTER_BOTTOM;
259    } else if (str[0] == 'r' && strcmp(str, "right_top") == 0) {
260        alignment = osgEarth::Symbology::IconSymbol::ALIGN_RIGHT_TOP;
261    } else if (str[0] == 'r' && strcmp(str, "right_center") == 0) {
262        alignment = osgEarth::Symbology::IconSymbol::ALIGN_RIGHT_CENTER;
263    } else if (str[0] == 'r' && strcmp(str, "right_bottom") == 0) {
264        alignment = osgEarth::Symbology::IconSymbol::ALIGN_RIGHT_BOTTOM;
265    }
266    return alignment;
267}
268
269static osgEarth::Symbology::InstanceSymbol::Placement
270ParseInstancePlacement(const char *str,
271                       osgEarth::Symbology::InstanceSymbol::Placement defPlacement =
272                       osgEarth::Symbology::InstanceSymbol::PLACEMENT_VERTEX)
273{
274    osgEarth::Symbology::InstanceSymbol::Placement placement = defPlacement;
275    if (str[0] == 'v' && strcmp(str, "vertex") == 0) {
276        placement = osgEarth::Symbology::InstanceSymbol::PLACEMENT_VERTEX;
277    } else if (str[0] == 'c' && strcmp(str, "centroid") == 0) {
278        placement = osgEarth::Symbology::InstanceSymbol::PLACEMENT_CENTROID;
279    } else if (str[0] == 'i' && strcmp(str, "interval") == 0) {
280        placement = osgEarth::Symbology::InstanceSymbol::PLACEMENT_INTERVAL;
281    } else if (str[0] == 'r' && strcmp(str, "random") == 0) {
282        placement = osgEarth::Symbology::InstanceSymbol::PLACEMENT_RANDOM;
283    }
284    return placement;
285}
286
287static osgEarth::Symbology::AltitudeSymbol::Clamping
288ParseClamping(const char *str,
289              osgEarth::Symbology::AltitudeSymbol::Clamping defTechnique =
290              osgEarth::Symbology::AltitudeSymbol::CLAMP_NONE)
291{
292    osgEarth::Symbology::AltitudeSymbol::Clamping clamping = defTechnique;
293    if (str[0] == 'n' && strcmp(str, "none") == 0) {
294        clamping = osgEarth::Symbology::AltitudeSymbol::CLAMP_NONE;
295    } else if (str[0] == 't' && strcmp(str, "terrain") == 0) {
296        clamping = osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
297    } else if (str[0] == 'r' && strcmp(str, "relative") == 0) {
298        clamping = osgEarth::Symbology::AltitudeSymbol::CLAMP_RELATIVE_TO_TERRAIN;
299    } else if (str[0] == 'a' && strcmp(str, "absolute") == 0) {
300        clamping = osgEarth::Symbology::AltitudeSymbol::CLAMP_ABSOLUTE;
301    }
302    return clamping;
303}
304
305static osgEarth::Symbology::AltitudeSymbol::Technique
306ParseClampingTechnique(const char *str,
307                       osgEarth::Symbology::AltitudeSymbol::Technique defTechnique =
308                       osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_DRAPE)
309{
310    osgEarth::Symbology::AltitudeSymbol::Technique technique = defTechnique;
311    if (str[0] == 'd' && strcmp(str, "drape") == 0) {
312        technique = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_DRAPE;
313    } else if (str[0] == 'g' && strcmp(str, "gpu") == 0) {
314        technique = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
315    } else if (str[0] == 's' && strcmp(str, "scene") == 0) {
316        technique = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_SCENE;
317    } else if (str[0] == 'm' && strcmp(str, "map") == 0) {
318        technique = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_MAP;
319    }
320    return technique;
321}
322
323static osgEarth::Symbology::Stroke::LineCapStyle
324ParseLineCapStyle(const char *str,
325                  osgEarth::Symbology::Stroke::LineCapStyle defLineCap =
326                  osgEarth::Symbology::Stroke::LINECAP_FLAT)
327{
328    osgEarth::Symbology::Stroke::LineCapStyle lineCap = defLineCap;
329    if (str[0] == 'f' && strcmp(str, "flat") == 0) {
330        lineCap = osgEarth::Symbology::Stroke::LINECAP_FLAT;
331    } else if (str[0] == 's' && strcmp(str, "square") == 0) {
332        lineCap = osgEarth::Symbology::Stroke::LINECAP_SQUARE;
333    } else if (str[0] == 'r' && strcmp(str, "round") == 0) {
334        lineCap = osgEarth::Symbology::Stroke::LINECAP_ROUND;
335    }
336    return lineCap;
337}
338
339static osgEarth::Symbology::Stroke::LineJoinStyle
340ParseLineJoinStyle(const char *str,
341                   osgEarth::Symbology::Stroke::LineJoinStyle defLineJoin =
342                   osgEarth::Symbology::Stroke::LINEJOIN_MITRE)
343{
344    osgEarth::Symbology::Stroke::LineJoinStyle lineJoin = defLineJoin;
345    if (str[0] == 'm' &&
346       (strcmp(str, "mitre") == 0 || strcmp(str, "miter") == 0)) {
347        lineJoin = osgEarth::Symbology::Stroke::LINEJOIN_MITRE;
348    } else if (str[0] == 'r' && strcmp(str, "round") == 0) {
349        lineJoin = osgEarth::Symbology::Stroke::LINEJOIN_ROUND;
350    }
351    return lineJoin;
352}
353
354static int
355CameraDeleteViewpointOp(ClientData clientData, Tcl_Interp *interp, int objc,
356                        Tcl_Obj *const *objv)
357{
358    char *name = Tcl_GetString(objv[2]);
359
360    g_renderer->removeNamedViewpoint(name);
361    return TCL_OK;
362}
363
364static int
365CameraCenterOp(ClientData clientData, Tcl_Interp *interp, int objc,
366               Tcl_Obj *const *objv)
367{
368    double x, y;
369    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
370        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
371        return TCL_ERROR;
372    }
373    double zoom = 1.0;
374    if (objc > 4) {
375        if (Tcl_GetDoubleFromObj(interp, objv[4], &zoom) != TCL_OK) {
376            return TCL_ERROR;
377        }
378    }
379    double duration = 1.0;
380    if (objc > 5) {
381        if (Tcl_GetDoubleFromObj(interp, objv[5], &duration) != TCL_OK) {
382            return TCL_ERROR;
383        }
384    }
385
386    osgEarth::Viewpoint vpt = g_renderer->getViewpoint();
387    vpt.focalPoint()->x() = x;
388    vpt.focalPoint()->y() = y;
389    //vpt.focalPoint()->z() = mapPoint.z();
390    vpt.range()->set(vpt.range()->getValue() * zoom, osgEarth::Units::METERS);
391
392    g_renderer->setViewpoint(vpt, duration);
393
394    return TCL_OK;
395}
396
397static int
398CameraGetViewpointOp(ClientData clientData, Tcl_Interp *interp, int objc,
399                     Tcl_Obj *const *objv)
400{
401    osgEarth::Viewpoint view = g_renderer->getViewpoint();
402
403    std::ostringstream oss;
404    size_t len = 0;
405    oss << "nv>camera get "
406        << view.focalPoint()->x() << " "
407        << view.focalPoint()->y() << " "
408        << view.focalPoint()->z() << " "
409        << view.heading()->getValue() << " "
410        << view.pitch()->getValue() << " "
411        << view.range()->getValue()
412        << " {" << ((view.focalPoint()->getSRS() == NULL) ? "" : view.focalPoint()->getSRS()->getHorizInitString()) << "}"
413        << " {" << ((view.focalPoint()->getSRS() == NULL) ? "" : view.focalPoint()->getSRS()->getVertInitString()) << "}"
414        << "\n";
415    std::string ostr = oss.str();
416    len = ostr.size();
417#ifdef USE_THREADS
418    queueResponse(ostr.c_str(), len, Response::VOLATILE);
419#else
420    ssize_t bytesWritten = SocketWrite(ostr.c_str(), len);
421
422    if (bytesWritten < 0) {
423        return TCL_ERROR;
424    }
425#endif /*USE_THREADS*/
426    return TCL_OK;
427}
428
429static int
430CameraGoOp(ClientData clientData, Tcl_Interp *interp, int objc,
431           Tcl_Obj *const *objv)
432{
433    int x, y;
434    if (Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK ||
435        Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK) {
436        return TCL_ERROR;
437    }
438    double zoom = 1.0;
439    if (objc > 4) {
440        if (Tcl_GetDoubleFromObj(interp, objv[4], &zoom) != TCL_OK) {
441            return TCL_ERROR;
442        }
443    }
444    double duration = 1.0;
445    if (objc > 5) {
446        if (Tcl_GetDoubleFromObj(interp, objv[5], &duration) != TCL_OK) {
447            return TCL_ERROR;
448        }
449    }
450
451    osgEarth::GeoPoint mapPoint;
452    if (g_renderer->mapMouseCoords(x, y, mapPoint)) {
453        osgEarth::Viewpoint vpt = g_renderer->getViewpoint();
454        vpt.focalPoint() = mapPoint;
455        vpt.range()->set(vpt.range()->getValue() * zoom, osgEarth::Units::METERS);
456        g_renderer->setViewpoint(vpt, duration);
457    } else {
458        // Out of map bounds
459    }
460    return TCL_OK;
461}
462
463static int
464CameraOrientOp(ClientData clientData, Tcl_Interp *interp, int objc,
465               Tcl_Obj *const *objv)
466{
467    double quat[4];
468
469    if (Tcl_GetDoubleFromObj(interp, objv[2], &quat[0]) != TCL_OK ||
470        Tcl_GetDoubleFromObj(interp, objv[3], &quat[1]) != TCL_OK ||
471        Tcl_GetDoubleFromObj(interp, objv[4], &quat[2]) != TCL_OK ||
472        Tcl_GetDoubleFromObj(interp, objv[5], &quat[3]) != TCL_OK) {
473        return TCL_ERROR;
474    }
475
476    g_renderer->setCameraOrientation(quat);
477    return TCL_OK;
478}
479
480static int
481CameraPanOp(ClientData clientData, Tcl_Interp *interp, int objc,
482            Tcl_Obj *const *objv)
483{
484    double x, y;
485
486    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
487        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
488        return TCL_ERROR;
489    }
490
491    g_renderer->panCamera(x, y);
492    return TCL_OK;
493}
494
495static int
496CameraResetOp(ClientData clientData, Tcl_Interp *interp, int objc,
497              Tcl_Obj *const *objv)
498{
499    if (objc == 3) {
500        const char *string = Tcl_GetString(objv[2]);
501        char c = string[0];
502        if ((c != 'a') || (strcmp(string, "all") != 0)) {
503            Tcl_AppendResult(interp, "bad camera reset option \"", string,
504                         "\": should be all", (char*)NULL);
505            return TCL_ERROR;
506        }
507        g_renderer->resetCamera(true);
508    } else {
509        g_renderer->resetCamera(false);
510    }
511    return TCL_OK;
512}
513
514static int
515CameraRestoreViewpointOp(ClientData clientData, Tcl_Interp *interp, int objc,
516                         Tcl_Obj *const *objv)
517{
518    char *name = Tcl_GetString(objv[2]);
519
520    double duration = 0.0;
521    if (objc > 3) {
522        if (Tcl_GetDoubleFromObj(interp, objv[3], &duration) != TCL_OK) {
523            return TCL_ERROR;
524        }
525    }
526    if (!g_renderer->restoreNamedViewpoint(name, duration)) {
527        Tcl_AppendResult(interp, "camera viewpoint \"", name,
528                         "\" not found", (char*)NULL);
529        return TCL_ERROR;
530    }
531    return TCL_OK;
532}
533
534static int
535CameraRotateOp(ClientData clientData, Tcl_Interp *interp, int objc,
536               Tcl_Obj *const *objv)
537{
538    double x, y;
539
540    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
541        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
542        return TCL_ERROR;
543    }
544
545    g_renderer->rotateCamera(x, y);
546    return TCL_OK;
547}
548
549static int
550CameraSaveViewpointOp(ClientData clientData, Tcl_Interp *interp, int objc,
551                      Tcl_Obj *const *objv)
552{
553    char *name = Tcl_GetString(objv[2]);
554
555    g_renderer->saveNamedViewpoint(name);
556    return TCL_OK;
557}
558
559static int
560CameraSetByExtentOp(ClientData clientData, Tcl_Interp *interp, int objc,
561                    Tcl_Obj *const *objv)
562{
563    double xmin, ymin, xmax, ymax;
564    double duration = 0.0;
565    if (Tcl_GetDoubleFromObj(interp, objv[2], &xmin) != TCL_OK ||
566        Tcl_GetDoubleFromObj(interp, objv[3], &ymin) != TCL_OK ||
567        Tcl_GetDoubleFromObj(interp, objv[4], &xmax) != TCL_OK ||
568        Tcl_GetDoubleFromObj(interp, objv[5], &ymax) != TCL_OK) {
569        return TCL_ERROR;
570    }
571    if (objc > 6) {
572        if (Tcl_GetDoubleFromObj(interp, objv[6], &duration) != TCL_OK) {
573            return TCL_ERROR;
574        }
575    }
576    osg::ref_ptr<const osgEarth::SpatialReference> srs = g_renderer->getMapSRS();
577    if (objc > 7) {
578        char *srsInit = Tcl_GetString(objv[7]);
579        if (strlen(srsInit) > 0) {
580            srs = osgEarth::SpatialReference::get(srsInit);
581        }
582    }
583    g_renderer->setViewpointFromRect(xmin, ymin, xmax, ymax, srs.get(), duration);
584    return TCL_OK;
585}
586
587static int
588CameraSetByLayerExtentOp(ClientData clientData, Tcl_Interp *interp, int objc,
589                         Tcl_Obj *const *objv)
590{
591    const char *name = Tcl_GetString(objv[2]);
592    double duration = 0.0;
593    if (objc > 3) {
594        if (Tcl_GetDoubleFromObj(interp, objv[3], &duration) != TCL_OK) {
595            return TCL_ERROR;
596        }
597    }
598    osgEarth::GeoExtent ext;
599    if (g_renderer->getImageLayerExtent(name, ext)) {
600        g_renderer->setViewpointFromExtent(ext, duration);
601        return TCL_OK;
602    } else if (g_renderer->getElevationLayerExtent(name, ext)) {
603        g_renderer->setViewpointFromExtent(ext, duration);
604        return TCL_OK;
605    } else if (g_renderer->getModelLayerExtent(name, ext)) {
606        g_renderer->setViewpointFromExtent(ext, duration);
607        return TCL_OK;
608    }
609    Tcl_AppendResult(interp, "Invalid layer \"", name,
610                     "\" or could not determine layer extent", (char*)NULL);
611    return TCL_ERROR;
612}
613
614static int
615CameraSetDistanceOp(ClientData clientData, Tcl_Interp *interp, int objc,
616                    Tcl_Obj *const *objv)
617{
618    double dist;
619
620    if (Tcl_GetDoubleFromObj(interp, objv[2], &dist) != TCL_OK) {
621        return TCL_ERROR;
622    }
623
624    g_renderer->setCameraDistance(dist);
625    return TCL_OK;
626}
627
628static int
629CameraSetViewpointOp(ClientData clientData, Tcl_Interp *interp, int objc,
630                     Tcl_Obj *const *objv)
631{
632    double x, y, z, heading, pitch, distance;
633    double duration = 0.0;
634    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
635        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK ||
636        Tcl_GetDoubleFromObj(interp, objv[4], &z) != TCL_OK ||
637        Tcl_GetDoubleFromObj(interp, objv[5], &heading) != TCL_OK ||
638        Tcl_GetDoubleFromObj(interp, objv[6], &pitch) != TCL_OK ||
639        Tcl_GetDoubleFromObj(interp, objv[7], &distance) != TCL_OK) {
640        return TCL_ERROR;
641    }
642    if (objc > 8) {
643        if (Tcl_GetDoubleFromObj(interp, objv[8], &duration) != TCL_OK) {
644            return TCL_ERROR;
645        }
646    }
647    const osgEarth::SpatialReference *srs = g_renderer->getMapSRS();
648    if (objc > 9) {
649        char *srsInit = Tcl_GetString(objv[9]);
650        if (strlen(srsInit) > 0) {
651            if (objc > 10) {
652                char *vertDatum = Tcl_GetString(objv[10]);
653                srs = osgEarth::SpatialReference::get(srsInit, vertDatum);
654            } else {
655                srs = osgEarth::SpatialReference::get(srsInit);
656            }
657            if (srs == NULL) {
658                ERROR("Couldn't get SRS from init string: %s", srsInit);
659                return TCL_ERROR;
660            }
661        }
662    }
663    osgEarth::Viewpoint view;
664    view.focalPoint() = osgEarth::GeoPoint(srs, x, y, z);
665    view.heading() = osgEarth::Angle(heading, osgEarth::Units::DEGREES);
666    view.pitch() = osgEarth::Angle(pitch, osgEarth::Units::DEGREES);
667    view.range() = osgEarth::Distance(distance, osgEarth::Units::METERS);
668    g_renderer->setViewpoint(view, duration);
669    return TCL_OK;
670}
671
672static int
673CameraThrowOp(ClientData clientData, Tcl_Interp *interp, int objc,
674              Tcl_Obj *const *objv)
675{
676    bool state;
677
678    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
679        return TCL_ERROR;
680    }
681
682    g_renderer->setThrowingEnabled(state);
683    return TCL_OK;
684}
685
686static int
687CameraZoomOp(ClientData clientData, Tcl_Interp *interp, int objc,
688             Tcl_Obj *const *objv)
689{
690    double z;
691
692    if (Tcl_GetDoubleFromObj(interp, objv[2], &z) != TCL_OK) {
693        return TCL_ERROR;
694    }
695
696    g_renderer->zoomCamera(z);
697    return TCL_OK;
698}
699
700static CmdSpec cameraOps[] = {
701    {"center",  1, CameraCenterOp,           4, 6, "x y ?zoomFactor? ?duration?"},
702    {"delete",  2, CameraDeleteViewpointOp,  3, 3, "name"},
703    {"dist",    2, CameraSetDistanceOp,      3, 3, "distance"},
704    {"extent",  1, CameraSetByExtentOp,      6, 8, "xmin ymin xmax ymax ?duration? ?srs?"},
705    {"get",     2, CameraGetViewpointOp,     2, 2, ""},
706    {"go",      2, CameraGoOp,               4, 6, "x y ?zoomFactor? ?duration?"},
707    {"lextent", 1, CameraSetByLayerExtentOp, 3, 4, "layerName ?duration?"},
708    {"orient",  1, CameraOrientOp,           6, 6, "qw qx qy qz"},
709    {"pan",     1, CameraPanOp,              4, 4, "panX panY"},
710    {"reset",   4, CameraResetOp,            2, 3, "?all?"},
711    {"restore", 4, CameraRestoreViewpointOp, 3, 4, "name ?duration?"},
712    {"rotate",  2, CameraRotateOp,           4, 4, "azimuth elevation"},
713    {"save",    2, CameraSaveViewpointOp,    3, 3, "name"},
714    {"set",     2, CameraSetViewpointOp,     8, 11, "x y z heading pitch distance ?duration? ?srs? ?vertDatum?"},
715    {"throw",   1, CameraThrowOp,            3, 3, "bool"},
716    {"zoom",    1, CameraZoomOp,             3, 3, "zoomAmount"}
717};
718static int nCameraOps = NumCmdSpecs(cameraOps);
719
720static int
721CameraCmd(ClientData clientData, Tcl_Interp *interp, int objc,
722          Tcl_Obj *const *objv)
723{
724    Tcl_ObjCmdProc *proc;
725
726    proc = GetOpFromObj(interp, nCameraOps, cameraOps,
727                        CMDSPEC_ARG1, objc, objv, 0);
728    if (proc == NULL) {
729        return TCL_ERROR;
730    }
731    return (*proc) (clientData, interp, objc, objv);
732}
733
734static int
735ClientInfoCmd(ClientData clientData, Tcl_Interp *interp, int objc,
736              Tcl_Obj *const *objv)
737{
738    Tcl_DString ds;
739    Tcl_Obj *objPtr, *listObjPtr, **items;
740    int numItems;
741    char buf[BUFSIZ];
742    const char *string;
743    int length;
744    int result;
745    static bool first = true;
746
747    /* Client arguments. */
748    if (Tcl_ListObjGetElements(interp, objv[1], &numItems, &items) != TCL_OK ||
749        numItems % 2 != 0) {
750        return TCL_ERROR;
751    }
752    const char *username = NULL;
753    const char *hub = NULL;
754    //const char *session = NULL;
755    for (int i = 0; i < numItems; i+=2) {
756        const char *name = Tcl_GetString(items[i]);
757        const char *val = Tcl_GetString(items[i+1]);
758        if (strcmp(name, "user") == 0) {
759            username = val;
760        }
761        if (strcmp(name, "hub") == 0) {
762            hub = val;
763        }
764        //if (strcmp(name, "session") == 0) {
765        //    session = val;
766        //}
767    }
768    if (username != NULL) {
769        IData::iDataInit(username, hub);
770    }
771
772    /* First try to see if we already have an open descriptor */
773    int fd = getStatsFile();
774    if (fd < 0) {
775        /* Use the initial client key value pairs as the parts for generating
776         * a unique file name. */
777        fd = GeoVis::getStatsFile(Tcl_GetString(objv[1]));
778        if (fd < 0) {
779            Tcl_AppendResult(interp, "can't open stats file: ",
780                             Tcl_PosixError(interp), (char *)NULL);
781            return TCL_ERROR;
782        }
783    }
784    listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
785    Tcl_IncrRefCount(listObjPtr);
786    if (first) {
787        first = false;
788        objPtr = Tcl_NewStringObj("render_start", 12);
789        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
790        /* renderer */
791        objPtr = Tcl_NewStringObj("renderer", 8);
792        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
793        objPtr = Tcl_NewStringObj("geovis", 6);
794        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
795        /* pid */
796        objPtr = Tcl_NewStringObj("pid", 3);
797        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
798        Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewIntObj(getpid()));
799        /* host */
800        objPtr = Tcl_NewStringObj("host", 4);
801        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
802        gethostname(buf, BUFSIZ-1);
803        buf[BUFSIZ-1] = '\0';
804        objPtr = Tcl_NewStringObj(buf, -1);
805        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
806    } else {
807        objPtr = Tcl_NewStringObj("render_info", 11);
808        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
809    }
810    /* date */
811    Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("date", 4));
812    strcpy(buf, ctime(&GeoVis::g_stats.start.tv_sec));
813    buf[strlen(buf) - 1] = '\0';
814    Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj(buf, -1));
815    /* date_secs */
816    Tcl_ListObjAppendElement(interp, listObjPtr,
817                             Tcl_NewStringObj("date_secs", 9));
818    Tcl_ListObjAppendElement(interp, listObjPtr,
819                             Tcl_NewLongObj(GeoVis::g_stats.start.tv_sec));
820    /* client items */
821    for (int i = 0; i < numItems; i++) {
822        Tcl_ListObjAppendElement(interp, listObjPtr, items[i]);
823    }
824    Tcl_DStringInit(&ds);
825    string = Tcl_GetStringFromObj(listObjPtr, &length);
826    Tcl_DStringAppend(&ds, string, length);
827    Tcl_DStringAppend(&ds, "\n", 1);
828    result = GeoVis::writeToStatsFile(fd, Tcl_DStringValue(&ds),
829                                      Tcl_DStringLength(&ds));
830    Tcl_DStringFree(&ds);
831    Tcl_DecrRefCount(listObjPtr);
832    return result;
833}
834
835static int
836ColorMapAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
837              Tcl_Obj *const *objv)
838{
839    const char *name = Tcl_GetString(objv[2]);
840    int cmapc;
841    Tcl_Obj **cmapv = NULL;
842
843    if (Tcl_ListObjGetElements(interp, objv[3], &cmapc, &cmapv) != TCL_OK) {
844        return TCL_ERROR;
845    }
846    if ((cmapc % 5) != 0) {
847        Tcl_AppendResult(interp, "wrong # elements in colormap: should be ",
848                         "{ value r g b a... }", (char*)NULL);
849        return TCL_ERROR;
850    }
851
852    osg::TransferFunction1D *colorMap = new osg::TransferFunction1D;
853    colorMap->allocate(256);
854
855    for (int i = 0; i < cmapc; i += 5) {
856        double val[5];
857        for (int j = 0; j < 5; j++) {
858            if (Tcl_GetDoubleFromObj(interp, cmapv[i+j], &val[j]) != TCL_OK) {
859                delete colorMap;
860                return TCL_ERROR;
861            }
862            // Need to permit un-normalized values
863            if (j > 0 && (val[j] < 0.0 || val[j] > 1.0)) {
864                Tcl_AppendResult(interp, "bad colormap value \"",
865                                 Tcl_GetString(cmapv[i+j]),
866                                 "\": should be in the range [0,1]", (char*)NULL);
867                delete colorMap;
868                return TCL_ERROR;
869            }
870        }
871        colorMap->setColor(val[0], osg::Vec4f(val[1], val[2], val[3], val[4]), false);
872    }
873
874    colorMap->updateImage();
875
876    g_renderer->addColorMap(name, colorMap);
877    return TCL_OK;
878}
879
880static int
881ColorMapDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
882                 Tcl_Obj *const *objv)
883{
884    if (objc == 3) {
885        const char *name = Tcl_GetString(objv[2]);
886#if 0
887        if (strcmp(name, "default") == 0 ||
888            strcmp(name, "grayDefault") == 0) {
889            WARN("Cannot delete a default color map");
890            return TCL_ERROR;
891        }
892#endif
893        g_renderer->deleteColorMap(name);
894    } else {
895        g_renderer->deleteColorMap("all");
896    }
897
898    return TCL_OK;
899}
900
901static int
902ColorMapNumTableEntriesOp(ClientData clientData, Tcl_Interp *interp, int objc,
903                          Tcl_Obj *const *objv)
904{
905    int numEntries;
906    if (Tcl_GetIntFromObj(interp, objv[2], &numEntries) != TCL_OK) {
907        const char *str = Tcl_GetString(objv[2]);
908        if (str[0] == 'd' && strcmp(str, "default") == 0) {
909            numEntries = -1;
910        } else {
911            Tcl_AppendResult(interp, "bad colormap resolution value \"", str,
912                             "\": should be a positive integer or \"default\"", (char*)NULL);
913            return TCL_ERROR;
914        }
915    } else if (numEntries < 1) {
916        Tcl_AppendResult(interp, "bad colormap resolution value \"", Tcl_GetString(objv[2]),
917                         "\": should be a positive integer or \"default\"", (char*)NULL);
918        return TCL_ERROR;
919    }
920    if (objc == 4) {
921        const char *name = Tcl_GetString(objv[3]);
922
923        g_renderer->setColorMapNumberOfTableEntries(name, numEntries);
924    } else {
925        g_renderer->setColorMapNumberOfTableEntries("all", numEntries);
926    }
927    return TCL_OK;
928}
929
930static CmdSpec colorMapOps[] = {
931    {"add",    1, ColorMapAddOp,             4, 4, "colorMapName colormap"},
932    {"define", 3, ColorMapAddOp,             4, 4, "colorMapName colormap"},
933    {"delete", 3, ColorMapDeleteOp,          2, 3, "?colorMapName?"},
934    {"res",    1, ColorMapNumTableEntriesOp, 3, 4, "numTableEntries ?colorMapName?"}
935};
936static int nColorMapOps = NumCmdSpecs(colorMapOps);
937
938static int
939ColorMapCmd(ClientData clientData, Tcl_Interp *interp, int objc,
940            Tcl_Obj *const *objv)
941{
942    Tcl_ObjCmdProc *proc;
943
944    proc = GetOpFromObj(interp, nColorMapOps, colorMapOps,
945                        CMDSPEC_ARG1, objc, objv, 0);
946    if (proc == NULL) {
947        return TCL_ERROR;
948    }
949    return (*proc) (clientData, interp, objc, objv);
950}
951
952static int
953FilePutOp(ClientData clientData, Tcl_Interp *interp, int objc,
954          Tcl_Obj *const *objv)
955{
956    long size;
957    const char *path = Tcl_GetString(objv[2]);
958    if (Tcl_GetLongFromObj(interp, objv[4], &size) != TCL_OK ||
959        size < 1) {
960        return TCL_ERROR;
961    }
962    char *data = (char *)malloc((size_t)size);
963    if (!SocketRead(data, size)) {
964        free(data);
965        return TCL_ERROR;
966    }
967    if (g_renderer->getCacheDirectory().empty()) {
968        g_renderer->setupCache();
969    }
970    std::string dirPath = osgDB::getFilePath(path);
971    std::string fileName = osgDB::getSimpleFileName(path);
972    std::ostringstream fullPath;
973    fullPath << g_renderer->getCacheDirectory() << "/" << dirPath;
974    if (!dirPath.empty()) {
975        TRACE("Make dir: %s", fullPath.str().c_str());
976        osgDB::makeDirectory(fullPath.str());
977        fullPath << "/";
978    }
979    fullPath << fileName;
980    FILE *fp = fopen(fullPath.str().c_str(), "wb");
981    if (fp == NULL) {
982        free(data);
983        return TCL_ERROR;
984    }
985    int bytesWritten = fwrite(data, 1, (size_t)size, fp);
986    fclose(fp);
987    free(data);
988    TRACE("Wrote %d bytes to %s", bytesWritten, fullPath.str().c_str());
989    return (bytesWritten == size) ? TCL_OK : TCL_ERROR;
990}
991
992static CmdSpec fileOps[] = {
993    {"put",    1, FilePutOp, 5, 5, "path type size"}
994};
995static int nFileOps = NumCmdSpecs(fileOps);
996
997static int
998FileCmd(ClientData clientData, Tcl_Interp *interp, int objc,
999          Tcl_Obj *const *objv)
1000{
1001    Tcl_ObjCmdProc *proc;
1002
1003    proc = GetOpFromObj(interp, nFileOps, fileOps,
1004                        CMDSPEC_ARG1, objc, objv, 0);
1005    if (proc == NULL) {
1006        return TCL_ERROR;
1007    }
1008    return (*proc) (clientData, interp, objc, objv);
1009}
1010
1011static int
1012ImageFlushCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1013              Tcl_Obj *const *objv)
1014{
1015    lastCmdStatus = TCL_BREAK;
1016    return TCL_OK;
1017}
1018
1019static int
1020KeyPressOp(ClientData clientData, Tcl_Interp *interp, int objc,
1021           Tcl_Obj *const *objv)
1022{
1023    int key;
1024    if (Tcl_GetIntFromObj(interp, objv[2], &key) != TCL_OK) {
1025        return TCL_ERROR;
1026    }
1027
1028    g_renderer->keyPress(key);
1029    return TCL_OK;
1030}
1031
1032static int
1033KeyReleaseOp(ClientData clientData, Tcl_Interp *interp, int objc,
1034               Tcl_Obj *const *objv)
1035{
1036    int key;
1037    if (Tcl_GetIntFromObj(interp, objv[2], &key) != TCL_OK) {
1038        return TCL_ERROR;
1039    }
1040
1041    g_renderer->keyRelease(key);
1042    return TCL_OK;
1043}
1044
1045static CmdSpec keyOps[] = {
1046    {"press",    1, KeyPressOp,       3, 3, "key"},
1047    {"release",  1, KeyReleaseOp,     3, 3, "key"},
1048};
1049static int nKeyOps = NumCmdSpecs(keyOps);
1050
1051static int
1052KeyCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1053         Tcl_Obj *const *objv)
1054{
1055    Tcl_ObjCmdProc *proc;
1056
1057    proc = GetOpFromObj(interp, nKeyOps, keyOps,
1058                        CMDSPEC_ARG1, objc, objv, 0);
1059    if (proc == NULL) {
1060        return TCL_ERROR;
1061    }
1062    return (*proc) (clientData, interp, objc, objv);
1063}
1064
1065static int
1066LegendCmd(ClientData clientData, Tcl_Interp *interp, int objc,
1067          Tcl_Obj *const *objv)
1068{
1069    if (objc < 4) {
1070        Tcl_AppendResult(interp, "wrong # args: should be \"",
1071                Tcl_GetString(objv[0]), " colormapName width height\"", (char*)NULL);
1072        return TCL_ERROR;
1073    }
1074    const char *colorMapName = Tcl_GetString(objv[1]);
1075
1076    int width, height;
1077    if (Tcl_GetIntFromObj(interp, objv[2], &width) != TCL_OK ||
1078        Tcl_GetIntFromObj(interp, objv[3], &height) != TCL_OK) {
1079        return TCL_ERROR;
1080    }
1081    bool opaque = true;
1082    if (objc > 4) {
1083        if (GetBooleanFromObj(interp, objv[4], &opaque) != TCL_OK) {
1084            return TCL_ERROR;
1085        }
1086    }
1087    float bgColor[3];
1088    if (objc > 5) {
1089        if (GetFloatFromObj(interp, objv[5], &bgColor[0]) != TCL_OK ||
1090            GetFloatFromObj(interp, objv[6], &bgColor[1]) != TCL_OK ||
1091            GetFloatFromObj(interp, objv[7], &bgColor[2]) != TCL_OK) {
1092            return TCL_ERROR;
1093        }
1094    } else {
1095        memset(bgColor, 0, sizeof(float)*3);
1096    }
1097    osg::ref_ptr<osg::Image> imgData = new osg::Image();
1098
1099#if defined(RENDER_TARGA) || defined(DEBUG)
1100    if (!g_renderer->renderColorMap(colorMapName, width, height, imgData, opaque,
1101                                    bgColor, true)) {
1102        Tcl_AppendResult(interp, "Color map \"",
1103                         colorMapName, "\" was not found", (char*)NULL);
1104        return TCL_ERROR;
1105    }
1106#else
1107    if (!g_renderer->renderColorMap(colorMapName, width, height, imgData, opaque,
1108                                    bgColor)) {
1109        Tcl_AppendResult(interp, "Color map \"",
1110                         colorMapName, "\" was not found", (char*)NULL);
1111        return TCL_ERROR;
1112    }
1113#endif
1114
1115#ifdef DEBUG
1116# ifdef RENDER_TARGA
1117    writeTGAFile("/tmp/legend.tga", imgData->data(), width, height,
1118                 TARGA_BYTES_PER_PIXEL);
1119# else
1120    writeTGAFile("/tmp/legend.tga", imgData->data(), width, height,
1121                 TARGA_BYTES_PER_PIXEL, true);
1122# endif
1123#else
1124    char cmd[256];
1125    float min, max;
1126    g_renderer->getColorMapRange(colorMapName, &min, &max);
1127    snprintf(cmd, sizeof(cmd), "nv>legend {%s} %g %g", colorMapName, min, max);
1128
1129# ifdef USE_THREADS
1130#  ifdef RENDER_TARGA
1131    queueTGA(g_outQueue, cmd, imgData->data(), width, height,
1132             TARGA_BYTES_PER_PIXEL);
1133#  else
1134    queuePPM(g_outQueue, cmd, imgData->data(), width, height);
1135#  endif
1136# else
1137#  ifdef RENDER_TARGA
1138    writeTGA(g_fdOut, cmd, imgData->data(), width, height,
1139             TARGA_BYTES_PER_PIXEL);
1140#  else
1141    writePPM(g_fdOut, cmd, imgData->data(), width, height);
1142#  endif
1143# endif // USE_THREADS
1144#endif // DEBUG
1145
1146    return TCL_OK;
1147}
1148
1149static int
1150MapAttributionOp(ClientData clientData, Tcl_Interp *interp, int objc,
1151                 Tcl_Obj *const *objv)
1152{
1153    int len;
1154    char *str = Tcl_GetStringFromObj(objv[2], &len);
1155#if 0
1156    Tcl_DString attrib;
1157    Tcl_DStringInit(&attrib);
1158    Tcl_Encoding encoding = Tcl_GetEncoding(interp, "identity");
1159    Tcl_ExternalToUtfDString(encoding, str, len, &attrib);
1160    Tcl_FreeEncoding(encoding);
1161    str = Tcl_DStringValue(&attrib);
1162#endif
1163    g_renderer->setAttribution(str);
1164#if 0
1165    Tcl_DStringFree(&attrib);
1166#endif
1167    return TCL_OK;
1168}
1169
1170static int
1171MapBoxClearOp(ClientData clientData, Tcl_Interp *interp, int objc,
1172              Tcl_Obj *const *objv)
1173{
1174    g_renderer->clearBoxSelection();
1175    return TCL_OK;
1176}
1177
1178static int
1179MapBoxEndOp(ClientData clientData, Tcl_Interp *interp, int objc,
1180            Tcl_Obj *const *objv)
1181{
1182    int x, y;
1183    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
1184        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
1185        return TCL_ERROR;
1186    }
1187    g_renderer->updateBoxSelection(x, y);
1188
1189    // Notify client of selected region
1190    double latMin, latMax, longMin, longMax;
1191    latMin = latMax = longMin = longMax = 0;
1192    g_renderer->getBoxSelection(&latMin, &latMax, &longMin, &longMax);
1193
1194    std::ostringstream oss;
1195    oss << "nv>select region "
1196        << longMin << " "
1197        << latMin << " "
1198        << longMax << " "
1199        << latMax << "\n";
1200    std::string ostr = oss.str();
1201    size_t len = ostr.size();
1202    queueResponse(ostr.c_str(), len, Response::VOLATILE);
1203    return TCL_OK;
1204}
1205
1206static int
1207MapBoxInitOp(ClientData clientData, Tcl_Interp *interp, int objc,
1208             Tcl_Obj *const *objv)
1209{
1210    int x, y;
1211    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
1212        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
1213        return TCL_ERROR;
1214    }
1215    g_renderer->initBoxSelection(x, y);
1216    return TCL_OK;
1217}
1218
1219static int
1220MapBoxUpdateOp(ClientData clientData, Tcl_Interp *interp, int objc,
1221               Tcl_Obj *const *objv)
1222{
1223    int x, y;
1224    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
1225        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
1226        return TCL_ERROR;
1227    }
1228    g_renderer->updateBoxSelection(x, y);
1229    return TCL_OK;
1230}
1231
1232static CmdSpec mapBoxOps[] = {
1233    {"clear",   1, MapBoxClearOp,   3, 3, ""},
1234    {"end",     1, MapBoxEndOp,     5, 5, "x y"},
1235    {"init",    1, MapBoxInitOp,    5, 5, "x y"},
1236    {"update",  1, MapBoxUpdateOp,  5, 5, "x y"},
1237};
1238static int nMapBoxOps = NumCmdSpecs(mapBoxOps);
1239
1240static int
1241MapBoxOp(ClientData clientData, Tcl_Interp *interp, int objc,
1242         Tcl_Obj *const *objv)
1243{
1244    Tcl_ObjCmdProc *proc;
1245
1246    proc = GetOpFromObj(interp, nMapBoxOps, mapBoxOps,
1247                        CMDSPEC_ARG2, objc, objv, 0);
1248    if (proc == NULL) {
1249        return TCL_ERROR;
1250    }
1251    return (*proc) (clientData, interp, objc, objv);
1252}
1253
1254static int
1255MapCoordsOp(ClientData clientData, Tcl_Interp *interp, int objc,
1256            Tcl_Obj *const *objv)
1257{
1258    int tokenLength;
1259    const char *token = Tcl_GetStringFromObj(objv[2], &tokenLength);
1260    int numCoords;
1261    Tcl_Obj **coords;
1262    if (Tcl_ListObjGetElements(interp, objv[3], &numCoords, &coords) != TCL_OK) {
1263        return TCL_ERROR;
1264    }
1265    if (numCoords == 0) {
1266        Tcl_AppendResult(interp, "no x,y coordinates in list", (char *)NULL);
1267        return TCL_ERROR;
1268    }
1269    if (numCoords & 1) {
1270        Tcl_AppendResult(interp, "odd number of x, y coordinates in list",
1271                         (char *)NULL);
1272        return TCL_ERROR;
1273    }
1274    const osgEarth::SpatialReference *outSRS = NULL;
1275    if (objc > 4) {
1276        std::string srsInit;
1277        std::string verticalDatum;
1278        srsInit = Tcl_GetString(objv[4]);
1279        if (objc > 5) {
1280            verticalDatum = Tcl_GetString(objv[5]);
1281        }
1282        outSRS = osgEarth::SpatialReference::get(srsInit, verticalDatum);
1283        if (outSRS == NULL) {
1284            Tcl_AppendResult(interp, "bad SRS \"", srsInit.c_str(), "\"",
1285                             (char*)NULL);
1286            return TCL_ERROR;
1287        }
1288    }
1289    Tcl_Obj *listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
1290    std::vector<osg::Vec3d> coordVec;
1291    for (int i = 0; i < numCoords; i += 2) {
1292        int x, y;
1293        if ((Tcl_GetIntFromObj(interp, coords[i], &x) != TCL_OK) ||
1294            (Tcl_GetIntFromObj(interp, coords[i+1], &y) != TCL_OK)) {
1295            return TCL_ERROR;
1296        }
1297        osgEarth::GeoPoint mapPoint;
1298        if (g_renderer->mapMouseCoords(x, y, mapPoint)) {
1299            // altmode absolute
1300            coordVec.push_back(osg::Vec3d(mapPoint.x(), mapPoint.y(), mapPoint.z()));
1301        } else {
1302            coordVec.push_back(osg::Vec3d(std::numeric_limits<double>::quiet_NaN(),
1303                                          std::numeric_limits<double>::quiet_NaN(),
1304                                          std::numeric_limits<double>::quiet_NaN()));
1305        }
1306    }
1307    if (outSRS != NULL) {
1308        g_renderer->getMapSRS()->transform(coordVec, outSRS);
1309    } else {
1310        outSRS = g_renderer->getMapSRS();
1311    }
1312    for (std::vector<osg::Vec3d>::iterator itr = coordVec.begin();
1313         itr != coordVec.end(); ++itr) {
1314        Tcl_Obj *objPtr = Tcl_NewDoubleObj(itr->x());
1315        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
1316        objPtr = Tcl_NewDoubleObj(itr->y());
1317        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
1318        objPtr = Tcl_NewDoubleObj(itr->z());
1319        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
1320    }
1321    // send coords to client
1322    int listLength;
1323    const char *string = Tcl_GetStringFromObj(listObjPtr, &listLength);
1324    size_t length = listLength + tokenLength +
1325        outSRS->getHorizInitString().length() +
1326        outSRS->getVertInitString().length() +
1327        25;
1328    char *mesg = new char[length];
1329    length = snprintf(mesg, length, "nv>map coords %s {%s} {%s} {%s}\n", token, string,
1330                      outSRS->getHorizInitString().c_str(),
1331                      outSRS->getVertInitString().c_str());
1332    Tcl_DecrRefCount(listObjPtr);
1333    queueResponse(mesg, length, Response::VOLATILE);
1334    delete [] mesg;
1335    return TCL_OK;
1336}
1337
1338static int
1339MapGraticuleOp(ClientData clientData, Tcl_Interp *interp, int objc,
1340               Tcl_Obj *const *objv)
1341{
1342    bool state;
1343    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
1344        return TCL_ERROR;
1345    }
1346    if (objc > 3) {
1347        Renderer::GraticuleType type;
1348        char *typeStr = Tcl_GetString(objv[3]);
1349        if (typeStr[0] == 'g' && strcmp(typeStr, "geodetic") == 0) {
1350            type = Renderer::GRATICULE_GEODETIC;
1351        } else if (typeStr[0] == 's' && strcmp(typeStr, "shader") == 0) {
1352            type = Renderer::GRATICULE_SHADER;
1353        } else if (typeStr[0] == 'u' && strcmp(typeStr, "utm") == 0) {
1354            type = Renderer::GRATICULE_UTM;
1355        } else if (typeStr[0] == 'm' && strcmp(typeStr, "mgrs") == 0) {
1356            type = Renderer::GRATICULE_MGRS;
1357        } else {
1358            return TCL_ERROR;
1359        }
1360        g_renderer->setGraticule(state, type);
1361    } else {
1362        g_renderer->setGraticule(state);
1363    }
1364    return TCL_OK;
1365}
1366
1367static int
1368MapLayerAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
1369              Tcl_Obj *const *objv)
1370{
1371    char *name = Tcl_GetString(objv[3]);
1372    char *type = Tcl_GetString(objv[4]);
1373    bool cache = true;
1374    bool visible = true;
1375    if (type[0] == 'i' && strcmp(type, "image") == 0) {
1376        bool ret;
1377        char *driver = Tcl_GetString(objv[5]);
1378        std::string url;
1379        if (objc > 6) {
1380            char *urlIn = Tcl_GetString(objv[6]);
1381            url = g_renderer->getCanonicalPath(std::string(urlIn));
1382            if (url.empty()) {
1383                Tcl_AppendResult(interp, "file not found: \"",
1384                                 urlIn, "\"", (char*)NULL);
1385                return TCL_ERROR;
1386            }
1387        }
1388        if (objc > 7) {
1389            if (GetBooleanFromObj(interp, objv[7], &cache) != TCL_OK) {
1390                return TCL_ERROR;
1391            }
1392        }
1393        bool coverage = false;
1394        if (objc > 8) {
1395            if (GetBooleanFromObj(interp, objv[8], &coverage) != TCL_OK) {
1396                return TCL_ERROR;
1397            }
1398        }
1399        bool shared = false;
1400        unsigned int minLOD = 0;
1401        unsigned int maxLOD = 23;
1402        float minRange = 0.f;
1403        float maxRange = FLT_MAX;
1404        if (driver[0] == 'a' && strcmp(driver, "agglite") == 0) {
1405            osgEarth::Drivers::AGGLiteOptions aggOpts;
1406
1407            const char *featureDriver = Tcl_GetString(objv[9]);
1408            const char *format = Tcl_GetString(objv[10]);
1409            if (format && strlen(format) > 0 &&
1410                strcmp(format, "json") != 0 && strcmp(format, "gml") != 0) {
1411                Tcl_AppendResult(interp, "unknown format \"", format,
1412                                 "\": should be 'json' or 'gml'", (char*)NULL);
1413                return TCL_ERROR;
1414            }
1415            const char *typeName = Tcl_GetString(objv[11]);
1416
1417            osgEarth::Config styleConf("style", Tcl_GetString(objv[12]));
1418            styleConf.add("type", "text/css");
1419            TRACE("style CSS: %s", styleConf.value().c_str());
1420            osgEarth::Config stylesheetConf;
1421            stylesheetConf.add(styleConf);
1422            aggOpts.styles() = new osgEarth::Symbology::StyleSheet(stylesheetConf);
1423
1424            aggOpts.optimizeLineSampling() = true;
1425            //aggOpts.gamma() = 1.3;
1426
1427            if (objc > 13) {
1428                std::string scripts(Tcl_GetString(objv[13]));
1429                if (!scripts.empty()) {
1430                    TRACE("script: %s", scripts.c_str());
1431                    osg::ref_ptr<osgEarth::Symbology::StyleSheet::ScriptDef> scriptDef =
1432                        new osgEarth::Symbology::StyleSheet::ScriptDef(scripts);
1433                    aggOpts.styles()->setScript(scriptDef.get());
1434                } else {
1435                    TRACE("no script");
1436                }
1437            }
1438            if (objc > 14) {
1439                int numSelectors;
1440                Tcl_Obj **selectors;
1441                if (Tcl_ListObjGetElements(interp, objv[14], &numSelectors, &selectors) != TCL_OK) {
1442                    return TCL_ERROR;
1443                }
1444                for (int i = 0; i < numSelectors; i++) {
1445                    int numFields;
1446                    Tcl_Obj **fields;
1447                    if (Tcl_ListObjGetElements(interp, selectors[i], &numFields, &fields) != TCL_OK ||
1448                        numFields % 2 != 0) {
1449                        return TCL_ERROR;
1450                    }
1451                    std::map<std::string, std::string> fieldMap;
1452                    std::map<std::string, std::string>::iterator itr;
1453                    for (int f = 0; f < numFields; f+=2) {
1454                        char *name = Tcl_GetString(fields[f]);
1455                        char *value = Tcl_GetString(fields[f+1]);
1456                        fieldMap[name] = value;
1457                        TRACE("selector[%s] = %s", name, value);
1458                    }
1459                    osgEarth::Symbology::StyleSelector ss;
1460                    itr = fieldMap.find("name");
1461                    if (itr != fieldMap.end()) {
1462                        ss.name() = itr->second;
1463                    }
1464                    itr = fieldMap.find("style");
1465                    if (itr != fieldMap.end()) {
1466                        ss.styleName() = itr->second;
1467                    }
1468                    itr = fieldMap.find("styleExpression");
1469                    if (itr != fieldMap.end()) {
1470                        TRACE("selector: %s", itr->second.c_str());
1471                        ss.styleExpression() = osgEarth::Symbology::StringExpression(itr->second);
1472                    }
1473                    itr = fieldMap.find("query");
1474                    if (itr != fieldMap.end()) {
1475                        osgEarth::Symbology::Query query;
1476                        query.expression() = itr->second;
1477                        itr = fieldMap.find("queryOrderBy");
1478                        if (itr != fieldMap.end()) {
1479                            query.orderby() = itr->second;
1480                        }
1481                        itr = fieldMap.find("queryBounds");
1482                        if (itr !=  fieldMap.end()) {
1483                            double xmin, ymin, xmax, ymax;
1484                            if (sscanf(itr->second.c_str(), "%lf %lf %lf %lf", &xmin, &ymin, &xmax, &ymax) != 4) {
1485                                return TCL_ERROR;
1486                            }
1487                            TRACE("query bounds: %g %g %g %g", xmin, ymin, xmax, ymax);
1488                            query.bounds() = osgEarth::Bounds(xmin, ymin, xmax, ymax);
1489                        }
1490                        ss.query() = query;
1491                    }
1492                    aggOpts.styles()->selectors().push_back(ss);
1493                }
1494            }
1495            if (featureDriver[0] == 'd' && strcmp(featureDriver, "db") == 0) {
1496                osgEarth::Drivers::OGRFeatureOptions opts;
1497                opts.name() = name;
1498                // Get unmodified connection string
1499                opts.connection() = Tcl_GetString(objv[6]);
1500                opts.layer() = typeName;
1501                aggOpts.featureOptions() = opts;
1502            } else if (featureDriver[0] == 'o' && strcmp(featureDriver, "ogr") == 0) {
1503                osgEarth::Drivers::OGRFeatureOptions opts;
1504                opts.name() = name;
1505                if (osgDB::getLowerCaseFileExtension(url) == "csv") {
1506                    url = loadCSVLayer(url.c_str());
1507                }
1508                opts.url() = url;
1509                aggOpts.featureOptions() = opts;
1510            } else if (featureDriver[0] == 'o' && strcmp(featureDriver, "tfs") == 0) {
1511                osgEarth::Drivers::TFSFeatureOptions opts;
1512                opts.name() = name;
1513                opts.url() = url;
1514                opts.format() = format;
1515                aggOpts.featureOptions() = opts;
1516            } else if (featureDriver[0] == 'o' && strcmp(featureDriver, "wfs") == 0) {
1517                osgEarth::Drivers::WFSFeatureOptions opts;
1518                opts.name() = name;
1519                opts.url() = url;
1520                opts.outputFormat() = format;
1521                opts.typeName() = typeName;
1522                aggOpts.featureOptions() = opts;
1523            } else {
1524                Tcl_AppendResult(interp, "unknown feature driver \"", driver,
1525                                 "\": should be 'db', 'ogr', 'tfs', or 'wfs'", (char*)NULL);
1526                return TCL_ERROR;
1527            }
1528            ret = g_renderer->addImageLayer(name, aggOpts, cache, coverage, shared, visible, minLOD, maxLOD);
1529        } else if (driver[0] == 'c' && strcmp(driver, "colorramp") == 0) {
1530            osgEarth::Drivers::ColorRampOptions colorRampOpts;
1531            char *edriver = Tcl_GetString(objv[9]);
1532            char *profile = Tcl_GetString(objv[10]);
1533            char *colormap = Tcl_GetString(objv[11]);
1534            if (edriver[0] == 'g' && strcmp(edriver, "gdal") == 0) {
1535                osgEarth::Drivers::GDALOptions opts;
1536                opts.url() = url;
1537                osgEarth::ElevationLayerOptions elevOpts(name, opts);
1538                if (!cache) {
1539                    elevOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
1540                }
1541                if (profile != NULL) {
1542                    elevOpts.driver()->profile() = osgEarth::ProfileOptions(profile);
1543                }
1544#if 0
1545                {
1546                    osgEarth::ProfileOptions profile;
1547                    profile.srsString() = srsString;
1548                    profile.vsrsString() = vsrsString;
1549                    profile.bounds() = osgEarth::Bounds(xmin, ymin, xmax, ymax);
1550                    elevOpts.driver()->profile() = profile;
1551                }
1552#endif
1553                colorRampOpts.elevationLayer() = elevOpts;
1554            } else if (edriver[0] == 't' && strcmp(edriver, "tms") == 0) {
1555                osgEarth::Drivers::TMSOptions opts;
1556                //char *tmsType = Tcl_GetString(objv[9]);
1557                //char *format = Tcl_GetString(objv[10]);
1558                opts.url() = url;
1559                //opts.tmsType() = tmsType;
1560                //opts.format() = format;
1561                osgEarth::ElevationLayerOptions elevOpts(name, opts);
1562                if (!cache) {
1563                    elevOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
1564                }
1565                if (profile != NULL) {
1566                    elevOpts.driver()->profile() = osgEarth::ProfileOptions(profile);
1567                }
1568                colorRampOpts.elevationLayer() = elevOpts;
1569            }
1570            colorRampOpts.ramp() = g_renderer->getColorMapFilePath(colormap);
1571            ret = g_renderer->addImageLayer(name, colorRampOpts, cache, coverage, shared, visible, minLOD, maxLOD);
1572        } else if (driver[0] == 'd' && strcmp(driver, "debug") == 0) {
1573            osgEarth::Drivers::DebugOptions opts;
1574            ret = g_renderer->addImageLayer(name, opts, cache, coverage, shared, visible, minLOD, maxLOD);
1575        } else if (driver[0] == 'a' && strcmp(driver, "arcgis") == 0) {
1576            osgEarth::Drivers::ArcGISOptions opts;
1577            opts.url() = url;
1578            if (objc > 9) {
1579                opts.token() = Tcl_GetString(objv[9]);
1580            }
1581            if (objc > 10) {
1582                opts.layers() = Tcl_GetString(objv[10]);
1583            }
1584            //opts.format() = Tcl_GetString(objv[11]);
1585            ret = g_renderer->addImageLayer(name, opts, cache, coverage, shared, visible, minLOD, maxLOD);
1586            g_renderer->setImageLayerVisibleRange(name, minRange, maxRange);
1587        } else if (driver[0] == 'g' && strcmp(driver, "gdal") == 0) {
1588            osgEarth::Drivers::GDALOptions opts;
1589            opts.url() = url;
1590            ret = g_renderer->addImageLayer(name, opts, cache, coverage, shared, visible, minLOD, maxLOD);
1591            g_renderer->setImageLayerVisibleRange(name, minRange, maxRange);
1592        } else if (driver[0] == 't' && strcmp(driver, "tms") == 0) {
1593            osgEarth::Drivers::TMSOptions opts;
1594            opts.url() = url;
1595            //opts.tmsType() = Tcl_GetString(objv[9]);
1596            //opts.format() = Tcl_GetString(objv[10]);
1597            ret = g_renderer->addImageLayer(name, opts, cache, coverage, shared, visible, minLOD, maxLOD);
1598            g_renderer->setImageLayerVisibleRange(name, minRange, maxRange);
1599        } else if (driver[0] == 'w' && strcmp(driver, "wms") == 0) {
1600            osgEarth::Drivers::WMSOptions opts;
1601            char *wmsLayers = Tcl_GetString(objv[9]);
1602            char *format = Tcl_GetString(objv[10]);
1603            bool transparent;
1604            if (GetBooleanFromObj(interp, objv[11], &transparent) != TCL_OK) {
1605                return TCL_ERROR;
1606            }
1607            opts.url() = url;
1608            opts.layers() = wmsLayers;
1609            opts.format() = format;
1610            opts.transparent() = transparent;
1611
1612            ret = g_renderer->addImageLayer(name, opts, cache, coverage, shared, visible, minLOD, maxLOD);
1613            g_renderer->setImageLayerVisibleRange(name, minRange, maxRange);
1614        } else if (driver[0] == 'x' && strcmp(driver, "xyz") == 0) {
1615            osgEarth::Drivers::XYZOptions opts;
1616            opts.url() = url;
1617            opts.profile() = osgEarth::ProfileOptions("global-mercator");
1618            //bool invertY = false;
1619            //opts.invertY() = invertY;
1620            //opts.format() = Tcl_GetString(objv[9]);
1621            ret = g_renderer->addImageLayer(name, opts, cache, coverage, shared, visible, minLOD, maxLOD);
1622            g_renderer->setImageLayerVisibleRange(name, minRange, maxRange);
1623        } else {
1624            Tcl_AppendResult(interp, "unknown image driver \"", driver,
1625                             "\": should be 'debug', 'gdal', 'tms', 'wms' or 'xyz'", (char*)NULL);
1626            return TCL_ERROR;
1627        }
1628        if (!ret) {
1629            Tcl_AppendResult(interp, "Failed to add image layer \"", name, "\"", (char*)NULL);
1630            return TCL_ERROR;
1631        }
1632    } else if (type[0] == 'e' && strcmp(type, "elevation") == 0) {
1633        char *driver = Tcl_GetString(objv[5]);
1634        char *urlIn = Tcl_GetString(objv[6]);
1635        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
1636        if (url.empty()) {
1637            Tcl_AppendResult(interp, "file not found: \"",
1638                             urlIn, "\"", (char*)NULL);
1639            return TCL_ERROR;
1640        }
1641        if (objc > 7) {
1642            if (GetBooleanFromObj(interp, objv[7], &cache) != TCL_OK) {
1643                return TCL_ERROR;
1644            }
1645        }
1646        int minLOD = 0;
1647        int maxLOD = 23;
1648        if (driver[0] == 'g' && strcmp(driver, "gdal") == 0) {
1649            osgEarth::Drivers::GDALOptions opts;
1650            opts.url() = url;
1651            g_renderer->addElevationLayer(name, opts, cache, visible, minLOD, maxLOD);
1652        } else if (driver[0] == 't' && strcmp(driver, "tms") == 0) {
1653            osgEarth::Drivers::TMSOptions opts;
1654            opts.url() = url;
1655            //opts.tmsType() = Tcl_GetString(objv[8]);
1656            //opts.format() = Tcl_GetString(objv[9]);
1657            g_renderer->addElevationLayer(name, opts, cache, visible, minLOD, maxLOD);
1658        } else if (driver[0] == 'w' && strcmp(driver, "wcs") == 0) {
1659            osgEarth::Drivers::WCSOptions opts;
1660            opts.url() = url;
1661            if (objc > 8) {
1662                opts.identifier() = Tcl_GetString(objv[8]);
1663            }
1664            if (objc > 9) {
1665                // default = 'm'
1666                opts.elevationUnit() = Tcl_GetString(objv[9]);
1667            }
1668            if (objc > 10) {
1669                // default = 'image/GeoTIFF'
1670                opts.format() = Tcl_GetString(objv[10]);
1671            }
1672            //opts.srs() = Tcl_GetString(objv[11]);
1673            //opts.rangeSubset() = Tcl_GetString(objv[12]);
1674            g_renderer->addElevationLayer(name, opts, cache, visible, minLOD, maxLOD);
1675        } else {
1676            Tcl_AppendResult(interp, "unknown elevation driver \"", driver,
1677                             "\": should be 'gdal' or 'tms'", (char*)NULL);
1678            return TCL_ERROR;
1679        }
1680    } else if (type[0] == 'f' && strcmp(type, "feature") == 0) {
1681        // Generic feature geometry layer
1682        char *driver = Tcl_GetString(objv[5]);
1683        char *format = Tcl_GetString(objv[6]);
1684        if (format && strlen(format) > 0 &&
1685            strcmp(format, "json") != 0 && strcmp(format, "gml") != 0) {
1686            Tcl_AppendResult(interp, "unknown format \"", format,
1687                             "\": should be 'json' or 'gml'", (char*)NULL);
1688            return TCL_ERROR;
1689        }
1690        char *typeName = Tcl_GetString(objv[7]);
1691        char *urlIn = Tcl_GetString(objv[8]);
1692        std::string url;
1693        if (driver[0] == 'd' && strcmp(driver, "db") == 0) {
1694            url = urlIn;
1695        } else {
1696            url = g_renderer->getCanonicalPath(std::string(urlIn));
1697            if (url.empty()) {
1698                Tcl_AppendResult(interp, "file not found: \"",
1699                                 urlIn, "\"", (char*)NULL);
1700                return TCL_ERROR;
1701            }
1702        }
1703        if (GetBooleanFromObj(interp, objv[9], &cache) != TCL_OK) {
1704            return TCL_ERROR;
1705        }
1706
1707        bool lighting = true;
1708        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
1709        osgEarth::Config styleConf("style", Tcl_GetString(objv[10]));
1710        styleConf.add("type", "text/css");
1711        TRACE("style CSS: %s", styleConf.value().c_str());
1712#if 1
1713        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
1714        // For relative paths:
1715        //styleConf.setReferrer(g_renderer->getCacheDirectory());
1716        std::string cssString = styleConf.value();
1717        std::vector<std::string> blocks;
1718        osgEarth::Symbology::CssUtils::split(cssString, blocks);
1719        for (std::vector<std::string>::iterator itr = blocks.begin();
1720             itr != blocks.end(); ++itr) {
1721            osgEarth::Config blockConf(styleConf);
1722            blockConf.value() = *itr;
1723            osgEarth::Symbology::Style style(blockConf);
1724
1725            TRACE("Style: %s", style.getName().c_str());
1726            TRACE("%s", itr->c_str());
1727
1728            if (style.has<osgEarth::Symbology::IconSymbol>()) {
1729                TRACE("Found icon symbol");
1730                osgEarth::Symbology::IconSymbol *is =
1731                    style.get<osgEarth::Symbology::IconSymbol>();
1732                if (is->url().isSet()) {
1733                    TRACE("Icon url before: expr: '%s' eval: '%s'",
1734                         is->url()->expr().c_str(), is->url()->eval().c_str());
1735                    // eval() will try to evaluate the expr()
1736                    std::string path = g_renderer->getCanonicalPath(is->url()->eval());
1737                    //setInfix() to set the expr() string, setLiteral() will quote
1738                    is->url()->setLiteral(path);
1739                    TRACE("Icon url after: %s", path.c_str());
1740                }
1741            }
1742            if (style.has<osgEarth::Symbology::ModelSymbol>()) {
1743                osgEarth::Symbology::ModelSymbol *ms =
1744                    style.get<osgEarth::Symbology::ModelSymbol>();
1745                if (ms->url().isSet()) {
1746                    // eval() will try to evaluate the expr()
1747                    std::string path = g_renderer->getCanonicalPath(ms->url()->eval());
1748                    //setInfix() to set the expr() string, setLiteral() will quote
1749                    ms->url()->setLiteral(path);
1750                }
1751            }
1752            // Need to create a new style otherwise the original CSS is used
1753            // without the re-written URLs
1754            geomOpts.styles()->addStyle(osgEarth::Symbology::Style(style.getConfig(false)));
1755        }
1756#else
1757        osgEarth::Config stylesheetConf;
1758        stylesheetConf.add(styleConf);
1759        geomOpts.styles() = new osgEarth::Symbology::StyleSheet(stylesheetConf);
1760#endif
1761
1762        if (objc > 11) {
1763            std::string scripts(Tcl_GetString(objv[11]));
1764            if (!scripts.empty()) {
1765                TRACE("script: %s", scripts.c_str());
1766                osg::ref_ptr<osgEarth::Symbology::StyleSheet::ScriptDef> scriptDef =
1767                    new osgEarth::Symbology::StyleSheet::ScriptDef(scripts);
1768                geomOpts.styles()->setScript(scriptDef.get());
1769            } else {
1770                TRACE("no script");
1771            }
1772        }
1773        if (objc > 12) {
1774            int numSelectors;
1775            Tcl_Obj **selectors;
1776            if (Tcl_ListObjGetElements(interp, objv[12], &numSelectors, &selectors) != TCL_OK) {
1777                return TCL_ERROR;
1778            }
1779            TRACE("Num Selectors: %d", numSelectors);
1780            for (int i = 0; i < numSelectors; i++) {
1781                int numFields;
1782                Tcl_Obj **fields;
1783                if (Tcl_ListObjGetElements(interp, selectors[i], &numFields, &fields) != TCL_OK ||
1784                    numFields % 2 != 0) {
1785                    return TCL_ERROR;
1786                }
1787                std::map<std::string, std::string> fieldMap;
1788                std::map<std::string, std::string>::iterator itr;
1789                for (int f = 0; f < numFields; f+=2) {
1790                    char *name = Tcl_GetString(fields[f]);
1791                    char *value = Tcl_GetString(fields[f+1]);
1792                    fieldMap[name] = value;
1793                    TRACE("selector[%s] = %s", name, value);
1794                }
1795                osgEarth::Symbology::StyleSelector ss;
1796                itr = fieldMap.find("name");
1797                if (itr != fieldMap.end()) {
1798                    ss.name() = itr->second;
1799                }
1800                itr = fieldMap.find("style");
1801                if (itr != fieldMap.end()) {
1802                    ss.styleName() = itr->second;
1803                }
1804                itr = fieldMap.find("styleExpression");
1805                if (itr != fieldMap.end()) {
1806                    TRACE("selector: %s", itr->second.c_str());
1807                    ss.styleExpression() = osgEarth::Symbology::StringExpression(itr->second);
1808                }
1809                itr = fieldMap.find("query");
1810                if (itr != fieldMap.end()) {
1811                    osgEarth::Symbology::Query query;
1812                    query.expression() = itr->second;
1813                    itr = fieldMap.find("queryOrderBy");
1814                    if (itr != fieldMap.end()) {
1815                        query.orderby() = itr->second;
1816                    }
1817                    itr = fieldMap.find("queryBounds");
1818                    if (itr !=  fieldMap.end()) {
1819                        double xmin, ymin, xmax, ymax;
1820                        if (sscanf(itr->second.c_str(), "%lf %lf %lf %lf", &xmin, &ymin, &xmax, &ymax) != 4) {
1821                            return TCL_ERROR;
1822                        }
1823                        TRACE("query bounds: %g %g %g %g", xmin, ymin, xmax, ymax);
1824                        query.bounds() = osgEarth::Bounds(xmin, ymin, xmax, ymax);
1825                    }
1826                    ss.query() = query;
1827                }
1828                geomOpts.styles()->selectors().push_back(ss);
1829            }
1830        }
1831        geomOpts.enableLighting() = lighting;
1832        geomOpts.minRange() = 0.f;
1833        geomOpts.maxRange() = FLT_MAX;
1834        if (objc > 13) {
1835            float min, max;
1836            if (GetFloatFromObj(interp, objv[13], &min) != TCL_OK ||
1837                GetFloatFromObj(interp, objv[14], &max) != TCL_OK) {
1838                return TCL_ERROR;
1839            }
1840            geomOpts.minRange() = min;
1841            geomOpts.maxRange() = max;
1842        }
1843        if (driver[0] == 'd' && strcmp(driver, "db") == 0) {
1844            osgEarth::Drivers::OGRFeatureOptions opts;
1845            opts.name() = name;
1846            opts.connection() = url;
1847            opts.layer() = typeName;
1848            geomOpts.featureOptions() = opts;
1849        } else if (driver[0] == 'o' && strcmp(driver, "ogr") == 0) {
1850            osgEarth::Drivers::OGRFeatureOptions opts;
1851            opts.name() = name;
1852            if (osgDB::getLowerCaseFileExtension(url) == "csv") {
1853                url = loadCSVLayer(url.c_str());
1854            }
1855            opts.url() = url;
1856            geomOpts.featureOptions() = opts;
1857        } else if (driver[0] == 't' && strcmp(driver, "tfs") == 0) {
1858            osgEarth::Drivers::TFSFeatureOptions opts;
1859            opts.name() = name;
1860            opts.url() = url;
1861            opts.format() = format;
1862            geomOpts.featureOptions() = opts;
1863        } else if (driver[0] == 'w' && strcmp(driver, "wfs") == 0) {
1864            osgEarth::Drivers::WFSFeatureOptions opts;
1865            opts.name() = name;
1866            opts.url() = url;
1867            opts.outputFormat() = format;
1868            opts.typeName() = typeName;
1869            geomOpts.featureOptions() = opts;
1870        } else {
1871            Tcl_AppendResult(interp, "unknown feature driver \"", driver,
1872                             "\": should be 'db', 'ogr', 'tfs', or 'wfs'", (char*)NULL);
1873            return TCL_ERROR;
1874        }
1875        g_renderer->addModelLayer(name, geomOpts, cache, lighting, visible);
1876    } else if (type[0] == 'i' && strcmp(type, "icon") == 0) {
1877        char *driver = Tcl_GetString(objv[5]);
1878        char *format = Tcl_GetString(objv[6]);
1879        if (format && strlen(format) > 0 &&
1880            strcmp(format, "json") != 0 && strcmp(format, "gml") != 0) {
1881            Tcl_AppendResult(interp, "unknown format \"", format,
1882                             "\": should be 'json' or 'gml'", (char*)NULL);
1883            return TCL_ERROR;
1884        }
1885        char *typeName = Tcl_GetString(objv[7]);
1886        char *urlIn = Tcl_GetString(objv[8]);
1887        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
1888        if (url.empty()) {
1889            Tcl_AppendResult(interp, "file not found: \"",
1890                             urlIn, "\"", (char*)NULL);
1891            return TCL_ERROR;
1892        }
1893        if (GetBooleanFromObj(interp, objv[9], &cache) != TCL_OK) {
1894            return TCL_ERROR;
1895        }
1896        char *icon = Tcl_GetString(objv[10]);
1897        char *scaleExpr = Tcl_GetString(objv[11]);
1898        char *headingExpr = Tcl_GetString(objv[12]);
1899        bool declutter = false;
1900        if (GetBooleanFromObj(interp, objv[13], &declutter) != TCL_OK) {
1901            return TCL_ERROR;
1902        }
1903        char *placementStr = Tcl_GetString(objv[14]);
1904        char *alignStr = Tcl_GetString(objv[15]);
1905
1906        bool lighting = true;
1907        osgEarth::Symbology::Style style;
1908        osgEarth::Symbology::IconSymbol *is = style.getOrCreateSymbol<osgEarth::Symbology::IconSymbol>();
1909        if (icon != NULL && strlen(icon) > 0) {
1910            std::string iconFile = g_renderer->getIconFile(icon);
1911            if (!iconFile.empty()) {
1912                is->url()->setLiteral(iconFile);
1913            } else {
1914                is->url() = osgEarth::Symbology::StringExpression(icon);
1915            }
1916        } else {
1917            is->url()->setLiteral(g_renderer->getPinIcon());
1918        }
1919        if (scaleExpr != NULL && strlen(scaleExpr) > 0) {
1920            is->scale() = osgEarth::Symbology::NumericExpression(scaleExpr);
1921        }
1922        if (headingExpr != NULL && strlen(headingExpr) > 0) {
1923            is->heading() = osgEarth::Symbology::NumericExpression(headingExpr);
1924        }
1925        is->declutter() = declutter;
1926        is->occlusionCull() = false;
1927        is->occlusionCullAltitude() = 200000;
1928        is->alignment() = ParseIconAlignment(alignStr, osgEarth::Symbology::IconSymbol::ALIGN_CENTER_BOTTOM);
1929        is->placement() = ParseInstancePlacement(placementStr, osgEarth::Symbology::InstanceSymbol::PLACEMENT_VERTEX);
1930        //is->density() = 1.0f; // For PLACEMENT_INTERVAL/RANDOM
1931#if 0
1932        osgEarth::Symbology::AltitudeSymbol *alt = style.getOrCreateSymbol<osgEarth::Symbology::AltitudeSymbol>();
1933        alt->clamping() = osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
1934        alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_DRAPE;
1935        //alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
1936        //alt->verticalOffset() = osgEarth::Symbology::NumericExpression();
1937        //alt->verticalScale() = osgEarth::Symbology::NumericExpression();
1938#endif
1939#ifdef USE_DEPTH_OFFSET
1940        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
1941        rs->depthOffset()->enabled() = true;
1942        rs->depthOffset()->minBias() = 1000;
1943#endif
1944        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
1945        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
1946        geomOpts.styles()->addStyle(style);
1947        geomOpts.enableLighting() = false;
1948        geomOpts.minRange() = 0.f;
1949        geomOpts.maxRange() = FLT_MAX;
1950        if (objc > 16) {
1951            float min, max;
1952            if (GetFloatFromObj(interp, objv[16], &min) != TCL_OK ||
1953                GetFloatFromObj(interp, objv[17], &max) != TCL_OK) {
1954                return TCL_ERROR;
1955            }
1956            geomOpts.minRange() = min;
1957            geomOpts.maxRange() = max;
1958        }
1959        //geomOpts.renderOrder() = int;
1960        //geomOpts.depthTestEnabled() = bool;
1961        if (driver[0] == 'o' && strcmp(driver, "ogr") == 0) {
1962            osgEarth::Drivers::OGRFeatureOptions opts;
1963            opts.name() = name;
1964            if (osgDB::getLowerCaseFileExtension(url) == "csv") {
1965                url = loadCSVLayer(url.c_str());
1966            }
1967            opts.url() = url;
1968            geomOpts.featureOptions() = opts;
1969        } else if (driver[0] == 't' && strcmp(driver, "tfs") == 0) {
1970            osgEarth::Drivers::TFSFeatureOptions opts;
1971            opts.name() = name;
1972            opts.url() = url;
1973            opts.format() = format;
1974            geomOpts.featureOptions() = opts;
1975        } else if (driver[0] == 'w' && strcmp(driver, "wfs") == 0) {
1976            osgEarth::Drivers::WFSFeatureOptions opts;
1977            opts.name() = name;
1978            opts.url() = url;
1979            opts.outputFormat() = format;
1980            opts.typeName() = typeName;
1981            geomOpts.featureOptions() = opts;
1982        } else {
1983            Tcl_AppendResult(interp, "unknown feature driver \"", driver,
1984                             "\": should be 'ogr', 'tfs', or 'wfs'", (char*)NULL);
1985            return TCL_ERROR;
1986        }
1987        g_renderer->addModelLayer(name, geomOpts, cache, lighting, visible);
1988    } else if (type[0] == 'p' && strcmp(type, "point") == 0) {
1989        char *driver = Tcl_GetString(objv[5]);
1990        char *format = Tcl_GetString(objv[6]);
1991        if (format && strlen(format) > 0 &&
1992            strcmp(format, "json") != 0 && strcmp(format, "gml") != 0) {
1993            Tcl_AppendResult(interp, "unknown format \"", format,
1994                             "\": should be 'json' or 'gml'", (char*)NULL);
1995            return TCL_ERROR;
1996        }
1997        char *typeName = Tcl_GetString(objv[7]);
1998        char *urlIn = Tcl_GetString(objv[8]);
1999        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
2000        if (url.empty()) {
2001            Tcl_AppendResult(interp, "file not found: \"",
2002                             urlIn, "\"", (char*)NULL);
2003            return TCL_ERROR;
2004        }
2005        if (GetBooleanFromObj(interp, objv[9], &cache) != TCL_OK) {
2006            return TCL_ERROR;
2007        }
2008        float r, g, b;
2009        if (GetFloatFromObj(interp, objv[10], &r) != TCL_OK ||
2010            GetFloatFromObj(interp, objv[11], &g) != TCL_OK ||
2011            GetFloatFromObj(interp, objv[12], &b) != TCL_OK) {
2012            return TCL_ERROR;
2013        }
2014        float ptSize;
2015        if (GetFloatFromObj(interp, objv[13], &ptSize) != TCL_OK) {
2016            return TCL_ERROR;
2017        }
2018        bool lighting = true;
2019        osgEarth::Symbology::Style style;
2020        osgEarth::Symbology::PointSymbol *ps = style.getOrCreateSymbol<osgEarth::Symbology::PointSymbol>();
2021        ps->fill()->color() = osgEarth::Symbology::Color(r, g, b);
2022        ps->size() = ptSize;
2023
2024        osgEarth::Symbology::AltitudeSymbol::Clamping clamping =
2025            osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
2026        if (objc > 14) {
2027            clamping = ParseClamping(Tcl_GetString(objv[14]), clamping);
2028        }
2029        osgEarth::Symbology::AltitudeSymbol::Technique technique =
2030            osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
2031        if (objc > 15) {
2032            technique = ParseClampingTechnique(Tcl_GetString(objv[15]), technique);
2033        }
2034        osgEarth::Symbology::AltitudeSymbol *alt = style.getOrCreateSymbol<osgEarth::Symbology::AltitudeSymbol>();
2035        alt->clamping() = clamping;
2036        alt->technique() = technique;
2037        //alt->verticalOffset() = osgEarth::Symbology::NumericExpression();
2038        //alt->verticalScale() = osgEarth::Symbology::NumericExpression();
2039
2040#if 1 || defined(USE_DEPTH_OFFSET)
2041        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
2042        rs->depthOffset()->enabled() = true;
2043        rs->depthOffset()->minBias() = 1000;
2044#endif
2045        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
2046        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
2047        geomOpts.styles()->addStyle(style);
2048        geomOpts.enableLighting() = false;
2049        geomOpts.minRange() = 0.f;
2050        geomOpts.maxRange() = FLT_MAX;
2051        if (objc > 16) {
2052            float min, max;
2053            if (GetFloatFromObj(interp, objv[16], &min) != TCL_OK ||
2054                GetFloatFromObj(interp, objv[17], &max) != TCL_OK) {
2055                return TCL_ERROR;
2056            }
2057            geomOpts.minRange() = min;
2058            geomOpts.maxRange() = max;
2059        }
2060        //geomOpts.renderOrder() = int;
2061        //geomOpts.depthTestEnabled() = bool;
2062        if (driver[0] == 'o' && strcmp(driver, "ogr") == 0) {
2063            osgEarth::Drivers::OGRFeatureOptions opts;
2064            opts.name() = name;
2065            if (osgDB::getLowerCaseFileExtension(url) == "csv") {
2066                url = loadCSVLayer(url.c_str());
2067            }
2068            opts.url() = url;
2069            geomOpts.featureOptions() = opts;
2070        } else if (driver[0] == 't' && strcmp(driver, "tfs") == 0) {
2071            osgEarth::Drivers::TFSFeatureOptions opts;
2072            opts.name() = name;
2073            opts.url() = url;
2074            opts.format() = format;
2075            geomOpts.featureOptions() = opts;
2076        } else if (driver[0] == 'w' && strcmp(driver, "wfs") == 0) {
2077            osgEarth::Drivers::WFSFeatureOptions opts;
2078            opts.name() = name;
2079            opts.url() = url;
2080            opts.outputFormat() = format;
2081            opts.typeName() = typeName;
2082            geomOpts.featureOptions() = opts;
2083        } else {
2084            Tcl_AppendResult(interp, "unknown feature driver \"", driver,
2085                             "\": should be 'ogr', 'tfs', or 'wfs'", (char*)NULL);
2086            return TCL_ERROR;
2087        }
2088        g_renderer->addModelLayer(name, geomOpts, cache, lighting, visible);
2089    } else if (type[0] == 'p' && strcmp(type, "polygon") == 0) {
2090        char *driver = Tcl_GetString(objv[5]);
2091        char *format = Tcl_GetString(objv[6]);
2092        if (format && strlen(format) > 0 &&
2093            strcmp(format, "json") != 0 && strcmp(format, "gml") != 0) {
2094            Tcl_AppendResult(interp, "unknown format \"", format,
2095                             "\": should be 'json' or 'gml'", (char*)NULL);
2096            return TCL_ERROR;
2097        }
2098        char *typeName = Tcl_GetString(objv[7]);
2099        char *urlIn = Tcl_GetString(objv[8]);
2100        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
2101        if (url.empty()) {
2102            Tcl_AppendResult(interp, "file not found: \"",
2103                             urlIn, "\"", (char*)NULL);
2104            return TCL_ERROR;
2105        }
2106        if (GetBooleanFromObj(interp, objv[9], &cache) != TCL_OK) {
2107            return TCL_ERROR;
2108        }
2109        float r, g, b;
2110        if (GetFloatFromObj(interp, objv[10], &r) != TCL_OK ||
2111            GetFloatFromObj(interp, objv[11], &g) != TCL_OK ||
2112            GetFloatFromObj(interp, objv[12], &b) != TCL_OK) {
2113            return TCL_ERROR;
2114        }
2115        float lineWidth = 0.0f;
2116        if (objc > 13) {
2117            if (GetFloatFromObj(interp, objv[13], &lineWidth) != TCL_OK) {
2118                return TCL_ERROR;
2119            }
2120        }
2121        float strokeR = 0, strokeG = 0, strokeB = 0;
2122        if (objc > 14) {
2123            if (GetFloatFromObj(interp, objv[14], &strokeR) != TCL_OK ||
2124                GetFloatFromObj(interp, objv[15], &strokeG) != TCL_OK ||
2125                GetFloatFromObj(interp, objv[16], &strokeB) != TCL_OK) {
2126                return TCL_ERROR;
2127            }
2128        }
2129        bool lighting = true;
2130        osgEarth::Symbology::Style style;
2131        osgEarth::Symbology::PolygonSymbol *ps = style.getOrCreateSymbol<osgEarth::Symbology::PolygonSymbol>();
2132        ps->fill()->color() = osgEarth::Symbology::Color(r, g, b);
2133
2134        if (lineWidth > 0.0f) {
2135            osgEarth::Symbology::LineSymbol *ls = style.getOrCreateSymbol<osgEarth::Symbology::LineSymbol>();
2136            ls->stroke()->color() = osgEarth::Symbology::Color(strokeR, strokeG, strokeB);
2137            ls->stroke()->width() = lineWidth;
2138            ls->stroke()->widthUnits() = osgEarth::Units::PIXELS;
2139            ls->stroke()->lineCap() = osgEarth::Symbology::Stroke::LINECAP_FLAT; // _SQUARE, _ROUND
2140            //ls->stroke()->roundingRatio() = 0.4f;
2141            ls->stroke()->lineJoin() = osgEarth::Symbology::Stroke::LINEJOIN_MITRE; // _ROUND
2142            //ls->stroke()->stipplePattern() = 0; // Bitmask: unsigned short
2143            //ls->stroke()->stippleFactor() = 0; // num reps: unsigned
2144        }
2145
2146        osgEarth::Symbology::AltitudeSymbol::Clamping clamping =
2147            osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
2148        if (objc > 17) {
2149            clamping = ParseClamping(Tcl_GetString(objv[17]), clamping);
2150        }
2151        osgEarth::Symbology::AltitudeSymbol::Technique technique =
2152            osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
2153        if (objc > 18) {
2154            technique = ParseClampingTechnique(Tcl_GetString(objv[18]), technique);
2155        }
2156        osgEarth::Symbology::AltitudeSymbol *alt = style.getOrCreateSymbol<osgEarth::Symbology::AltitudeSymbol>();
2157        alt->clamping() = clamping;
2158        alt->technique() = technique;
2159        //alt->verticalOffset() = osgEarth::Symbology::NumericExpression();
2160        //alt->verticalScale() = osgEarth::Symbology::NumericExpression();
2161
2162#if 1 || defined(USE_DEPTH_OFFSET)
2163        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
2164        rs->depthOffset()->enabled() = true;
2165        rs->depthOffset()->minBias() = 1000;
2166#endif
2167        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
2168        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
2169        geomOpts.styles()->addStyle(style);
2170        geomOpts.enableLighting() = false;
2171        geomOpts.minRange() = 0.f;
2172        geomOpts.maxRange() = FLT_MAX;
2173        if (objc > 19) {
2174            float min, max;
2175            if (GetFloatFromObj(interp, objv[19], &min) != TCL_OK ||
2176                GetFloatFromObj(interp, objv[20], &max) != TCL_OK) {
2177                return TCL_ERROR;
2178            }
2179            geomOpts.minRange() = min;
2180            geomOpts.maxRange() = max;
2181        }
2182        if (driver[0] == 'o' && strcmp(driver, "ogr") == 0) {
2183            osgEarth::Drivers::OGRFeatureOptions opts;
2184            opts.name() = name;
2185            if (osgDB::getLowerCaseFileExtension(url) == "csv") {
2186                url = loadCSVLayer(url.c_str());
2187            }
2188            opts.url() = url;
2189            geomOpts.featureOptions() = opts;
2190        } else if (driver[0] == 't' && strcmp(driver, "tfs") == 0) {
2191            osgEarth::Drivers::TFSFeatureOptions opts;
2192            opts.name() = name;
2193            opts.url() = url;
2194            opts.format() = format;
2195            geomOpts.featureOptions() = opts;
2196        } else if (driver[0] == 'w' && strcmp(driver, "wfs") == 0) {
2197            osgEarth::Drivers::WFSFeatureOptions opts;
2198            opts.name() = name;
2199            opts.url() = url;
2200            opts.outputFormat() = format;
2201            opts.typeName() = typeName;
2202            geomOpts.featureOptions() = opts;
2203        } else {
2204            Tcl_AppendResult(interp, "unknown feature driver \"", driver,
2205                             "\": should be 'ogr', 'tfs', or 'wfs'", (char*)NULL);
2206            return TCL_ERROR;
2207        }
2208        g_renderer->addModelLayer(name, geomOpts, cache, lighting, visible);
2209    } else if (type[0] == 'l' && strcmp(type, "line") == 0) {
2210        char *driver = Tcl_GetString(objv[5]);
2211        char *format = Tcl_GetString(objv[6]);
2212        if (format && strlen(format) > 0 &&
2213            strcmp(format, "json") != 0 && strcmp(format, "gml") != 0) {
2214            Tcl_AppendResult(interp, "unknown format \"", format,
2215                             "\": should be 'json' or 'gml'", (char*)NULL);
2216            return TCL_ERROR;
2217        }
2218        char *typeName = Tcl_GetString(objv[7]);
2219        char *urlIn = Tcl_GetString(objv[8]);
2220        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
2221        if (url.empty()) {
2222            Tcl_AppendResult(interp, "file not found: \"",
2223                             urlIn, "\"", (char*)NULL);
2224            return TCL_ERROR;
2225        }
2226        if (GetBooleanFromObj(interp, objv[9], &cache) != TCL_OK) {
2227            return TCL_ERROR;
2228        }
2229        float r, g, b;
2230        if (GetFloatFromObj(interp, objv[10], &r) != TCL_OK ||
2231            GetFloatFromObj(interp, objv[11], &g) != TCL_OK ||
2232            GetFloatFromObj(interp, objv[12], &b) != TCL_OK) {
2233            return TCL_ERROR;
2234        }
2235        float lineWidth;
2236        osgEarth::Units units = osgEarth::Units::PIXELS;
2237        if (!osgEarth::Units::parse(Tcl_GetString(objv[13]), lineWidth,
2238                                    units, osgEarth::Units::PIXELS)) {
2239            Tcl_AppendResult(interp, "Unkown units: \"",
2240                             Tcl_GetString(objv[13]), "\"", (char*)NULL);
2241            return TCL_ERROR;
2242        }
2243
2244        osgEarth::Symbology::Stroke::LineCapStyle cap = osgEarth::Symbology::Stroke::LINECAP_FLAT;
2245        osgEarth::Symbology::Stroke::LineJoinStyle join =  osgEarth::Symbology::Stroke::LINEJOIN_MITRE;
2246        if (objc > 14) {
2247            cap = ParseLineCapStyle(Tcl_GetString(objv[14]), cap);
2248        }
2249        if (objc > 15) {
2250            join = ParseLineJoinStyle(Tcl_GetString(objv[15]), join);
2251        }
2252        bool lighting = true;
2253        osgEarth::Symbology::Style style;
2254        osgEarth::Symbology::LineSymbol *ls = style.getOrCreateSymbol<osgEarth::Symbology::LineSymbol>();
2255        ls->stroke()->color() = osgEarth::Symbology::Color(r, g, b);
2256        ls->stroke()->width() = lineWidth;
2257        ls->stroke()->widthUnits() = units;
2258        ls->stroke()->minPixels() = 1.0f;
2259        ls->stroke()->lineCap() = cap;
2260        //ls->stroke()->roundingRatio() = 0.4f;
2261        ls->stroke()->lineJoin() = join;
2262        if (objc > 16) {
2263            unsigned short stipplePattern = 0U;
2264            unsigned int stippleFactor = 1U;
2265            if (GetUShortFromObj(interp, objv[16], &stipplePattern) != TCL_OK ||
2266                GetUIntFromObj(interp, objv[17], &stippleFactor) != TCL_OK) {
2267                return TCL_ERROR;
2268            }
2269            if (stipplePattern > 0U) {
2270                ls->stroke()->stipplePattern() = stipplePattern; // Bitmask: unsigned short
2271                ls->stroke()->stippleFactor() = stippleFactor; // num reps: unsigned
2272            }
2273        }
2274
2275        osgEarth::Symbology::AltitudeSymbol::Clamping clamping =
2276            osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
2277        if (objc > 18) {
2278            clamping = ParseClamping(Tcl_GetString(objv[18]), clamping);
2279        }
2280        osgEarth::Symbology::AltitudeSymbol::Technique technique =
2281            osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
2282        if (objc > 18) {
2283            technique = ParseClampingTechnique(Tcl_GetString(objv[19]), technique);
2284        }
2285        osgEarth::Symbology::AltitudeSymbol *alt = style.getOrCreateSymbol<osgEarth::Symbology::AltitudeSymbol>();
2286        alt->clamping() = clamping;
2287        alt->technique() = technique;
2288        //alt->clampingResolution() = 1.0f;
2289        //alt->binding() = osgEarth::Symbology::AltitudeSymbol::BINDING_VERTEX; //BINDING_CENTROID
2290        //alt->verticalOffset() = osgEarth::Symbology::NumericExpression();
2291        //alt->verticalScale() = osgEarth::Symbology::NumericExpression();
2292
2293#if 1 || defined(USE_DEPTH_OFFSET)
2294        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
2295        rs->depthOffset()->enabled() = true;
2296        rs->depthOffset()->minBias() = 1000;
2297#endif
2298        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
2299        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
2300        geomOpts.styles()->addStyle(style);
2301        geomOpts.enableLighting() = false;
2302        geomOpts.minRange() = 0.f;
2303        geomOpts.maxRange() = FLT_MAX;
2304        if (objc > 20) {
2305            float min, max;
2306            if (GetFloatFromObj(interp, objv[20], &min) != TCL_OK ||
2307                GetFloatFromObj(interp, objv[21], &max) != TCL_OK) {
2308                return TCL_ERROR;
2309            }
2310            geomOpts.minRange() = min;
2311            geomOpts.maxRange() = max;
2312        }
2313        if (driver[0] == 'o' && strcmp(driver, "ogr") == 0) {
2314            osgEarth::Drivers::OGRFeatureOptions opts;
2315            opts.name() = name;
2316            if (osgDB::getLowerCaseFileExtension(url) == "csv") {
2317                url = loadCSVLayer(url.c_str());
2318            }
2319            opts.url() = url;
2320            geomOpts.featureOptions() = opts;
2321        } else if (driver[0] == 't' && strcmp(driver, "tfs") == 0) {
2322            osgEarth::Drivers::TFSFeatureOptions opts;
2323            opts.name() = name;
2324            opts.url() = url;
2325            opts.format() = format;
2326            geomOpts.featureOptions() = opts;
2327        } else if (driver[0] == 'w' && strcmp(driver, "wfs") == 0) {
2328            osgEarth::Drivers::WFSFeatureOptions opts;
2329            opts.name() = name;
2330            opts.url() = url;
2331            opts.outputFormat() = format;
2332            opts.typeName() = typeName;
2333            geomOpts.featureOptions() = opts;
2334        } else {
2335            Tcl_AppendResult(interp, "unknown feature driver \"", driver,
2336                             "\": should be 'ogr', 'tfs', or 'wfs'", (char*)NULL);
2337            return TCL_ERROR;
2338        }
2339        g_renderer->addModelLayer(name, geomOpts, cache, lighting, visible);
2340   } else if (type[0] == 't' && strcmp(type, "text") == 0) {
2341        char *driver = Tcl_GetString(objv[5]);
2342        char *format = Tcl_GetString(objv[6]);
2343        if (format && strlen(format) > 0 &&
2344            strcmp(format, "json") != 0 && strcmp(format, "gml") != 0) {
2345            Tcl_AppendResult(interp, "unknown format \"", format,
2346                             "\": should be 'json' or 'gml'", (char*)NULL);
2347            return TCL_ERROR;
2348        }
2349        char *typeName = Tcl_GetString(objv[7]);
2350        char *urlIn = Tcl_GetString(objv[8]);
2351        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
2352        if (url.empty()) {
2353            Tcl_AppendResult(interp, "file not found: \"",
2354                             urlIn, "\"", (char*)NULL);
2355            return TCL_ERROR;
2356        }
2357        if (GetBooleanFromObj(interp, objv[9], &cache) != TCL_OK) {
2358            return TCL_ERROR;
2359        }
2360        char *content = Tcl_GetString(objv[10]);
2361        char *priority = Tcl_GetString(objv[11]);
2362        float fgR, fgG, fgB;
2363        float bgR, bgG, bgB;
2364        float haloWidth, ftSize;
2365        osg::Vec2s pixelOffset(0, 0);
2366        if (GetFloatFromObj(interp, objv[12], &fgR) != TCL_OK ||
2367            GetFloatFromObj(interp, objv[13], &fgG) != TCL_OK ||
2368            GetFloatFromObj(interp, objv[14], &fgB) != TCL_OK ||
2369            GetFloatFromObj(interp, objv[15], &bgR) != TCL_OK ||
2370            GetFloatFromObj(interp, objv[16], &bgG) != TCL_OK ||
2371            GetFloatFromObj(interp, objv[17], &bgB) != TCL_OK ||
2372            GetFloatFromObj(interp, objv[18], &haloWidth) != TCL_OK ||
2373            GetFloatFromObj(interp, objv[19], &ftSize) != TCL_OK) {
2374            return TCL_ERROR;
2375        }
2376        bool removeDupes, declutter;
2377        if (GetBooleanFromObj(interp, objv[20], &removeDupes) != TCL_OK ||
2378            GetBooleanFromObj(interp, objv[21], &declutter) != TCL_OK) {
2379            return TCL_ERROR;
2380        }
2381        osgEarth::Symbology::TextSymbol::Alignment alignment =
2382            ParseTextAlignment(Tcl_GetString(objv[22]));
2383        int xoff, yoff;
2384        if (Tcl_GetIntFromObj(interp, objv[23], &xoff) != TCL_OK ||
2385            Tcl_GetIntFromObj(interp, objv[24], &yoff) != TCL_OK) {
2386            return TCL_ERROR;
2387        }
2388        pixelOffset.x() = (short)xoff;
2389        pixelOffset.y() = (short)yoff;
2390        bool lighting = true;
2391        osgEarth::Symbology::Style style;
2392        osgEarth::Symbology::TextSymbol *ts = style.getOrCreateSymbol<osgEarth::Symbology::TextSymbol>();
2393        ts->halo()->color() = osgEarth::Symbology::Color(bgR, bgG, bgB);
2394        ts->halo()->width() = haloWidth;
2395        ts->fill()->color() = osgEarth::Symbology::Color(fgR, fgG, fgB);
2396        ts->content() = osgEarth::Symbology::StringExpression(content);
2397        if (priority != NULL && strlen(priority) > 0) {
2398            ts->priority() = osgEarth::Symbology::NumericExpression(priority);
2399        }
2400        ts->removeDuplicateLabels() = removeDupes;
2401        //ts->font() = "Arial";
2402        ts->size() = ftSize;
2403        ts->alignment() = alignment;
2404        ts->declutter() = declutter;
2405        ts->pixelOffset() = pixelOffset;
2406        ts->encoding() = osgEarth::Symbology::TextSymbol::ENCODING_UTF8;
2407#ifdef USE_DEPTH_OFFSET
2408        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
2409        rs->depthOffset()->enabled() = true;
2410        rs->depthOffset()->minBias() = 1000;
2411#endif
2412        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
2413        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
2414        geomOpts.styles()->addStyle(style);
2415        geomOpts.enableLighting() = false;
2416        geomOpts.minRange() = 0.f;
2417        geomOpts.maxRange() = FLT_MAX;
2418        if (objc > 25) {
2419            float min, max;
2420            if (GetFloatFromObj(interp, objv[25], &min) != TCL_OK ||
2421                GetFloatFromObj(interp, objv[26], &max) != TCL_OK) {
2422                return TCL_ERROR;
2423            }
2424            geomOpts.minRange() = min;
2425            geomOpts.maxRange() = max;
2426        }
2427        if (driver[0] == 'o' && strcmp(driver, "ogr") == 0) {
2428            osgEarth::Drivers::OGRFeatureOptions opts;
2429            opts.name() = name;
2430            if (osgDB::getLowerCaseFileExtension(url) == "csv") {
2431                url = loadCSVLayer(url.c_str());
2432            }
2433            opts.url() = url;
2434            geomOpts.featureOptions() = opts;
2435        } else if (driver[0] == 't' && strcmp(driver, "tfs") == 0) {
2436            osgEarth::Drivers::TFSFeatureOptions opts;
2437            opts.name() = name;
2438            opts.url() = url;
2439            opts.format() = format;
2440            geomOpts.featureOptions() = opts;
2441        } else if (driver[0] == 'w' && strcmp(driver, "wfs") == 0) {
2442            osgEarth::Drivers::WFSFeatureOptions opts;
2443            opts.name() = name;
2444            opts.url() = url;
2445            opts.outputFormat() = format;
2446            opts.typeName() = typeName;
2447            geomOpts.featureOptions() = opts;
2448        } else {
2449            Tcl_AppendResult(interp, "unknown feature driver \"", driver,
2450                             "\": should be 'ogr', 'tfs', or 'wfs'", (char*)NULL);
2451            return TCL_ERROR;
2452        }
2453        g_renderer->addModelLayer(name, geomOpts, cache, lighting, visible);
2454    } else {
2455        Tcl_AppendResult(interp, "unknown map layer type \"", type,
2456                         "\": should be 'image', 'elevation' or 'model'", (char*)NULL);
2457        return TCL_ERROR;
2458    }
2459    return TCL_OK;
2460}
2461
2462static int
2463MapLayerDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
2464                 Tcl_Obj *const *objv)
2465{
2466    if (objc > 3) {
2467        char *name = Tcl_GetString(objv[3]);
2468        g_renderer->removeImageLayer(name);
2469        g_renderer->removeElevationLayer(name);
2470        g_renderer->removeModelLayer(name);
2471    } else {
2472        g_renderer->clearMap();
2473    }
2474
2475    return TCL_OK;
2476}
2477
2478static int
2479MapLayerMoveOp(ClientData clientData, Tcl_Interp *interp, int objc,
2480               Tcl_Obj *const *objv)
2481{
2482    int pos;
2483    if (Tcl_GetIntFromObj(interp, objv[3], &pos) != TCL_OK) {
2484        return TCL_ERROR;
2485    }
2486    char *name = Tcl_GetString(objv[4]);
2487    if (pos < 0) {
2488        Tcl_AppendResult(interp, "bad layer pos ", pos,
2489                         ": must be positive", (char*)NULL);
2490        return TCL_ERROR;
2491    }
2492    g_renderer->moveImageLayer(name, (unsigned int)pos);
2493    g_renderer->moveElevationLayer(name, (unsigned int)pos);
2494    g_renderer->moveModelLayer(name, (unsigned int)pos);
2495
2496    return TCL_OK;
2497}
2498
2499static int
2500MapLayerOpacityOp(ClientData clientData, Tcl_Interp *interp, int objc,
2501                  Tcl_Obj *const *objv)
2502{
2503    double opacity;
2504    if (Tcl_GetDoubleFromObj(interp, objv[3], &opacity) != TCL_OK) {
2505        return TCL_ERROR;
2506    }
2507    char *name = Tcl_GetString(objv[4]);
2508    if (opacity < 0.0 || opacity > 1.0) {
2509        Tcl_AppendResult(interp, "bad layer opacity ", opacity,
2510                         ": must be [0,1]", (char*)NULL);
2511        return TCL_ERROR;
2512    }
2513    g_renderer->setImageLayerOpacity(name, opacity);
2514    g_renderer->setModelLayerOpacity(name, opacity);
2515
2516    return TCL_OK;
2517}
2518
2519static int
2520MapLayerNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
2521                Tcl_Obj *const *objv)
2522{
2523    std::vector<std::string> layers;
2524    if (objc < 4) {
2525        g_renderer->getImageLayerNames(layers);
2526        g_renderer->getElevationLayerNames(layers);
2527        g_renderer->getModelLayerNames(layers);
2528    } else {
2529        char *type = Tcl_GetString(objv[3]);
2530        if (type[0] == 'i' && strcmp(type, "image") == 0) {
2531            g_renderer->getImageLayerNames(layers);
2532        } else if (type[0] == 'e' && strcmp(type, "elevation") == 0) {
2533            g_renderer->getElevationLayerNames(layers);
2534        } else if (type[0] == 'm' && strcmp(type, "model") == 0) {
2535            g_renderer->getModelLayerNames(layers);
2536        } else {
2537            Tcl_AppendResult(interp, "uknown type \"", type,
2538                         "\": must be image, elevation or model", (char*)NULL);
2539            return TCL_ERROR;
2540        }
2541    }
2542    std::ostringstream oss;
2543    size_t len = 0;
2544    oss << "nv>map names {";
2545    len += 14;
2546    for (size_t i = 0; i < layers.size(); i++) {
2547        oss << "\"" << layers[i] << "\"";
2548        len += 2 + layers[i].length();
2549        if (i < layers.size() - 1) {
2550            oss << " ";
2551            len++;
2552        }
2553    }
2554    oss << "}\n";
2555    len += 2;
2556#ifdef USE_THREADS
2557    queueResponse(oss.str().c_str(), len, Response::VOLATILE);
2558#else
2559    ssize_t bytesWritten = SocketWrite(oss.str().c_str(), len);
2560
2561    if (bytesWritten < 0) {
2562        return TCL_ERROR;
2563    }
2564#endif /*USE_THREADS*/
2565    return TCL_OK;
2566}
2567
2568static int
2569MapLayerVisibleOp(ClientData clientData, Tcl_Interp *interp, int objc,
2570                  Tcl_Obj *const *objv)
2571{
2572    bool visible;
2573    if (GetBooleanFromObj(interp, objv[3], &visible) != TCL_OK) {
2574        return TCL_ERROR;
2575    }
2576
2577    if (objc > 4) {
2578        char *name = Tcl_GetString(objv[4]);
2579        g_renderer->setImageLayerVisibility(name, visible);
2580        g_renderer->setElevationLayerVisibility(name, visible);
2581        g_renderer->setModelLayerVisibility(name, visible);
2582    } else {
2583        std::vector<std::string> layers;
2584        g_renderer->getImageLayerNames(layers);
2585        g_renderer->getElevationLayerNames(layers);
2586        g_renderer->getModelLayerNames(layers);
2587        for (std::vector<std::string>::iterator itr = layers.begin();
2588             itr != layers.end(); ++itr) {
2589            g_renderer->setImageLayerVisibility(itr->c_str(), visible);
2590            g_renderer->setElevationLayerVisibility(itr->c_str(), visible);
2591            g_renderer->setModelLayerVisibility(itr->c_str(), visible);
2592        }
2593    }
2594
2595    return TCL_OK;
2596}
2597
2598static CmdSpec mapLayerOps[] = {
2599    {"add",     1, MapLayerAddOp,       6, 0, "name type driver ?url? ?args?"},
2600    {"delete",  1, MapLayerDeleteOp,    3, 4, "?name?"},
2601    {"move",    1, MapLayerMoveOp,      5, 5, "pos name"},
2602    {"names",   1, MapLayerNamesOp,     3, 4, "?type?"},
2603    {"opacity", 1, MapLayerOpacityOp,   5, 5, "opacity name"},
2604    {"visible", 1, MapLayerVisibleOp,   4, 5, "bool ?name?"},
2605};
2606static int nMapLayerOps = NumCmdSpecs(mapLayerOps);
2607
2608static int
2609MapLayerOp(ClientData clientData, Tcl_Interp *interp, int objc,
2610           Tcl_Obj *const *objv)
2611{
2612    Tcl_ObjCmdProc *proc;
2613
2614    proc = GetOpFromObj(interp, nMapLayerOps, mapLayerOps,
2615                        CMDSPEC_ARG2, objc, objv, 0);
2616    if (proc == NULL) {
2617        return TCL_ERROR;
2618    }
2619    return (*proc) (clientData, interp, objc, objv);
2620}
2621
2622static int
2623MapLoadOp(ClientData clientData, Tcl_Interp *interp, int objc,
2624          Tcl_Obj *const *objv)
2625{
2626    char *opt = Tcl_GetString(objv[2]);
2627    if (opt[0] == 'f' && strcmp(opt, "file") == 0) {
2628        g_renderer->loadEarthFile(Tcl_GetString(objv[3]));
2629    } else if (opt[0] == 'u' && strcmp(opt, "url") == 0) {
2630        std::ostringstream path;
2631        path << "server:" << Tcl_GetString(objv[3]);
2632        g_renderer->loadEarthFile(path.str().c_str());
2633    } else if (opt[0] == 'd' && strcmp(opt, "data") == 0) {
2634        opt = Tcl_GetString(objv[3]);
2635        if (opt[0] != 'f' || strcmp(opt, "follows") != 0) {
2636            return TCL_ERROR;
2637        }
2638        int len;
2639        if (Tcl_GetIntFromObj(interp, objv[4], &len) != TCL_OK) {
2640            return TCL_ERROR;
2641        }
2642        // Read Earth file from socket
2643        char *buf = (char *)malloc((size_t)len);
2644        SocketRead(buf, (size_t)len);
2645        std::ostringstream path;
2646        path << "/tmp/tmp" << getpid() << ".earth";
2647        const char *pathStr = path.str().c_str();
2648        FILE *tmpFile = fopen(pathStr, "w");
2649        fwrite(buf, len, 1, tmpFile);
2650        fclose(tmpFile);
2651        g_renderer->loadEarthFile(pathStr);
2652        unlink(pathStr);
2653        free(buf);
2654    } else {
2655        return TCL_ERROR;
2656    }
2657    return TCL_OK;
2658}
2659
2660static int
2661MapPinAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
2662            Tcl_Obj *const *objv)
2663{
2664    int x, y;
2665    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
2666        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
2667        return TCL_ERROR;
2668    }
2669    char *label = NULL;
2670    if (objc > 5) {
2671        label = Tcl_GetString(objv[5]);
2672    }
2673
2674    if (g_renderer->getMapSRS() == NULL) {
2675        Tcl_AppendResult(interp, "Could not get map SRS", (char*)NULL);
2676        return TCL_ERROR;
2677    }
2678
2679    // Get lat/long
2680    double latitude, longitude;
2681    if (!g_renderer->mouseToLatLong(x, y, &latitude, &longitude)) {
2682        USER_ERROR("Can't add pin here");
2683        return TCL_OK;
2684    }
2685
2686    g_renderer->addPlaceNode(latitude, longitude, label);
2687    return TCL_OK;
2688}
2689
2690static int
2691MapPinDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
2692               Tcl_Obj *const *objv)
2693{
2694    int x, y;
2695    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
2696        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
2697        return TCL_ERROR;
2698    }
2699
2700    g_renderer->deletePlaceNode(x, y);
2701    return TCL_OK;
2702}
2703
2704static int
2705MapPinHoverOp(ClientData clientData, Tcl_Interp *interp, int objc,
2706              Tcl_Obj *const *objv)
2707{
2708    int x, y;
2709    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
2710        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
2711        return TCL_ERROR;
2712    }
2713
2714    g_renderer->hoverPlaceNode(x, y);
2715    return TCL_OK;
2716}
2717
2718static CmdSpec mapPinOps[] = {
2719    {"add",     1, MapPinAddOp,     5, 6, "x y ?label?"},
2720    {"delete",  1, MapPinDeleteOp,  5, 5, "x y"},
2721    {"hover",   1, MapPinHoverOp,   5, 5, "x y"},
2722};
2723static int nMapPinOps = NumCmdSpecs(mapPinOps);
2724
2725static int
2726MapPinOp(ClientData clientData, Tcl_Interp *interp, int objc,
2727           Tcl_Obj *const *objv)
2728{
2729    Tcl_ObjCmdProc *proc;
2730
2731    proc = GetOpFromObj(interp, nMapPinOps, mapPinOps,
2732                        CMDSPEC_ARG2, objc, objv, 0);
2733    if (proc == NULL) {
2734        return TCL_ERROR;
2735    }
2736    return (*proc) (clientData, interp, objc, objv);
2737}
2738
2739static int
2740MapPositionDisplayOp(ClientData clientData, Tcl_Interp *interp, int objc,
2741                     Tcl_Obj *const *objv)
2742{
2743    bool state;
2744    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
2745        return TCL_ERROR;
2746    }
2747    Renderer::CoordinateDisplayType type = Renderer::COORDS_LATLONG_DECIMAL_DEGREES;
2748    if (state && objc > 3) {
2749        const char *str = Tcl_GetString(objv[3]);
2750        if (str[0] == 'l' && strcmp(str, "latlong_decimal_degrees") == 0) {
2751            type = Renderer::COORDS_LATLONG_DECIMAL_DEGREES;
2752        } else if (str[0] == 'l' && strcmp(str, "latlong_degrees_decimal_minutes") == 0) {
2753            type = Renderer::COORDS_LATLONG_DEGREES_DECIMAL_MINUTES;
2754        } else if (str[0] == 'l' && strcmp(str, "latlong_degrees_minutes_seconds") == 0) {
2755            type = Renderer::COORDS_LATLONG_DEGREES_MINUTES_SECONDS;
2756        } else if (str[0] == 'm' && strcmp(str, "mgrs") == 0) {
2757            type = Renderer::COORDS_MGRS;
2758        } else {
2759            Tcl_AppendResult(interp, "invalid type: \"", str,
2760                             "\": should be 'latlong_decimal_degrees', 'latlong_degrees_decimal_minutes', 'latlong_degrees_minutes_seconds', or 'mgrs'",
2761                             (char*)NULL);
2762            return TCL_ERROR;
2763        }
2764    }
2765    if (state && objc > 4) {
2766        int precision;
2767        if (Tcl_GetIntFromObj(interp, objv[4], &precision) != TCL_OK) {
2768            return TCL_ERROR;
2769        }
2770        g_renderer->setCoordinateReadout(state, type, precision);
2771    } else {
2772        g_renderer->setCoordinateReadout(state, type);
2773    }
2774
2775    return TCL_OK;
2776}
2777
2778static int
2779MapResetOp(ClientData clientData, Tcl_Interp *interp, int objc,
2780           Tcl_Obj *const *objv)
2781{
2782    char *typeStr = Tcl_GetString(objv[2]);
2783    osgEarth::MapOptions::CoordinateSystemType type;
2784    if (typeStr[0] == 'g' && strcmp(typeStr, "geocentric") == 0) {
2785        type = osgEarth::MapOptions::CSTYPE_GEOCENTRIC;
2786    } else if (typeStr[0] == 'g' && strcmp(typeStr, "geocentric_cube") == 0) {
2787        type = osgEarth::MapOptions::CSTYPE_GEOCENTRIC_CUBE;
2788    } else if (typeStr[0] == 'p' && strcmp(typeStr, "projected") == 0) {
2789        type = osgEarth::MapOptions::CSTYPE_PROJECTED;
2790    } else {
2791        Tcl_AppendResult(interp, "bad map type \"", typeStr,
2792                         "\": must be geocentric or projected", (char*)NULL);
2793        return TCL_ERROR;
2794    }
2795    float color[3];
2796    if (GetFloatFromObj(interp, objv[3], &color[0]) != TCL_OK ||
2797        GetFloatFromObj(interp, objv[4], &color[1]) != TCL_OK ||
2798        GetFloatFromObj(interp, objv[5], &color[2]) != TCL_OK) {
2799        return TCL_ERROR;
2800    }
2801
2802    osg::Vec4f bgColor(color[0],color[1],color[2],1);
2803    if (type == osgEarth::MapOptions::CSTYPE_PROJECTED) {
2804        if (objc < 7) {
2805            Tcl_AppendResult(interp, "wrong # arguments: profile required for projected maps", (char*)NULL);
2806            return TCL_ERROR;
2807        }
2808        char *profile = Tcl_GetString(objv[6]);
2809        if (objc > 7) {
2810            if (objc < 11) {
2811                Tcl_AppendResult(interp, "wrong # arguments: 4 bounds arguments required", (char*)NULL);
2812                return TCL_ERROR;
2813            }
2814            double bounds[4];
2815            for (int i = 0; i < 4; i++) {
2816                if (Tcl_GetDoubleFromObj(interp, objv[7+i], &bounds[i]) != TCL_OK) {
2817                    return TCL_ERROR;
2818                }
2819            }
2820            // Check if min > max
2821            if (bounds[0] > bounds[2] ||
2822                bounds[1] > bounds[3]) {
2823                Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
2824                return TCL_ERROR;
2825            }
2826            // Note: plate-carre generates same SRS as others, but with
2827            // _is_plate_carre flag set
2828            // In map profile, _is_plate_carre is forced on for
2829            // geographic+projected SRS
2830            if (strcmp(profile, "geodetic") == 0 ||
2831                strcmp(profile, "epsg:4326") == 0 ||
2832                strcmp(profile, "wgs84") == 0 ||
2833                strcmp(profile, "plate-carre") == 0 ||
2834                strcmp(profile, "plate-carree") == 0) {
2835                if (bounds[0] < -180. || bounds[0] > 180. ||
2836                    bounds[2] < -180. || bounds[2] > 180. ||
2837                    bounds[1] < -90. || bounds[1] > 90. ||
2838                    bounds[3] < -90. || bounds[3] > 90.) {
2839                    Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
2840                    return TCL_ERROR;
2841                }
2842                // As of osgearth 2.7, these are not permitted as map profiles.
2843                // Use epsg:32663 instead
2844                Tcl_AppendResult(interp, "Invalid profile: can't use geographic coordinate system as projection.  Consider using an equirectangular projection (epsg:32663) instead.", (char*)NULL);
2845                return TCL_ERROR;
2846            } else if (strcmp(profile, "spherical-mercator") == 0 ||
2847                       strcmp(profile, "epsg:900913") == 0 ||
2848                       strcmp(profile, "epsg:3785") == 0 ||
2849                       strcmp(profile, "epsg:3857") == 0 ||
2850                       strcmp(profile, "epsg:102113") == 0) {
2851                for (int i = 0; i < 4; i++) {
2852                    if (bounds[i] < -20037508.34278925 || bounds[i] > 20037508.34278925) {
2853                        Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
2854                        return TCL_ERROR;
2855                    }
2856                }
2857            } else if (strcmp(profile, "epsg:32662") == 0 ||
2858                       strcmp(profile, "epsg:32663") == 0) {
2859                // epsg:32662 is deprecated: spherical method applied to ellipsoid WGS84
2860                // Equirectangular projection (WGS84/World Equidistant Cylindrical)
2861                if (bounds[0] < -20037508.34278925 || bounds[0] > 20037508.34278925 ||
2862                    bounds[2] < -20037508.34278925 || bounds[2] > 20037508.34278925 ||
2863                    bounds[1] < -10018754.17139463 || bounds[1] > 10018754.17139463 ||
2864                    bounds[3] < -10018754.17139463 || bounds[3] > 10018754.17139463) {
2865                    Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
2866                    return TCL_ERROR;
2867                }
2868            }
2869            g_renderer->resetMap(type, bgColor, profile, bounds);
2870        } else {
2871            // If no bounds are given, this must be a named profile with implicit bounds
2872            if (osgEarth::Registry::instance()->getNamedProfile(profile) == NULL) {
2873                Tcl_AppendResult(interp, "bad named profile \"", profile,
2874                                 "\": must be e.g. 'global-geodetic', 'global-mercator'...", (char*)NULL);
2875                return TCL_ERROR;
2876            }
2877            g_renderer->resetMap(type, bgColor, profile);
2878        }
2879    } else {
2880        // No profile required for geocentric (3D) maps
2881        g_renderer->resetMap(type, bgColor);
2882    }
2883
2884    return TCL_OK;
2885}
2886
2887static int
2888MapScaleBarOp(ClientData clientData, Tcl_Interp *interp, int objc,
2889              Tcl_Obj *const *objv)
2890{
2891    bool state;
2892    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
2893        return TCL_ERROR;
2894    }
2895    g_renderer->setScaleBar(state);
2896    if (state && objc > 3) {
2897        const char *unitStr = Tcl_GetString(objv[3]);
2898        ScaleBarUnits units;
2899        if (unitStr[0] == 'm' && strcmp(unitStr, "meters") == 0) {
2900            units = UNITS_METERS;
2901        } else if (unitStr[0] == 'f' && strcmp(unitStr, "feet") == 0) {
2902            units = UNITS_INTL_FEET;
2903        } else if (unitStr[0] == 'u' && strcmp(unitStr, "us_survey_feet") == 0) {
2904            units = UNITS_US_SURVEY_FEET;
2905        } else if (unitStr[0] == 'n' && strcmp(unitStr, "nautical_miles") == 0) {
2906            units = UNITS_NAUTICAL_MILES;
2907        } else {
2908            Tcl_AppendResult(interp, "bad units \"", unitStr,
2909                             "\": must be 'meters', 'feet', 'us_survey_feet' or 'nautical_miles'", (char*)NULL);
2910            return TCL_ERROR;
2911        }
2912        g_renderer->setScaleBarUnits(units);
2913    }
2914    return TCL_OK;
2915}
2916
2917static int
2918MapSetPositionOp(ClientData clientData, Tcl_Interp *interp, int objc,
2919            Tcl_Obj *const *objv)
2920{
2921    if (objc < 3) {
2922        g_renderer->clearReadout();
2923    } else {
2924        int x, y;
2925        if (Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK ||
2926            Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK) {
2927            return TCL_ERROR;
2928        }
2929        g_renderer->setReadout(x, y);
2930    }
2931    return TCL_OK;
2932}
2933
2934static int
2935MapTerrainAmbientOp(ClientData clientData, Tcl_Interp *interp, int objc,
2936                    Tcl_Obj *const *objv)
2937{
2938    float ambient;
2939    if (GetFloatFromObj(interp, objv[3], &ambient) != TCL_OK) {
2940        return TCL_ERROR;
2941    }
2942    g_renderer->setSkyAmbient(ambient);
2943    return TCL_OK;
2944}
2945
2946static int
2947MapTerrainColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
2948                  Tcl_Obj *const *objv)
2949{
2950    float color[3];
2951    if (GetFloatFromObj(interp, objv[3], &color[0]) != TCL_OK ||
2952        GetFloatFromObj(interp, objv[4], &color[1]) != TCL_OK ||
2953        GetFloatFromObj(interp, objv[5], &color[2]) != TCL_OK) {
2954        return TCL_ERROR;
2955    }
2956    g_renderer->setTerrainColor(osg::Vec4f(color[0],color[1],color[2],1));
2957    return TCL_OK;
2958}
2959
2960static int
2961MapTerrainEdgesOp(ClientData clientData, Tcl_Interp *interp, int objc,
2962                  Tcl_Obj *const *objv)
2963{
2964    bool state;
2965    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
2966        return TCL_ERROR;
2967    }
2968    g_renderer->setTerrainEdges(state);
2969    return TCL_OK;
2970}
2971
2972static int
2973MapTerrainLightingOp(ClientData clientData, Tcl_Interp *interp, int objc,
2974                     Tcl_Obj *const *objv)
2975{
2976    bool state;
2977    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
2978        return TCL_ERROR;
2979    }
2980
2981    g_renderer->setTerrainLighting(state);
2982    return TCL_OK;
2983}
2984
2985static int
2986MapTerrainLineColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
2987                      Tcl_Obj *const *objv)
2988{
2989    float color[3];
2990    if (GetFloatFromObj(interp, objv[3], &color[0]) != TCL_OK ||
2991        GetFloatFromObj(interp, objv[4], &color[1]) != TCL_OK ||
2992        GetFloatFromObj(interp, objv[5], &color[2]) != TCL_OK) {
2993        return TCL_ERROR;
2994    }
2995    g_renderer->setTerrainLineColor(osg::Vec4f(color[0], color[1], color[2],1));
2996    return TCL_OK;
2997}
2998
2999static int
3000MapTerrainLineWidthOp(ClientData clientData, Tcl_Interp *interp, int objc,
3001                      Tcl_Obj *const *objv)
3002{
3003    float width;
3004    if (GetFloatFromObj(interp, objv[3], &width) != TCL_OK) {
3005        return TCL_ERROR;
3006    }
3007    g_renderer->setTerrainLineWidth(width);
3008    return TCL_OK;
3009}
3010
3011static int
3012MapTerrainVertScaleOp(ClientData clientData, Tcl_Interp *interp, int objc,
3013                      Tcl_Obj *const *objv)
3014{
3015    double scale;
3016    if (Tcl_GetDoubleFromObj(interp, objv[3], &scale) != TCL_OK) {
3017        return TCL_ERROR;
3018    }
3019
3020    g_renderer->setTerrainVerticalScale(scale);
3021    return TCL_OK;
3022}
3023
3024static int
3025MapTerrainWireframeOp(ClientData clientData, Tcl_Interp *interp, int objc,
3026                      Tcl_Obj *const *objv)
3027{
3028    bool state;
3029    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
3030        return TCL_ERROR;
3031    }
3032
3033    g_renderer->setTerrainWireframe(state);
3034    return TCL_OK;
3035}
3036
3037static CmdSpec mapTerrainOps[] = {
3038    {"ambient",   1, MapTerrainAmbientOp,   4, 4, "val"},
3039    {"color",     1, MapTerrainColorOp,     6, 6, "r g b"},
3040    {"edges",     1, MapTerrainEdgesOp,     4, 4, "bool"},
3041    {"lighting",  2, MapTerrainLightingOp,  4, 4, "bool"},
3042    {"linecolor", 5, MapTerrainLineColorOp, 6, 6, "r g b"},
3043    {"linewidth", 5, MapTerrainLineWidthOp, 4, 4, "val"},
3044    {"vertscale", 1, MapTerrainVertScaleOp, 4, 4, "val"},
3045    {"wireframe", 1, MapTerrainWireframeOp, 4, 4, "bool"},
3046};
3047static int nMapTerrainOps = NumCmdSpecs(mapTerrainOps);
3048
3049static int
3050MapTerrainOp(ClientData clientData, Tcl_Interp *interp, int objc,
3051           Tcl_Obj *const *objv)
3052{
3053    Tcl_ObjCmdProc *proc;
3054
3055    proc = GetOpFromObj(interp, nMapTerrainOps, mapTerrainOps,
3056                        CMDSPEC_ARG2, objc, objv, 0);
3057    if (proc == NULL) {
3058        return TCL_ERROR;
3059    }
3060    return (*proc) (clientData, interp, objc, objv);
3061}
3062
3063static int
3064MapTimeOp(ClientData clientData, Tcl_Interp *interp, int objc,
3065          Tcl_Obj *const *objv)
3066{
3067    osgEarth::DateTime now;
3068    int year, month, day;
3069    double hours;
3070    year = now.year();
3071    month = now.month();
3072    day = now.day();
3073    hours = now.hours();
3074    if (objc > 2) {
3075        if (Tcl_GetDoubleFromObj(interp, objv[2], &hours) != TCL_OK) {
3076            return TCL_ERROR;
3077        }
3078    }
3079    if (objc > 3) {
3080        if (Tcl_GetIntFromObj(interp, objv[3], &day) != TCL_OK) {
3081            return TCL_ERROR;
3082        }
3083    }
3084    if (objc > 4) {
3085        if (Tcl_GetIntFromObj(interp, objv[4], &month) != TCL_OK) {
3086            return TCL_ERROR;
3087        }
3088    }
3089    if (objc > 5) {
3090        if (Tcl_GetIntFromObj(interp, objv[5], &year) != TCL_OK) {
3091            return TCL_ERROR;
3092        }
3093    }
3094
3095    g_renderer->setEphemerisTime(year, month, day, hours);
3096    return TCL_OK;
3097}
3098
3099static CmdSpec mapOps[] = {
3100    {"attrib",   1, MapAttributionOp,     3, 3, "string"},
3101    {"box",      1, MapBoxOp,             3, 0, "op ?params..."},
3102    {"coords",   1, MapCoordsOp,          4, 6, "token coords ?srs? ?verticalDatum?"},
3103    {"grid",     1, MapGraticuleOp,       3, 4, "bool ?type?"},
3104    {"layer",    2, MapLayerOp,           3, 0, "op ?params...?"},
3105    {"load",     2, MapLoadOp,            4, 5, "options"},
3106    {"pin",      2, MapPinOp,             3, 0, "op ?params...?"},
3107    {"posdisp",  2, MapPositionDisplayOp, 3, 5, "bool ?format? ?precision?"},
3108    {"reset",    1, MapResetOp,           6, 11, "type r g b ?profile xmin ymin xmax ymax?"},
3109    {"scalebar", 1, MapScaleBarOp,        3, 4, "bool ?units?"},
3110    {"setpos",   1, MapSetPositionOp,     2, 4, "x y"},
3111    {"terrain",  1, MapTerrainOp,         3, 0, "op ?params...?"},
3112    {"time",     1, MapTimeOp,            2, 6, "?hours? ?day? ?month? ?year?"},
3113};
3114static int nMapOps = NumCmdSpecs(mapOps);
3115
3116static int
3117MapCmd(ClientData clientData, Tcl_Interp *interp, int objc,
3118       Tcl_Obj *const *objv)
3119{
3120    Tcl_ObjCmdProc *proc;
3121
3122    proc = GetOpFromObj(interp, nMapOps, mapOps,
3123                        CMDSPEC_ARG1, objc, objv, 0);
3124    if (proc == NULL) {
3125        return TCL_ERROR;
3126    }
3127    return (*proc) (clientData, interp, objc, objv);
3128}
3129
3130static int
3131MouseClickOp(ClientData clientData, Tcl_Interp *interp, int objc,
3132             Tcl_Obj *const *objv)
3133{
3134    int button;
3135    double x, y;
3136
3137    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
3138        return TCL_ERROR;
3139    }
3140    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
3141        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
3142        return TCL_ERROR;
3143    }
3144
3145    g_renderer->mouseClick(button, x, y);
3146    return TCL_OK;
3147}
3148
3149static int
3150MouseDoubleClickOp(ClientData clientData, Tcl_Interp *interp, int objc,
3151                   Tcl_Obj *const *objv)
3152{
3153    int button;
3154    double x, y;
3155
3156    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
3157        return TCL_ERROR;
3158    }
3159    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
3160        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
3161        return TCL_ERROR;
3162    }
3163
3164    g_renderer->mouseDoubleClick(button, x, y);
3165    return TCL_OK;
3166}
3167
3168static int
3169MouseDragOp(ClientData clientData, Tcl_Interp *interp, int objc,
3170            Tcl_Obj *const *objv)
3171{
3172    int button;
3173    double x, y;
3174
3175    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
3176        return TCL_ERROR;
3177    }
3178    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
3179        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
3180        return TCL_ERROR;
3181    }
3182
3183    g_renderer->mouseDrag(button, x, y);
3184    return TCL_OK;
3185}
3186
3187static int
3188MouseMotionOp(ClientData clientData, Tcl_Interp *interp, int objc,
3189              Tcl_Obj *const *objv)
3190{
3191    double x, y;
3192
3193    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
3194        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
3195        return TCL_ERROR;
3196    }
3197
3198    g_renderer->mouseMotion(x, y);
3199    return TCL_OK;
3200}
3201
3202static int
3203MouseReleaseOp(ClientData clientData, Tcl_Interp *interp, int objc,
3204               Tcl_Obj *const *objv)
3205{
3206    int button;
3207    double x, y;
3208
3209    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
3210        return TCL_ERROR;
3211    }
3212    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
3213        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
3214        return TCL_ERROR;
3215    }
3216
3217    g_renderer->mouseRelease(button, x, y);
3218    return TCL_OK;
3219}
3220
3221static int
3222MouseScrollOp(ClientData clientData, Tcl_Interp *interp, int objc,
3223              Tcl_Obj *const *objv)
3224{
3225    int direction;
3226
3227    if (Tcl_GetIntFromObj(interp, objv[2], &direction) != TCL_OK) {
3228        return TCL_ERROR;
3229    }
3230
3231    g_renderer->mouseScroll(direction);
3232    return TCL_OK;
3233}
3234
3235static CmdSpec mouseOps[] = {
3236    {"click",    1, MouseClickOp,       5, 5, "button x y"},
3237    {"dblclick", 2, MouseDoubleClickOp, 5, 5, "button x y"},
3238    {"drag",     2, MouseDragOp,        5, 5, "button x y"},
3239    {"motion",   1, MouseMotionOp,      4, 4, "x y"},
3240    {"release",  1, MouseReleaseOp,     5, 5, "button x y"},
3241    {"scroll",   1, MouseScrollOp,      3, 3, "direction"},
3242};
3243static int nMouseOps = NumCmdSpecs(mouseOps);
3244
3245static int
3246MouseCmd(ClientData clientData, Tcl_Interp *interp, int objc,
3247         Tcl_Obj *const *objv)
3248{
3249    Tcl_ObjCmdProc *proc;
3250
3251    proc = GetOpFromObj(interp, nMouseOps, mouseOps,
3252                        CMDSPEC_ARG1, objc, objv, 0);
3253    if (proc == NULL) {
3254        return TCL_ERROR;
3255    }
3256    return (*proc) (clientData, interp, objc, objv);
3257}
3258
3259static int
3260PlacardConfigOp(ClientData clientData, Tcl_Interp *interp, int objc,
3261                Tcl_Obj *const *objv)
3262{
3263    int namec;
3264    Tcl_Obj **namev = NULL;
3265    const char *layerName = Tcl_GetString(objv[5]);
3266
3267    if (Tcl_ListObjGetElements(interp, objv[2], &namec, &namev) != TCL_OK) {
3268        return TCL_ERROR;
3269    }
3270    if (namec % 2 != 0) {
3271        Tcl_AppendResult(interp, "invalid attribute list",
3272                         (char *)NULL);
3273        return TCL_ERROR;
3274    }
3275    Placard placardConf;
3276    for (int i = 0; i < namec; i+=2) {
3277        std::string name(Tcl_GetString(namev[i]));
3278        std::string label(Tcl_GetString(namev[i+1]));
3279        placardConf.addEntry(name, label);
3280    }
3281    osgEarth::Config styleConf("style", Tcl_GetString(objv[3]));
3282    styleConf.add("type", "text/css");
3283    placardConf.setStyle(osgEarth::Symbology::Style(styleConf));
3284
3285    float padding;
3286    if (GetFloatFromObj(interp, objv[4], &padding) != TCL_OK) {
3287        return TCL_ERROR;
3288    }
3289    placardConf.setPadding(padding);
3290    g_renderer->setPlacardConfig(placardConf, layerName);
3291    return TCL_OK;
3292}
3293
3294static int
3295PlacardEnableOp(ClientData clientData, Tcl_Interp *interp, int objc,
3296                Tcl_Obj *const *objv)
3297{
3298    const char *layerName = Tcl_GetString(objv[3]);
3299    bool enable;
3300    if (GetBooleanFromObj(interp, objv[2], &enable) != TCL_OK) {
3301        return TCL_ERROR;
3302    }
3303    g_renderer->enablePlacard(layerName, enable);
3304    return TCL_OK;
3305}
3306
3307static CmdSpec placardOps[] = {
3308    {"config", 1, PlacardConfigOp, 6, 6, "attrlist style padding layerName"},
3309    {"enable", 1, PlacardEnableOp, 4, 4, "bool layerName"},
3310};
3311static int nPlacardOps = NumCmdSpecs(placardOps);
3312
3313static int
3314PlacardCmd(ClientData clientData, Tcl_Interp *interp, int objc,
3315          Tcl_Obj *const *objv)
3316{
3317    Tcl_ObjCmdProc *proc;
3318
3319    proc = GetOpFromObj(interp, nPlacardOps, placardOps,
3320                        CMDSPEC_ARG1, objc, objv, 0);
3321    if (proc == NULL) {
3322        return TCL_ERROR;
3323    }
3324    return (*proc) (clientData, interp, objc, objv);
3325}
3326
3327static int
3328RendererRenderOp(ClientData clientData, Tcl_Interp *interp, int objc,
3329                 Tcl_Obj *const *objv)
3330{
3331    g_renderer->eventuallyRender();
3332    return TCL_OK;
3333}
3334
3335static CmdSpec rendererOps[] = {
3336    {"render",     1, RendererRenderOp, 2, 2, ""},
3337};
3338static int nRendererOps = NumCmdSpecs(rendererOps);
3339
3340static int
3341RendererCmd(ClientData clientData, Tcl_Interp *interp, int objc,
3342            Tcl_Obj *const *objv)
3343{
3344    Tcl_ObjCmdProc *proc;
3345
3346    proc = GetOpFromObj(interp, nRendererOps, rendererOps,
3347                        CMDSPEC_ARG1, objc, objv, 0);
3348    if (proc == NULL) {
3349        return TCL_ERROR;
3350    }
3351    return (*proc) (clientData, interp, objc, objv);
3352}
3353
3354static int
3355ScreenBgColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
3356                Tcl_Obj *const *objv)
3357{
3358    float color[3];
3359
3360    if (GetFloatFromObj(interp, objv[2], &color[0]) != TCL_OK ||
3361        GetFloatFromObj(interp, objv[3], &color[1]) != TCL_OK ||
3362        GetFloatFromObj(interp, objv[4], &color[2]) != TCL_OK) {
3363        return TCL_ERROR;
3364    }
3365
3366    g_renderer->setBackgroundColor(color);
3367    return TCL_OK;
3368}
3369
3370static int
3371ScreenCoordsOp(ClientData clientData, Tcl_Interp *interp, int objc,
3372               Tcl_Obj *const *objv)
3373{
3374    int tokenLength;
3375    const char *token = Tcl_GetStringFromObj(objv[2], &tokenLength);
3376    int numCoords;
3377    Tcl_Obj **coords;
3378    if (Tcl_ListObjGetElements(interp, objv[3], &numCoords, &coords) != TCL_OK) {
3379        return TCL_ERROR;
3380    }
3381    if (numCoords == 0) {
3382        Tcl_AppendResult(interp, "no x,y,z coordinates in list", (char *)NULL);
3383        return TCL_ERROR;
3384    }
3385    if (numCoords % 3 != 0) {
3386        Tcl_AppendResult(interp, "invalid number of coordinates in list",
3387                         (char *)NULL);
3388        return TCL_ERROR;
3389    }
3390
3391    const osgEarth::SpatialReference *srs = NULL;
3392    if (objc < 5) {
3393        srs = g_renderer->getMapSRS();
3394        if (srs == NULL) {
3395            Tcl_AppendResult(interp, "Could not determine map SRS", (char*)NULL);
3396            return TCL_ERROR;
3397        }
3398    } else {
3399        std::string srsInit(Tcl_GetString(objv[4]));
3400        std::string verticalDatum;
3401        if (objc > 5) {
3402            verticalDatum = Tcl_GetString(objv[5]);
3403        }
3404        srs = osgEarth::SpatialReference::get(srsInit, verticalDatum);
3405        if (srs == NULL) {
3406            Tcl_AppendResult(interp, "bad SRS \"", srsInit.c_str(), "\"", (char*)NULL);
3407            return TCL_ERROR;
3408        }
3409    }
3410    Tcl_Obj *listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
3411    std::vector<osg::Vec3d> coordVec;
3412    for (int i = 0; i < numCoords; i += 3) {
3413        double x, y, z;
3414        if (Tcl_GetDoubleFromObj(interp, coords[i], &x) != TCL_OK ||
3415            Tcl_GetDoubleFromObj(interp, coords[i+1], &y) != TCL_OK ||
3416            Tcl_GetDoubleFromObj(interp, coords[i+2], &z) != TCL_OK) {
3417            return TCL_ERROR;
3418        }
3419        // ALTMODE_RELATIVE is height above terrain, ALTMODE_ABSOLUTE means
3420        // relative to the vertical datum
3421        osgEarth::GeoPoint mapPoint(srs, x, y, z, osgEarth::ALTMODE_ABSOLUTE);
3422        osg::Vec3d world;
3423        if (g_renderer->getWorldCoords(mapPoint, &world)) {
3424            coordVec.push_back(world);
3425        } else {
3426            coordVec.push_back(osg::Vec3d(std::numeric_limits<double>::quiet_NaN(),
3427                                          std::numeric_limits<double>::quiet_NaN(),
3428                                          std::numeric_limits<double>::quiet_NaN()));
3429        }
3430    }
3431    g_renderer->worldToScreen(coordVec);
3432    for (std::vector<osg::Vec3d>::iterator itr = coordVec.begin();
3433         itr != coordVec.end(); ++itr) {
3434        Tcl_Obj *objPtr = Tcl_NewDoubleObj(itr->x());
3435        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
3436        objPtr = Tcl_NewDoubleObj(itr->y());
3437        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
3438        objPtr = Tcl_NewDoubleObj(itr->z());
3439        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
3440    }
3441    // send coords to client
3442    int listLength;
3443    const char *string = Tcl_GetStringFromObj(listObjPtr, &listLength);
3444    size_t length = listLength + tokenLength + 22;
3445    char *mesg = new char[length];
3446    length = snprintf(mesg, length, "nv>screen coords %s {%s}\n", token, string);
3447    Tcl_DecrRefCount(listObjPtr);
3448    queueResponse(mesg, length, Response::VOLATILE);
3449    delete [] mesg;
3450    return TCL_OK;
3451}
3452
3453static int
3454ScreenSizeOp(ClientData clientData, Tcl_Interp *interp, int objc,
3455             Tcl_Obj *const *objv)
3456{
3457    int width, height;
3458
3459    if (Tcl_GetIntFromObj(interp, objv[2], &width) != TCL_OK ||
3460        Tcl_GetIntFromObj(interp, objv[3], &height) != TCL_OK) {
3461        return TCL_ERROR;
3462    }
3463
3464    g_renderer->setWindowSize(width, height);
3465    return TCL_OK;
3466}
3467
3468static CmdSpec screenOps[] = {
3469    {"bgcolor", 1, ScreenBgColorOp, 5, 5, "r g b"},
3470    {"coords",  1, ScreenCoordsOp, 4, 6, "token coords ?srs? ?verticalDatum?"},
3471    {"size",    1, ScreenSizeOp, 4, 4, "width height"}
3472};
3473static int nScreenOps = NumCmdSpecs(screenOps);
3474
3475static int
3476ScreenCmd(ClientData clientData, Tcl_Interp *interp, int objc,
3477          Tcl_Obj *const *objv)
3478{
3479    Tcl_ObjCmdProc *proc;
3480
3481    proc = GetOpFromObj(interp, nScreenOps, screenOps,
3482                        CMDSPEC_ARG1, objc, objv, 0);
3483    if (proc == NULL) {
3484        return TCL_ERROR;
3485    }
3486    return (*proc) (clientData, interp, objc, objv);
3487}
3488
3489static int
3490SelectAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
3491            Tcl_Obj *const *objv)
3492{
3493    int numIDs;
3494    Tcl_Obj **ids;
3495    if (Tcl_ListObjGetElements(interp, objv[2], &numIDs, &ids) != TCL_OK) {
3496        return TCL_ERROR;
3497    }
3498    if (numIDs == 0) {
3499        Tcl_AppendResult(interp, "no IDs in list", (char *)NULL);
3500        return TCL_ERROR;
3501    }
3502    std::vector<unsigned long> featureIDs;
3503    for (int i = 0; i < numIDs; i++) {
3504        long id;
3505        if (Tcl_GetLongFromObj(interp, ids[i], &id) != TCL_OK) {
3506            return TCL_ERROR;
3507        }
3508        featureIDs.push_back((unsigned long)id);
3509    }
3510    const char *layerName = Tcl_GetString(objv[3]);
3511
3512    g_renderer->selectFeatures(featureIDs, layerName, false);
3513    return TCL_OK;
3514}
3515
3516static int
3517SelectClearOp(ClientData clientData, Tcl_Interp *interp, int objc,
3518              Tcl_Obj *const *objv)
3519{
3520    g_renderer->clearSelection();
3521    return TCL_OK;
3522}
3523
3524static int
3525SelectDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
3526               Tcl_Obj *const *objv)
3527{
3528    int numIDs;
3529    Tcl_Obj **ids;
3530    if (Tcl_ListObjGetElements(interp, objv[2], &numIDs, &ids) != TCL_OK) {
3531        return TCL_ERROR;
3532    }
3533    if (numIDs == 0) {
3534        Tcl_AppendResult(interp, "no IDs in list", (char *)NULL);
3535        return TCL_ERROR;
3536    }
3537    std::vector<unsigned long> featureIDs;
3538    for (int i = 0; i < numIDs; i++) {
3539        long id;
3540        if (Tcl_GetLongFromObj(interp, ids[i], &id) != TCL_OK) {
3541            return TCL_ERROR;
3542        }
3543        featureIDs.push_back((unsigned long)id);
3544    }
3545    const char *layerName = Tcl_GetString(objv[3]);
3546
3547    g_renderer->deselectFeatures(featureIDs, layerName);
3548    return TCL_OK;
3549}
3550
3551static int
3552SelectFeatureOp(ClientData clientData, Tcl_Interp *interp, int objc,
3553                Tcl_Obj *const *objv)
3554{
3555    int numIDs;
3556    Tcl_Obj **ids;
3557    if (Tcl_ListObjGetElements(interp, objv[2], &numIDs, &ids) != TCL_OK) {
3558        return TCL_ERROR;
3559    }
3560    if (numIDs == 0) {
3561        Tcl_AppendResult(interp, "no IDs in list", (char *)NULL);
3562        return TCL_ERROR;
3563    }
3564    std::vector<unsigned long> featureIDs;
3565    for (int i = 0; i < numIDs; i++) {
3566        long id;
3567        if (Tcl_GetLongFromObj(interp, ids[i], &id) != TCL_OK) {
3568            return TCL_ERROR;
3569        }
3570        featureIDs.push_back((unsigned long)id);
3571    }
3572    const char *layerName = Tcl_GetString(objv[3]);
3573
3574    g_renderer->selectFeatures(featureIDs, layerName);
3575    return TCL_OK;
3576}
3577
3578static int
3579SelectModeOp(ClientData clientData, Tcl_Interp *interp, int objc,
3580             Tcl_Obj *const *objv)
3581{
3582    const char *modeStr = Tcl_GetString(objv[2]);
3583    // Parse mode string
3584    Renderer::SelectMode mode = Renderer::SELECT_OFF;
3585    if (modeStr[0] == 'o' && strcmp(modeStr, "off") == 0) {
3586        mode = Renderer::SELECT_OFF;
3587    } else if (modeStr[0] == 'o' && strcmp(modeStr, "on") == 0) {
3588        mode = Renderer::SELECT_ON;
3589    } else {
3590        Tcl_AppendResult(interp, "bad select mode \"", modeStr,
3591                             "\": must be 'on' or 'off'", (char*)NULL);
3592        return TCL_ERROR;
3593    }
3594    g_renderer->setSelectMode(mode);
3595    return TCL_OK;
3596}
3597
3598static CmdSpec selectOps[] = {
3599    {"clear",   1, SelectClearOp,   2, 2, ""},
3600    {"fadd",    2, SelectAddOp,     4, 4, "idlist layerName"},
3601    {"fdelete", 2, SelectDeleteOp,  4, 4, "idlist layerName"},
3602    {"feature", 2, SelectFeatureOp, 4, 4, "idlist layerName"},
3603    {"mode",    1, SelectModeOp,    3, 3, "mode"},
3604};
3605static int nSelectOps = NumCmdSpecs(selectOps);
3606
3607static int
3608SelectCmd(ClientData clientData, Tcl_Interp *interp, int objc,
3609          Tcl_Obj *const *objv)
3610{
3611    Tcl_ObjCmdProc *proc;
3612
3613    proc = GetOpFromObj(interp, nSelectOps, selectOps,
3614                        CMDSPEC_ARG1, objc, objv, 0);
3615    if (proc == NULL) {
3616        return TCL_ERROR;
3617    }
3618    return (*proc) (clientData, interp, objc, objv);
3619}
3620
3621#ifdef USE_READ_THREAD
3622int
3623GeoVis::queueCommands(Tcl_Interp *interp,
3624                      ClientData clientData,
3625                      ReadBuffer *inBufPtr)
3626{
3627    Tcl_DString commandString;
3628    Tcl_DStringInit(&commandString);
3629    fd_set readFds;
3630
3631    FD_ZERO(&readFds);
3632    FD_SET(inBufPtr->file(), &readFds);
3633    while (inBufPtr->isLineAvailable() ||
3634           (select(inBufPtr->file()+1, &readFds, NULL, NULL, NULL) > 0)) {
3635        size_t numBytes;
3636        unsigned char *buffer;
3637
3638        /* A short read is treated as an error here because we assume that we
3639         * will always get commands line by line. */
3640        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
3641            /* Terminate the server if we can't communicate with the client
3642             * anymore. */
3643            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
3644                TRACE("Exiting server on EOF from client");
3645                return -1;
3646            } else {
3647                ERROR("Exiting server, failed to read from client: %s",
3648                      strerror(errno));
3649                return -1;
3650            }
3651        }
3652        Tcl_DStringAppend(&commandString, (char *)buffer, numBytes);
3653        if (Tcl_CommandComplete(Tcl_DStringValue(&commandString))) {
3654            // Add to queue
3655            Command *command = new Command(Command::COMMAND);
3656            command->setMessage((unsigned char *)Tcl_DStringValue(&commandString),
3657                                Tcl_DStringLength(&commandString), Command::VOLATILE);
3658            g_inQueue->enqueue(command);
3659            Tcl_DStringSetLength(&commandString, 0);
3660        }
3661        FD_SET(inBufPtr->file(), &readFds);
3662    }
3663
3664    return 1;
3665}
3666#endif
3667
3668/**
3669 * \brief Execute commands from client in Tcl interpreter
3670 *
3671 * In this threaded model, the select call is for event compression.  We
3672 * want to execute render server commands as long as they keep coming. 
3673 * This lets us execute a stream of many commands but render once.  This
3674 * benefits camera movements, screen resizing, and opacity changes
3675 * (using a slider on the client).  The down side is you don't render
3676 * until there's a lull in the command stream.  If the client needs an
3677 * image, it can issue an "imgflush" command.  That breaks us out of the
3678 * read loop.
3679 */
3680int
3681GeoVis::processCommands(Tcl_Interp *interp,
3682                        ClientData clientData,
3683                        ReadBuffer *inBufPtr,
3684                        int fdOut,
3685                        long timeout)
3686{
3687    int ret = 1;
3688    int status = TCL_OK;
3689
3690    Tcl_DString command;
3691    Tcl_DStringInit(&command);
3692    fd_set readFds;
3693    struct timeval tv, *tvPtr;
3694
3695    FD_ZERO(&readFds);
3696    FD_SET(inBufPtr->file(), &readFds);
3697    tvPtr = NULL;                       /* Wait for the first read. This is so
3698                                         * that we don't spin when no data is
3699                                         * available. */
3700    if (timeout >= 0L) {
3701        tv.tv_sec = 0L;
3702        tv.tv_usec = timeout;
3703        tvPtr = &tv;
3704    } else {
3705        TRACE("Blocking on select()");
3706    }
3707    while (inBufPtr->isLineAvailable() ||
3708           (ret = select(inBufPtr->file()+1, &readFds, NULL, NULL, tvPtr)) > 0) {
3709        size_t numBytes;
3710        unsigned char *buffer;
3711
3712        /* A short read is treated as an error here because we assume that we
3713         * will always get commands line by line. */
3714        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
3715            /* Terminate the server if we can't communicate with the client
3716             * anymore. */
3717            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
3718                TRACE("Exiting server on EOF from client");
3719                return -1;
3720            } else {
3721                ERROR("Exiting server, failed to read from client: %s",
3722                      strerror(errno));
3723                return -1;
3724            }
3725        }
3726#if 0
3727        Tcl_DString tmp;
3728        Tcl_DStringInit(&tmp);
3729        Tcl_Encoding encoding = Tcl_GetEncoding(interp, "identity");
3730        TRACE("Encoding name: %s", Tcl_GetEncodingName(encoding));
3731        Tcl_ExternalToUtfDString(encoding, (const char *)buffer, numBytes, &tmp);
3732        Tcl_FreeEncoding(encoding);
3733        Tcl_DStringAppend(&command, Tcl_DStringValue(&tmp), Tcl_DStringLength(&tmp));
3734        Tcl_DStringFree(&tmp);
3735#else
3736        Tcl_DStringAppend(&command, (char *)buffer, numBytes);
3737#endif
3738        if (Tcl_CommandComplete(Tcl_DStringValue(&command))) {
3739            struct timeval start, finish;
3740            gettimeofday(&start, NULL);
3741            g_stats.nCommands++;
3742            status = ExecuteCommand(interp, &command);
3743            gettimeofday(&finish, NULL);
3744            g_stats.cmdTime += (MSECS_ELAPSED(start, finish) / 1.0e+3);
3745            if (status == TCL_BREAK) {
3746                return 2;               /* This was caused by a "imgflush"
3747                                         * command. Break out of the read loop
3748                                         * and allow a new image to be
3749                                         * rendered. */
3750            } else { //if (status != TCL_OK) {
3751                ret = 0;
3752                if (handleError(interp, clientData, status, fdOut) < 0) {
3753                    return -1;
3754                }
3755            }
3756            if (status == TCL_OK) {
3757                ret = 3;
3758            }
3759        }
3760
3761        tv.tv_sec = tv.tv_usec = 0L;    /* On successive reads, we break out
3762                                         * if no data is available. */
3763        FD_SET(inBufPtr->file(), &readFds);
3764        tvPtr = &tv;
3765    }
3766
3767    return ret;
3768}
3769
3770/**
3771 * \brief Send error message to client socket
3772 */
3773int
3774GeoVis::handleError(Tcl_Interp *interp,
3775                    ClientData clientData,
3776                    int status, int fdOut)
3777{
3778    const char *string;
3779    int nBytes;
3780
3781    if (status != TCL_OK) {
3782        string = Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY);
3783        nBytes = strlen(string);
3784        if (nBytes > 0) {
3785            TRACE("status=%d errorInfo=(%s)", status, string);
3786
3787            std::ostringstream oss;
3788            oss << "nv>viserror -type internal_error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
3789            std::string ostr = oss.str();
3790            nBytes = ostr.length();
3791
3792            if (queueResponse(ostr.c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
3793                return -1;
3794            }
3795        }
3796    }
3797
3798    std::string msg = getUserMessages();
3799    nBytes = msg.length();
3800    if (nBytes > 0) {
3801        string = msg.c_str();
3802        TRACE("userError=(%s)", string);
3803
3804        std::ostringstream oss;
3805        oss << "nv>viserror -type error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
3806        std::string ostr = oss.str();
3807        nBytes = ostr.length();
3808
3809        if (queueResponse(ostr.c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
3810            return -1;
3811        }
3812
3813        clearUserMessages();
3814    }
3815
3816    return 0;
3817}
3818
3819/**
3820 * \brief Create Tcl interpreter and add commands
3821 *
3822 * \return The initialized Tcl interpreter
3823 */
3824void
3825GeoVis::initTcl(Tcl_Interp *interp, ClientData clientData)
3826{
3827    TRACE("LANG: %s", getenv("LANG"));
3828    Tcl_GetEncodingNames(interp);
3829    TRACE("Supported encodings: %s", Tcl_GetStringResult(interp));
3830    int result = Tcl_Eval(interp, "encoding system\n");
3831    if (result == TCL_OK) {
3832        TRACE("Current system encoding: %s", Tcl_GetStringResult(interp));
3833    } else {
3834        ERROR("Couldn't determine system encoding");
3835    }
3836    const char *encoding = "utf-8";
3837    if (Tcl_SetSystemEncoding(interp, encoding) != TCL_OK) {
3838        TRACE("Failed to set Tcl encoding to %s", encoding);
3839    } else {
3840        TRACE("Set system encoding to %s", encoding);
3841    }
3842
3843    Tcl_MakeSafe(interp);
3844
3845    Tcl_CreateObjCommand(interp, "camera",     CameraCmd,     clientData, NULL);
3846    Tcl_CreateObjCommand(interp, "clientinfo", ClientInfoCmd, clientData, NULL);
3847    Tcl_CreateObjCommand(interp, "colormap",   ColorMapCmd,   clientData, NULL);
3848    Tcl_CreateObjCommand(interp, "file",       FileCmd,       clientData, NULL);
3849    Tcl_CreateObjCommand(interp, "imgflush",   ImageFlushCmd, clientData, NULL);
3850    Tcl_CreateObjCommand(interp, "key",        KeyCmd,        clientData, NULL);
3851    Tcl_CreateObjCommand(interp, "legend",     LegendCmd,     clientData, NULL);
3852    Tcl_CreateObjCommand(interp, "map",        MapCmd,        clientData, NULL);
3853    Tcl_CreateObjCommand(interp, "mouse",      MouseCmd,      clientData, NULL);
3854    Tcl_CreateObjCommand(interp, "placard",    PlacardCmd,    clientData, NULL);
3855    Tcl_CreateObjCommand(interp, "renderer",   RendererCmd,   clientData, NULL);
3856    Tcl_CreateObjCommand(interp, "screen",     ScreenCmd,     clientData, NULL);
3857    Tcl_CreateObjCommand(interp, "select",     SelectCmd,     clientData, NULL);
3858}
3859
3860/**
3861 * \brief Delete Tcl commands and interpreter
3862 */
3863void GeoVis::exitTcl(Tcl_Interp *interp)
3864{
3865    Tcl_DeleteCommand(interp, "camera");
3866    Tcl_DeleteCommand(interp, "clientinfo");
3867    Tcl_DeleteCommand(interp, "colormap");
3868    Tcl_DeleteCommand(interp, "file");
3869    Tcl_DeleteCommand(interp, "imgflush");
3870    Tcl_DeleteCommand(interp, "key");
3871    Tcl_DeleteCommand(interp, "legend");
3872    Tcl_DeleteCommand(interp, "map");
3873    Tcl_DeleteCommand(interp, "mouse");
3874    Tcl_DeleteCommand(interp, "placard");
3875    Tcl_DeleteCommand(interp, "renderer");
3876    Tcl_DeleteCommand(interp, "screen");
3877    Tcl_DeleteCommand(interp, "select");
3878
3879    Tcl_DeleteInterp(interp);
3880}
Note: See TracBrowser for help on using the repository browser.