/*
 * tclgeomapPlace.c --
 *
 *	This file defines the structures and functions that add the ability
 *	to manage named geographic locations in Tcl.
 * 
 * Copyright (c) 2004 Gordon D. Carrie.  All rights reserved.
 * 
 * Licensed under the Open Software License version 2.1
 * 
 * Please address questions and feedback to user0@tkgeomap.org
 *
 * @(#) $Id: tclgeomapPlace.c,v 1.5 2004/09/22 21:57:51 tkgeomap Exp $
 *
 ********************************************
 *
 */

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

/*
 * Forward declarations
 */

static Tclgeomap_Place	createPlace _ANSI_ARGS_((Tcl_Interp *, char *, GeoPt));
static int		geoplaceCallback _ANSI_ARGS_((ClientData clientData,
				Tcl_Interp *interp, int objc,
				Tcl_Obj *CONST objv[]));
static int		placeCmdCallback _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		distance _ANSI_ARGS_((ClientData clientData,
				Tcl_Interp *interp, int objc,
				Tcl_Obj *CONST objv[]));
static int		azrng _ANSI_ARGS_((ClientData clientData,
				Tcl_Interp *interp, int objc,
				Tcl_Obj *CONST objv[]));
static int		nearest _ANSI_ARGS_((ClientData clientData,
				Tcl_Interp *interp, int objc,
				Tcl_Obj *CONST objv[]));
static int		step _ANSI_ARGS_((ClientData clientData,
				Tcl_Interp *interp, int objc,
				Tcl_Obj *CONST objv[]));
static int		inrange _ANSI_ARGS_((ClientData clientData,
				Tcl_Interp *interp, int objc,
				Tcl_Obj *CONST objv[]));
static void		deleteProc _ANSI_ARGS_((ClientData));

/*
 * All geoplaces are entered in the following table.  One-word-keys are
 * Tclgeomap_Place structures.  Values are not used.
 */

static Tcl_HashTable places;

/*
 * The following array and enum are used to process units on command line.
 */

static char *units[] = {"nmiles", "smiles", "km", "gsdeg", NULL};
enum UnitIdx	       {NMILES,   SMILES,   KM,   GSDEG};


/*
 *------------------------------------------------------------------------
 *
 * TclgeomapPlaceInit --
 *
 *	This procedure initializes the Tclgeomap_Place interface and provides
 *	the tclgeoplace package.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	The "geomap::place" command is added to the interpreter.
 *	The places table (defined above) is initialized.
 *	The geoplaceSubCmdNmPtr and plcCmdSubCmdNmPtr subcommand arrays are
 *	initialized.
 *
 *
 *------------------------------------------------------------------------
 */

int
TclgeomapPlaceInit(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_CreateObjCommand(interp, "::geomap::place", geoplaceCallback, NULL,
	    NULL);
    Tcl_InitHashTable(&places, TCL_ONE_WORD_KEYS);
    loaded = 1;
    return TCL_OK;
}

/*
 *------------------------------------------------------------------------
 *
 * geoplaceCallback --
 *
 *	This is the callback for the "geomap::place" command.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	This procedure invokes the callback corresponding to the first
 *	argument given to the "geomap::place" command.  Side effects depend
 *	on the subcommand called.
 *
 *------------------------------------------------------------------------
 */

int
geoplaceCallback(clientData, interp, objc, objv)
    ClientData clientData;	/* Not used */
    Tcl_Interp *interp;		/* Current interpreter */
    int objc;			/* Number of arguments */
    Tcl_Obj *const objv[];	/* Argument objects */
{
    char *nmPtr[] = {
	"new", "set", "distance", "azrng", "nearest", "step", "inrange",
	NULL
    };
    Tcl_ObjCmdProc *procPtr[] = {
	new,   set,   distance,   azrng,   nearest,   step,   inrange
    };
    int i;

    if (objc < 2) {
	Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?");
	return TCL_ERROR;
    }
    if (Tcl_GetIndexFromObj(interp, objv[1], nmPtr, "subcommand", 0, &i)
	    != TCL_OK) {
	return TCL_ERROR;
    }
    return (procPtr[i])(NULL, interp, objc, objv);
}

/*
 *------------------------------------------------------------------------
 *
 * placeCmdCallback --
 *
 *	This is the callback for a commands of form "placeName subcommand ..."
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	This procedure invokes the callback corresponding to the first
 *	argument given to the "placeName" command.  Side effects depend
 *	on the subcommand called.
 *
 *------------------------------------------------------------------------
 */

int
placeCmdCallback(clientData, interp, objc, objv)
    ClientData clientData;	/* A Tclgeomap_Place struct */
    Tcl_Interp *interp;		/* Current interpreter */
    int objc;			/* Number of arguments */
    Tcl_Obj *const objv[];	/* Argument objects */
{
    int i;
    static char *nmPtr[] = {
	"set", "nearest", "step", "inrange", NULL
    };
    Tcl_ObjCmdProc *procPtr[] = {
	set,   nearest,   step,   inrange
    };

    if (objc < 2) {
	Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?");
	return TCL_ERROR;
    }
    if (Tcl_GetIndexFromObj(interp, objv[1], nmPtr, "subcommand", 0, &i)
	    != TCL_OK) {
	return TCL_ERROR;
    }
    return (procPtr[i])(clientData, interp, objc, objv);
}

/*
 *------------------------------------------------------------------------
 *
 * createPlace --
 *
 *	This procedure creates a new place in the database.
 *
 * Results:
 *	If successful, this procedure returns a Tclgeomap_Place structure whose
 *	coordinates are set to the given lat-lon.  The return value is
 *	dynamically allocated and should eventually be freed with a call to
 *	CKFREE.
 *
 * Side effects:
 *	A new Tcl command is created, whose name is the place name,
 *	to access and manipulate the place.
 *	The new Tclgeomap_Place structure is added to the places table.
 *
 *------------------------------------------------------------------------
 */

Tclgeomap_Place
createPlace(interp, name, geoPt)
    Tcl_Interp *interp;		/* Current interpreter */
    char *name;			/* Place name = place command name.  Can
				 * contain namespace qualifiers, otherwise it
				 * becomes global. */
    GeoPt geoPt;		/* Coordinates of the new place */
{
    struct Tclgeomap_Place *plcPtr;	/* Structure for the new place */
    int newPtr;			/* Not used */

    plcPtr = (Tclgeomap_Place)CKALLOC(sizeof(*plcPtr));
    Tcl_CreateHashEntry(&places, (char *)plcPtr, &newPtr);
    plcPtr->interp = interp;
    plcPtr->geoPt = geoPt;
    Tcl_InitHashTable(&plcPtr->updateTasks, TCL_ONE_WORD_KEYS);
    Tcl_InitHashTable(&plcPtr->deleteTasks, TCL_ONE_WORD_KEYS);
    plcPtr->cmd = Tcl_CreateObjCommand(interp, name, placeCmdCallback,
	    (ClientData)plcPtr, deleteProc);
    return plcPtr;
}

/*
 *------------------------------------------------------------------------
 *
 * Tclgeomap_AddPlaceUpdateTask --
 *
 *	This procedure arranges for a function to be called when a place
 *	moves.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	See user documentation.
 *
 *------------------------------------------------------------------------
 */

void
Tclgeomap_AddPlaceUpdateTask(placePtr, proc, clientData)
    Tclgeomap_Place placePtr;
    Tclgeomap_PlaceUpdateProc proc;
    ClientData clientData;
{
    int n;
    Tcl_HashEntry *entry;

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

void
Tclgeomap_CnxPlaceUpdateTask(placePtr, clientData)
    Tclgeomap_Place placePtr;
    ClientData clientData;
{
    Tcl_HashEntry *entry;

    if ( !placePtr || !clientData ) {
	return;
    }
    if ( !(entry = Tcl_FindHashEntry(&placePtr->updateTasks,
		    (char *)clientData)) ) {
	return;
    }
    Tcl_DeleteHashEntry(entry);
}

/*
 *------------------------------------------------------------------------
 *
 * Tclgeomap_AddPlaceDeleteTask --
 *
 *	This procedure arranges for a function to be called when a place
 *	is deleted.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	See the user documentation.
 *
 *------------------------------------------------------------------------
 */

void
Tclgeomap_AddPlaceDeleteTask(placePtr, proc, clientData)
    Tclgeomap_Place placePtr;
    Tclgeomap_PlaceDeleteProc proc;
    ClientData clientData;
{
    int n;
    Tcl_HashEntry *entry;

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

void
Tclgeomap_CnxPlaceDeleteTask (placePtr, clientData)
    Tclgeomap_Place placePtr;
    ClientData clientData;
{
    Tcl_HashEntry *entry;

    if ( !placePtr || !clientData ) {
	return;
    }
    if ( !(entry = Tcl_FindHashEntry(&placePtr->deleteTasks,
		    (char *)clientData)) ) {
	return;
    }
    Tcl_DeleteHashEntry(entry);
}

/*
 *------------------------------------------------------------------------
 *
 * Tclgeomap_GetPlace --
 *
 *	Return a Tclgeomap_Place struct given the place name.
 *
 * Results:
 *	A Tclgeomap_Place struct or NULL.
 *
 * Side effects:
 *	None.
 *------------------------------------------------------------------------
 */

Tclgeomap_Place
Tclgeomap_GetPlace(interp, name)
    Tcl_Interp *interp;		/* Current interpreter */
    CONST char *name;		/* Alleged place name */
{
    Tcl_CmdInfo infoPtr;	/* Command info for command named name */
    if (   Tcl_GetCommandInfo(interp, (char *)name, &infoPtr)
	&& Tcl_FindHashEntry(&places, infoPtr.objClientData)) {
	return (Tclgeomap_Place)infoPtr.objClientData;
    } else {
	return NULL;
    }
}

/*
 *------------------------------------------------------------------------
 *
 * Tclgeomap_PlaceName --
 *
 *	This procedure returns the name of a place.
 *
 * Results:
 *	See the user documentation.
 *
 * Side effects:
 *	See the user documentation.
 *
 *------------------------------------------------------------------------
 */

CONST char *
Tclgeomap_PlaceName(placePtr)
    Tclgeomap_Place placePtr;
{
    return placePtr
	? Tcl_GetCommandName(placePtr->interp, placePtr->cmd)
	: NULL;
}

/*
 *------------------------------------------------------------------------
 *
 * Tclgeomap_PlaceLoc --
 *
 *	This procedure returns the {lat lon} coordinates of a Tclgeomap_Place.
 *
 * Results:
 *	A GeoPt (declared in geography.h)
 *
 * Side effects:
 *	None.
 *
 *------------------------------------------------------------------------
 */

GeoPt
Tclgeomap_PlaceLoc(plcPtr)
    struct Tclgeomap_Place *plcPtr;	/* Place of interest */
{
    return plcPtr->geoPt;
}

/*
 *------------------------------------------------------------------------
 *
 * deleteProc --
 *
 *	This is the deletion procedure for the Tcl command created for a
 *	place.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	A Tclgeomap_Place structure is deleted and its entry is removed from
 *	the places hash table.
 *
 *------------------------------------------------------------------------
 */

void
deleteProc(clientData)
    ClientData clientData;		/* The place to remove */
{
    Tcl_HashEntry *entry;		/* Entry for delete tasks loop */
    Tcl_HashSearch search;		/* Delete task loop parameter */
    ClientData cd;			/* Clientdata for an delete proc */
    Tclgeomap_PlaceDeleteProc *deleteProc;
					/* Procedure from deleteTasks table */
    struct Tclgeomap_Place *plcPtr;	/* The place being deleted */

    plcPtr = (Tclgeomap_Place)clientData;
    for (entry = Tcl_FirstHashEntry(&plcPtr->deleteTasks, &search);
	    entry != NULL; entry = Tcl_NextHashEntry(&search)) {
	cd = (ClientData)Tcl_GetHashKey(&plcPtr->deleteTasks, entry);
	deleteProc = (Tclgeomap_PlaceDeleteProc *)Tcl_GetHashValue(entry);
	(*deleteProc)(cd);
    }
    Tcl_DeleteHashEntry(Tcl_FindHashEntry(&places, (char *)plcPtr));
    Tcl_DeleteHashTable(&plcPtr->updateTasks);
    Tcl_DeleteHashTable(&plcPtr->deleteTasks);
    CKFREE((char *)plcPtr);
}

/*
 *------------------------------------------------------------------------
 *
 * new --
 *
 *	This is the callback for the "geomap::place new ..." command.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	This procedure allocates and initializes a new Tclgeomap_Place
 *	structure, and adds it to the places table.
 *	It creates a new Tcl command named for the place to access it.
 *	The data structures and command associated with the place will be
 *	destroyed when the place command is destroyed.
 *
 *------------------------------------------------------------------------
 */

int
new(clientData, interp, objc, objv)
    ClientData clientData;	/* Not used */
    Tcl_Interp *interp;		/* The current interpreter */
    int objc;			/* Number of arguments */
    Tcl_Obj *const objv[];	/* Argument objects */
{
    char *name;			/* Place name */
    GeoPt geoPt;		/* Lat-lon of the new place */
    Tcl_CmdInfo info;		/* Not used. */

    if (objc != 4) {
	Tcl_WrongNumArgs(interp, 2, objv, "placeName {lat lon}");
	return TCL_ERROR;
    } 
    if (Tclgeomap_GetGeoPtFromObj(interp, objv[3], &geoPt) != TCL_OK) {
	return TCL_ERROR;
    }
    name = Tcl_GetString(objv[2]);
    if (Tcl_GetCommandInfo(interp, name, &info)) {
	Tcl_AppendResult(interp, "Could not create place named ",
		name, " because a command by that name already exists.\n",
		NULL);
	return TCL_ERROR;
    }
    createPlace(interp, name, geoPt);
    Tcl_SetObjResult(interp, Tcl_NewStringObj(name, -1));
    return TCL_OK;
}

/*
 *------------------------------------------------------------------------
 *
 * set --
 *
 *	This is the callback for commands of form "geomap::place set ..." and
 *	"placeName set ...".
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 * 	See the user documentation.
 *
 *------------------------------------------------------------------------
 */

int
set(clientData, interp, objc, objv)
    ClientData clientData;		/* If not NULL, a Tclgeomap_Place
					 * structure */
    Tcl_Interp *interp;			/* The current interpreter */
    int objc;				/* Number of arguments */
    Tcl_Obj *const objv[];		/* Argument objects */
{

    char *name;				/* Fully qualified name of place */
    Tcl_Obj *geoPtObj;			/* Lat-lon for the place */
    GeoPt geoPt;			/* GeoPt from geoPtObj */
    struct Tclgeomap_Place *plcPtr;		/* Place of interest */
    Tcl_HashEntry *entry;		/* Entry for update loop */
    Tcl_HashSearch search;		/* Update loop parameter */
    ClientData cd;			/* Clientdata for an update proc */
    Tclgeomap_PlaceUpdateProc *updateProc;
    					/* Procedure from updateTasks table */

    if (clientData) {
	/*
	 * Command has form "placeName set" or "placeName set {lat lon}"
	 */

	plcPtr = (Tclgeomap_Place)clientData;
	if (objc == 2) {
	    Tcl_SetObjResult(interp, Tclgeomap_NewGeoPtObj(plcPtr->geoPt));
	} else if (objc == 3) {
	    geoPtObj = objv[2];
	    if (Tclgeomap_GetGeoPtFromObj(interp, geoPtObj, &geoPt)
		    != TCL_OK)
		return TCL_ERROR;
	    plcPtr->geoPt = geoPt;
	    for (entry = Tcl_FirstHashEntry(&plcPtr->updateTasks, &search);
		    entry != NULL;
		    entry = Tcl_NextHashEntry(&search)) {
		cd = (ClientData)Tcl_GetHashKey(&plcPtr->updateTasks, entry);
		updateProc
		    = (Tclgeomap_PlaceUpdateProc *)Tcl_GetHashValue(entry);
		(*updateProc)(cd);
	    }
	    Tcl_SetObjResult(interp, objv[2]);
	} else {
	    Tcl_WrongNumArgs(interp, 2, objv, "?{lat lon}?");
	    return TCL_ERROR;
	}
    } else {
	/*
	 * Command has form "geomap::place set placeName"
	 * or "geomap::place set placeName {lat lon}".
	 */

	if (objc == 3) {
	    name = Tcl_GetString(objv[2]);
	    if ( !(plcPtr = Tclgeomap_GetPlace(interp, name)) ) {
		Tcl_AppendResult(interp, "No place named ", name, NULL);
		return TCL_ERROR;
	    } else {
		Tcl_SetObjResult(interp,
			Tclgeomap_NewGeoPtObj(plcPtr->geoPt));
	    }
	} else if (objc == 4) {
	    geoPtObj = objv[3];
	    if (Tclgeomap_GetGeoPtFromObj(interp, geoPtObj, &geoPt)
		    != TCL_OK) {
		return TCL_ERROR;
	    }
	    name = Tcl_GetString(objv[2]);
	    if ((plcPtr = Tclgeomap_GetPlace(interp, name))) {
		plcPtr->geoPt = geoPt;
	    } else {
		plcPtr = createPlace(interp, name, geoPt);
	    }
	    Tcl_SetObjResult(interp, objv[3]);
	} else {
	    Tcl_WrongNumArgs(interp, 2, objv, "placeName ?{lat lon}?");
	    return TCL_ERROR;
	}
    }
    return TCL_OK;
}

/*
 *------------------------------------------------------------------------
 *
 * distance --
 *
 *	This is the callback for the "geomap::place distance" command.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 * 	See the user documentation.
 *
 *------------------------------------------------------------------------
 */

int
distance(clientData, interp, objc, objv)
    ClientData clientData;			/* Not used */
    Tcl_Interp *interp;				/* The current interpreter */
    int objc;					/* Number of arguments */
    Tcl_Obj *const objv[];			/* Argument objects */
{
    char *plcNm;				/* Place name on command line */
    Tclgeomap_Place plc1Ptr, plc2Ptr;		/* Input named places */
    GeoPt geoPt1, geoPt2;			/* Input lat-lon's */
    double dist;				/* Result */

    if (objc != 4 && objc != 5) {
	Tcl_WrongNumArgs(interp, 2, objv, 
		"placeOR{lat lon} placeOR{lat lon} ?unit?");
	return TCL_ERROR;
    }

    /*
     * Get geoPt1 from objv[2], which is either a place name or
     * a {lat lon} pair.
     */

    if (Tclgeomap_GetGeoPtFromObj(NULL, objv[2], &geoPt1) != TCL_OK) {
	plcNm = Tcl_GetString(objv[2]);
	if ((plc1Ptr = Tclgeomap_GetPlace(interp, plcNm)) ) {
	    geoPt1 = plc1Ptr->geoPt;
	} else {
	    Tcl_AppendResult(interp, plcNm, " not a location", NULL);
	    return TCL_ERROR;
	}
    }

    /*
     * Get geoPt2 from objv[3], which is either a place name or
     * a {lat lon} pair.
     */

    if (Tclgeomap_GetGeoPtFromObj(NULL, objv[3], &geoPt2) != TCL_OK) {
	plcNm = Tcl_GetString(objv[3]);
	if ((plc2Ptr = Tclgeomap_GetPlace(interp, plcNm)) ) {
	    geoPt2 = plc2Ptr->geoPt;
	} else {
	    Tcl_AppendResult(interp, plcNm, " not a location", NULL);
	    return TCL_ERROR;
	}
    }

    dist = AngleToDeg(GeoDistance(geoPt1, geoPt2));
    if (objc == 5) {
	/*
	 * Apply optional distance unit.
	 */

	Tcl_Obj *unit = objv[4];
	int idx;
	if (Tcl_GetIndexFromObj(interp, unit, units, "unit", 0, &idx)
		!= TCL_OK) {
	    return TCL_ERROR;
	}
	switch ((enum UnitIdx)idx) {
	    case NMILES:	dist *= NMIPERDEG;	break;
	    case SMILES:	dist *= SMIPERDEG;	break;
	    case KM:		dist *= KMPERDEG;	break;
	    case GSDEG:					break;
	}
    }
    Tcl_SetObjResult(interp, Tcl_NewDoubleObj(dist));
    return TCL_OK;
}

/*
 *------------------------------------------------------------------------
 *
 * azrng --
 *
 *	This is the callback for the "geomap::place azrng ..." command.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 * 	See the user documentation.
 *
 *------------------------------------------------------------------------
 */

int
azrng(clientData, interp, objc, objv)
    ClientData clientData;			/* Not used */
    Tcl_Interp *interp;				/* The current interpreter */
    int objc;					/* Number of arguments */
    Tcl_Obj *const objv[];			/* Argument objects */
{
    char *plcNm;				/* Place name on command line */
    Tclgeomap_Place plc1Ptr, plc2Ptr;		/* Input named places */
    GeoPt geoPt1, geoPt2;			/* Input lat-lon's */
    double azm, dist;				/* Input azimuth and distance */
    Tcl_Obj *rslt;				/* Hold result */

    rslt = Tcl_GetObjResult(interp);

    if (objc != 4 && objc != 5) {
	Tcl_WrongNumArgs(interp, 2, objv, 
		"placeOR{lat lon} placeOR{lat lon} ?unit?");
	return TCL_ERROR;
    }

    /*
     * Get geoPt1 from objv[2], which is either a place name or
     * a {lat lon} pair.
     */

    if (Tclgeomap_GetGeoPtFromObj(NULL, objv[2], &geoPt1) != TCL_OK) {
	plcNm = Tcl_GetString(objv[2]);
	if ((plc1Ptr = Tclgeomap_GetPlace(interp, plcNm)) ) {
	    geoPt1 = plc1Ptr->geoPt;
	} else {
	    Tcl_AppendResult(interp, plcNm, " not a location", NULL);
	    return TCL_ERROR;
	}
    }

    /*
     * Get geoPt2 from objv[3], which is either a place name or
     * a {lat lon} pair.
     */

    if (Tclgeomap_GetGeoPtFromObj(NULL, objv[3], &geoPt2) != TCL_OK) {
	plcNm = Tcl_GetString(objv[3]);
	if ((plc2Ptr = Tclgeomap_GetPlace(interp, plcNm)) ) {
	    geoPt2 = plc2Ptr->geoPt;
	} else {
	    Tcl_AppendResult(interp, plcNm, " not a location", NULL);
	    return TCL_ERROR;
	}
    }

    dist = AngleToDeg(GeoDistance(geoPt1, geoPt2));
    if (objc == 5) {
	/*
	 * Apply optional distance unit.
	 */

	Tcl_Obj *unit = objv[4];
	int idx;
	if (Tcl_GetIndexFromObj(interp, unit, units, "unit", 0, &idx)
		!= TCL_OK) {
	    return TCL_ERROR;
	}
	switch ((enum UnitIdx)idx) {
	    case NMILES:	dist *= NMIPERDEG;	break;
	    case SMILES:	dist *= SMIPERDEG;	break;
	    case KM:		dist *= KMPERDEG;	break;
	    case GSDEG:					break;
	}
    }
    azm = AngleToDeg(Azimuth(geoPt1, geoPt2));
    Tcl_ListObjAppendElement(interp, rslt, Tcl_NewDoubleObj(azm));
    Tcl_ListObjAppendElement(interp, rslt, Tcl_NewDoubleObj(dist));
    Tcl_SetObjResult(interp, rslt);
    return TCL_OK;
}

/*
 *------------------------------------------------------------------------
 *
 * nearest --
 *
 *	This is the callback for commands of form "geomap::place nearest ..."
 *	and "placeName nearest ...".
 *	usage details.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 * 	See the user documentation.
 *
 *------------------------------------------------------------------------
 */

int
nearest(clientData, interp, objc, objv)
    ClientData clientData;	/* If not NULL, a Tclgeomap_Place struct */
    Tcl_Interp *interp;		/* The current interpreter */
    int objc;			/* Number of arguments */
    Tcl_Obj *const objv[];	/* Argument objects */
{
    char *plcNm;		/* Name of place of interest */
    Tclgeomap_Place
	plcPtr,			/* Place of interest */
	skipPtr = NULL,		/* If place of interest is in database,
				 * store it here so that we do not compare it
				 * to itself during search */
	schPlcPtr;		/* Current place in search loop */
    GeoPt geoPt;		/* Lat-lon of place of interest */
    CONST char *nearPlcNm = NULL;/* Name of nearest place so far */
    Tcl_Obj *cmdLnPList = NULL;	/* List of places to search on command line */
    Angle d180 = AngleFmDeg(180.0);
    Angle nearDist = d180 + 1000;/* Hold distance to nearest place so far.
				 * Note that GeoDistance returns great circle
				 * arc measured in microdegrees. */
    Angle cDistance;		/* Distance to current place (microdegrees) */
    Tcl_Obj **placeList;	/* Optional list of places on command line */
    int placeCnt;		/* Number of places in placeList */
    int np;			/* Loop index */
    char *lsElemNm;		/* Name of place from list on command line */

    if (clientData) {
	/*
	 * Command is of form "placeName nearest {place place ...}"
	 */

	if (objc != 3) {
	    Tcl_WrongNumArgs(interp, 2, objv, "{place place ...}");
	    return TCL_ERROR;
	}
	plcPtr = (Tclgeomap_Place)clientData;
	geoPt = plcPtr->geoPt;
	skipPtr = plcPtr;
	cmdLnPList = objv[2];

    } else {
	/*
	 * Command is of form
	 * "geomap::place nearest placeORgeoPt {place place ...}"
	 */

	if (objc != 4) {
	    Tcl_WrongNumArgs(interp, 2, objv,
		    "placeOR{lat lon} {place place ...}");
	    return TCL_ERROR;
	}
	if (Tclgeomap_GetGeoPtFromObj(NULL, objv[2], &geoPt) != TCL_OK) {
	    plcNm = Tcl_GetString(objv[2]);
	    if ( !(plcPtr = Tclgeomap_GetPlace(interp, plcNm)) ) {
		Tcl_AppendResult(interp, plcNm, " not a location.", NULL);
		return TCL_ERROR;
	    }
	    geoPt = plcPtr->geoPt;
	    skipPtr = plcPtr;
	}
	if (objc == 4) {
	    cmdLnPList = objv[3];
	}

    }

    /*
     * Search list given on command line
     */

    if (Tcl_ListObjGetElements(interp, cmdLnPList, &placeCnt, &placeList)
	    != TCL_OK) {
	return TCL_ERROR;
    }
    for (np = 0, nearDist = d180 + 1000; np < placeCnt; np++) {
	lsElemNm = Tcl_GetString(placeList[np]);
	if ( !(schPlcPtr = Tclgeomap_GetPlace(interp, lsElemNm)) ) {
	    Tcl_AppendResult(interp, "No place named ", lsElemNm,
		    " in current namespace.", NULL);
	    return TCL_ERROR;
	}
	if (schPlcPtr == skipPtr) {
	    continue;
	}
	cDistance = GeoDistance(schPlcPtr->geoPt, geoPt);
	if (cDistance < nearDist) {
	    nearDist = cDistance;
	    nearPlcNm = Tcl_GetCommandName(interp, schPlcPtr->cmd);
	}
    }

    if (nearPlcNm) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj(nearPlcNm, -1));
	return TCL_OK;
    } else {
	Tcl_AppendResult(interp, "No places to compare", NULL);
	return TCL_ERROR;
    }
}

/*
 *------------------------------------------------------------------------
 *
 * step --
 *
 *	This is the callback for the "geomap::place step ..." and
 *	"placeName step ..." commands.
 *	for usage details.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 * 	See the user documentation.
 *
 *------------------------------------------------------------------------
 */

int
step(clientData, interp, objc, objv)
    ClientData clientData;	/* Not used */
    Tcl_Interp *interp;		/* The current interpreter */
    int objc;			/* Number of arguments */
    Tcl_Obj *const objv[];	/* Argument objects */
{
    char *plcNm;		/* Place name */
    GeoPt geoPt;		/* Starting location */
    GeoPt endPt;		/* Point at bearing and range from geoPt */
    struct Tclgeomap_Place *plcPtr;
				/* Current place */
    double brg, rng;		/* Desired bearing and range */
    Tcl_Obj
	*brgObj,		/* Bearing on command line */
	*rngObj;		/* Range on command line */
    Tcl_Obj *unit = NULL;	/* Optional unit on command line */
    int idx;			/* Index returned by Tcl_GetIndexFromObj */
    static char *brgs[] = {
	"north",	"nneast",	"neast",	"eneast",
	"east",		"eseast",	"seast",	"sseast",
	"south",	"sswest",	"swest",	"wswest",
	"west",		"wnwest",	"nwest",	"nnwest",	NULL
    };				/* Bearing names */
    enum brgIdx {
	N,		NNE,		NE,		ENE,
	E,		ESE,		SE,		SSE,
	S,		SSW,		SW,		WSW,
	W,		WNW,		NW,		NNW
    };
    				/* Bearing indices */

    if (clientData) {
	/*
	 * Command is of form "placeName step az rng ?unit?"
	 */

	if (objc != 4 && objc != 5) {
	    Tcl_WrongNumArgs(interp, 2, objv,
		    "bearing range ?unit?");
	    return TCL_ERROR;
	}
	plcPtr = (Tclgeomap_Place)clientData;
	geoPt = plcPtr->geoPt;
	brgObj = objv[2];
	rngObj = objv[3];
	if (objc == 5) {
	    unit = objv[4];
	}

    } else {
	/*
	 * Command is of form
	 * "geomap::place step placeNameOR{lat lon} az rng ?unit?"
	 */

	if (objc != 5 && objc != 6) {
	Tcl_WrongNumArgs(interp, 2, objv,
		"placeName bearing range ?unit?");
	    return TCL_ERROR;
	}
	if (Tclgeomap_GetGeoPtFromObj(NULL, objv[2], &geoPt) != TCL_OK) {
	    plcNm = Tcl_GetString(objv[2]);
	    if ( !(plcPtr = Tclgeomap_GetPlace(interp, plcNm)) ) {
		Tcl_AppendResult(interp, plcNm, " not a location.", NULL);
		return TCL_ERROR;
	    }
	    geoPt = plcPtr->geoPt;
	}
	brgObj = objv[3];
	rngObj = objv[4];
	if (objc == 6) {
	    unit = objv[5];
	}
    }

    /*
     * Read bearing, which can be a number of degrees or a string
     * from the brgs array.
     */

    if (Tcl_GetDoubleFromObj(interp, brgObj, &brg) != TCL_OK) {
	Tcl_ResetResult(interp);
	if (Tcl_GetIndexFromObj(interp, brgObj, brgs, "bearing", 0, &idx)
		!= TCL_OK) {
	    Tcl_AppendResult(interp, ", or a double value", NULL);
	    return TCL_ERROR;
	}
	switch ((enum brgIdx)idx) {
	    case N:	brg =   0.0;	break;
	    case NNE:	brg =  22.5;	break;
	    case NE:	brg =  45.0;	break;
	    case ENE:	brg =  67.5;	break;
	    case E:	brg =  90.0;	break;
	    case ESE:	brg = 112.5;	break;
	    case SE:	brg = 135.0;	break;
	    case SSE:	brg = 157.5;	break;
	    case S:	brg = 180.0;	break;
	    case SSW:	brg = 202.5;	break;
	    case SW:	brg = 225.0;	break;
	    case WSW:	brg = 247.5;	break;
	    case W:	brg = 270.0;	break;
	    case WNW:	brg = 292.5;	break;
	    case NW:	brg = 315.0;	break;
	    case NNW:	brg = 337.5;	break;
	}
    }

    if (Tcl_GetDoubleFromObj(interp, rngObj, &rng) != TCL_OK) {
	return TCL_ERROR;
    }

    if (unit) {
	if (Tcl_GetIndexFromObj(interp, unit, units, "unit", 0, &idx)
		!= TCL_OK) {
	    return TCL_ERROR;
	}
	switch ((enum UnitIdx)idx) {
	    case NMILES:	rng /= NMIPERDEG;	break;
	    case SMILES:	rng /= SMIPERDEG;	break;
	    case KM:		rng /= KMPERDEG;	break;
	    case GSDEG:					break;
	}
    }

    endPt = GeoStep(geoPt, AngleFmDeg(brg), AngleFmDeg(rng));
    Tcl_SetObjResult(interp, Tclgeomap_NewGeoPtObj(endPt));
    return TCL_OK;
}

/*
 *------------------------------------------------------------------------
 *
 * inrange --
 *
 *	This is the callback for the "geomap::place inrange ..." and
 *	"placeName inrange ..." commands.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 * 	See the user documentation.
 *
 *------------------------------------------------------------------------
 */

int
inrange(clientData, interp, objc, objv)
    ClientData clientData;	/* Not used */
    Tcl_Interp *interp;		/* The current interpreter */
    int objc;			/* Number of arguments */
    Tcl_Obj *const objv[];	/* Argument objects */
{
    GeoPt
	refPt,			/* References to use in units conversion */
	origin = {0.0, 0.0};
    double rng;			/* Look for places within range of ctr */
    Tclgeomap_Place
	schPlcPtr,		/* Current place in search loop */
	ctrPtr = NULL;		/* Place we are measuring from */
    char *ctrNm;		/* Name of place we are measuring from */
    GeoPt ctr;			/* Location we are measuring from */
    int rngArgC;		/* Number of elements in rng argument */
    Tcl_Obj **rngArgv;		/* Range argument:  rng ?unit? */
    Tcl_Obj *rslt = NULL;
    Tcl_Obj *cmdLnRng;		/* Range term on command line */
    Tcl_Obj *cmdLnPList = NULL;	/* List of places to search on command line */
    int np, placeCnt;
    Tcl_Obj **placeList;
    char *lsElemNm;			/* Name of place from list */

    if (clientData) {
	/*
	 * Command has form "placeName inrange {rng ?unit?} ?list?"
	 */

	if (objc != 4) {
	    Tcl_WrongNumArgs(interp, 2, objv,
		    "{range ?unit?} {place place ...}");
	    return TCL_ERROR;
	}
	ctrPtr = (Tclgeomap_Place)clientData;
	ctr = ctrPtr->geoPt;
	cmdLnRng = objv[2];
	cmdLnPList = objv[3];

    } else {
	/*
	 * Command has form
	 * "geomap::place inrange placeORgeoPt {rng ?unit?} ?list?"
	 */

	if (objc != 5) {
	    Tcl_WrongNumArgs(interp, 2, objv,
		    " placeNameOR{lat lon} {range ?unit?} {place place ...}");
	    return TCL_ERROR;
	}
	if (Tclgeomap_GetGeoPtFromObj(NULL, objv[2], &ctr) != TCL_OK) {
	    ctrNm = Tcl_GetString(objv[2]);
	    if ( !(ctrPtr = Tclgeomap_GetPlace(interp, ctrNm)) ) {
		Tcl_AppendResult(interp, ctrNm, " not a location.", NULL);
		return TCL_ERROR;
	    }
	    ctr = ctrPtr->geoPt;
	}
	cmdLnRng = objv[3];
	cmdLnPList = objv[4];
    }

    /*
     * Get range.  If range is a two element list, second element is optional
     * unit.
     */

    if (Tcl_ListObjGetElements(interp, cmdLnRng, &rngArgC, &rngArgv) != TCL_OK
	    || Tcl_GetDoubleFromObj(interp, rngArgv[0], &rng) != TCL_OK) {
	return TCL_ERROR;
    }
    if (rngArgC == 2) {
	Tcl_Obj *unit = rngArgv[1];
	int idx;

	if (Tcl_GetIndexFromObj(interp, unit, units, "unit", 0, &idx)
		!= TCL_OK) {
	    return TCL_ERROR;
	}
	switch ((enum UnitIdx)idx) {
	    case NMILES:	rng /= NMIPERDEG;	break;
	    case SMILES:	rng /= SMIPERDEG;	break;
	    case KM:		rng /= KMPERDEG;	break;
	    case GSDEG:					break;
	}
    }

    /*
     * This algorithm uses GeoQuickDistance for the comparisons, which
     * actually computes the Cartesian distance between two points.  To
     * get the distance in "GeoQuickDistance units" make a fictitious point
     * {rng 0} and use GeoQuickDistance to compute the distance from {0 0}
     * to {rng 0}.
     */

    refPt.lat = AngleFmDeg(rng);
    refPt.lon = 0;
    rng = GeoQuickDistance(refPt, origin);

    /*
     * Search list of places from command line.
     */

    rslt = Tcl_NewObj();
    if (Tcl_ListObjGetElements(interp, cmdLnPList, &placeCnt, &placeList)
	    != TCL_OK) {
	return TCL_ERROR;
    }
    for (np = 0; np < placeCnt; np++) {
	lsElemNm = Tcl_GetString(placeList[np]);
	if ( !(schPlcPtr = Tclgeomap_GetPlace(interp, lsElemNm)) ) {
	    Tcl_AppendResult(interp, "No place named ", lsElemNm, NULL);
	    return TCL_ERROR;
	}
	if (schPlcPtr == ctrPtr) {
	    continue;
	}
	if (GeoQuickDistance(schPlcPtr->geoPt, ctr) < rng) {
	    Tcl_ListObjAppendElement(interp, rslt,
		    Tcl_NewStringObj(lsElemNm, -1));
	}
    }

    Tcl_SetObjResult(interp, rslt);
    return TCL_OK;
}


syntax highlighted by Code2HTML, v. 0.9.1