source: geovis/trunk/RendererCmd.cpp @ 6350

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

Update protocol for agglite rasterizing driver to match feature layer.

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