source: geovis/trunk/RendererCmd.cpp @ 6219

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

Stubs for editing feature selection set

File size: 139.7 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
1179MapBoxInitOp(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->initBoxSelection(x, y);
1188    return TCL_OK;
1189}
1190
1191static int
1192MapBoxUpdateOp(ClientData clientData, Tcl_Interp *interp, int objc,
1193               Tcl_Obj *const *objv)
1194{
1195    int x, y;
1196    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
1197        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
1198        return TCL_ERROR;
1199    }
1200    g_renderer->updateBoxSelection(x, y);
1201    return TCL_OK;
1202}
1203
1204static CmdSpec mapBoxOps[] = {
1205    {"clear",   1, MapBoxClearOp,   3, 3, ""},
1206    {"init",    1, MapBoxInitOp,    5, 5, "x y"},
1207    {"update",  1, MapBoxUpdateOp,  5, 5, "x y"},
1208};
1209static int nMapBoxOps = NumCmdSpecs(mapBoxOps);
1210
1211static int
1212MapBoxOp(ClientData clientData, Tcl_Interp *interp, int objc,
1213         Tcl_Obj *const *objv)
1214{
1215    Tcl_ObjCmdProc *proc;
1216
1217    proc = GetOpFromObj(interp, nMapBoxOps, mapBoxOps,
1218                        CMDSPEC_ARG2, objc, objv, 0);
1219    if (proc == NULL) {
1220        return TCL_ERROR;
1221    }
1222    return (*proc) (clientData, interp, objc, objv);
1223}
1224
1225static int
1226MapCoordsOp(ClientData clientData, Tcl_Interp *interp, int objc,
1227            Tcl_Obj *const *objv)
1228{
1229    int tokenLength;
1230    const char *token = Tcl_GetStringFromObj(objv[2], &tokenLength);
1231    int numCoords;
1232    Tcl_Obj **coords;
1233    if (Tcl_ListObjGetElements(interp, objv[3], &numCoords, &coords) != TCL_OK) {
1234        return TCL_ERROR;
1235    }
1236    if (numCoords == 0) {
1237        Tcl_AppendResult(interp, "no x,y coordinates in list", (char *)NULL);
1238        return TCL_ERROR;
1239    }
1240    if (numCoords & 1) {
1241        Tcl_AppendResult(interp, "odd number of x, y coordinates in list",
1242                         (char *)NULL);
1243        return TCL_ERROR;
1244    }
1245    const osgEarth::SpatialReference *outSRS = NULL;
1246    if (objc > 4) {
1247        std::string srsInit;
1248        std::string verticalDatum;
1249        srsInit = Tcl_GetString(objv[4]);
1250        if (objc > 5) {
1251            verticalDatum = Tcl_GetString(objv[5]);
1252        }
1253        outSRS = osgEarth::SpatialReference::get(srsInit, verticalDatum);
1254        if (outSRS == NULL) {
1255            Tcl_AppendResult(interp, "bad SRS \"", srsInit.c_str(), "\"",
1256                             (char*)NULL);
1257            return TCL_ERROR;
1258        }
1259    }
1260    Tcl_Obj *listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
1261    std::vector<osg::Vec3d> coordVec;
1262    for (int i = 0; i < numCoords; i += 2) {
1263        int x, y;
1264        if ((Tcl_GetIntFromObj(interp, coords[i], &x) != TCL_OK) ||
1265            (Tcl_GetIntFromObj(interp, coords[i+1], &y) != TCL_OK)) {
1266            return TCL_ERROR;
1267        }
1268        osgEarth::GeoPoint mapPoint;
1269        if (g_renderer->mapMouseCoords(x, y, mapPoint)) {
1270            // altmode absolute
1271            coordVec.push_back(osg::Vec3d(mapPoint.x(), mapPoint.y(), mapPoint.z()));
1272        } else {
1273            coordVec.push_back(osg::Vec3d(std::numeric_limits<double>::quiet_NaN(),
1274                                          std::numeric_limits<double>::quiet_NaN(),
1275                                          std::numeric_limits<double>::quiet_NaN()));
1276        }
1277    }
1278    if (outSRS != NULL) {
1279        g_renderer->getMapSRS()->transform(coordVec, outSRS);
1280    } else {
1281        outSRS = g_renderer->getMapSRS();
1282    }
1283    for (std::vector<osg::Vec3d>::iterator itr = coordVec.begin();
1284         itr != coordVec.end(); ++itr) {
1285        Tcl_Obj *objPtr = Tcl_NewDoubleObj(itr->x());
1286        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
1287        objPtr = Tcl_NewDoubleObj(itr->y());
1288        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
1289        objPtr = Tcl_NewDoubleObj(itr->z());
1290        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
1291    }
1292    // send coords to client
1293    int listLength;
1294    const char *string = Tcl_GetStringFromObj(listObjPtr, &listLength);
1295    size_t length = listLength + tokenLength +
1296        outSRS->getHorizInitString().length() +
1297        outSRS->getVertInitString().length() +
1298        25;
1299    char *mesg = new char[length];
1300    length = snprintf(mesg, length, "nv>map coords %s {%s} {%s} {%s}\n", token, string,
1301                      outSRS->getHorizInitString().c_str(),
1302                      outSRS->getVertInitString().c_str());
1303    Tcl_DecrRefCount(listObjPtr);
1304    queueResponse(mesg, length, Response::VOLATILE);
1305    delete [] mesg;
1306    return TCL_OK;
1307}
1308
1309static int
1310MapGraticuleOp(ClientData clientData, Tcl_Interp *interp, int objc,
1311               Tcl_Obj *const *objv)
1312{
1313    bool state;
1314    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
1315        return TCL_ERROR;
1316    }
1317    if (objc > 3) {
1318        Renderer::GraticuleType type;
1319        char *typeStr = Tcl_GetString(objv[3]);
1320        if (typeStr[0] == 'g' && strcmp(typeStr, "geodetic") == 0) {
1321            type = Renderer::GRATICULE_GEODETIC;
1322        } else if (typeStr[0] == 's' && strcmp(typeStr, "shader") == 0) {
1323            type = Renderer::GRATICULE_SHADER;
1324        } else if (typeStr[0] == 'u' && strcmp(typeStr, "utm") == 0) {
1325            type = Renderer::GRATICULE_UTM;
1326        } else if (typeStr[0] == 'm' && strcmp(typeStr, "mgrs") == 0) {
1327            type = Renderer::GRATICULE_MGRS;
1328        } else {
1329            return TCL_ERROR;
1330        }
1331        g_renderer->setGraticule(state, type);
1332    } else {
1333        g_renderer->setGraticule(state);
1334    }
1335    return TCL_OK;
1336}
1337
1338static int
1339MapLayerAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
1340              Tcl_Obj *const *objv)
1341{
1342    char *name = Tcl_GetString(objv[3]);
1343    char *type = Tcl_GetString(objv[4]);
1344    bool cache = true;
1345    bool visible = true;
1346    if (type[0] == 'i' && strcmp(type, "image") == 0) {
1347        bool ret;
1348        char *driver = Tcl_GetString(objv[5]);
1349        std::string url;
1350        if (objc > 6) {
1351            char *urlIn = Tcl_GetString(objv[6]);
1352            url = g_renderer->getCanonicalPath(std::string(urlIn));
1353            if (url.empty()) {
1354                Tcl_AppendResult(interp, "file not found: \"",
1355                                 urlIn, "\"", (char*)NULL);
1356                return TCL_ERROR;
1357            }
1358        }
1359        if (objc > 7) {
1360            if (GetBooleanFromObj(interp, objv[7], &cache) != TCL_OK) {
1361                return TCL_ERROR;
1362            }
1363        }
1364        bool coverage = false;
1365        if (objc > 8) {
1366            if (GetBooleanFromObj(interp, objv[8], &coverage) != TCL_OK) {
1367                return TCL_ERROR;
1368            }
1369        }
1370        bool shared = false;
1371        unsigned int minLOD = 0;
1372        unsigned int maxLOD = 23;
1373        float minRange = 0.f;
1374        float maxRange = FLT_MAX;
1375        if (driver[0] == 'a' && strcmp(driver, "agglite") == 0) {
1376            osgEarth::Drivers::AGGLiteOptions aggOpts;
1377
1378            const char *featureDriver = Tcl_GetString(objv[9]);
1379            const char *format = Tcl_GetString(objv[10]);
1380            if (format && strlen(format) > 0 &&
1381                strcmp(format, "json") != 0 && strcmp(format, "gml") != 0) {
1382                Tcl_AppendResult(interp, "unknown format \"", format,
1383                                 "\": should be 'json' or 'gml'", (char*)NULL);
1384                return TCL_ERROR;
1385            }
1386            const char *typeName = Tcl_GetString(objv[11]);
1387
1388            osgEarth::Config styleConf("style", Tcl_GetString(objv[12]));
1389            styleConf.add("type", "text/css");
1390            TRACE("style CSS: %s", styleConf.value().c_str());
1391            osgEarth::Config stylesheetConf;
1392            stylesheetConf.add(styleConf);
1393            aggOpts.styles() = new osgEarth::Symbology::StyleSheet(stylesheetConf);
1394
1395            aggOpts.optimizeLineSampling() = true;
1396            //aggOpts.gamma() = 1.3;
1397
1398            if (objc > 13) {
1399                std::string scripts(Tcl_GetString(objv[13]));
1400                if (!scripts.empty()) {
1401                    TRACE("script: %s", scripts.c_str());
1402                    osg::ref_ptr<osgEarth::Symbology::StyleSheet::ScriptDef> scriptDef =
1403                        new osgEarth::Symbology::StyleSheet::ScriptDef(scripts);
1404                    aggOpts.styles()->setScript(scriptDef.get());
1405                } else {
1406                    TRACE("no script");
1407                }
1408            }
1409            if (objc > 14) {
1410                int numSelectors;
1411                Tcl_Obj **selectors;
1412                if (Tcl_ListObjGetElements(interp, objv[14], &numSelectors, &selectors) != TCL_OK) {
1413                    return TCL_ERROR;
1414                }
1415                for (int i = 0; i < numSelectors; i++) {
1416                    int numFields;
1417                    Tcl_Obj **fields;
1418                    if (Tcl_ListObjGetElements(interp, selectors[i], &numFields, &fields) != TCL_OK ||
1419                        numFields % 2 != 0) {
1420                        return TCL_ERROR;
1421                    }
1422                    std::map<std::string, std::string> fieldMap;
1423                    std::map<std::string, std::string>::iterator itr;
1424                    for (int f = 0; f < numFields; f+=2) {
1425                        char *name = Tcl_GetString(fields[f]);
1426                        char *value = Tcl_GetString(fields[f+1]);
1427                        fieldMap[name] = value;
1428                        TRACE("selector[%s] = %s", name, value);
1429                    }
1430                    osgEarth::Symbology::StyleSelector ss;
1431                    itr = fieldMap.find("name");
1432                    if (itr != fieldMap.end()) {
1433                        ss.name() = itr->second;
1434                    }
1435                    itr = fieldMap.find("style");
1436                    if (itr != fieldMap.end()) {
1437                        ss.styleName() = itr->second;
1438                    }
1439                    itr = fieldMap.find("styleExpression");
1440                    if (itr != fieldMap.end()) {
1441                        TRACE("selector: %s", itr->second.c_str());
1442                        ss.styleExpression() = osgEarth::Symbology::StringExpression(itr->second);
1443                    }
1444                    itr = fieldMap.find("query");
1445                    if (itr != fieldMap.end()) {
1446                        osgEarth::Symbology::Query query;
1447                        query.expression() = itr->second;
1448                        itr = fieldMap.find("queryOrderBy");
1449                        if (itr != fieldMap.end()) {
1450                            query.orderby() = itr->second;
1451                        }
1452                        itr = fieldMap.find("queryBounds");
1453                        if (itr !=  fieldMap.end()) {
1454                            double xmin, ymin, xmax, ymax;
1455                            if (sscanf(itr->second.c_str(), "%lf %lf %lf %lf", &xmin, &ymin, &xmax, &ymax) != 4) {
1456                                return TCL_ERROR;
1457                            }
1458                            TRACE("query bounds: %g %g %g %g", xmin, ymin, xmax, ymax);
1459                            query.bounds() = osgEarth::Bounds(xmin, ymin, xmax, ymax);
1460                        }
1461                        ss.query() = query;
1462                    }
1463                    aggOpts.styles()->selectors().push_back(ss);
1464                }
1465            }
1466            if (featureDriver[0] == 'd' && strcmp(featureDriver, "db") == 0) {
1467                osgEarth::Drivers::OGRFeatureOptions opts;
1468                opts.name() = name;
1469                // Get unmodified connection string
1470                opts.connection() = Tcl_GetString(objv[6]);
1471                opts.layer() = typeName;
1472                aggOpts.featureOptions() = opts;
1473            } else if (featureDriver[0] == 'o' && strcmp(featureDriver, "ogr") == 0) {
1474                osgEarth::Drivers::OGRFeatureOptions opts;
1475                opts.name() = name;
1476                if (osgDB::getLowerCaseFileExtension(url) == "csv") {
1477                    url = loadCSVLayer(url.c_str());
1478                }
1479                opts.url() = url;
1480                aggOpts.featureOptions() = opts;
1481            } else if (featureDriver[0] == 'o' && strcmp(featureDriver, "tfs") == 0) {
1482                osgEarth::Drivers::TFSFeatureOptions opts;
1483                opts.name() = name;
1484                opts.url() = url;
1485                opts.format() = format;
1486                aggOpts.featureOptions() = opts;
1487            } else if (featureDriver[0] == 'o' && strcmp(featureDriver, "wfs") == 0) {
1488                osgEarth::Drivers::WFSFeatureOptions opts;
1489                opts.name() = name;
1490                opts.url() = url;
1491                opts.outputFormat() = format;
1492                opts.typeName() = typeName;
1493                aggOpts.featureOptions() = opts;
1494            } else {
1495                Tcl_AppendResult(interp, "unknown feature driver \"", driver,
1496                                 "\": should be 'db', 'ogr', 'tfs', or 'wfs'", (char*)NULL);
1497                return TCL_ERROR;
1498            }
1499            ret = g_renderer->addImageLayer(name, aggOpts, cache, coverage, shared, visible, minLOD, maxLOD);
1500        } else if (driver[0] == 'c' && strcmp(driver, "colorramp") == 0) {
1501            osgEarth::Drivers::ColorRampOptions colorRampOpts;
1502            char *edriver = Tcl_GetString(objv[9]);
1503            char *profile = Tcl_GetString(objv[10]);
1504            char *colormap = Tcl_GetString(objv[11]);
1505            if (edriver[0] == 'g' && strcmp(edriver, "gdal") == 0) {
1506                osgEarth::Drivers::GDALOptions opts;
1507                opts.url() = url;
1508                osgEarth::ElevationLayerOptions elevOpts(name, opts);
1509                if (!cache) {
1510                    elevOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
1511                }
1512                if (profile != NULL) {
1513                    elevOpts.driver()->profile() = osgEarth::ProfileOptions(profile);
1514                }
1515#if 0
1516                {
1517                    osgEarth::ProfileOptions profile;
1518                    profile.srsString() = srsString;
1519                    profile.vsrsString() = vsrsString;
1520                    profile.bounds() = osgEarth::Bounds(xmin, ymin, xmax, ymax);
1521                    elevOpts.driver()->profile() = profile;
1522                }
1523#endif
1524                colorRampOpts.elevationLayer() = elevOpts;
1525            } else if (edriver[0] == 't' && strcmp(edriver, "tms") == 0) {
1526                osgEarth::Drivers::TMSOptions opts;
1527                //char *tmsType = Tcl_GetString(objv[9]);
1528                //char *format = Tcl_GetString(objv[10]);
1529                opts.url() = url;
1530                //opts.tmsType() = tmsType;
1531                //opts.format() = format;
1532                osgEarth::ElevationLayerOptions elevOpts(name, opts);
1533                if (!cache) {
1534                    elevOpts.cachePolicy() = osgEarth::CachePolicy(osgEarth::CachePolicy::USAGE_NO_CACHE);
1535                }
1536                if (profile != NULL) {
1537                    elevOpts.driver()->profile() = osgEarth::ProfileOptions(profile);
1538                }
1539                colorRampOpts.elevationLayer() = elevOpts;
1540            }
1541            colorRampOpts.ramp() = g_renderer->getColorMapFilePath(colormap);
1542            ret = g_renderer->addImageLayer(name, colorRampOpts, cache, coverage, shared, visible, minLOD, maxLOD);
1543        } else if (driver[0] == 'd' && strcmp(driver, "debug") == 0) {
1544            osgEarth::Drivers::DebugOptions opts;
1545            ret = g_renderer->addImageLayer(name, opts, cache, coverage, shared, visible, minLOD, maxLOD);
1546        } else if (driver[0] == 'a' && strcmp(driver, "arcgis") == 0) {
1547            osgEarth::Drivers::ArcGISOptions opts;
1548            opts.url() = url;
1549            if (objc > 9) {
1550                opts.token() = Tcl_GetString(objv[9]);
1551            }
1552            if (objc > 10) {
1553                opts.layers() = Tcl_GetString(objv[10]);
1554            }
1555            //opts.format() = Tcl_GetString(objv[11]);
1556            ret = g_renderer->addImageLayer(name, opts, cache, coverage, shared, visible, minLOD, maxLOD);
1557            g_renderer->setImageLayerVisibleRange(name, minRange, maxRange);
1558        } else if (driver[0] == 'g' && strcmp(driver, "gdal") == 0) {
1559            osgEarth::Drivers::GDALOptions opts;
1560            opts.url() = url;
1561            ret = g_renderer->addImageLayer(name, opts, cache, coverage, shared, visible, minLOD, maxLOD);
1562            g_renderer->setImageLayerVisibleRange(name, minRange, maxRange);
1563        } else if (driver[0] == 't' && strcmp(driver, "tms") == 0) {
1564            osgEarth::Drivers::TMSOptions opts;
1565            opts.url() = url;
1566            //opts.tmsType() = Tcl_GetString(objv[9]);
1567            //opts.format() = Tcl_GetString(objv[10]);
1568            ret = g_renderer->addImageLayer(name, opts, cache, coverage, shared, visible, minLOD, maxLOD);
1569            g_renderer->setImageLayerVisibleRange(name, minRange, maxRange);
1570        } else if (driver[0] == 'w' && strcmp(driver, "wms") == 0) {
1571            osgEarth::Drivers::WMSOptions opts;
1572            char *wmsLayers = Tcl_GetString(objv[9]);
1573            char *format = Tcl_GetString(objv[10]);
1574            bool transparent;
1575            if (GetBooleanFromObj(interp, objv[11], &transparent) != TCL_OK) {
1576                return TCL_ERROR;
1577            }
1578            opts.url() = url;
1579            opts.layers() = wmsLayers;
1580            opts.format() = format;
1581            opts.transparent() = transparent;
1582
1583            ret = g_renderer->addImageLayer(name, opts, cache, coverage, shared, visible, minLOD, maxLOD);
1584            g_renderer->setImageLayerVisibleRange(name, minRange, maxRange);
1585        } else if (driver[0] == 'x' && strcmp(driver, "xyz") == 0) {
1586            osgEarth::Drivers::XYZOptions opts;
1587            opts.url() = url;
1588            opts.profile() = osgEarth::ProfileOptions("global-mercator");
1589            //bool invertY = false;
1590            //opts.invertY() = invertY;
1591            //opts.format() = Tcl_GetString(objv[9]);
1592            ret = g_renderer->addImageLayer(name, opts, cache, coverage, shared, visible, minLOD, maxLOD);
1593            g_renderer->setImageLayerVisibleRange(name, minRange, maxRange);
1594        } else {
1595            Tcl_AppendResult(interp, "unknown image driver \"", driver,
1596                             "\": should be 'debug', 'gdal', 'tms', 'wms' or 'xyz'", (char*)NULL);
1597            return TCL_ERROR;
1598        }
1599        if (!ret) {
1600            Tcl_AppendResult(interp, "Failed to add image layer \"", name, "\"", (char*)NULL);
1601            return TCL_ERROR;
1602        }
1603    } else if (type[0] == 'e' && strcmp(type, "elevation") == 0) {
1604        char *driver = Tcl_GetString(objv[5]);
1605        char *urlIn = Tcl_GetString(objv[6]);
1606        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
1607        if (url.empty()) {
1608            Tcl_AppendResult(interp, "file not found: \"",
1609                             urlIn, "\"", (char*)NULL);
1610            return TCL_ERROR;
1611        }
1612        if (objc > 7) {
1613            if (GetBooleanFromObj(interp, objv[7], &cache) != TCL_OK) {
1614                return TCL_ERROR;
1615            }
1616        }
1617        int minLOD = 0;
1618        int maxLOD = 23;
1619        if (driver[0] == 'g' && strcmp(driver, "gdal") == 0) {
1620            osgEarth::Drivers::GDALOptions opts;
1621            opts.url() = url;
1622            g_renderer->addElevationLayer(name, opts, cache, visible, minLOD, maxLOD);
1623        } else if (driver[0] == 't' && strcmp(driver, "tms") == 0) {
1624            osgEarth::Drivers::TMSOptions opts;
1625            opts.url() = url;
1626            //opts.tmsType() = Tcl_GetString(objv[8]);
1627            //opts.format() = Tcl_GetString(objv[9]);
1628            g_renderer->addElevationLayer(name, opts, cache, visible, minLOD, maxLOD);
1629        } else if (driver[0] == 'w' && strcmp(driver, "wcs") == 0) {
1630            osgEarth::Drivers::WCSOptions opts;
1631            opts.url() = url;
1632            if (objc > 8) {
1633                opts.identifier() = Tcl_GetString(objv[8]);
1634            }
1635            if (objc > 9) {
1636                // default = 'm'
1637                opts.elevationUnit() = Tcl_GetString(objv[9]);
1638            }
1639            if (objc > 10) {
1640                // default = 'image/GeoTIFF'
1641                opts.format() = Tcl_GetString(objv[10]);
1642            }
1643            //opts.srs() = Tcl_GetString(objv[11]);
1644            //opts.rangeSubset() = Tcl_GetString(objv[12]);
1645            g_renderer->addElevationLayer(name, opts, cache, visible, minLOD, maxLOD);
1646        } else {
1647            Tcl_AppendResult(interp, "unknown elevation driver \"", driver,
1648                             "\": should be 'gdal' or 'tms'", (char*)NULL);
1649            return TCL_ERROR;
1650        }
1651    } else if (type[0] == 'f' && strcmp(type, "feature") == 0) {
1652        // Generic feature geometry layer
1653        char *driver = Tcl_GetString(objv[5]);
1654        char *format = Tcl_GetString(objv[6]);
1655        if (format && strlen(format) > 0 &&
1656            strcmp(format, "json") != 0 && strcmp(format, "gml") != 0) {
1657            Tcl_AppendResult(interp, "unknown format \"", format,
1658                             "\": should be 'json' or 'gml'", (char*)NULL);
1659            return TCL_ERROR;
1660        }
1661        char *typeName = Tcl_GetString(objv[7]);
1662        char *urlIn = Tcl_GetString(objv[8]);
1663        std::string url;
1664        if (driver[0] == 'd' && strcmp(driver, "db") == 0) {
1665            url = urlIn;
1666        } else {
1667            url = g_renderer->getCanonicalPath(std::string(urlIn));
1668            if (url.empty()) {
1669                Tcl_AppendResult(interp, "file not found: \"",
1670                                 urlIn, "\"", (char*)NULL);
1671                return TCL_ERROR;
1672            }
1673        }
1674        if (GetBooleanFromObj(interp, objv[9], &cache) != TCL_OK) {
1675            return TCL_ERROR;
1676        }
1677
1678        bool lighting = true;
1679        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
1680        osgEarth::Config styleConf("style", Tcl_GetString(objv[10]));
1681        styleConf.add("type", "text/css");
1682        TRACE("style CSS: %s", styleConf.value().c_str());
1683#if 1
1684        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
1685        // For relative paths:
1686        //styleConf.setReferrer(g_renderer->getCacheDirectory());
1687        std::string cssString = styleConf.value();
1688        std::vector<std::string> blocks;
1689        osgEarth::Symbology::CssUtils::split(cssString, blocks);
1690        for (std::vector<std::string>::iterator itr = blocks.begin();
1691             itr != blocks.end(); ++itr) {
1692            osgEarth::Config blockConf(styleConf);
1693            blockConf.value() = *itr;
1694            osgEarth::Symbology::Style style(blockConf);
1695
1696            TRACE("Style: %s", style.getName().c_str());
1697            TRACE("%s", itr->c_str());
1698
1699            if (style.has<osgEarth::Symbology::IconSymbol>()) {
1700                TRACE("Found icon symbol");
1701                osgEarth::Symbology::IconSymbol *is =
1702                    style.get<osgEarth::Symbology::IconSymbol>();
1703                if (is->url().isSet()) {
1704                    TRACE("Icon url before: expr: '%s' eval: '%s'",
1705                         is->url()->expr().c_str(), is->url()->eval().c_str());
1706                    // eval() will try to evaluate the expr()
1707                    std::string path = g_renderer->getCanonicalPath(is->url()->eval());
1708                    //setInfix() to set the expr() string, setLiteral() will quote
1709                    is->url()->setLiteral(path);
1710                    TRACE("Icon url after: %s", path.c_str());
1711                }
1712            }
1713            if (style.has<osgEarth::Symbology::ModelSymbol>()) {
1714                osgEarth::Symbology::ModelSymbol *ms =
1715                    style.get<osgEarth::Symbology::ModelSymbol>();
1716                if (ms->url().isSet()) {
1717                    // eval() will try to evaluate the expr()
1718                    std::string path = g_renderer->getCanonicalPath(ms->url()->eval());
1719                    //setInfix() to set the expr() string, setLiteral() will quote
1720                    ms->url()->setLiteral(path);
1721                }
1722            }
1723            // Need to create a new style otherwise the original CSS is used
1724            // without the re-written URLs
1725            geomOpts.styles()->addStyle(osgEarth::Symbology::Style(style.getConfig(false)));
1726        }
1727#else
1728        osgEarth::Config stylesheetConf;
1729        stylesheetConf.add(styleConf);
1730        geomOpts.styles() = new osgEarth::Symbology::StyleSheet(stylesheetConf);
1731#endif
1732
1733        if (objc > 11) {
1734            std::string scripts(Tcl_GetString(objv[11]));
1735            if (!scripts.empty()) {
1736                TRACE("script: %s", scripts.c_str());
1737                osg::ref_ptr<osgEarth::Symbology::StyleSheet::ScriptDef> scriptDef =
1738                    new osgEarth::Symbology::StyleSheet::ScriptDef(scripts);
1739                geomOpts.styles()->setScript(scriptDef.get());
1740            } else {
1741                TRACE("no script");
1742            }
1743        }
1744        if (objc > 12) {
1745            int numSelectors;
1746            Tcl_Obj **selectors;
1747            if (Tcl_ListObjGetElements(interp, objv[12], &numSelectors, &selectors) != TCL_OK) {
1748                return TCL_ERROR;
1749            }
1750            TRACE("Num Selectors: %d", numSelectors);
1751            for (int i = 0; i < numSelectors; i++) {
1752                int numFields;
1753                Tcl_Obj **fields;
1754                if (Tcl_ListObjGetElements(interp, selectors[i], &numFields, &fields) != TCL_OK ||
1755                    numFields % 2 != 0) {
1756                    return TCL_ERROR;
1757                }
1758                std::map<std::string, std::string> fieldMap;
1759                std::map<std::string, std::string>::iterator itr;
1760                for (int f = 0; f < numFields; f+=2) {
1761                    char *name = Tcl_GetString(fields[f]);
1762                    char *value = Tcl_GetString(fields[f+1]);
1763                    fieldMap[name] = value;
1764                    TRACE("selector[%s] = %s", name, value);
1765                }
1766                osgEarth::Symbology::StyleSelector ss;
1767                itr = fieldMap.find("name");
1768                if (itr != fieldMap.end()) {
1769                    ss.name() = itr->second;
1770                }
1771                itr = fieldMap.find("style");
1772                if (itr != fieldMap.end()) {
1773                    ss.styleName() = itr->second;
1774                }
1775                itr = fieldMap.find("styleExpression");
1776                if (itr != fieldMap.end()) {
1777                    TRACE("selector: %s", itr->second.c_str());
1778                    ss.styleExpression() = osgEarth::Symbology::StringExpression(itr->second);
1779                }
1780                itr = fieldMap.find("query");
1781                if (itr != fieldMap.end()) {
1782                    osgEarth::Symbology::Query query;
1783                    query.expression() = itr->second;
1784                    itr = fieldMap.find("queryOrderBy");
1785                    if (itr != fieldMap.end()) {
1786                        query.orderby() = itr->second;
1787                    }
1788                    itr = fieldMap.find("queryBounds");
1789                    if (itr !=  fieldMap.end()) {
1790                        double xmin, ymin, xmax, ymax;
1791                        if (sscanf(itr->second.c_str(), "%lf %lf %lf %lf", &xmin, &ymin, &xmax, &ymax) != 4) {
1792                            return TCL_ERROR;
1793                        }
1794                        TRACE("query bounds: %g %g %g %g", xmin, ymin, xmax, ymax);
1795                        query.bounds() = osgEarth::Bounds(xmin, ymin, xmax, ymax);
1796                    }
1797                    ss.query() = query;
1798                }
1799                geomOpts.styles()->selectors().push_back(ss);
1800            }
1801        }
1802        geomOpts.enableLighting() = lighting;
1803        geomOpts.minRange() = 0.f;
1804        geomOpts.maxRange() = FLT_MAX;
1805        if (objc > 13) {
1806            float min, max;
1807            if (GetFloatFromObj(interp, objv[13], &min) != TCL_OK ||
1808                GetFloatFromObj(interp, objv[14], &max) != TCL_OK) {
1809                return TCL_ERROR;
1810            }
1811            geomOpts.minRange() = min;
1812            geomOpts.maxRange() = max;
1813        }
1814        if (driver[0] == 'd' && strcmp(driver, "db") == 0) {
1815            osgEarth::Drivers::OGRFeatureOptions opts;
1816            opts.name() = name;
1817            opts.connection() = url;
1818            opts.layer() = typeName;
1819            geomOpts.featureOptions() = opts;
1820        } else if (driver[0] == 'o' && strcmp(driver, "ogr") == 0) {
1821            osgEarth::Drivers::OGRFeatureOptions opts;
1822            opts.name() = name;
1823            if (osgDB::getLowerCaseFileExtension(url) == "csv") {
1824                url = loadCSVLayer(url.c_str());
1825            }
1826            opts.url() = url;
1827            geomOpts.featureOptions() = opts;
1828        } else if (driver[0] == 't' && strcmp(driver, "tfs") == 0) {
1829            osgEarth::Drivers::TFSFeatureOptions opts;
1830            opts.name() = name;
1831            opts.url() = url;
1832            opts.format() = format;
1833            geomOpts.featureOptions() = opts;
1834        } else if (driver[0] == 'w' && strcmp(driver, "wfs") == 0) {
1835            osgEarth::Drivers::WFSFeatureOptions opts;
1836            opts.name() = name;
1837            opts.url() = url;
1838            opts.outputFormat() = format;
1839            opts.typeName() = typeName;
1840            geomOpts.featureOptions() = opts;
1841        } else {
1842            Tcl_AppendResult(interp, "unknown feature driver \"", driver,
1843                             "\": should be 'db', 'ogr', 'tfs', or 'wfs'", (char*)NULL);
1844            return TCL_ERROR;
1845        }
1846        g_renderer->addModelLayer(name, geomOpts, cache, lighting, visible);
1847    } else if (type[0] == 'i' && strcmp(type, "icon") == 0) {
1848        char *driver = Tcl_GetString(objv[5]);
1849        char *format = Tcl_GetString(objv[6]);
1850        if (format && strlen(format) > 0 &&
1851            strcmp(format, "json") != 0 && strcmp(format, "gml") != 0) {
1852            Tcl_AppendResult(interp, "unknown format \"", format,
1853                             "\": should be 'json' or 'gml'", (char*)NULL);
1854            return TCL_ERROR;
1855        }
1856        char *typeName = Tcl_GetString(objv[7]);
1857        char *urlIn = Tcl_GetString(objv[8]);
1858        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
1859        if (url.empty()) {
1860            Tcl_AppendResult(interp, "file not found: \"",
1861                             urlIn, "\"", (char*)NULL);
1862            return TCL_ERROR;
1863        }
1864        if (GetBooleanFromObj(interp, objv[9], &cache) != TCL_OK) {
1865            return TCL_ERROR;
1866        }
1867        char *icon = Tcl_GetString(objv[10]);
1868        char *scaleExpr = Tcl_GetString(objv[11]);
1869        char *headingExpr = Tcl_GetString(objv[12]);
1870        bool declutter = false;
1871        if (GetBooleanFromObj(interp, objv[13], &declutter) != TCL_OK) {
1872            return TCL_ERROR;
1873        }
1874        char *placementStr = Tcl_GetString(objv[14]);
1875        char *alignStr = Tcl_GetString(objv[15]);
1876
1877        bool lighting = true;
1878        osgEarth::Symbology::Style style;
1879        osgEarth::Symbology::IconSymbol *is = style.getOrCreateSymbol<osgEarth::Symbology::IconSymbol>();
1880        if (icon != NULL && strlen(icon) > 0) {
1881            std::string iconFile = g_renderer->getIconFile(icon);
1882            if (!iconFile.empty()) {
1883                is->url()->setLiteral(iconFile);
1884            } else {
1885                is->url() = osgEarth::Symbology::StringExpression(icon);
1886            }
1887        } else {
1888            is->url()->setLiteral(g_renderer->getPinIcon());
1889        }
1890        if (scaleExpr != NULL && strlen(scaleExpr) > 0) {
1891            is->scale() = osgEarth::Symbology::NumericExpression(scaleExpr);
1892        }
1893        if (headingExpr != NULL && strlen(headingExpr) > 0) {
1894            is->heading() = osgEarth::Symbology::NumericExpression(headingExpr);
1895        }
1896        is->declutter() = declutter;
1897        is->occlusionCull() = false;
1898        is->occlusionCullAltitude() = 200000;
1899        is->alignment() = ParseIconAlignment(alignStr, osgEarth::Symbology::IconSymbol::ALIGN_CENTER_BOTTOM);
1900        is->placement() = ParseInstancePlacement(placementStr, osgEarth::Symbology::InstanceSymbol::PLACEMENT_VERTEX);
1901        //is->density() = 1.0f; // For PLACEMENT_INTERVAL/RANDOM
1902#if 0
1903        osgEarth::Symbology::AltitudeSymbol *alt = style.getOrCreateSymbol<osgEarth::Symbology::AltitudeSymbol>();
1904        alt->clamping() = osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
1905        alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_DRAPE;
1906        //alt->technique() = osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
1907        //alt->verticalOffset() = osgEarth::Symbology::NumericExpression();
1908        //alt->verticalScale() = osgEarth::Symbology::NumericExpression();
1909#endif
1910#ifdef USE_DEPTH_OFFSET
1911        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
1912        rs->depthOffset()->enabled() = true;
1913        rs->depthOffset()->minBias() = 1000;
1914#endif
1915        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
1916        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
1917        geomOpts.styles()->addStyle(style);
1918        geomOpts.enableLighting() = false;
1919        geomOpts.minRange() = 0.f;
1920        geomOpts.maxRange() = FLT_MAX;
1921        if (objc > 16) {
1922            float min, max;
1923            if (GetFloatFromObj(interp, objv[16], &min) != TCL_OK ||
1924                GetFloatFromObj(interp, objv[17], &max) != TCL_OK) {
1925                return TCL_ERROR;
1926            }
1927            geomOpts.minRange() = min;
1928            geomOpts.maxRange() = max;
1929        }
1930        //geomOpts.renderOrder() = int;
1931        //geomOpts.depthTestEnabled() = bool;
1932        if (driver[0] == 'o' && strcmp(driver, "ogr") == 0) {
1933            osgEarth::Drivers::OGRFeatureOptions opts;
1934            opts.name() = name;
1935            if (osgDB::getLowerCaseFileExtension(url) == "csv") {
1936                url = loadCSVLayer(url.c_str());
1937            }
1938            opts.url() = url;
1939            geomOpts.featureOptions() = opts;
1940        } else if (driver[0] == 't' && strcmp(driver, "tfs") == 0) {
1941            osgEarth::Drivers::TFSFeatureOptions opts;
1942            opts.name() = name;
1943            opts.url() = url;
1944            opts.format() = format;
1945            geomOpts.featureOptions() = opts;
1946        } else if (driver[0] == 'w' && strcmp(driver, "wfs") == 0) {
1947            osgEarth::Drivers::WFSFeatureOptions opts;
1948            opts.name() = name;
1949            opts.url() = url;
1950            opts.outputFormat() = format;
1951            opts.typeName() = typeName;
1952            geomOpts.featureOptions() = opts;
1953        } else {
1954            Tcl_AppendResult(interp, "unknown feature driver \"", driver,
1955                             "\": should be 'ogr', 'tfs', or 'wfs'", (char*)NULL);
1956            return TCL_ERROR;
1957        }
1958        g_renderer->addModelLayer(name, geomOpts, cache, lighting, visible);
1959    } else if (type[0] == 'p' && strcmp(type, "point") == 0) {
1960        char *driver = Tcl_GetString(objv[5]);
1961        char *format = Tcl_GetString(objv[6]);
1962        if (format && strlen(format) > 0 &&
1963            strcmp(format, "json") != 0 && strcmp(format, "gml") != 0) {
1964            Tcl_AppendResult(interp, "unknown format \"", format,
1965                             "\": should be 'json' or 'gml'", (char*)NULL);
1966            return TCL_ERROR;
1967        }
1968        char *typeName = Tcl_GetString(objv[7]);
1969        char *urlIn = Tcl_GetString(objv[8]);
1970        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
1971        if (url.empty()) {
1972            Tcl_AppendResult(interp, "file not found: \"",
1973                             urlIn, "\"", (char*)NULL);
1974            return TCL_ERROR;
1975        }
1976        if (GetBooleanFromObj(interp, objv[9], &cache) != TCL_OK) {
1977            return TCL_ERROR;
1978        }
1979        float r, g, b;
1980        if (GetFloatFromObj(interp, objv[10], &r) != TCL_OK ||
1981            GetFloatFromObj(interp, objv[11], &g) != TCL_OK ||
1982            GetFloatFromObj(interp, objv[12], &b) != TCL_OK) {
1983            return TCL_ERROR;
1984        }
1985        float ptSize;
1986        if (GetFloatFromObj(interp, objv[13], &ptSize) != TCL_OK) {
1987            return TCL_ERROR;
1988        }
1989        bool lighting = true;
1990        osgEarth::Symbology::Style style;
1991        osgEarth::Symbology::PointSymbol *ps = style.getOrCreateSymbol<osgEarth::Symbology::PointSymbol>();
1992        ps->fill()->color() = osgEarth::Symbology::Color(r, g, b);
1993        ps->size() = ptSize;
1994
1995        osgEarth::Symbology::AltitudeSymbol::Clamping clamping =
1996            osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
1997        if (objc > 14) {
1998            clamping = ParseClamping(Tcl_GetString(objv[14]), clamping);
1999        }
2000        osgEarth::Symbology::AltitudeSymbol::Technique technique =
2001            osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
2002        if (objc > 15) {
2003            technique = ParseClampingTechnique(Tcl_GetString(objv[15]), technique);
2004        }
2005        osgEarth::Symbology::AltitudeSymbol *alt = style.getOrCreateSymbol<osgEarth::Symbology::AltitudeSymbol>();
2006        alt->clamping() = clamping;
2007        alt->technique() = technique;
2008        //alt->verticalOffset() = osgEarth::Symbology::NumericExpression();
2009        //alt->verticalScale() = osgEarth::Symbology::NumericExpression();
2010
2011#if 1 || defined(USE_DEPTH_OFFSET)
2012        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
2013        rs->depthOffset()->enabled() = true;
2014        rs->depthOffset()->minBias() = 1000;
2015#endif
2016        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
2017        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
2018        geomOpts.styles()->addStyle(style);
2019        geomOpts.enableLighting() = false;
2020        geomOpts.minRange() = 0.f;
2021        geomOpts.maxRange() = FLT_MAX;
2022        if (objc > 16) {
2023            float min, max;
2024            if (GetFloatFromObj(interp, objv[16], &min) != TCL_OK ||
2025                GetFloatFromObj(interp, objv[17], &max) != TCL_OK) {
2026                return TCL_ERROR;
2027            }
2028            geomOpts.minRange() = min;
2029            geomOpts.maxRange() = max;
2030        }
2031        //geomOpts.renderOrder() = int;
2032        //geomOpts.depthTestEnabled() = bool;
2033        if (driver[0] == 'o' && strcmp(driver, "ogr") == 0) {
2034            osgEarth::Drivers::OGRFeatureOptions opts;
2035            opts.name() = name;
2036            if (osgDB::getLowerCaseFileExtension(url) == "csv") {
2037                url = loadCSVLayer(url.c_str());
2038            }
2039            opts.url() = url;
2040            geomOpts.featureOptions() = opts;
2041        } else if (driver[0] == 't' && strcmp(driver, "tfs") == 0) {
2042            osgEarth::Drivers::TFSFeatureOptions opts;
2043            opts.name() = name;
2044            opts.url() = url;
2045            opts.format() = format;
2046            geomOpts.featureOptions() = opts;
2047        } else if (driver[0] == 'w' && strcmp(driver, "wfs") == 0) {
2048            osgEarth::Drivers::WFSFeatureOptions opts;
2049            opts.name() = name;
2050            opts.url() = url;
2051            opts.outputFormat() = format;
2052            opts.typeName() = typeName;
2053            geomOpts.featureOptions() = opts;
2054        } else {
2055            Tcl_AppendResult(interp, "unknown feature driver \"", driver,
2056                             "\": should be 'ogr', 'tfs', or 'wfs'", (char*)NULL);
2057            return TCL_ERROR;
2058        }
2059        g_renderer->addModelLayer(name, geomOpts, cache, lighting, visible);
2060    } else if (type[0] == 'p' && strcmp(type, "polygon") == 0) {
2061        char *driver = Tcl_GetString(objv[5]);
2062        char *format = Tcl_GetString(objv[6]);
2063        if (format && strlen(format) > 0 &&
2064            strcmp(format, "json") != 0 && strcmp(format, "gml") != 0) {
2065            Tcl_AppendResult(interp, "unknown format \"", format,
2066                             "\": should be 'json' or 'gml'", (char*)NULL);
2067            return TCL_ERROR;
2068        }
2069        char *typeName = Tcl_GetString(objv[7]);
2070        char *urlIn = Tcl_GetString(objv[8]);
2071        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
2072        if (url.empty()) {
2073            Tcl_AppendResult(interp, "file not found: \"",
2074                             urlIn, "\"", (char*)NULL);
2075            return TCL_ERROR;
2076        }
2077        if (GetBooleanFromObj(interp, objv[9], &cache) != TCL_OK) {
2078            return TCL_ERROR;
2079        }
2080        float r, g, b;
2081        if (GetFloatFromObj(interp, objv[10], &r) != TCL_OK ||
2082            GetFloatFromObj(interp, objv[11], &g) != TCL_OK ||
2083            GetFloatFromObj(interp, objv[12], &b) != TCL_OK) {
2084            return TCL_ERROR;
2085        }
2086        float lineWidth = 0.0f;
2087        if (objc > 13) {
2088            if (GetFloatFromObj(interp, objv[13], &lineWidth) != TCL_OK) {
2089                return TCL_ERROR;
2090            }
2091        }
2092        float strokeR = 0, strokeG = 0, strokeB = 0;
2093        if (objc > 14) {
2094            if (GetFloatFromObj(interp, objv[14], &strokeR) != TCL_OK ||
2095                GetFloatFromObj(interp, objv[15], &strokeG) != TCL_OK ||
2096                GetFloatFromObj(interp, objv[16], &strokeB) != TCL_OK) {
2097                return TCL_ERROR;
2098            }
2099        }
2100        bool lighting = true;
2101        osgEarth::Symbology::Style style;
2102        osgEarth::Symbology::PolygonSymbol *ps = style.getOrCreateSymbol<osgEarth::Symbology::PolygonSymbol>();
2103        ps->fill()->color() = osgEarth::Symbology::Color(r, g, b);
2104
2105        if (lineWidth > 0.0f) {
2106            osgEarth::Symbology::LineSymbol *ls = style.getOrCreateSymbol<osgEarth::Symbology::LineSymbol>();
2107            ls->stroke()->color() = osgEarth::Symbology::Color(strokeR, strokeG, strokeB);
2108            ls->stroke()->width() = lineWidth;
2109            ls->stroke()->widthUnits() = osgEarth::Units::PIXELS;
2110            ls->stroke()->lineCap() = osgEarth::Symbology::Stroke::LINECAP_FLAT; // _SQUARE, _ROUND
2111            //ls->stroke()->roundingRatio() = 0.4f;
2112            ls->stroke()->lineJoin() = osgEarth::Symbology::Stroke::LINEJOIN_MITRE; // _ROUND
2113            //ls->stroke()->stipplePattern() = 0; // Bitmask: unsigned short
2114            //ls->stroke()->stippleFactor() = 0; // num reps: unsigned
2115        }
2116
2117        osgEarth::Symbology::AltitudeSymbol::Clamping clamping =
2118            osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
2119        if (objc > 17) {
2120            clamping = ParseClamping(Tcl_GetString(objv[17]), clamping);
2121        }
2122        osgEarth::Symbology::AltitudeSymbol::Technique technique =
2123            osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
2124        if (objc > 18) {
2125            technique = ParseClampingTechnique(Tcl_GetString(objv[18]), technique);
2126        }
2127        osgEarth::Symbology::AltitudeSymbol *alt = style.getOrCreateSymbol<osgEarth::Symbology::AltitudeSymbol>();
2128        alt->clamping() = clamping;
2129        alt->technique() = technique;
2130        //alt->verticalOffset() = osgEarth::Symbology::NumericExpression();
2131        //alt->verticalScale() = osgEarth::Symbology::NumericExpression();
2132
2133#if 1 || defined(USE_DEPTH_OFFSET)
2134        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
2135        rs->depthOffset()->enabled() = true;
2136        rs->depthOffset()->minBias() = 1000;
2137#endif
2138        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
2139        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
2140        geomOpts.styles()->addStyle(style);
2141        geomOpts.enableLighting() = false;
2142        geomOpts.minRange() = 0.f;
2143        geomOpts.maxRange() = FLT_MAX;
2144        if (objc > 19) {
2145            float min, max;
2146            if (GetFloatFromObj(interp, objv[19], &min) != TCL_OK ||
2147                GetFloatFromObj(interp, objv[20], &max) != TCL_OK) {
2148                return TCL_ERROR;
2149            }
2150            geomOpts.minRange() = min;
2151            geomOpts.maxRange() = max;
2152        }
2153        if (driver[0] == 'o' && strcmp(driver, "ogr") == 0) {
2154            osgEarth::Drivers::OGRFeatureOptions opts;
2155            opts.name() = name;
2156            if (osgDB::getLowerCaseFileExtension(url) == "csv") {
2157                url = loadCSVLayer(url.c_str());
2158            }
2159            opts.url() = url;
2160            geomOpts.featureOptions() = opts;
2161        } else if (driver[0] == 't' && strcmp(driver, "tfs") == 0) {
2162            osgEarth::Drivers::TFSFeatureOptions opts;
2163            opts.name() = name;
2164            opts.url() = url;
2165            opts.format() = format;
2166            geomOpts.featureOptions() = opts;
2167        } else if (driver[0] == 'w' && strcmp(driver, "wfs") == 0) {
2168            osgEarth::Drivers::WFSFeatureOptions opts;
2169            opts.name() = name;
2170            opts.url() = url;
2171            opts.outputFormat() = format;
2172            opts.typeName() = typeName;
2173            geomOpts.featureOptions() = opts;
2174        } else {
2175            Tcl_AppendResult(interp, "unknown feature driver \"", driver,
2176                             "\": should be 'ogr', 'tfs', or 'wfs'", (char*)NULL);
2177            return TCL_ERROR;
2178        }
2179        g_renderer->addModelLayer(name, geomOpts, cache, lighting, visible);
2180    } else if (type[0] == 'l' && strcmp(type, "line") == 0) {
2181        char *driver = Tcl_GetString(objv[5]);
2182        char *format = Tcl_GetString(objv[6]);
2183        if (format && strlen(format) > 0 &&
2184            strcmp(format, "json") != 0 && strcmp(format, "gml") != 0) {
2185            Tcl_AppendResult(interp, "unknown format \"", format,
2186                             "\": should be 'json' or 'gml'", (char*)NULL);
2187            return TCL_ERROR;
2188        }
2189        char *typeName = Tcl_GetString(objv[7]);
2190        char *urlIn = Tcl_GetString(objv[8]);
2191        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
2192        if (url.empty()) {
2193            Tcl_AppendResult(interp, "file not found: \"",
2194                             urlIn, "\"", (char*)NULL);
2195            return TCL_ERROR;
2196        }
2197        if (GetBooleanFromObj(interp, objv[9], &cache) != TCL_OK) {
2198            return TCL_ERROR;
2199        }
2200        float r, g, b;
2201        if (GetFloatFromObj(interp, objv[10], &r) != TCL_OK ||
2202            GetFloatFromObj(interp, objv[11], &g) != TCL_OK ||
2203            GetFloatFromObj(interp, objv[12], &b) != TCL_OK) {
2204            return TCL_ERROR;
2205        }
2206        float lineWidth;
2207        osgEarth::Units units = osgEarth::Units::PIXELS;
2208        if (!osgEarth::Units::parse(Tcl_GetString(objv[13]), lineWidth,
2209                                    units, osgEarth::Units::PIXELS)) {
2210            Tcl_AppendResult(interp, "Unkown units: \"",
2211                             Tcl_GetString(objv[13]), "\"", (char*)NULL);
2212            return TCL_ERROR;
2213        }
2214
2215        osgEarth::Symbology::Stroke::LineCapStyle cap = osgEarth::Symbology::Stroke::LINECAP_FLAT;
2216        osgEarth::Symbology::Stroke::LineJoinStyle join =  osgEarth::Symbology::Stroke::LINEJOIN_MITRE;
2217        if (objc > 14) {
2218            cap = ParseLineCapStyle(Tcl_GetString(objv[14]), cap);
2219        }
2220        if (objc > 15) {
2221            join = ParseLineJoinStyle(Tcl_GetString(objv[15]), join);
2222        }
2223        bool lighting = true;
2224        osgEarth::Symbology::Style style;
2225        osgEarth::Symbology::LineSymbol *ls = style.getOrCreateSymbol<osgEarth::Symbology::LineSymbol>();
2226        ls->stroke()->color() = osgEarth::Symbology::Color(r, g, b);
2227        ls->stroke()->width() = lineWidth;
2228        ls->stroke()->widthUnits() = units;
2229        ls->stroke()->minPixels() = 1.0f;
2230        ls->stroke()->lineCap() = cap;
2231        //ls->stroke()->roundingRatio() = 0.4f;
2232        ls->stroke()->lineJoin() = join;
2233        if (objc > 16) {
2234            unsigned short stipplePattern = 0U;
2235            unsigned int stippleFactor = 1U;
2236            if (GetUShortFromObj(interp, objv[16], &stipplePattern) != TCL_OK ||
2237                GetUIntFromObj(interp, objv[17], &stippleFactor) != TCL_OK) {
2238                return TCL_ERROR;
2239            }
2240            if (stipplePattern > 0U) {
2241                ls->stroke()->stipplePattern() = stipplePattern; // Bitmask: unsigned short
2242                ls->stroke()->stippleFactor() = stippleFactor; // num reps: unsigned
2243            }
2244        }
2245
2246        osgEarth::Symbology::AltitudeSymbol::Clamping clamping =
2247            osgEarth::Symbology::AltitudeSymbol::CLAMP_TO_TERRAIN;
2248        if (objc > 18) {
2249            clamping = ParseClamping(Tcl_GetString(objv[18]), clamping);
2250        }
2251        osgEarth::Symbology::AltitudeSymbol::Technique technique =
2252            osgEarth::Symbology::AltitudeSymbol::TECHNIQUE_GPU;
2253        if (objc > 18) {
2254            technique = ParseClampingTechnique(Tcl_GetString(objv[19]), technique);
2255        }
2256        osgEarth::Symbology::AltitudeSymbol *alt = style.getOrCreateSymbol<osgEarth::Symbology::AltitudeSymbol>();
2257        alt->clamping() = clamping;
2258        alt->technique() = technique;
2259        //alt->clampingResolution() = 1.0f;
2260        //alt->binding() = osgEarth::Symbology::AltitudeSymbol::BINDING_VERTEX; //BINDING_CENTROID
2261        //alt->verticalOffset() = osgEarth::Symbology::NumericExpression();
2262        //alt->verticalScale() = osgEarth::Symbology::NumericExpression();
2263
2264#if 1 || defined(USE_DEPTH_OFFSET)
2265        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
2266        rs->depthOffset()->enabled() = true;
2267        rs->depthOffset()->minBias() = 1000;
2268#endif
2269        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
2270        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
2271        geomOpts.styles()->addStyle(style);
2272        geomOpts.enableLighting() = false;
2273        geomOpts.minRange() = 0.f;
2274        geomOpts.maxRange() = FLT_MAX;
2275        if (objc > 20) {
2276            float min, max;
2277            if (GetFloatFromObj(interp, objv[20], &min) != TCL_OK ||
2278                GetFloatFromObj(interp, objv[21], &max) != TCL_OK) {
2279                return TCL_ERROR;
2280            }
2281            geomOpts.minRange() = min;
2282            geomOpts.maxRange() = max;
2283        }
2284        if (driver[0] == 'o' && strcmp(driver, "ogr") == 0) {
2285            osgEarth::Drivers::OGRFeatureOptions opts;
2286            opts.name() = name;
2287            if (osgDB::getLowerCaseFileExtension(url) == "csv") {
2288                url = loadCSVLayer(url.c_str());
2289            }
2290            opts.url() = url;
2291            geomOpts.featureOptions() = opts;
2292        } else if (driver[0] == 't' && strcmp(driver, "tfs") == 0) {
2293            osgEarth::Drivers::TFSFeatureOptions opts;
2294            opts.name() = name;
2295            opts.url() = url;
2296            opts.format() = format;
2297            geomOpts.featureOptions() = opts;
2298        } else if (driver[0] == 'w' && strcmp(driver, "wfs") == 0) {
2299            osgEarth::Drivers::WFSFeatureOptions opts;
2300            opts.name() = name;
2301            opts.url() = url;
2302            opts.outputFormat() = format;
2303            opts.typeName() = typeName;
2304            geomOpts.featureOptions() = opts;
2305        } else {
2306            Tcl_AppendResult(interp, "unknown feature driver \"", driver,
2307                             "\": should be 'ogr', 'tfs', or 'wfs'", (char*)NULL);
2308            return TCL_ERROR;
2309        }
2310        g_renderer->addModelLayer(name, geomOpts, cache, lighting, visible);
2311   } else if (type[0] == 't' && strcmp(type, "text") == 0) {
2312        char *driver = Tcl_GetString(objv[5]);
2313        char *format = Tcl_GetString(objv[6]);
2314        if (format && strlen(format) > 0 &&
2315            strcmp(format, "json") != 0 && strcmp(format, "gml") != 0) {
2316            Tcl_AppendResult(interp, "unknown format \"", format,
2317                             "\": should be 'json' or 'gml'", (char*)NULL);
2318            return TCL_ERROR;
2319        }
2320        char *typeName = Tcl_GetString(objv[7]);
2321        char *urlIn = Tcl_GetString(objv[8]);
2322        std::string url = g_renderer->getCanonicalPath(std::string(urlIn));
2323        if (url.empty()) {
2324            Tcl_AppendResult(interp, "file not found: \"",
2325                             urlIn, "\"", (char*)NULL);
2326            return TCL_ERROR;
2327        }
2328        if (GetBooleanFromObj(interp, objv[9], &cache) != TCL_OK) {
2329            return TCL_ERROR;
2330        }
2331        char *content = Tcl_GetString(objv[10]);
2332        char *priority = Tcl_GetString(objv[11]);
2333        float fgR, fgG, fgB;
2334        float bgR, bgG, bgB;
2335        float haloWidth, ftSize;
2336        osg::Vec2s pixelOffset(0, 0);
2337        if (GetFloatFromObj(interp, objv[12], &fgR) != TCL_OK ||
2338            GetFloatFromObj(interp, objv[13], &fgG) != TCL_OK ||
2339            GetFloatFromObj(interp, objv[14], &fgB) != TCL_OK ||
2340            GetFloatFromObj(interp, objv[15], &bgR) != TCL_OK ||
2341            GetFloatFromObj(interp, objv[16], &bgG) != TCL_OK ||
2342            GetFloatFromObj(interp, objv[17], &bgB) != TCL_OK ||
2343            GetFloatFromObj(interp, objv[18], &haloWidth) != TCL_OK ||
2344            GetFloatFromObj(interp, objv[19], &ftSize) != TCL_OK) {
2345            return TCL_ERROR;
2346        }
2347        bool removeDupes, declutter;
2348        if (GetBooleanFromObj(interp, objv[20], &removeDupes) != TCL_OK ||
2349            GetBooleanFromObj(interp, objv[21], &declutter) != TCL_OK) {
2350            return TCL_ERROR;
2351        }
2352        osgEarth::Symbology::TextSymbol::Alignment alignment =
2353            ParseTextAlignment(Tcl_GetString(objv[22]));
2354        int xoff, yoff;
2355        if (Tcl_GetIntFromObj(interp, objv[23], &xoff) != TCL_OK ||
2356            Tcl_GetIntFromObj(interp, objv[24], &yoff) != TCL_OK) {
2357            return TCL_ERROR;
2358        }
2359        pixelOffset.x() = (short)xoff;
2360        pixelOffset.y() = (short)yoff;
2361        bool lighting = true;
2362        osgEarth::Symbology::Style style;
2363        osgEarth::Symbology::TextSymbol *ts = style.getOrCreateSymbol<osgEarth::Symbology::TextSymbol>();
2364        ts->halo()->color() = osgEarth::Symbology::Color(bgR, bgG, bgB);
2365        ts->halo()->width() = haloWidth;
2366        ts->fill()->color() = osgEarth::Symbology::Color(fgR, fgG, fgB);
2367        ts->content() = osgEarth::Symbology::StringExpression(content);
2368        if (priority != NULL && strlen(priority) > 0) {
2369            ts->priority() = osgEarth::Symbology::NumericExpression(priority);
2370        }
2371        ts->removeDuplicateLabels() = removeDupes;
2372        //ts->font() = "Arial";
2373        ts->size() = ftSize;
2374        ts->alignment() = alignment;
2375        ts->declutter() = declutter;
2376        ts->pixelOffset() = pixelOffset;
2377        ts->encoding() = osgEarth::Symbology::TextSymbol::ENCODING_UTF8;
2378#ifdef USE_DEPTH_OFFSET
2379        osgEarth::Symbology::RenderSymbol* rs = style.getOrCreateSymbol<osgEarth::Symbology::RenderSymbol>();
2380        rs->depthOffset()->enabled() = true;
2381        rs->depthOffset()->minBias() = 1000;
2382#endif
2383        osgEarth::Drivers::FeatureGeomModelOptions geomOpts;
2384        geomOpts.styles() = new osgEarth::Symbology::StyleSheet();
2385        geomOpts.styles()->addStyle(style);
2386        geomOpts.enableLighting() = false;
2387        geomOpts.minRange() = 0.f;
2388        geomOpts.maxRange() = FLT_MAX;
2389        if (objc > 25) {
2390            float min, max;
2391            if (GetFloatFromObj(interp, objv[25], &min) != TCL_OK ||
2392                GetFloatFromObj(interp, objv[26], &max) != TCL_OK) {
2393                return TCL_ERROR;
2394            }
2395            geomOpts.minRange() = min;
2396            geomOpts.maxRange() = max;
2397        }
2398        if (driver[0] == 'o' && strcmp(driver, "ogr") == 0) {
2399            osgEarth::Drivers::OGRFeatureOptions opts;
2400            opts.name() = name;
2401            if (osgDB::getLowerCaseFileExtension(url) == "csv") {
2402                url = loadCSVLayer(url.c_str());
2403            }
2404            opts.url() = url;
2405            geomOpts.featureOptions() = opts;
2406        } else if (driver[0] == 't' && strcmp(driver, "tfs") == 0) {
2407            osgEarth::Drivers::TFSFeatureOptions opts;
2408            opts.name() = name;
2409            opts.url() = url;
2410            opts.format() = format;
2411            geomOpts.featureOptions() = opts;
2412        } else if (driver[0] == 'w' && strcmp(driver, "wfs") == 0) {
2413            osgEarth::Drivers::WFSFeatureOptions opts;
2414            opts.name() = name;
2415            opts.url() = url;
2416            opts.outputFormat() = format;
2417            opts.typeName() = typeName;
2418            geomOpts.featureOptions() = opts;
2419        } else {
2420            Tcl_AppendResult(interp, "unknown feature driver \"", driver,
2421                             "\": should be 'ogr', 'tfs', or 'wfs'", (char*)NULL);
2422            return TCL_ERROR;
2423        }
2424        g_renderer->addModelLayer(name, geomOpts, cache, lighting, visible);
2425    } else {
2426        Tcl_AppendResult(interp, "unknown map layer type \"", type,
2427                         "\": should be 'image', 'elevation' or 'model'", (char*)NULL);
2428        return TCL_ERROR;
2429    }
2430    return TCL_OK;
2431}
2432
2433static int
2434MapLayerDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
2435                 Tcl_Obj *const *objv)
2436{
2437    if (objc > 3) {
2438        char *name = Tcl_GetString(objv[3]);
2439        g_renderer->removeImageLayer(name);
2440        g_renderer->removeElevationLayer(name);
2441        g_renderer->removeModelLayer(name);
2442    } else {
2443        g_renderer->clearMap();
2444    }
2445
2446    return TCL_OK;
2447}
2448
2449static int
2450MapLayerMoveOp(ClientData clientData, Tcl_Interp *interp, int objc,
2451               Tcl_Obj *const *objv)
2452{
2453    int pos;
2454    if (Tcl_GetIntFromObj(interp, objv[3], &pos) != TCL_OK) {
2455        return TCL_ERROR;
2456    }
2457    char *name = Tcl_GetString(objv[4]);
2458    if (pos < 0) {
2459        Tcl_AppendResult(interp, "bad layer pos ", pos,
2460                         ": must be positive", (char*)NULL);
2461        return TCL_ERROR;
2462    }
2463    g_renderer->moveImageLayer(name, (unsigned int)pos);
2464    g_renderer->moveElevationLayer(name, (unsigned int)pos);
2465    g_renderer->moveModelLayer(name, (unsigned int)pos);
2466
2467    return TCL_OK;
2468}
2469
2470static int
2471MapLayerOpacityOp(ClientData clientData, Tcl_Interp *interp, int objc,
2472                  Tcl_Obj *const *objv)
2473{
2474    double opacity;
2475    if (Tcl_GetDoubleFromObj(interp, objv[3], &opacity) != TCL_OK) {
2476        return TCL_ERROR;
2477    }
2478    char *name = Tcl_GetString(objv[4]);
2479    if (opacity < 0.0 || opacity > 1.0) {
2480        Tcl_AppendResult(interp, "bad layer opacity ", opacity,
2481                         ": must be [0,1]", (char*)NULL);
2482        return TCL_ERROR;
2483    }
2484    g_renderer->setImageLayerOpacity(name, opacity);
2485    g_renderer->setModelLayerOpacity(name, opacity);
2486
2487    return TCL_OK;
2488}
2489
2490static int
2491MapLayerNamesOp(ClientData clientData, Tcl_Interp *interp, int objc,
2492                Tcl_Obj *const *objv)
2493{
2494    std::vector<std::string> layers;
2495    if (objc < 4) {
2496        g_renderer->getImageLayerNames(layers);
2497        g_renderer->getElevationLayerNames(layers);
2498        g_renderer->getModelLayerNames(layers);
2499    } else {
2500        char *type = Tcl_GetString(objv[3]);
2501        if (type[0] == 'i' && strcmp(type, "image") == 0) {
2502            g_renderer->getImageLayerNames(layers);
2503        } else if (type[0] == 'e' && strcmp(type, "elevation") == 0) {
2504            g_renderer->getElevationLayerNames(layers);
2505        } else if (type[0] == 'm' && strcmp(type, "model") == 0) {
2506            g_renderer->getModelLayerNames(layers);
2507        } else {
2508            Tcl_AppendResult(interp, "uknown type \"", type,
2509                         "\": must be image, elevation or model", (char*)NULL);
2510            return TCL_ERROR;
2511        }
2512    }
2513    std::ostringstream oss;
2514    size_t len = 0;
2515    oss << "nv>map names {";
2516    len += 18;
2517    for (size_t i = 0; i < layers.size(); i++) {
2518        oss << "\"" << layers[i] << "\"";
2519        len += 2 + layers[i].length();
2520        if (i < layers.size() - 1) {
2521            oss << " ";
2522            len++;
2523        }
2524    }
2525    oss << "}\n";
2526    len += 2;
2527#ifdef USE_THREADS
2528    queueResponse(oss.str().c_str(), len, Response::VOLATILE);
2529#else
2530    ssize_t bytesWritten = SocketWrite(oss.str().c_str(), len);
2531
2532    if (bytesWritten < 0) {
2533        return TCL_ERROR;
2534    }
2535#endif /*USE_THREADS*/
2536    return TCL_OK;
2537}
2538
2539static int
2540MapLayerVisibleOp(ClientData clientData, Tcl_Interp *interp, int objc,
2541                  Tcl_Obj *const *objv)
2542{
2543    bool visible;
2544    if (GetBooleanFromObj(interp, objv[3], &visible) != TCL_OK) {
2545        return TCL_ERROR;
2546    }
2547
2548    if (objc > 4) {
2549        char *name = Tcl_GetString(objv[4]);
2550        g_renderer->setImageLayerVisibility(name, visible);
2551        g_renderer->setElevationLayerVisibility(name, visible);
2552        g_renderer->setModelLayerVisibility(name, visible);
2553    } else {
2554        std::vector<std::string> layers;
2555        g_renderer->getImageLayerNames(layers);
2556        g_renderer->getElevationLayerNames(layers);
2557        g_renderer->getModelLayerNames(layers);
2558        for (std::vector<std::string>::iterator itr = layers.begin();
2559             itr != layers.end(); ++itr) {
2560            g_renderer->setImageLayerVisibility(itr->c_str(), visible);
2561            g_renderer->setElevationLayerVisibility(itr->c_str(), visible);
2562            g_renderer->setModelLayerVisibility(itr->c_str(), visible);
2563        }
2564    }
2565
2566    return TCL_OK;
2567}
2568
2569static CmdSpec mapLayerOps[] = {
2570    {"add",     1, MapLayerAddOp,       6, 0, "name type driver ?url? ?args?"},
2571    {"delete",  1, MapLayerDeleteOp,    3, 4, "?name?"},
2572    {"move",    1, MapLayerMoveOp,      5, 5, "pos name"},
2573    {"names",   1, MapLayerNamesOp,     3, 4, "?type?"},
2574    {"opacity", 1, MapLayerOpacityOp,   5, 5, "opacity name"},
2575    {"visible", 1, MapLayerVisibleOp,   4, 5, "bool ?name?"},
2576};
2577static int nMapLayerOps = NumCmdSpecs(mapLayerOps);
2578
2579static int
2580MapLayerOp(ClientData clientData, Tcl_Interp *interp, int objc,
2581           Tcl_Obj *const *objv)
2582{
2583    Tcl_ObjCmdProc *proc;
2584
2585    proc = GetOpFromObj(interp, nMapLayerOps, mapLayerOps,
2586                        CMDSPEC_ARG2, objc, objv, 0);
2587    if (proc == NULL) {
2588        return TCL_ERROR;
2589    }
2590    return (*proc) (clientData, interp, objc, objv);
2591}
2592
2593static int
2594MapLoadOp(ClientData clientData, Tcl_Interp *interp, int objc,
2595          Tcl_Obj *const *objv)
2596{
2597    char *opt = Tcl_GetString(objv[2]);
2598    if (opt[0] == 'f' && strcmp(opt, "file") == 0) {
2599        g_renderer->loadEarthFile(Tcl_GetString(objv[3]));
2600    } else if (opt[0] == 'u' && strcmp(opt, "url") == 0) {
2601        std::ostringstream path;
2602        path << "server:" << Tcl_GetString(objv[3]);
2603        g_renderer->loadEarthFile(path.str().c_str());
2604    } else if (opt[0] == 'd' && strcmp(opt, "data") == 0) {
2605        opt = Tcl_GetString(objv[3]);
2606        if (opt[0] != 'f' || strcmp(opt, "follows") != 0) {
2607            return TCL_ERROR;
2608        }
2609        int len;
2610        if (Tcl_GetIntFromObj(interp, objv[4], &len) != TCL_OK) {
2611            return TCL_ERROR;
2612        }
2613        // Read Earth file from socket
2614        char *buf = (char *)malloc((size_t)len);
2615        SocketRead(buf, (size_t)len);
2616        std::ostringstream path;
2617        path << "/tmp/tmp" << getpid() << ".earth";
2618        const char *pathStr = path.str().c_str();
2619        FILE *tmpFile = fopen(pathStr, "w");
2620        fwrite(buf, len, 1, tmpFile);
2621        fclose(tmpFile);
2622        g_renderer->loadEarthFile(pathStr);
2623        unlink(pathStr);
2624        free(buf);
2625    } else {
2626        return TCL_ERROR;
2627    }
2628    return TCL_OK;
2629}
2630
2631static int
2632MapPinAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
2633            Tcl_Obj *const *objv)
2634{
2635    int x, y;
2636    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
2637        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
2638        return TCL_ERROR;
2639    }
2640    char *label = NULL;
2641    if (objc > 5) {
2642        label = Tcl_GetString(objv[5]);
2643    }
2644
2645    if (g_renderer->getMapSRS() == NULL) {
2646        Tcl_AppendResult(interp, "Could not get map SRS", (char*)NULL);
2647        return TCL_ERROR;
2648    }
2649
2650    // Get lat/long
2651    double latitude, longitude;
2652    if (!g_renderer->mouseToLatLong(x, y, &latitude, &longitude)) {
2653        USER_ERROR("Can't add pin here");
2654        return TCL_OK;
2655    }
2656
2657    g_renderer->addPlaceNode(latitude, longitude, label);
2658    return TCL_OK;
2659}
2660
2661static int
2662MapPinDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
2663               Tcl_Obj *const *objv)
2664{
2665    int x, y;
2666    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
2667        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
2668        return TCL_ERROR;
2669    }
2670
2671    g_renderer->deletePlaceNode(x, y);
2672    return TCL_OK;
2673}
2674
2675static int
2676MapPinHoverOp(ClientData clientData, Tcl_Interp *interp, int objc,
2677              Tcl_Obj *const *objv)
2678{
2679    int x, y;
2680    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK ||
2681        Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
2682        return TCL_ERROR;
2683    }
2684
2685    g_renderer->hoverPlaceNode(x, y);
2686    return TCL_OK;
2687}
2688
2689static CmdSpec mapPinOps[] = {
2690    {"add",     1, MapPinAddOp,     5, 6, "x y ?label?"},
2691    {"delete",  1, MapPinDeleteOp,  5, 5, "x y"},
2692    {"hover",   1, MapPinHoverOp,   5, 5, "x y"},
2693};
2694static int nMapPinOps = NumCmdSpecs(mapPinOps);
2695
2696static int
2697MapPinOp(ClientData clientData, Tcl_Interp *interp, int objc,
2698           Tcl_Obj *const *objv)
2699{
2700    Tcl_ObjCmdProc *proc;
2701
2702    proc = GetOpFromObj(interp, nMapPinOps, mapPinOps,
2703                        CMDSPEC_ARG2, objc, objv, 0);
2704    if (proc == NULL) {
2705        return TCL_ERROR;
2706    }
2707    return (*proc) (clientData, interp, objc, objv);
2708}
2709
2710static int
2711MapPositionDisplayOp(ClientData clientData, Tcl_Interp *interp, int objc,
2712                     Tcl_Obj *const *objv)
2713{
2714    bool state;
2715    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
2716        return TCL_ERROR;
2717    }
2718    Renderer::CoordinateDisplayType type = Renderer::COORDS_LATLONG_DECIMAL_DEGREES;
2719    if (state && objc > 3) {
2720        const char *str = Tcl_GetString(objv[3]);
2721        if (str[0] == 'l' && strcmp(str, "latlong_decimal_degrees") == 0) {
2722            type = Renderer::COORDS_LATLONG_DECIMAL_DEGREES;
2723        } else if (str[0] == 'l' && strcmp(str, "latlong_degrees_decimal_minutes") == 0) {
2724            type = Renderer::COORDS_LATLONG_DEGREES_DECIMAL_MINUTES;
2725        } else if (str[0] == 'l' && strcmp(str, "latlong_degrees_minutes_seconds") == 0) {
2726            type = Renderer::COORDS_LATLONG_DEGREES_MINUTES_SECONDS;
2727        } else if (str[0] == 'm' && strcmp(str, "mgrs") == 0) {
2728            type = Renderer::COORDS_MGRS;
2729        } else {
2730            Tcl_AppendResult(interp, "invalid type: \"", str,
2731                             "\": should be 'latlong_decimal_degrees', 'latlong_degrees_decimal_minutes', 'latlong_degrees_minutes_seconds', or 'mgrs'",
2732                             (char*)NULL);
2733            return TCL_ERROR;
2734        }
2735    }
2736    if (state && objc > 4) {
2737        int precision;
2738        if (Tcl_GetIntFromObj(interp, objv[4], &precision) != TCL_OK) {
2739            return TCL_ERROR;
2740        }
2741        g_renderer->setCoordinateReadout(state, type, precision);
2742    } else {
2743        g_renderer->setCoordinateReadout(state, type);
2744    }
2745
2746    return TCL_OK;
2747}
2748
2749static int
2750MapResetOp(ClientData clientData, Tcl_Interp *interp, int objc,
2751           Tcl_Obj *const *objv)
2752{
2753    char *typeStr = Tcl_GetString(objv[2]);
2754    osgEarth::MapOptions::CoordinateSystemType type;
2755    if (typeStr[0] == 'g' && strcmp(typeStr, "geocentric") == 0) {
2756        type = osgEarth::MapOptions::CSTYPE_GEOCENTRIC;
2757    } else if (typeStr[0] == 'g' && strcmp(typeStr, "geocentric_cube") == 0) {
2758        type = osgEarth::MapOptions::CSTYPE_GEOCENTRIC_CUBE;
2759    } else if (typeStr[0] == 'p' && strcmp(typeStr, "projected") == 0) {
2760        type = osgEarth::MapOptions::CSTYPE_PROJECTED;
2761    } else {
2762        Tcl_AppendResult(interp, "bad map type \"", typeStr,
2763                         "\": must be geocentric or projected", (char*)NULL);
2764        return TCL_ERROR;
2765    }
2766    float color[3];
2767    if (GetFloatFromObj(interp, objv[3], &color[0]) != TCL_OK ||
2768        GetFloatFromObj(interp, objv[4], &color[1]) != TCL_OK ||
2769        GetFloatFromObj(interp, objv[5], &color[2]) != TCL_OK) {
2770        return TCL_ERROR;
2771    }
2772
2773    osg::Vec4f bgColor(color[0],color[1],color[2],1);
2774    if (type == osgEarth::MapOptions::CSTYPE_PROJECTED) {
2775        if (objc < 7) {
2776            Tcl_AppendResult(interp, "wrong # arguments: profile required for projected maps", (char*)NULL);
2777            return TCL_ERROR;
2778        }
2779        char *profile = Tcl_GetString(objv[6]);
2780        if (objc > 7) {
2781            if (objc < 11) {
2782                Tcl_AppendResult(interp, "wrong # arguments: 4 bounds arguments required", (char*)NULL);
2783                return TCL_ERROR;
2784            }
2785            double bounds[4];
2786            for (int i = 0; i < 4; i++) {
2787                if (Tcl_GetDoubleFromObj(interp, objv[7+i], &bounds[i]) != TCL_OK) {
2788                    return TCL_ERROR;
2789                }
2790            }
2791            // Check if min > max
2792            if (bounds[0] > bounds[2] ||
2793                bounds[1] > bounds[3]) {
2794                Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
2795                return TCL_ERROR;
2796            }
2797            // Note: plate-carre generates same SRS as others, but with
2798            // _is_plate_carre flag set
2799            // In map profile, _is_plate_carre is forced on for
2800            // geographic+projected SRS
2801            if (strcmp(profile, "geodetic") == 0 ||
2802                strcmp(profile, "epsg:4326") == 0 ||
2803                strcmp(profile, "wgs84") == 0 ||
2804                strcmp(profile, "plate-carre") == 0 ||
2805                strcmp(profile, "plate-carree") == 0) {
2806                if (bounds[0] < -180. || bounds[0] > 180. ||
2807                    bounds[2] < -180. || bounds[2] > 180. ||
2808                    bounds[1] < -90. || bounds[1] > 90. ||
2809                    bounds[3] < -90. || bounds[3] > 90.) {
2810                    Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
2811                    return TCL_ERROR;
2812                }
2813                // As of osgearth 2.7, these are not permitted as map profiles.
2814                // Use epsg:32663 instead
2815                Tcl_AppendResult(interp, "Invalid profile: can't use geographic coordinate system as projection.  Consider using an equirectangular projection (epsg:32663) instead.", (char*)NULL);
2816                return TCL_ERROR;
2817            } else if (strcmp(profile, "spherical-mercator") == 0 ||
2818                       strcmp(profile, "epsg:900913") == 0 ||
2819                       strcmp(profile, "epsg:3785") == 0 ||
2820                       strcmp(profile, "epsg:3857") == 0 ||
2821                       strcmp(profile, "epsg:102113") == 0) {
2822                for (int i = 0; i < 4; i++) {
2823                    if (bounds[i] < -20037508.34278925 || bounds[i] > 20037508.34278925) {
2824                        Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
2825                        return TCL_ERROR;
2826                    }
2827                }
2828            } else if (strcmp(profile, "epsg:32662") == 0 ||
2829                       strcmp(profile, "epsg:32663") == 0) {
2830                // epsg:32662 is deprecated: spherical method applied to ellipsoid WGS84
2831                // Equirectangular projection (WGS84/World Equidistant Cylindrical)
2832                if (bounds[0] < -20037508.34278925 || bounds[0] > 20037508.34278925 ||
2833                    bounds[2] < -20037508.34278925 || bounds[2] > 20037508.34278925 ||
2834                    bounds[1] < -10018754.17139463 || bounds[1] > 10018754.17139463 ||
2835                    bounds[3] < -10018754.17139463 || bounds[3] > 10018754.17139463) {
2836                    Tcl_AppendResult(interp, "invalid bounds", (char*)NULL);
2837                    return TCL_ERROR;
2838                }
2839            }
2840            g_renderer->resetMap(type, bgColor, profile, bounds);
2841        } else {
2842            // If no bounds are given, this must be a named profile with implicit bounds
2843            if (osgEarth::Registry::instance()->getNamedProfile(profile) == NULL) {
2844                Tcl_AppendResult(interp, "bad named profile \"", profile,
2845                                 "\": must be e.g. 'global-geodetic', 'global-mercator'...", (char*)NULL);
2846                return TCL_ERROR;
2847            }
2848            g_renderer->resetMap(type, bgColor, profile);
2849        }
2850    } else {
2851        // No profile required for geocentric (3D) maps
2852        g_renderer->resetMap(type, bgColor);
2853    }
2854
2855    return TCL_OK;
2856}
2857
2858static int
2859MapScaleBarOp(ClientData clientData, Tcl_Interp *interp, int objc,
2860              Tcl_Obj *const *objv)
2861{
2862    bool state;
2863    if (GetBooleanFromObj(interp, objv[2], &state) != TCL_OK) {
2864        return TCL_ERROR;
2865    }
2866    g_renderer->setScaleBar(state);
2867    if (state && objc > 3) {
2868        const char *unitStr = Tcl_GetString(objv[3]);
2869        ScaleBarUnits units;
2870        if (unitStr[0] == 'm' && strcmp(unitStr, "meters") == 0) {
2871            units = UNITS_METERS;
2872        } else if (unitStr[0] == 'f' && strcmp(unitStr, "feet") == 0) {
2873            units = UNITS_INTL_FEET;
2874        } else if (unitStr[0] == 'u' && strcmp(unitStr, "us_survey_feet") == 0) {
2875            units = UNITS_US_SURVEY_FEET;
2876        } else if (unitStr[0] == 'n' && strcmp(unitStr, "nautical_miles") == 0) {
2877            units = UNITS_NAUTICAL_MILES;
2878        } else {
2879            Tcl_AppendResult(interp, "bad units \"", unitStr,
2880                             "\": must be 'meters', 'feet', 'us_survey_feet' or 'nautical_miles'", (char*)NULL);
2881            return TCL_ERROR;
2882        }
2883        g_renderer->setScaleBarUnits(units);
2884    }
2885    return TCL_OK;
2886}
2887
2888static int
2889MapSetPositionOp(ClientData clientData, Tcl_Interp *interp, int objc,
2890            Tcl_Obj *const *objv)
2891{
2892    if (objc < 3) {
2893        g_renderer->clearReadout();
2894    } else {
2895        int x, y;
2896        if (Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK ||
2897            Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK) {
2898            return TCL_ERROR;
2899        }
2900        g_renderer->setReadout(x, y);
2901    }
2902    return TCL_OK;
2903}
2904
2905static int
2906MapTerrainAmbientOp(ClientData clientData, Tcl_Interp *interp, int objc,
2907                    Tcl_Obj *const *objv)
2908{
2909    float ambient;
2910    if (GetFloatFromObj(interp, objv[3], &ambient) != TCL_OK) {
2911        return TCL_ERROR;
2912    }
2913    g_renderer->setSkyAmbient(ambient);
2914    return TCL_OK;
2915}
2916
2917static int
2918MapTerrainColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
2919                  Tcl_Obj *const *objv)
2920{
2921    float color[3];
2922    if (GetFloatFromObj(interp, objv[3], &color[0]) != TCL_OK ||
2923        GetFloatFromObj(interp, objv[4], &color[1]) != TCL_OK ||
2924        GetFloatFromObj(interp, objv[5], &color[2]) != TCL_OK) {
2925        return TCL_ERROR;
2926    }
2927    g_renderer->setTerrainColor(osg::Vec4f(color[0],color[1],color[2],1));
2928    return TCL_OK;
2929}
2930
2931static int
2932MapTerrainEdgesOp(ClientData clientData, Tcl_Interp *interp, int objc,
2933                  Tcl_Obj *const *objv)
2934{
2935    bool state;
2936    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
2937        return TCL_ERROR;
2938    }
2939    g_renderer->setTerrainEdges(state);
2940    return TCL_OK;
2941}
2942
2943static int
2944MapTerrainLightingOp(ClientData clientData, Tcl_Interp *interp, int objc,
2945                     Tcl_Obj *const *objv)
2946{
2947    bool state;
2948    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
2949        return TCL_ERROR;
2950    }
2951
2952    g_renderer->setTerrainLighting(state);
2953    return TCL_OK;
2954}
2955
2956static int
2957MapTerrainLineColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
2958                      Tcl_Obj *const *objv)
2959{
2960    float color[3];
2961    if (GetFloatFromObj(interp, objv[3], &color[0]) != TCL_OK ||
2962        GetFloatFromObj(interp, objv[4], &color[1]) != TCL_OK ||
2963        GetFloatFromObj(interp, objv[5], &color[2]) != TCL_OK) {
2964        return TCL_ERROR;
2965    }
2966    g_renderer->setTerrainLineColor(osg::Vec4f(color[0], color[1], color[2],1));
2967    return TCL_OK;
2968}
2969
2970static int
2971MapTerrainLineWidthOp(ClientData clientData, Tcl_Interp *interp, int objc,
2972                      Tcl_Obj *const *objv)
2973{
2974    float width;
2975    if (GetFloatFromObj(interp, objv[3], &width) != TCL_OK) {
2976        return TCL_ERROR;
2977    }
2978    g_renderer->setTerrainLineWidth(width);
2979    return TCL_OK;
2980}
2981
2982static int
2983MapTerrainVertScaleOp(ClientData clientData, Tcl_Interp *interp, int objc,
2984                      Tcl_Obj *const *objv)
2985{
2986    double scale;
2987    if (Tcl_GetDoubleFromObj(interp, objv[3], &scale) != TCL_OK) {
2988        return TCL_ERROR;
2989    }
2990
2991    g_renderer->setTerrainVerticalScale(scale);
2992    return TCL_OK;
2993}
2994
2995static int
2996MapTerrainWireframeOp(ClientData clientData, Tcl_Interp *interp, int objc,
2997                      Tcl_Obj *const *objv)
2998{
2999    bool state;
3000    if (GetBooleanFromObj(interp, objv[3], &state) != TCL_OK) {
3001        return TCL_ERROR;
3002    }
3003
3004    g_renderer->setTerrainWireframe(state);
3005    return TCL_OK;
3006}
3007
3008static CmdSpec mapTerrainOps[] = {
3009    {"ambient",   1, MapTerrainAmbientOp,   4, 4, "val"},
3010    {"color",     1, MapTerrainColorOp,     6, 6, "r g b"},
3011    {"edges",     1, MapTerrainEdgesOp,     4, 4, "bool"},
3012    {"lighting",  2, MapTerrainLightingOp,  4, 4, "bool"},
3013    {"linecolor", 5, MapTerrainLineColorOp, 6, 6, "r g b"},
3014    {"linewidth", 5, MapTerrainLineWidthOp, 4, 4, "val"},
3015    {"vertscale", 1, MapTerrainVertScaleOp, 4, 4, "val"},
3016    {"wireframe", 1, MapTerrainWireframeOp, 4, 4, "bool"},
3017};
3018static int nMapTerrainOps = NumCmdSpecs(mapTerrainOps);
3019
3020static int
3021MapTerrainOp(ClientData clientData, Tcl_Interp *interp, int objc,
3022           Tcl_Obj *const *objv)
3023{
3024    Tcl_ObjCmdProc *proc;
3025
3026    proc = GetOpFromObj(interp, nMapTerrainOps, mapTerrainOps,
3027                        CMDSPEC_ARG2, objc, objv, 0);
3028    if (proc == NULL) {
3029        return TCL_ERROR;
3030    }
3031    return (*proc) (clientData, interp, objc, objv);
3032}
3033
3034static int
3035MapTimeOp(ClientData clientData, Tcl_Interp *interp, int objc,
3036          Tcl_Obj *const *objv)
3037{
3038    osgEarth::DateTime now;
3039    int year, month, day;
3040    double hours;
3041    year = now.year();
3042    month = now.month();
3043    day = now.day();
3044    hours = now.hours();
3045    if (objc > 2) {
3046        if (Tcl_GetDoubleFromObj(interp, objv[2], &hours) != TCL_OK) {
3047            return TCL_ERROR;
3048        }
3049    }
3050    if (objc > 3) {
3051        if (Tcl_GetIntFromObj(interp, objv[3], &day) != TCL_OK) {
3052            return TCL_ERROR;
3053        }
3054    }
3055    if (objc > 4) {
3056        if (Tcl_GetIntFromObj(interp, objv[4], &month) != TCL_OK) {
3057            return TCL_ERROR;
3058        }
3059    }
3060    if (objc > 5) {
3061        if (Tcl_GetIntFromObj(interp, objv[5], &year) != TCL_OK) {
3062            return TCL_ERROR;
3063        }
3064    }
3065
3066    g_renderer->setEphemerisTime(year, month, day, hours);
3067    return TCL_OK;
3068}
3069
3070static CmdSpec mapOps[] = {
3071    {"attrib",   1, MapAttributionOp,     3, 3, "string"},
3072    {"box",      1, MapBoxOp,             3, 0, "op ?params..."},
3073    {"coords",   1, MapCoordsOp,          4, 6, "token coords ?srs? ?verticalDatum?"},
3074    {"grid",     1, MapGraticuleOp,       3, 4, "bool ?type?"},
3075    {"layer",    2, MapLayerOp,           3, 0, "op ?params...?"},
3076    {"load",     2, MapLoadOp,            4, 5, "options"},
3077    {"pin",      2, MapPinOp,             3, 0, "op ?params...?"},
3078    {"posdisp",  2, MapPositionDisplayOp, 3, 5, "bool ?format? ?precision?"},
3079    {"reset",    1, MapResetOp,           6, 11, "type r g b ?profile xmin ymin xmax ymax?"},
3080    {"scalebar", 1, MapScaleBarOp,        3, 4, "bool ?units?"},
3081    {"setpos",   1, MapSetPositionOp,     2, 4, "x y"},
3082    {"terrain",  1, MapTerrainOp,         3, 0, "op ?params...?"},
3083    {"time",     1, MapTimeOp,            2, 6, "?hours? ?day? ?month? ?year?"},
3084};
3085static int nMapOps = NumCmdSpecs(mapOps);
3086
3087static int
3088MapCmd(ClientData clientData, Tcl_Interp *interp, int objc,
3089       Tcl_Obj *const *objv)
3090{
3091    Tcl_ObjCmdProc *proc;
3092
3093    proc = GetOpFromObj(interp, nMapOps, mapOps,
3094                        CMDSPEC_ARG1, objc, objv, 0);
3095    if (proc == NULL) {
3096        return TCL_ERROR;
3097    }
3098    return (*proc) (clientData, interp, objc, objv);
3099}
3100
3101static int
3102MouseClickOp(ClientData clientData, Tcl_Interp *interp, int objc,
3103             Tcl_Obj *const *objv)
3104{
3105    int button;
3106    double x, y;
3107
3108    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
3109        return TCL_ERROR;
3110    }
3111    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
3112        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
3113        return TCL_ERROR;
3114    }
3115
3116    g_renderer->mouseClick(button, x, y);
3117    return TCL_OK;
3118}
3119
3120static int
3121MouseDoubleClickOp(ClientData clientData, Tcl_Interp *interp, int objc,
3122                   Tcl_Obj *const *objv)
3123{
3124    int button;
3125    double x, y;
3126
3127    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
3128        return TCL_ERROR;
3129    }
3130    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
3131        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
3132        return TCL_ERROR;
3133    }
3134
3135    g_renderer->mouseDoubleClick(button, x, y);
3136    return TCL_OK;
3137}
3138
3139static int
3140MouseDragOp(ClientData clientData, Tcl_Interp *interp, int objc,
3141            Tcl_Obj *const *objv)
3142{
3143    int button;
3144    double x, y;
3145
3146    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
3147        return TCL_ERROR;
3148    }
3149    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
3150        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
3151        return TCL_ERROR;
3152    }
3153
3154    g_renderer->mouseDrag(button, x, y);
3155    return TCL_OK;
3156}
3157
3158static int
3159MouseMotionOp(ClientData clientData, Tcl_Interp *interp, int objc,
3160              Tcl_Obj *const *objv)
3161{
3162    double x, y;
3163
3164    if (Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK ||
3165        Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK) {
3166        return TCL_ERROR;
3167    }
3168
3169    g_renderer->mouseMotion(x, y);
3170    return TCL_OK;
3171}
3172
3173static int
3174MouseReleaseOp(ClientData clientData, Tcl_Interp *interp, int objc,
3175               Tcl_Obj *const *objv)
3176{
3177    int button;
3178    double x, y;
3179
3180    if (Tcl_GetIntFromObj(interp, objv[2], &button) != TCL_OK) {
3181        return TCL_ERROR;
3182    }
3183    if (Tcl_GetDoubleFromObj(interp, objv[3], &x) != TCL_OK ||
3184        Tcl_GetDoubleFromObj(interp, objv[4], &y) != TCL_OK) {
3185        return TCL_ERROR;
3186    }
3187
3188    g_renderer->mouseRelease(button, x, y);
3189    return TCL_OK;
3190}
3191
3192static int
3193MouseScrollOp(ClientData clientData, Tcl_Interp *interp, int objc,
3194              Tcl_Obj *const *objv)
3195{
3196    int direction;
3197
3198    if (Tcl_GetIntFromObj(interp, objv[2], &direction) != TCL_OK) {
3199        return TCL_ERROR;
3200    }
3201
3202    g_renderer->mouseScroll(direction);
3203    return TCL_OK;
3204}
3205
3206static CmdSpec mouseOps[] = {
3207    {"click",    1, MouseClickOp,       5, 5, "button x y"},
3208    {"dblclick", 2, MouseDoubleClickOp, 5, 5, "button x y"},
3209    {"drag",     2, MouseDragOp,        5, 5, "button x y"},
3210    {"motion",   1, MouseMotionOp,      4, 4, "x y"},
3211    {"release",  1, MouseReleaseOp,     5, 5, "button x y"},
3212    {"scroll",   1, MouseScrollOp,      3, 3, "direction"},
3213};
3214static int nMouseOps = NumCmdSpecs(mouseOps);
3215
3216static int
3217MouseCmd(ClientData clientData, Tcl_Interp *interp, int objc,
3218         Tcl_Obj *const *objv)
3219{
3220    Tcl_ObjCmdProc *proc;
3221
3222    proc = GetOpFromObj(interp, nMouseOps, mouseOps,
3223                        CMDSPEC_ARG1, objc, objv, 0);
3224    if (proc == NULL) {
3225        return TCL_ERROR;
3226    }
3227    return (*proc) (clientData, interp, objc, objv);
3228}
3229
3230static int
3231PlacardConfigOp(ClientData clientData, Tcl_Interp *interp, int objc,
3232                Tcl_Obj *const *objv)
3233{
3234    int namec;
3235    Tcl_Obj **namev = NULL;
3236    const char *layerName = Tcl_GetString(objv[5]);
3237
3238    if (Tcl_ListObjGetElements(interp, objv[2], &namec, &namev) != TCL_OK) {
3239        return TCL_ERROR;
3240    }
3241    if (namec % 2 != 0) {
3242        Tcl_AppendResult(interp, "invalid attribute list",
3243                         (char *)NULL);
3244        return TCL_ERROR;
3245    }
3246    Placard placardConf;
3247    for (int i = 0; i < namec; i+=2) {
3248        std::string name(Tcl_GetString(namev[i]));
3249        std::string label(Tcl_GetString(namev[i+1]));
3250        placardConf.addEntry(name, label);
3251    }
3252    osgEarth::Config styleConf("style", Tcl_GetString(objv[3]));
3253    styleConf.add("type", "text/css");
3254    placardConf.setStyle(osgEarth::Symbology::Style(styleConf));
3255
3256    float padding;
3257    if (GetFloatFromObj(interp, objv[4], &padding) != TCL_OK) {
3258        return TCL_ERROR;
3259    }
3260    placardConf.setPadding(padding);
3261    g_renderer->setPlacardConfig(placardConf, layerName);
3262    return TCL_OK;
3263}
3264
3265static int
3266PlacardEnableOp(ClientData clientData, Tcl_Interp *interp, int objc,
3267                Tcl_Obj *const *objv)
3268{
3269    const char *layerName = Tcl_GetString(objv[3]);
3270    bool enable;
3271    if (GetBooleanFromObj(interp, objv[2], &enable) != TCL_OK) {
3272        return TCL_ERROR;
3273    }
3274    //g_renderer->enablePlacard(layerName, enable);
3275    return TCL_OK;
3276}
3277
3278static CmdSpec placardOps[] = {
3279    {"config", 1, PlacardConfigOp, 6, 6, "attrlist style padding layerName"},
3280    {"enable", 1, PlacardEnableOp, 4, 4, "bool layerName"},
3281};
3282static int nPlacardOps = NumCmdSpecs(placardOps);
3283
3284static int
3285PlacardCmd(ClientData clientData, Tcl_Interp *interp, int objc,
3286          Tcl_Obj *const *objv)
3287{
3288    Tcl_ObjCmdProc *proc;
3289
3290    proc = GetOpFromObj(interp, nPlacardOps, placardOps,
3291                        CMDSPEC_ARG1, objc, objv, 0);
3292    if (proc == NULL) {
3293        return TCL_ERROR;
3294    }
3295    return (*proc) (clientData, interp, objc, objv);
3296}
3297
3298static int
3299RendererRenderOp(ClientData clientData, Tcl_Interp *interp, int objc,
3300                 Tcl_Obj *const *objv)
3301{
3302    g_renderer->eventuallyRender();
3303    return TCL_OK;
3304}
3305
3306static CmdSpec rendererOps[] = {
3307    {"render",     1, RendererRenderOp, 2, 2, ""},
3308};
3309static int nRendererOps = NumCmdSpecs(rendererOps);
3310
3311static int
3312RendererCmd(ClientData clientData, Tcl_Interp *interp, int objc,
3313            Tcl_Obj *const *objv)
3314{
3315    Tcl_ObjCmdProc *proc;
3316
3317    proc = GetOpFromObj(interp, nRendererOps, rendererOps,
3318                        CMDSPEC_ARG1, objc, objv, 0);
3319    if (proc == NULL) {
3320        return TCL_ERROR;
3321    }
3322    return (*proc) (clientData, interp, objc, objv);
3323}
3324
3325static int
3326ScreenBgColorOp(ClientData clientData, Tcl_Interp *interp, int objc,
3327                Tcl_Obj *const *objv)
3328{
3329    float color[3];
3330
3331    if (GetFloatFromObj(interp, objv[2], &color[0]) != TCL_OK ||
3332        GetFloatFromObj(interp, objv[3], &color[1]) != TCL_OK ||
3333        GetFloatFromObj(interp, objv[4], &color[2]) != TCL_OK) {
3334        return TCL_ERROR;
3335    }
3336
3337    g_renderer->setBackgroundColor(color);
3338    return TCL_OK;
3339}
3340
3341static int
3342ScreenCoordsOp(ClientData clientData, Tcl_Interp *interp, int objc,
3343               Tcl_Obj *const *objv)
3344{
3345    int tokenLength;
3346    const char *token = Tcl_GetStringFromObj(objv[2], &tokenLength);
3347    int numCoords;
3348    Tcl_Obj **coords;
3349    if (Tcl_ListObjGetElements(interp, objv[3], &numCoords, &coords) != TCL_OK) {
3350        return TCL_ERROR;
3351    }
3352    if (numCoords == 0) {
3353        Tcl_AppendResult(interp, "no x,y,z coordinates in list", (char *)NULL);
3354        return TCL_ERROR;
3355    }
3356    if (numCoords % 3 != 0) {
3357        Tcl_AppendResult(interp, "invalid number of coordinates in list",
3358                         (char *)NULL);
3359        return TCL_ERROR;
3360    }
3361
3362    const osgEarth::SpatialReference *srs = NULL;
3363    if (objc < 5) {
3364        srs = g_renderer->getMapSRS();
3365        if (srs == NULL) {
3366            Tcl_AppendResult(interp, "Could not determine map SRS", (char*)NULL);
3367            return TCL_ERROR;
3368        }
3369    } else {
3370        std::string srsInit(Tcl_GetString(objv[4]));
3371        std::string verticalDatum;
3372        if (objc > 5) {
3373            verticalDatum = Tcl_GetString(objv[5]);
3374        }
3375        srs = osgEarth::SpatialReference::get(srsInit, verticalDatum);
3376        if (srs == NULL) {
3377            Tcl_AppendResult(interp, "bad SRS \"", srsInit.c_str(), "\"", (char*)NULL);
3378            return TCL_ERROR;
3379        }
3380    }
3381    Tcl_Obj *listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
3382    std::vector<osg::Vec3d> coordVec;
3383    for (int i = 0; i < numCoords; i += 3) {
3384        double x, y, z;
3385        if (Tcl_GetDoubleFromObj(interp, coords[i], &x) != TCL_OK ||
3386            Tcl_GetDoubleFromObj(interp, coords[i+1], &y) != TCL_OK ||
3387            Tcl_GetDoubleFromObj(interp, coords[i+2], &z) != TCL_OK) {
3388            return TCL_ERROR;
3389        }
3390        // ALTMODE_RELATIVE is height above terrain, ALTMODE_ABSOLUTE means
3391        // relative to the vertical datum
3392        osgEarth::GeoPoint mapPoint(srs, x, y, z, osgEarth::ALTMODE_ABSOLUTE);
3393        osg::Vec3d world;
3394        if (g_renderer->getWorldCoords(mapPoint, &world)) {
3395            coordVec.push_back(world);
3396        } else {
3397            coordVec.push_back(osg::Vec3d(std::numeric_limits<double>::quiet_NaN(),
3398                                          std::numeric_limits<double>::quiet_NaN(),
3399                                          std::numeric_limits<double>::quiet_NaN()));
3400        }
3401    }
3402    g_renderer->worldToScreen(coordVec);
3403    for (std::vector<osg::Vec3d>::iterator itr = coordVec.begin();
3404         itr != coordVec.end(); ++itr) {
3405        Tcl_Obj *objPtr = Tcl_NewDoubleObj(itr->x());
3406        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
3407        objPtr = Tcl_NewDoubleObj(itr->y());
3408        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
3409        objPtr = Tcl_NewDoubleObj(itr->z());
3410        Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
3411    }
3412    // send coords to client
3413    int listLength;
3414    const char *string = Tcl_GetStringFromObj(listObjPtr, &listLength);
3415    size_t length = listLength + tokenLength + 22;
3416    char *mesg = new char[length];
3417    length = snprintf(mesg, length, "nv>screen coords %s {%s}\n", token, string);
3418    Tcl_DecrRefCount(listObjPtr);
3419    queueResponse(mesg, length, Response::VOLATILE);
3420    delete [] mesg;
3421    return TCL_OK;
3422}
3423
3424static int
3425ScreenSizeOp(ClientData clientData, Tcl_Interp *interp, int objc,
3426             Tcl_Obj *const *objv)
3427{
3428    int width, height;
3429
3430    if (Tcl_GetIntFromObj(interp, objv[2], &width) != TCL_OK ||
3431        Tcl_GetIntFromObj(interp, objv[3], &height) != TCL_OK) {
3432        return TCL_ERROR;
3433    }
3434
3435    g_renderer->setWindowSize(width, height);
3436    return TCL_OK;
3437}
3438
3439static CmdSpec screenOps[] = {
3440    {"bgcolor", 1, ScreenBgColorOp, 5, 5, "r g b"},
3441    {"coords",  1, ScreenCoordsOp, 4, 6, "token coords ?srs? ?verticalDatum?"},
3442    {"size",    1, ScreenSizeOp, 4, 4, "width height"}
3443};
3444static int nScreenOps = NumCmdSpecs(screenOps);
3445
3446static int
3447ScreenCmd(ClientData clientData, Tcl_Interp *interp, int objc,
3448          Tcl_Obj *const *objv)
3449{
3450    Tcl_ObjCmdProc *proc;
3451
3452    proc = GetOpFromObj(interp, nScreenOps, screenOps,
3453                        CMDSPEC_ARG1, objc, objv, 0);
3454    if (proc == NULL) {
3455        return TCL_ERROR;
3456    }
3457    return (*proc) (clientData, interp, objc, objv);
3458}
3459
3460static int
3461SelectAddOp(ClientData clientData, Tcl_Interp *interp, int objc,
3462            Tcl_Obj *const *objv)
3463{
3464    int numIDs;
3465    Tcl_Obj **ids;
3466    if (Tcl_ListObjGetElements(interp, objv[2], &numIDs, &ids) != TCL_OK) {
3467        return TCL_ERROR;
3468    }
3469    if (numIDs == 0) {
3470        Tcl_AppendResult(interp, "no IDs in list", (char *)NULL);
3471        return TCL_ERROR;
3472    }
3473    std::vector<unsigned long> featureIDs;
3474    for (int i = 0; i < numIDs; i++) {
3475        long id;
3476        if (Tcl_GetLongFromObj(interp, ids[i], &id) != TCL_OK) {
3477            return TCL_ERROR;
3478        }
3479        featureIDs.push_back((unsigned long)id);
3480    }
3481    const char *layerName = Tcl_GetString(objv[3]);
3482
3483    g_renderer->selectFeatures(featureIDs, layerName, false);
3484    return TCL_OK;
3485}
3486
3487static int
3488SelectClearOp(ClientData clientData, Tcl_Interp *interp, int objc,
3489              Tcl_Obj *const *objv)
3490{
3491    g_renderer->clearSelection();
3492    return TCL_OK;
3493}
3494
3495static int
3496SelectDeleteOp(ClientData clientData, Tcl_Interp *interp, int objc,
3497               Tcl_Obj *const *objv)
3498{
3499    int numIDs;
3500    Tcl_Obj **ids;
3501    if (Tcl_ListObjGetElements(interp, objv[2], &numIDs, &ids) != TCL_OK) {
3502        return TCL_ERROR;
3503    }
3504    if (numIDs == 0) {
3505        Tcl_AppendResult(interp, "no IDs in list", (char *)NULL);
3506        return TCL_ERROR;
3507    }
3508    std::vector<unsigned long> featureIDs;
3509    for (int i = 0; i < numIDs; i++) {
3510        long id;
3511        if (Tcl_GetLongFromObj(interp, ids[i], &id) != TCL_OK) {
3512            return TCL_ERROR;
3513        }
3514        featureIDs.push_back((unsigned long)id);
3515    }
3516    const char *layerName = Tcl_GetString(objv[3]);
3517
3518    g_renderer->deselectFeatures(featureIDs, layerName);
3519    return TCL_OK;
3520}
3521
3522static int
3523SelectFeatureOp(ClientData clientData, Tcl_Interp *interp, int objc,
3524                Tcl_Obj *const *objv)
3525{
3526    int numIDs;
3527    Tcl_Obj **ids;
3528    if (Tcl_ListObjGetElements(interp, objv[2], &numIDs, &ids) != TCL_OK) {
3529        return TCL_ERROR;
3530    }
3531    if (numIDs == 0) {
3532        Tcl_AppendResult(interp, "no IDs in list", (char *)NULL);
3533        return TCL_ERROR;
3534    }
3535    std::vector<unsigned long> featureIDs;
3536    for (int i = 0; i < numIDs; i++) {
3537        long id;
3538        if (Tcl_GetLongFromObj(interp, ids[i], &id) != TCL_OK) {
3539            return TCL_ERROR;
3540        }
3541        featureIDs.push_back((unsigned long)id);
3542    }
3543    const char *layerName = Tcl_GetString(objv[3]);
3544
3545    g_renderer->selectFeatures(featureIDs, layerName);
3546    return TCL_OK;
3547}
3548
3549static int
3550SelectModeOp(ClientData clientData, Tcl_Interp *interp, int objc,
3551             Tcl_Obj *const *objv)
3552{
3553    const char *modeStr = Tcl_GetString(objv[2]);
3554    // Parse mode string
3555    Renderer::SelectMode mode = Renderer::SELECT_OFF;
3556    if (modeStr[0] == 'o' && strcmp(modeStr, "off") == 0) {
3557        mode = Renderer::SELECT_OFF;
3558    } else if (modeStr[0] == 'o' && strcmp(modeStr, "on") == 0) {
3559        mode = Renderer::SELECT_ON;
3560    } else {
3561        Tcl_AppendResult(interp, "bad select mode \"", modeStr,
3562                             "\": must be 'on' or 'off'", (char*)NULL);
3563        return TCL_ERROR;
3564    }
3565    g_renderer->setSelectMode(mode);
3566    return TCL_OK;
3567}
3568
3569static CmdSpec selectOps[] = {
3570    {"clear",   1, SelectClearOp,   2, 2, ""},
3571    {"fadd",    2, SelectAddOp,     4, 4, "idlist layerName"},
3572    {"fdelete", 2, SelectDeleteOp,  4, 4, "idlist layerName"},
3573    {"feature", 1, SelectFeatureOp, 4, 4, "idlist layerName"},
3574    {"mode",    1, SelectModeOp,    3, 3, "mode"},
3575};
3576static int nSelectOps = NumCmdSpecs(selectOps);
3577
3578static int
3579SelectCmd(ClientData clientData, Tcl_Interp *interp, int objc,
3580          Tcl_Obj *const *objv)
3581{
3582    Tcl_ObjCmdProc *proc;
3583
3584    proc = GetOpFromObj(interp, nSelectOps, selectOps,
3585                        CMDSPEC_ARG1, objc, objv, 0);
3586    if (proc == NULL) {
3587        return TCL_ERROR;
3588    }
3589    return (*proc) (clientData, interp, objc, objv);
3590}
3591
3592#ifdef USE_READ_THREAD
3593int
3594GeoVis::queueCommands(Tcl_Interp *interp,
3595                      ClientData clientData,
3596                      ReadBuffer *inBufPtr)
3597{
3598    Tcl_DString commandString;
3599    Tcl_DStringInit(&commandString);
3600    fd_set readFds;
3601
3602    FD_ZERO(&readFds);
3603    FD_SET(inBufPtr->file(), &readFds);
3604    while (inBufPtr->isLineAvailable() ||
3605           (select(inBufPtr->file()+1, &readFds, NULL, NULL, NULL) > 0)) {
3606        size_t numBytes;
3607        unsigned char *buffer;
3608
3609        /* A short read is treated as an error here because we assume that we
3610         * will always get commands line by line. */
3611        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
3612            /* Terminate the server if we can't communicate with the client
3613             * anymore. */
3614            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
3615                TRACE("Exiting server on EOF from client");
3616                return -1;
3617            } else {
3618                ERROR("Exiting server, failed to read from client: %s",
3619                      strerror(errno));
3620                return -1;
3621            }
3622        }
3623        Tcl_DStringAppend(&commandString, (char *)buffer, numBytes);
3624        if (Tcl_CommandComplete(Tcl_DStringValue(&commandString))) {
3625            // Add to queue
3626            Command *command = new Command(Command::COMMAND);
3627            command->setMessage((unsigned char *)Tcl_DStringValue(&commandString),
3628                                Tcl_DStringLength(&commandString), Command::VOLATILE);
3629            g_inQueue->enqueue(command);
3630            Tcl_DStringSetLength(&commandString, 0);
3631        }
3632        FD_SET(inBufPtr->file(), &readFds);
3633    }
3634
3635    return 1;
3636}
3637#endif
3638
3639/**
3640 * \brief Execute commands from client in Tcl interpreter
3641 *
3642 * In this threaded model, the select call is for event compression.  We
3643 * want to execute render server commands as long as they keep coming. 
3644 * This lets us execute a stream of many commands but render once.  This
3645 * benefits camera movements, screen resizing, and opacity changes
3646 * (using a slider on the client).  The down side is you don't render
3647 * until there's a lull in the command stream.  If the client needs an
3648 * image, it can issue an "imgflush" command.  That breaks us out of the
3649 * read loop.
3650 */
3651int
3652GeoVis::processCommands(Tcl_Interp *interp,
3653                        ClientData clientData,
3654                        ReadBuffer *inBufPtr,
3655                        int fdOut,
3656                        long timeout)
3657{
3658    int ret = 1;
3659    int status = TCL_OK;
3660
3661    Tcl_DString command;
3662    Tcl_DStringInit(&command);
3663    fd_set readFds;
3664    struct timeval tv, *tvPtr;
3665
3666    FD_ZERO(&readFds);
3667    FD_SET(inBufPtr->file(), &readFds);
3668    tvPtr = NULL;                       /* Wait for the first read. This is so
3669                                         * that we don't spin when no data is
3670                                         * available. */
3671    if (timeout >= 0L) {
3672        tv.tv_sec = 0L;
3673        tv.tv_usec = timeout;
3674        tvPtr = &tv;
3675    } else {
3676        TRACE("Blocking on select()");
3677    }
3678    while (inBufPtr->isLineAvailable() ||
3679           (ret = select(inBufPtr->file()+1, &readFds, NULL, NULL, tvPtr)) > 0) {
3680        size_t numBytes;
3681        unsigned char *buffer;
3682
3683        /* A short read is treated as an error here because we assume that we
3684         * will always get commands line by line. */
3685        if (inBufPtr->getLine(&numBytes, &buffer) != ReadBuffer::OK) {
3686            /* Terminate the server if we can't communicate with the client
3687             * anymore. */
3688            if (inBufPtr->status() == ReadBuffer::ENDFILE) {
3689                TRACE("Exiting server on EOF from client");
3690                return -1;
3691            } else {
3692                ERROR("Exiting server, failed to read from client: %s",
3693                      strerror(errno));
3694                return -1;
3695            }
3696        }
3697#if 0
3698        Tcl_DString tmp;
3699        Tcl_DStringInit(&tmp);
3700        Tcl_Encoding encoding = Tcl_GetEncoding(interp, "identity");
3701        TRACE("Encoding name: %s", Tcl_GetEncodingName(encoding));
3702        Tcl_ExternalToUtfDString(encoding, (const char *)buffer, numBytes, &tmp);
3703        Tcl_FreeEncoding(encoding);
3704        Tcl_DStringAppend(&command, Tcl_DStringValue(&tmp), Tcl_DStringLength(&tmp));
3705        Tcl_DStringFree(&tmp);
3706#else
3707        Tcl_DStringAppend(&command, (char *)buffer, numBytes);
3708#endif
3709        if (Tcl_CommandComplete(Tcl_DStringValue(&command))) {
3710            struct timeval start, finish;
3711            gettimeofday(&start, NULL);
3712            g_stats.nCommands++;
3713            status = ExecuteCommand(interp, &command);
3714            gettimeofday(&finish, NULL);
3715            g_stats.cmdTime += (MSECS_ELAPSED(start, finish) / 1.0e+3);
3716            if (status == TCL_BREAK) {
3717                return 2;               /* This was caused by a "imgflush"
3718                                         * command. Break out of the read loop
3719                                         * and allow a new image to be
3720                                         * rendered. */
3721            } else { //if (status != TCL_OK) {
3722                ret = 0;
3723                if (handleError(interp, clientData, status, fdOut) < 0) {
3724                    return -1;
3725                }
3726            }
3727            if (status == TCL_OK) {
3728                ret = 3;
3729            }
3730        }
3731
3732        tv.tv_sec = tv.tv_usec = 0L;    /* On successive reads, we break out
3733                                         * if no data is available. */
3734        FD_SET(inBufPtr->file(), &readFds);
3735        tvPtr = &tv;
3736    }
3737
3738    return ret;
3739}
3740
3741/**
3742 * \brief Send error message to client socket
3743 */
3744int
3745GeoVis::handleError(Tcl_Interp *interp,
3746                    ClientData clientData,
3747                    int status, int fdOut)
3748{
3749    const char *string;
3750    int nBytes;
3751
3752    if (status != TCL_OK) {
3753        string = Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY);
3754        nBytes = strlen(string);
3755        if (nBytes > 0) {
3756            TRACE("status=%d errorInfo=(%s)", status, string);
3757
3758            std::ostringstream oss;
3759            oss << "nv>viserror -type internal_error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
3760            std::string ostr = oss.str();
3761            nBytes = ostr.length();
3762
3763            if (queueResponse(ostr.c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
3764                return -1;
3765            }
3766        }
3767    }
3768
3769    std::string msg = getUserMessages();
3770    nBytes = msg.length();
3771    if (nBytes > 0) {
3772        string = msg.c_str();
3773        TRACE("userError=(%s)", string);
3774
3775        std::ostringstream oss;
3776        oss << "nv>viserror -type error -token " << g_stats.nCommands << " -bytes " << nBytes << "\n" << string;
3777        std::string ostr = oss.str();
3778        nBytes = ostr.length();
3779
3780        if (queueResponse(ostr.c_str(), nBytes, Response::VOLATILE, Response::ERROR) < 0) {
3781            return -1;
3782        }
3783
3784        clearUserMessages();
3785    }
3786
3787    return 0;
3788}
3789
3790/**
3791 * \brief Create Tcl interpreter and add commands
3792 *
3793 * \return The initialized Tcl interpreter
3794 */
3795void
3796GeoVis::initTcl(Tcl_Interp *interp, ClientData clientData)
3797{
3798    TRACE("LANG: %s", getenv("LANG"));
3799    Tcl_GetEncodingNames(interp);
3800    TRACE("Supported encodings: %s", Tcl_GetStringResult(interp));
3801    int result = Tcl_Eval(interp, "encoding system\n");
3802    if (result == TCL_OK) {
3803        TRACE("Current system encoding: %s", Tcl_GetStringResult(interp));
3804    } else {
3805        ERROR("Couldn't determine system encoding");
3806    }
3807    const char *encoding = "utf-8";
3808    if (Tcl_SetSystemEncoding(interp, encoding) != TCL_OK) {
3809        TRACE("Failed to set Tcl encoding to %s", encoding);
3810    } else {
3811        TRACE("Set system encoding to %s", encoding);
3812    }
3813
3814    Tcl_MakeSafe(interp);
3815
3816    Tcl_CreateObjCommand(interp, "camera",     CameraCmd,     clientData, NULL);
3817    Tcl_CreateObjCommand(interp, "clientinfo", ClientInfoCmd, clientData, NULL);
3818    Tcl_CreateObjCommand(interp, "colormap",   ColorMapCmd,   clientData, NULL);
3819    Tcl_CreateObjCommand(interp, "file",       FileCmd,       clientData, NULL);
3820    Tcl_CreateObjCommand(interp, "imgflush",   ImageFlushCmd, clientData, NULL);
3821    Tcl_CreateObjCommand(interp, "key",        KeyCmd,        clientData, NULL);
3822    Tcl_CreateObjCommand(interp, "legend",     LegendCmd,     clientData, NULL);
3823    Tcl_CreateObjCommand(interp, "map",        MapCmd,        clientData, NULL);
3824    Tcl_CreateObjCommand(interp, "mouse",      MouseCmd,      clientData, NULL);
3825    Tcl_CreateObjCommand(interp, "placard",    PlacardCmd,    clientData, NULL);
3826    Tcl_CreateObjCommand(interp, "renderer",   RendererCmd,   clientData, NULL);
3827    Tcl_CreateObjCommand(interp, "screen",     ScreenCmd,     clientData, NULL);
3828    Tcl_CreateObjCommand(interp, "select",     SelectCmd,     clientData, NULL);
3829}
3830
3831/**
3832 * \brief Delete Tcl commands and interpreter
3833 */
3834void GeoVis::exitTcl(Tcl_Interp *interp)
3835{
3836    Tcl_DeleteCommand(interp, "camera");
3837    Tcl_DeleteCommand(interp, "clientinfo");
3838    Tcl_DeleteCommand(interp, "colormap");
3839    Tcl_DeleteCommand(interp, "file");
3840    Tcl_DeleteCommand(interp, "imgflush");
3841    Tcl_DeleteCommand(interp, "key");
3842    Tcl_DeleteCommand(interp, "legend");
3843    Tcl_DeleteCommand(interp, "map");
3844    Tcl_DeleteCommand(interp, "mouse");
3845    Tcl_DeleteCommand(interp, "placard");
3846    Tcl_DeleteCommand(interp, "renderer");
3847    Tcl_DeleteCommand(interp, "screen");
3848    Tcl_DeleteCommand(interp, "select");
3849
3850    Tcl_DeleteInterp(interp);
3851}
Note: See TracBrowser for help on using the repository browser.