source: geovis/trunk/RendererCmd.cpp @ 5972

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

Add style settings for placard

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