source: geovis/trunk/RendererCmd.cpp @ 6562

Last change on this file since 6562 was 6562, checked in by ldelgass, 6 years ago

Remove unused removeDupes setting (doesn't exist in OE trunk)

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