/* * 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; }