/*
 * tclgeomapProj.c --
 *
 *	This file defines structures and functions that add the ability to
 *	convert between geographic (lat-lon) and map coordinates in Tcl.
 *
 * @(#) $Id: tclgeomapProj.c,v 1.7 2007/06/27 18:38:56 tkgeomap Exp $
 *
 */

#include "tclgeomap.h"
#include "tclgeomapInt.h"

/*
 * Forward declarations.
 */

static int		set_proj _ANSI_ARGS_((GeoProj proj,
				Tcl_Interp *interp, int objc,
				Tcl_Obj *CONST objv[]));
static int		geoProjCmd _ANSI_ARGS_((ClientData clientData,
				Tcl_Interp *interp, int objc,
				Tcl_Obj *CONST objv[]));
static int		new _ANSI_ARGS_((ClientData clientData,
				Tcl_Interp *interp, int objc,
				Tcl_Obj *CONST objv[]));
static int		set _ANSI_ARGS_((ClientData clientData,
				Tcl_Interp *interp, int objc,
				Tcl_Obj *CONST objv[]));
static int		rotation _ANSI_ARGS_((ClientData clientData,
				Tcl_Interp *interp, int objc,
				Tcl_Obj *CONST objv[]));
static void		deleteProc _ANSI_ARGS_((ClientData clientData));
static int		info _ANSI_ARGS_((ClientData clientData,
				Tcl_Interp *interp, int objc,
				Tcl_Obj *CONST objv[]));
static int		fmLatLon _ANSI_ARGS_((ClientData clientData,
				Tcl_Interp *interp, int objc,
				Tcl_Obj *CONST objv[]));
static int		toLatLon _ANSI_ARGS_((ClientData clientData,
				Tcl_Interp *interp, int objc,
				Tcl_Obj *CONST objv[]));

/*
 * This table keeps a list of available projections.  Keys are Tclgeomap_Proj
 * structures, which are also clientData's of the corresponding commands.
 * Values are not used.
 */

static Tcl_HashTable projections;


/*
 *------------------------------------------------------------------------
 *
 * TclgeomapProjInit --
 *
 * 	This procedure extends Tcl with the ability to define and use
 * 	certain geographic projections.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	The "geomap::projection" command is added to the interpreter.
 *
 *------------------------------------------------------------------------
 */

int
TclgeomapProjInit(interp)
    Tcl_Interp *interp;	/* Current Tcl interpreter */
{
    static int loaded;	/* Tell if package already loaded */

    if (loaded) {
	return TCL_OK;
    }
#ifdef USE_TCL_STUBS
    if (Tcl_InitStubs(interp, TCL_VERSION, 0) == NULL) {
	return TCL_ERROR;
    }
#endif
    Tcl_InitHashTable(&projections, TCL_ONE_WORD_KEYS);
    Tcl_CreateObjCommand(interp, "::geomap::projection", new, NULL,
	    NULL);
    loaded = 1;
    return TCL_OK;
}

/*
 *------------------------------------------------------------------------
 *
 * Tclgeomap_GetProj --
 *
 * 	This procedure finds a GeoProj given the name of its command in Tcl.
 *
 * Results:
 * 	See the user documentation.
 *
 * Side effects:
 * 	None.
 *
 *------------------------------------------------------------------------
 */

struct Tclgeomap_Proj*
Tclgeomap_GetProj(interp, name)
    Tcl_Interp *interp;		/* Current interpreter */
    char *name;			/* Name of a Tcl command used to access a
				 * projection */
{
    Tcl_CmdInfo info;

    if (Tcl_GetCommandInfo(interp, name, &info)) {
	return info.objClientData;
    } else {
	return NULL;
    }
}

/*
 *------------------------------------------------------------------------
 *
 * set_proj --
 *
 *	This procedure modifies an existing projection.
 *
 * Results:
 * 	A standard Tcl result.
 *
 * Side effects:
 * 	The fields of a Tclgeomap_Proj structure are modified in accordance
 * 	with options given on the command line.
 *
 *------------------------------------------------------------------------
 */

int
set_proj(proj, interp, objc, objv)
    GeoProj proj;		/* The projection to modify */
    Tcl_Interp *interp;		/* Current interpreter, for error messages */
    int objc;			/* Number of arguments in command line chunk */
    Tcl_Obj *CONST objv[];	/* Argument objects in command line chunk */
{
    double rLatDeg, rLonDeg;	/* Reference latitude and longitude (degrees) */
    Angle rLat, rLon;		/* Reference latitude and longitude */
    GeoPt refPt;		/* Reference point*/
    static char *projections[] = {
	"CylEqDist",		"Mercator",		"CylEqArea",
	"LambertConfConic",	"LambertEqArea",	"Stereographic",
	"PolarStereographic",	"Orthographic",		NULL
    };				/* Projection names */
    enum index {
	CYL_EQ_DIST,		MERCATOR,		CYL_EQ_AREA,
	LAMBERT_CONF_CONIC,	LAMBERT_EQ_AREA,	STEREOGRAPHIC,
	POLAR_STEREOGRAPHIC,	ORTHOGRAPHIC
    };				/* Indices in projections array */
    int idx;			/* Index for projection name given
				 * on command line */
    char *nsStr;		/* "N" or "S" for Polar Stereographic */
    Angle d90 = AngleFmDeg(90.0);

    if (objc < 1) {
	Tcl_AppendResult(interp, "Projection specifier must "
		"have at least projection type\n", NULL);
	return TCL_ERROR;
    }
    if (Tcl_GetIndexFromObj(interp, objv[0], projections, "projection", 0,
		&idx) != TCL_OK) {
	return TCL_ERROR;
    }
    switch ((enum index)idx) {
	case CYL_EQ_DIST:
	    if (objc == 2) {
		if (Tclgeomap_GetGeoPtFromObj(interp, objv[1], &refPt)
			!= TCL_OK) {
		    return TCL_ERROR;
		}
		GeoPtGetDeg(refPt, &rLatDeg, &rLonDeg);
	    } else if (objc == 3) {
		if (Tcl_GetDoubleFromObj(interp, objv[1], &rLatDeg) != TCL_OK
			|| Tcl_GetDoubleFromObj(interp, objv[2], &rLonDeg)
			!= TCL_OK) {
		    return TCL_ERROR;
		}
	    } else {
		Tcl_AppendResult(interp,
			"Cylindrical Equidistant must have refPoint OR"
			" refLat and refLon.  ", NULL);
		return TCL_ERROR;
	    }
	    SetCylEqDist(proj, AngleFmDeg(rLatDeg), AngleFmDeg(rLonDeg));
	    break;
	case MERCATOR:
	    if (objc == 2) {
		if (Tcl_GetDoubleFromObj(interp, objv[1], &rLonDeg) != TCL_OK) {
		    return TCL_ERROR;
		}
	    } else {
		Tcl_AppendResult(interp, "Mercator must have reflon.  ", NULL);
		return TCL_ERROR;
	    }
	    SetMercator(proj, AngleFmDeg(rLonDeg));
	    break;
	case CYL_EQ_AREA:
	    if (objc == 2) {
		if (Tcl_GetDoubleFromObj(interp, objv[1], &rLonDeg) != TCL_OK) {
		    return TCL_ERROR;
		}
	    } else {
		Tcl_AppendResult(interp, "CylEqArea must have reflon.  ", NULL);
		return TCL_ERROR;
	    }
	    SetCylEqArea(proj, AngleFmDeg(rLonDeg));
	    break;
	case LAMBERT_CONF_CONIC:
	    if (objc == 2) {
		if (Tclgeomap_GetGeoPtFromObj(interp, objv[1], &refPt)
			!= TCL_OK) {
		    return TCL_ERROR;
		}
		rLat = refPt.lat;
		rLon = refPt.lon;
	    } else if (objc == 3) {
		if (Tcl_GetDoubleFromObj(interp, objv[1], &rLatDeg)  != TCL_OK
			|| Tcl_GetDoubleFromObj(interp, objv[2], &rLonDeg)
			!= TCL_OK) {
		    return TCL_ERROR;
		}
		rLat = AngleFmDeg(rLatDeg);
		rLon = AngleFmDeg(rLonDeg);
	    } else {
		Tcl_AppendResult(interp,
			"LambertConfConic must have refPoint OR"
			" refLat and refLon.  ", NULL);
		return TCL_ERROR;
	    }
	    if (AngleCmp(rLat, 0) == 0) {
		/*
		 * Lambert Conformal Conic with reference latitude 0.0 is
		 * equivalent to Mercator.
		 */

		SetMercator(proj, rLon);
	    } else if (AngleCmp(rLat, d90) == 0) {
		/*
		 * Lambert conformal conic with reference latitude 90.0 is
		 * equivalent to North Polar Stereographic.
		 */

		SetStereographic(proj, refPt);
	    } else if (AngleCmp(rLat, -d90) == 0) {
		/*
		 * Lambert conformal conic with reference latitude 90.0 is
		 * equivalent to South Polar Stereographic.
		 */

		SetStereographic(proj, refPt);
	    } else {
		SetLambertConfConic(proj, rLat, rLon);
	    }
	    break;
	case LAMBERT_EQ_AREA:
	    if (objc == 2) {
		if (Tclgeomap_GetGeoPtFromObj(interp, objv[1], &refPt)
			!= TCL_OK) {
		    return TCL_ERROR;
		}
	    } else if (objc == 3) {
		if (Tcl_GetDoubleFromObj(interp, objv[1], &rLatDeg)  != TCL_OK
			|| Tcl_GetDoubleFromObj(interp, objv[2], &rLonDeg)
			!= TCL_OK) {
		    return TCL_ERROR;
		}
		refPt = GeoPtFmDeg(rLatDeg, rLonDeg);
	    } else {
		Tcl_AppendResult(interp, "LambertEqArea must have refPoint or "
			"refLat and refLon.  ", NULL);
		return TCL_ERROR;
	    }
	    SetLambertEqArea(proj, refPt);
	    break;
	case STEREOGRAPHIC:
	    if (objc == 2) {
		if (Tclgeomap_GetGeoPtFromObj(interp, objv[1], &refPt)
			!= TCL_OK) {
		    return TCL_ERROR;
		}
	    } else if (objc == 3) {
		if (Tcl_GetDoubleFromObj(interp, objv[1], &rLatDeg)  != TCL_OK
			|| Tcl_GetDoubleFromObj(interp, objv[2], &rLonDeg)
			!= TCL_OK) {
		    return TCL_ERROR;
		}
		refPt = GeoPtFmDeg(rLatDeg, rLonDeg);
	    } else {
		Tcl_AppendResult(interp, "Stereographic must have refPoint or "
			"{refLat refLon}.  ", NULL);
		return TCL_ERROR;
	    }
	    SetStereographic(proj, refPt);
	    break;
	case POLAR_STEREOGRAPHIC:
	    if (objc != 2) {
		Tcl_AppendResult(interp,
			"Must indicate N or S for PolarStereographic.  ", NULL);
		return TCL_ERROR;
	    }
	    nsStr = Tcl_GetString(objv[1]);
	    if (strcmp("N", nsStr) == 0) {
		/*
		 * Set Arctic polar stereographic with Prime Meridian
		 * vertical on map.
		 */

		refPt.lat = d90;
		refPt.lon = 0;
	    } else if (strcmp("S", nsStr) == 0) {
		/*
		 * Set Antarctic polar stereographic with Prime Meridian
		 * vertical on map
		 */

		refPt.lat = -d90;
		refPt.lon = 0;
	    } else {
		Tcl_AppendResult(interp,
			"PolarStereographic requires \"N\" or \"S\".  ", NULL);
		return TCL_ERROR;
	    }
	    SetStereographic(proj, refPt);
	    break;
	case ORTHOGRAPHIC:
	    if (objc == 2) {
		if (Tclgeomap_GetGeoPtFromObj(interp, objv[1], &refPt)
			!= TCL_OK) {
		    return TCL_ERROR;
		}
	    } else if (objc == 3) {
		if (Tcl_GetDoubleFromObj(interp, objv[1], &rLatDeg)  != TCL_OK
			|| Tcl_GetDoubleFromObj(interp, objv[2], &rLonDeg)
			!= TCL_OK) {
		    return TCL_ERROR;
		}
		refPt = GeoPtFmDeg(rLatDeg, rLonDeg);
	    } else {
		Tcl_AppendResult(interp, "Orthographic must have refPoint or "
			"refLat and refLon.  ", NULL);
		return TCL_ERROR;
	    }
	    SetOrthographic(proj, refPt);
    }
    return TCL_OK;
}

/*
 *------------------------------------------------------------------------
 *
 * Tclgeomap_ProjName --
 *
 *	This procedure returns the name of a projection.
 *
 * Results:
 *	See the user documentation.
 *
 * Side effects:
 *	See the user documentation.
 *
 *------------------------------------------------------------------------
 */

CONST char *
Tclgeomap_ProjName(projPtr)
    struct Tclgeomap_Proj *projPtr;
{
    return Tcl_GetCommandName(projPtr->interp, projPtr->cmd);
}

/*
 *------------------------------------------------------------------------
 *
 * new --
 *
 *	This is the callback for the "geomap::projection ..." command.  See the
 *	user documentation for usage details.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 * 	See the user documentation.
 *	
 *------------------------------------------------------------------------
 */

static int
new(clientData, interp, objc, objv)
    ClientData clientData;	/* Not used */
    Tcl_Interp *interp;		/* Current interpreter */
    int objc;			/* Number of arguments */
    Tcl_Obj *const objv[];	/* Argument objects */
{
    struct Tclgeomap_Proj *projPtr;
    				/* New projection */
    static int cnt;		/* Counter used to make projection identifier */
    static Tcl_Obj *cntObj;	/* Object that holds cnt */
    Tcl_Obj *rslt;		/* Command result */
    int n;

    if (objc < 2) {
	Tcl_WrongNumArgs(interp, 2, objv, "projectionName [options ...]");
	return TCL_ERROR;
    }
    if ( !cntObj ) {
	cntObj = Tcl_NewObj();
    }
    projPtr = (struct Tclgeomap_Proj *)CKALLOC(sizeof(*projPtr));
    GeoProjInit((GeoProj)projPtr);
    if (set_proj((GeoProj)projPtr, interp, objc - 1, objv + 1)
	    != TCL_OK) {
	Tcl_AppendResult(interp, "Could not set values for new projection.  ",
		NULL);
	GeoProjFree((GeoProj)projPtr);
	CKFREE((char *)projPtr);
	return TCL_ERROR;
    }
    Tcl_InitHashTable(&projPtr->updateTasks, TCL_ONE_WORD_KEYS);
    Tcl_InitHashTable(&projPtr->deleteTasks, TCL_ONE_WORD_KEYS);
    Tcl_CreateHashEntry(&projections, (ClientData)projPtr, &n);
    rslt = Tcl_GetObjResult(interp);
    Tcl_SetStringObj(rslt, "::geomap::proj", -1);
    Tcl_SetIntObj(cntObj, cnt);
    Tcl_AppendObjToObj(rslt, cntObj);
    cnt++;
    projPtr->interp = interp;
    projPtr->cmd = Tcl_CreateObjCommand(interp, Tcl_GetString(rslt), geoProjCmd,
	    projPtr, deleteProc);
    Tcl_SetObjResult(interp, rslt);
    return TCL_OK;
}

/*
 *------------------------------------------------------------------------
 *
 * geoProjCmd --
 *
 *	This is the callback for the Tcl commands created by the
 *	"geomap::projection" command.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	The procedure corresponding to the second word on the command line
 *	is called.
 *
 *------------------------------------------------------------------------
 */

static int
geoProjCmd(clientData, interp, objc, objv)
    ClientData clientData;	/* Projection managed by the command */
    Tcl_Interp *interp;		/* Current interpreter */
    int objc;			/* Number of arguments */
    Tcl_Obj *const objv[];	/* Argument objects */
{
    static char *subCmdNms[] = {
	"set",	"rotation",	"info",	"fmlatlon",	"tolatlon",	NULL
    };
    Tcl_ObjCmdProc *subCmdProcPtr[] = {
	set,	rotation,	info,	fmLatLon,	toLatLon
    };
    int i;

    if (objc == 1) {
	Tcl_WrongNumArgs(interp, 1, objv, "subcommand");
	return TCL_ERROR;
    }
    if (Tcl_GetIndexFromObj(interp, objv[1], subCmdNms, "subcommand", 0, &i)
	    != TCL_OK) {
	return TCL_ERROR;
    };
    return (subCmdProcPtr[i])(clientData, interp, objc, objv);
}

/*
 *------------------------------------------------------------------------
 *
 * set --
 *
 *	This is the callback for the "projName set ..." command.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	Fields in a Tclgeomap_Proj structure are modified in accordance with
 *	options given on the command line.
 *	The updateTasks for the projection are run.
 *	
 *------------------------------------------------------------------------
 */

static int
set(clientData, interp, objc, objv)
    ClientData clientData;		/* Projection to set */
    Tcl_Interp *interp;			/* Current interpreter */
    int objc;				/* Number of arguments */
    Tcl_Obj *const objv[];		/* Argument objects */
{
    struct Tclgeomap_Proj *projPtr;	/* Projection to set */
    Tcl_HashEntry *entry;		/* GeoProj entry */
    Tcl_HashSearch search;		/* Update loop parameter */
    ClientData cd;			/* ClientData for an update proc */
    Tclgeomap_ProjUpdateProc *updateProc;	/* Update procedure */

    if (objc < 4) {
	Tcl_WrongNumArgs(interp, 2, objv, "?option ...?");
	return TCL_ERROR;
    }
    projPtr = (struct Tclgeomap_Proj *)clientData;
    if (set_proj((GeoProj)projPtr, interp, objc - 2, objv + 2)
	    != TCL_OK) {
	Tcl_AppendResult(interp, "Could not set values for projection", NULL);
	return TCL_ERROR;
    }
    for (entry = Tcl_FirstHashEntry(&projPtr->updateTasks, &search);
	    entry != NULL;
	    entry = Tcl_NextHashEntry(&search)) {
	cd = (ClientData)Tcl_GetHashKey(&projPtr->updateTasks, entry);
	updateProc = (Tclgeomap_ProjUpdateProc *)Tcl_GetHashValue(entry);
	(*updateProc)(cd);
    }

    return TCL_OK;
}

/*
 *------------------------------------------------------------------------
 *
 * rotation --
 *
 *	This is the callback for the "projName rotation ..." command.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	Fields in a Tclgeomap_Proj structure are modified in accordance with
 *	options given on the command line.
 *	The updateTasks for the projection are run.
 *	
 *------------------------------------------------------------------------
 */

static int
rotation(clientData, interp, objc, objv)
    ClientData clientData;		/* Projection to set */
    Tcl_Interp *interp;			/* Current interpreter */
    int objc;				/* Number of arguments */
    Tcl_Obj *const objv[];		/* Argument objects */
{
    struct Tclgeomap_Proj *projPtr;	/* Projection to set */
    GeoProj proj;
    Tcl_HashEntry *entry;		/* GeoProj entry */
    Tcl_HashSearch search;		/* Update loop parameter */
    ClientData cd;			/* ClientData for an update proc */
    Tclgeomap_ProjUpdateProc *updateProc;	/* Update procedure */

    projPtr = (struct Tclgeomap_Proj *)clientData;
    proj = (GeoProj)projPtr;
    if (objc == 2) {
	struct GeoProjInfo info = GeoProjGetInfo(proj);
	Tcl_SetObjResult(interp, Tcl_NewDoubleObj(AngleToDeg(info.rotation)));
	return TCL_OK;
    } else if (objc == 3) {
	double a;
	int i;			/* Index returned by Tcl_GetIndexFromObj */
	static char *brgNms[] = {
	    "north",		"nneast",	"neast",	"eneast",
	    "east",		"eseast",	"seast",	"sseast",
	    "south",		"sswest",	"swest",	"wswest",
	    "west",		"wnwest",	"nwest",	"nnwest",
	    NULL
	};			/* Bearing names */
	static double brgs[] = {
	    0.0,		-22.5,		-45.0,		-67.5,
	   -90.0,		-112.5,		-135.0,		-157.5,
	    180.0,		157.5,		135.0,		112.5,
	   90.0,		 67.5,		 45.0,		 22.5
	};			/* Angles corresponding to bearing names */

	if (Tcl_GetIndexFromObj(NULL, objv[2], brgNms, "", 0, &i) == TCL_OK) {
	    a = AngleFmDeg(brgs[i]);
	} else if (Tcl_GetDoubleFromObj(NULL, objv[2], &a) == TCL_OK) {
	    a = GwchLon(AngleFmDeg(a));
	} else {
	    Tcl_AppendResult(interp, "  Rotation should be a float-point number ",
		    "of one of: north, nneast, neast, eneast, east, eseast, "
		    "seast, sseast, south, sswest, swest, wswest, west, wnwest, "
		    "nwest, or nnwest", NULL);
	    return TCL_ERROR;
	}
	GeoProjSetRotation(proj, a);
	for (entry = Tcl_FirstHashEntry(&projPtr->updateTasks, &search);
		entry != NULL;
		entry = Tcl_NextHashEntry(&search)) {
	    cd = (ClientData)Tcl_GetHashKey(&projPtr->updateTasks, entry);
	    updateProc = (Tclgeomap_ProjUpdateProc *)Tcl_GetHashValue(entry);
	    (*updateProc)(cd);
	}
    } else {
	Tcl_WrongNumArgs(interp, 2, objv, "?rotation_angle?");
	return TCL_ERROR;
    }

    return TCL_OK;
}

/*
 *------------------------------------------------------------------------
 *
 * info --
 *
 *	This is the callback for the "projName info ..." command.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 * 	See the user documentation.
 *
 *------------------------------------------------------------------------
 */

static int
info(clientData, interp, objc, objv)
    ClientData clientData;	/* Projection to get info about */
    Tcl_Interp *interp;		/* Current interpreter */
    int objc;			/* Number of arguments */
    Tcl_Obj *const objv[];	/* Argument objects */
{
    char *descr;

    if (objc != 2) {
	Tcl_WrongNumArgs(interp, 2, objv, NULL);
	return TCL_ERROR;
    }
    descr = GeoProjDescriptor((GeoProj)clientData);
    Tcl_SetObjResult(interp, Tcl_NewStringObj(descr, -1));
    return TCL_OK;
}

/*
 *------------------------------------------------------------------------
 *
 * deleteProc --
 *
 * 	This procedure is called when the command associated with a projection
 * 	is deleted.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	A Tclgeomap_Proj structure and its contents are freed.
 *
 *------------------------------------------------------------------------
 */

static void
deleteProc(clientData)
    ClientData clientData;	/* Projection being deleted */
{
    struct Tclgeomap_Proj *projPtr;
    				/* Tclgeomap_Proj from hash table */
    Tcl_HashEntry *entry;	/* Entry for projPtr->updateTasks */
    Tcl_HashSearch search;
    ClientData cd;		/* Clientdata in a deletion task */
    Tclgeomap_ProjDeleteProc *deleteProc;

    projPtr = (struct Tclgeomap_Proj *)clientData;
    for (entry = Tcl_FirstHashEntry(&projPtr->deleteTasks, &search);
	    entry != NULL; entry = Tcl_NextHashEntry(&search)) {
	cd = (ClientData)Tcl_GetHashKey(&projPtr->deleteTasks, entry);
	deleteProc = (Tclgeomap_ProjDeleteProc *)Tcl_GetHashValue(entry);
	(*deleteProc)(cd);
    }
    entry = Tcl_FindHashEntry(&projections, (ClientData)projPtr);
    Tcl_DeleteHashEntry(entry);
    GeoProjFree((GeoProj)projPtr);
    Tcl_DeleteHashTable(&projPtr->updateTasks);
    Tcl_DeleteHashTable(&projPtr->deleteTasks);
    CKFREE((char *)projPtr);
}

/*
 *------------------------------------------------------------------------
 *
 * fmLatLon --
 *
 *	This is the callback for the "projName fmLatLon ..." command.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 * 	See the user documentation.
 *
 *------------------------------------------------------------------------
 */

static int
fmLatLon(clientData, interp, objc, objv)
    ClientData clientData;		/* Projection */
    Tcl_Interp *interp;			/* Current interpreter */
    int objc;				/* Number of arguments */
    Tcl_Obj *const objv[];		/* Argument objects */
{
    GeoProj proj;			/* Projection for making conversion */
    GeoPt geoPt;			/* Input point from command line */
    MapPt mapPt;			/* Result */

    if (objc != 3) {
	Tcl_WrongNumArgs(interp, 2, objv, "{lat lon}");
	return TCL_ERROR;
    }
    proj = (GeoProj)clientData;
    if (Tclgeomap_GetGeoPtFromObj(interp, objv[2], &geoPt) != TCL_OK) {
	return TCL_ERROR;
    }
    mapPt = LatLonToProj(geoPt, proj);
    if (MapPtIsSomewhere(mapPt)) {
	Tcl_SetObjResult(interp, Tclgeomap_NewMapPtObj(mapPt));
	return TCL_OK;
    } else {
	double lat, lon;
	char lats[TCL_DOUBLE_SPACE], lons[TCL_DOUBLE_SPACE];

	GeoPtGetDeg(geoPt, &lat, &lon);
	Tcl_PrintDouble(NULL, lat, lats);
	Tcl_PrintDouble(NULL, lon, lons);
	Tcl_AppendResult(interp, "Could not get map point for {", lats, " ",
		lons, "}", NULL);
	return TCL_ERROR;
    }
}

/*
 *------------------------------------------------------------------------
 *
 * toLatLon --
 *
 *	This is the callback for the "projName toLatLon ..." command.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 * 	See the user documentation.
 *
 *------------------------------------------------------------------------
 */

static int
toLatLon(clientData, interp, objc, objv)
    ClientData clientData;		/* Projection */
    Tcl_Interp *interp;			/* Current interpreter */
    int objc;				/* Number of arguments */
    Tcl_Obj *const objv[];		/* Argument objects */
{
    GeoProj proj;			/* Projection for making conversion */
    MapPt projPt;			/* Input point from command line */
    GeoPt geoPt;			/* Result */

    if (objc != 3) {
	Tcl_WrongNumArgs(interp, 2, objv, "{abs ord}");
	return TCL_ERROR;
    }
    proj = (GeoProj)clientData;
    if (Tclgeomap_GetMapPtFromObj(interp, objv[2], &projPt) != TCL_OK) {
	return TCL_ERROR;
    }
    geoPt = ProjToLatLon(projPt, proj);
    if (GeoPtIsSomewhere(geoPt)) {
	Tcl_SetObjResult(interp, Tclgeomap_NewGeoPtObj(geoPt));
	return TCL_OK;
    } else {
	double lat, lon;
	char lats[TCL_DOUBLE_SPACE], lons[TCL_DOUBLE_SPACE];

	GeoPtGetDeg(geoPt, &lat, &lon);
	Tcl_PrintDouble(NULL, lat, lats);
	Tcl_PrintDouble(NULL, lon, lons);
	Tcl_AppendResult(interp, "Could not get geographic point for {",
		lats, " ", lons, "} for projection ", GeoProjDescriptor(proj),
		NULL);
	return TCL_ERROR;
    }
}

/*
 *------------------------------------------------------------------------
 *
 * Tclgeomap_AddProjUpdateTask --
 *
 *	This procedure arranges for a given action to be taken when a
 *	projection changes.
 *
 * Results:
 *	None.
 *
 * Side effects:
 * 	An entry is added to the updateTasks table of a Tclgeomap_Proj structure.
 * 	It can be removed with a call to Tclgeomap_CnxProjUpdateTask.
 *
 *------------------------------------------------------------------------
 */

void
Tclgeomap_AddProjUpdateTask(projPtr, updateProc, clientData)
    struct Tclgeomap_Proj *projPtr;
    Tclgeomap_ProjUpdateProc updateProc;/* Procedure to call when the
					 * projection changes */
    ClientData clientData;		/* Additional information provided to
					 * updateProc, and identifier for 
					 * this task in subsequent call to
					 * Tclgeomap_CnxProjUpdateTask. */
{
    Tcl_HashEntry *entry;
    int n;

    if ( !updateProc || !clientData
	    || !Tcl_FindHashEntry(&projections, (ClientData)projPtr) ) {
	return;
    }
    entry = Tcl_CreateHashEntry(&projPtr->updateTasks, (char *)clientData, &n);
    Tcl_SetHashValue(entry, updateProc);
    return;
}

/*
 *------------------------------------------------------------------------
 *
 * Tclgeomap_CnxProjUpdateTask --
 *
 *	This procedure cancels an update task created by an earlier call to
 *	Tclgeomap_AddProjUpdateTask.
 *
 * Results:
 *	None.
 *
 * Side effects:
 * 	An entry is removed from the updateTasks table of a Tclgeomap_Proj
 * 	structure.
 *
 *------------------------------------------------------------------------
 */

void
Tclgeomap_CnxProjUpdateTask(projPtr, clientData)
    struct Tclgeomap_Proj *projPtr;
    ClientData clientData;	/* clientData argument from earlier call to
				 * Tclgeomap_AddProjUpdateTask.*/
{
    Tcl_HashEntry *entry;

    if ( !projPtr
	    || !(entry = Tcl_FindHashEntry(&projPtr->updateTasks,
		    (char *)clientData)) ) {
	return;
    }
    Tcl_DeleteHashEntry(entry);
}

/*
 *------------------------------------------------------------------------
 *
 * Tclgeomap_AddProjDeleteTask --
 *
 * Results:
 * 	None.
 *
 * Side effects:
 * 	See the user documentation.
 *
 *------------------------------------------------------------------------
 */

void
Tclgeomap_AddProjDeleteTask(projPtr, proc, clientData)
    Tclgeomap_Proj projPtr;
    Tclgeomap_ProjDeleteProc proc;
    ClientData clientData;
{
    int n;
    Tcl_HashEntry *entry;

    if ( !projPtr || ! proc || !clientData 
	    || !Tcl_FindHashEntry(&projections, (ClientData)projPtr) ) {
	return;
    }
    entry = Tcl_CreateHashEntry(&projPtr->deleteTasks, clientData, &n);
    Tcl_SetHashValue(entry, (ClientData)proc);
}

/*
 *------------------------------------------------------------------------
 *
 * Tclgeomap_CnxProjDeleteTask --
 *
 * 	This procedure cancels a callback added by Tclgeomap_AddProjDeleteTask.
 *
 * Results:
 * 	None.
 *
 * Side effects:
 * 	See the user documentation.
 *
 *------------------------------------------------------------------------
 */

void
Tclgeomap_CnxProjDeleteTask(projPtr, clientData)
    Tclgeomap_Proj projPtr;
    ClientData clientData;
{
    Tcl_HashEntry *entry;

    if ( !projPtr || !clientData ) {
	return;
    }
    if ( !(entry = Tcl_FindHashEntry(&projPtr->deleteTasks,
		    (char *)clientData)) ) {
	return;
    }
    Tcl_DeleteHashEntry(entry);
}


syntax highlighted by Code2HTML, v. 0.9.1