1 /*
2  * tclgeomapPlace.c --
3  *
4  *	This file defines the structures and functions that add the ability
5  *	to manage named geographic locations in Tcl.
6  *
7  * Copyright (c) 2004 Gordon D. Carrie.  All rights reserved.
8  *
9  * Licensed under the Open Software License version 2.1
10  *
11  * Please address questions and feedback to user0@tkgeomap.org
12  *
13  * @(#) $Id: tclgeomapPlace.c,v 1.5 2004/09/22 21:57:51 tkgeomap Exp $
14  *
15  ********************************************
16  *
17  */
18 
19 #include "tclgeomap.h"
20 #include "tclgeomapInt.h"
21 
22 /*
23  * Forward declarations
24  */
25 
26 static Tclgeomap_Place	createPlace _ANSI_ARGS_((Tcl_Interp *, char *, GeoPt));
27 static int		geoplaceCallback _ANSI_ARGS_((ClientData clientData,
28 				Tcl_Interp *interp, int objc,
29 				Tcl_Obj *CONST objv[]));
30 static int		placeCmdCallback _ANSI_ARGS_((ClientData clientData,
31 				Tcl_Interp *interp, int objc,
32 				Tcl_Obj *CONST objv[]));
33 static int		new _ANSI_ARGS_((ClientData clientData,
34 				Tcl_Interp *interp, int objc,
35 				Tcl_Obj *CONST objv[]));
36 static int		set _ANSI_ARGS_((ClientData clientData,
37 				Tcl_Interp *interp, int objc,
38 				Tcl_Obj *CONST objv[]));
39 static int		distance _ANSI_ARGS_((ClientData clientData,
40 				Tcl_Interp *interp, int objc,
41 				Tcl_Obj *CONST objv[]));
42 static int		azrng _ANSI_ARGS_((ClientData clientData,
43 				Tcl_Interp *interp, int objc,
44 				Tcl_Obj *CONST objv[]));
45 static int		nearest _ANSI_ARGS_((ClientData clientData,
46 				Tcl_Interp *interp, int objc,
47 				Tcl_Obj *CONST objv[]));
48 static int		step _ANSI_ARGS_((ClientData clientData,
49 				Tcl_Interp *interp, int objc,
50 				Tcl_Obj *CONST objv[]));
51 static int		inrange _ANSI_ARGS_((ClientData clientData,
52 				Tcl_Interp *interp, int objc,
53 				Tcl_Obj *CONST objv[]));
54 static void		deleteProc _ANSI_ARGS_((ClientData));
55 
56 /*
57  * All geoplaces are entered in the following table.  One-word-keys are
58  * Tclgeomap_Place structures.  Values are not used.
59  */
60 
61 static Tcl_HashTable places;
62 
63 /*
64  * The following array and enum are used to process units on command line.
65  */
66 
67 static char *units[] = {"nmiles", "smiles", "km", "gsdeg", NULL};
68 enum UnitIdx	       {NMILES,   SMILES,   KM,   GSDEG};
69 
70 
71 /*
72  *------------------------------------------------------------------------
73  *
74  * TclgeomapPlaceInit --
75  *
76  *	This procedure initializes the Tclgeomap_Place interface and provides
77  *	the tclgeoplace package.
78  *
79  * Results:
80  *	A standard Tcl result.
81  *
82  * Side effects:
83  *	The "geomap::place" command is added to the interpreter.
84  *	The places table (defined above) is initialized.
85  *	The geoplaceSubCmdNmPtr and plcCmdSubCmdNmPtr subcommand arrays are
86  *	initialized.
87  *
88  *
89  *------------------------------------------------------------------------
90  */
91 
92 int
TclgeomapPlaceInit(interp)93 TclgeomapPlaceInit(interp)
94     Tcl_Interp *interp;		/* Current Tcl interpreter */
95 {
96     static int loaded;		/* Tell if package already loaded */
97 
98     if (loaded) {
99 	return TCL_OK;
100     }
101 #ifdef USE_TCL_STUBS
102     if (Tcl_InitStubs(interp, TCL_VERSION, 0) == NULL) {
103 	return TCL_ERROR;
104     }
105 #endif
106     Tcl_CreateObjCommand(interp, "::geomap::place", geoplaceCallback, NULL,
107 	    NULL);
108     Tcl_InitHashTable(&places, TCL_ONE_WORD_KEYS);
109     loaded = 1;
110     return TCL_OK;
111 }
112 
113 /*
114  *------------------------------------------------------------------------
115  *
116  * geoplaceCallback --
117  *
118  *	This is the callback for the "geomap::place" command.
119  *
120  * Results:
121  *	A standard Tcl result.
122  *
123  * Side effects:
124  *	This procedure invokes the callback corresponding to the first
125  *	argument given to the "geomap::place" command.  Side effects depend
126  *	on the subcommand called.
127  *
128  *------------------------------------------------------------------------
129  */
130 
131 int
geoplaceCallback(clientData,interp,objc,objv)132 geoplaceCallback(clientData, interp, objc, objv)
133     ClientData clientData;	/* Not used */
134     Tcl_Interp *interp;		/* Current interpreter */
135     int objc;			/* Number of arguments */
136     Tcl_Obj *const objv[];	/* Argument objects */
137 {
138     char *nmPtr[] = {
139 	"new", "set", "distance", "azrng", "nearest", "step", "inrange",
140 	NULL
141     };
142     Tcl_ObjCmdProc *procPtr[] = {
143 	new,   set,   distance,   azrng,   nearest,   step,   inrange
144     };
145     int i;
146 
147     if (objc < 2) {
148 	Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?");
149 	return TCL_ERROR;
150     }
151     if (Tcl_GetIndexFromObj(interp, objv[1], nmPtr, "subcommand", 0, &i)
152 	    != TCL_OK) {
153 	return TCL_ERROR;
154     }
155     return (procPtr[i])(NULL, interp, objc, objv);
156 }
157 
158 /*
159  *------------------------------------------------------------------------
160  *
161  * placeCmdCallback --
162  *
163  *	This is the callback for a commands of form "placeName subcommand ..."
164  *
165  * Results:
166  *	A standard Tcl result.
167  *
168  * Side effects:
169  *	This procedure invokes the callback corresponding to the first
170  *	argument given to the "placeName" command.  Side effects depend
171  *	on the subcommand called.
172  *
173  *------------------------------------------------------------------------
174  */
175 
176 int
placeCmdCallback(clientData,interp,objc,objv)177 placeCmdCallback(clientData, interp, objc, objv)
178     ClientData clientData;	/* A Tclgeomap_Place struct */
179     Tcl_Interp *interp;		/* Current interpreter */
180     int objc;			/* Number of arguments */
181     Tcl_Obj *const objv[];	/* Argument objects */
182 {
183     int i;
184     static char *nmPtr[] = {
185 	"set", "nearest", "step", "inrange", NULL
186     };
187     Tcl_ObjCmdProc *procPtr[] = {
188 	set,   nearest,   step,   inrange
189     };
190 
191     if (objc < 2) {
192 	Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?");
193 	return TCL_ERROR;
194     }
195     if (Tcl_GetIndexFromObj(interp, objv[1], nmPtr, "subcommand", 0, &i)
196 	    != TCL_OK) {
197 	return TCL_ERROR;
198     }
199     return (procPtr[i])(clientData, interp, objc, objv);
200 }
201 
202 /*
203  *------------------------------------------------------------------------
204  *
205  * createPlace --
206  *
207  *	This procedure creates a new place in the database.
208  *
209  * Results:
210  *	If successful, this procedure returns a Tclgeomap_Place structure whose
211  *	coordinates are set to the given lat-lon.  The return value is
212  *	dynamically allocated and should eventually be freed with a call to
213  *	CKFREE.
214  *
215  * Side effects:
216  *	A new Tcl command is created, whose name is the place name,
217  *	to access and manipulate the place.
218  *	The new Tclgeomap_Place structure is added to the places table.
219  *
220  *------------------------------------------------------------------------
221  */
222 
223 Tclgeomap_Place
createPlace(interp,name,geoPt)224 createPlace(interp, name, geoPt)
225     Tcl_Interp *interp;		/* Current interpreter */
226     char *name;			/* Place name = place command name.  Can
227 				 * contain namespace qualifiers, otherwise it
228 				 * becomes global. */
229     GeoPt geoPt;		/* Coordinates of the new place */
230 {
231     struct Tclgeomap_Place *plcPtr;	/* Structure for the new place */
232     int newPtr;			/* Not used */
233 
234     plcPtr = (Tclgeomap_Place)CKALLOC(sizeof(*plcPtr));
235     Tcl_CreateHashEntry(&places, (char *)plcPtr, &newPtr);
236     plcPtr->interp = interp;
237     plcPtr->geoPt = geoPt;
238     Tcl_InitHashTable(&plcPtr->updateTasks, TCL_ONE_WORD_KEYS);
239     Tcl_InitHashTable(&plcPtr->deleteTasks, TCL_ONE_WORD_KEYS);
240     plcPtr->cmd = Tcl_CreateObjCommand(interp, name, placeCmdCallback,
241 	    (ClientData)plcPtr, deleteProc);
242     return plcPtr;
243 }
244 
245 /*
246  *------------------------------------------------------------------------
247  *
248  * Tclgeomap_AddPlaceUpdateTask --
249  *
250  *	This procedure arranges for a function to be called when a place
251  *	moves.
252  *
253  * Results:
254  *	None.
255  *
256  * Side effects:
257  *	See user documentation.
258  *
259  *------------------------------------------------------------------------
260  */
261 
262 void
Tclgeomap_AddPlaceUpdateTask(placePtr,proc,clientData)263 Tclgeomap_AddPlaceUpdateTask(placePtr, proc, clientData)
264     Tclgeomap_Place placePtr;
265     Tclgeomap_PlaceUpdateProc proc;
266     ClientData clientData;
267 {
268     int n;
269     Tcl_HashEntry *entry;
270 
271     if ( !placePtr || !proc || !clientData ) {
272 	return;
273     }
274     entry = Tcl_CreateHashEntry(&placePtr->updateTasks, clientData, &n);
275     Tcl_SetHashValue(entry, (ClientData)proc);
276 }
277 
278 /*
279  *------------------------------------------------------------------------
280  *
281  * Tclgeomap_CnxPlaceUpdateTask --
282  *
283  *	This procedure cancels a callback added by Tclgeomap_AddPlaceUpdateTask.
284  *
285  * Results:
286  *	None.
287  *
288  * Side effects:
289  *	See the user documentation.
290  *
291  *------------------------------------------------------------------------
292  */
293 
294 void
Tclgeomap_CnxPlaceUpdateTask(placePtr,clientData)295 Tclgeomap_CnxPlaceUpdateTask(placePtr, clientData)
296     Tclgeomap_Place placePtr;
297     ClientData clientData;
298 {
299     Tcl_HashEntry *entry;
300 
301     if ( !placePtr || !clientData ) {
302 	return;
303     }
304     if ( !(entry = Tcl_FindHashEntry(&placePtr->updateTasks,
305 		    (char *)clientData)) ) {
306 	return;
307     }
308     Tcl_DeleteHashEntry(entry);
309 }
310 
311 /*
312  *------------------------------------------------------------------------
313  *
314  * Tclgeomap_AddPlaceDeleteTask --
315  *
316  *	This procedure arranges for a function to be called when a place
317  *	is deleted.
318  *
319  * Results:
320  *	None.
321  *
322  * Side effects:
323  *	See the user documentation.
324  *
325  *------------------------------------------------------------------------
326  */
327 
328 void
Tclgeomap_AddPlaceDeleteTask(placePtr,proc,clientData)329 Tclgeomap_AddPlaceDeleteTask(placePtr, proc, clientData)
330     Tclgeomap_Place placePtr;
331     Tclgeomap_PlaceDeleteProc proc;
332     ClientData clientData;
333 {
334     int n;
335     Tcl_HashEntry *entry;
336 
337     if ( !placePtr || !proc || !clientData ) {
338 	return;
339     }
340     entry = Tcl_CreateHashEntry(&placePtr->deleteTasks, clientData, &n);
341     Tcl_SetHashValue(entry, (ClientData)proc);
342 }
343 
344 /*
345  *------------------------------------------------------------------------
346  *
347  * Tclgeomap_CnxPlaceDeleteTask --
348  *
349  *	This procedure cancels a callback added by Tclgeomap_AddPlaceDeleteTask.
350  *
351  * Results:
352  *	None.
353  *
354  * Side effects:
355  *	See the user documentation.
356  *
357  *------------------------------------------------------------------------
358  */
359 
360 void
Tclgeomap_CnxPlaceDeleteTask(placePtr,clientData)361 Tclgeomap_CnxPlaceDeleteTask (placePtr, clientData)
362     Tclgeomap_Place placePtr;
363     ClientData clientData;
364 {
365     Tcl_HashEntry *entry;
366 
367     if ( !placePtr || !clientData ) {
368 	return;
369     }
370     if ( !(entry = Tcl_FindHashEntry(&placePtr->deleteTasks,
371 		    (char *)clientData)) ) {
372 	return;
373     }
374     Tcl_DeleteHashEntry(entry);
375 }
376 
377 /*
378  *------------------------------------------------------------------------
379  *
380  * Tclgeomap_GetPlace --
381  *
382  *	Return a Tclgeomap_Place struct given the place name.
383  *
384  * Results:
385  *	A Tclgeomap_Place struct or NULL.
386  *
387  * Side effects:
388  *	None.
389  *------------------------------------------------------------------------
390  */
391 
392 Tclgeomap_Place
Tclgeomap_GetPlace(interp,name)393 Tclgeomap_GetPlace(interp, name)
394     Tcl_Interp *interp;		/* Current interpreter */
395     CONST char *name;		/* Alleged place name */
396 {
397     Tcl_CmdInfo infoPtr;	/* Command info for command named name */
398     if (   Tcl_GetCommandInfo(interp, (char *)name, &infoPtr)
399 	&& Tcl_FindHashEntry(&places, infoPtr.objClientData)) {
400 	return (Tclgeomap_Place)infoPtr.objClientData;
401     } else {
402 	return NULL;
403     }
404 }
405 
406 /*
407  *------------------------------------------------------------------------
408  *
409  * Tclgeomap_PlaceName --
410  *
411  *	This procedure returns the name of a place.
412  *
413  * Results:
414  *	See the user documentation.
415  *
416  * Side effects:
417  *	See the user documentation.
418  *
419  *------------------------------------------------------------------------
420  */
421 
422 CONST char *
Tclgeomap_PlaceName(placePtr)423 Tclgeomap_PlaceName(placePtr)
424     Tclgeomap_Place placePtr;
425 {
426     return placePtr
427 	? Tcl_GetCommandName(placePtr->interp, placePtr->cmd)
428 	: NULL;
429 }
430 
431 /*
432  *------------------------------------------------------------------------
433  *
434  * Tclgeomap_PlaceLoc --
435  *
436  *	This procedure returns the {lat lon} coordinates of a Tclgeomap_Place.
437  *
438  * Results:
439  *	A GeoPt (declared in geography.h)
440  *
441  * Side effects:
442  *	None.
443  *
444  *------------------------------------------------------------------------
445  */
446 
447 GeoPt
Tclgeomap_PlaceLoc(plcPtr)448 Tclgeomap_PlaceLoc(plcPtr)
449     struct Tclgeomap_Place *plcPtr;	/* Place of interest */
450 {
451     return plcPtr->geoPt;
452 }
453 
454 /*
455  *------------------------------------------------------------------------
456  *
457  * deleteProc --
458  *
459  *	This is the deletion procedure for the Tcl command created for a
460  *	place.
461  *
462  * Results:
463  *	None.
464  *
465  * Side effects:
466  *	A Tclgeomap_Place structure is deleted and its entry is removed from
467  *	the places hash table.
468  *
469  *------------------------------------------------------------------------
470  */
471 
472 void
deleteProc(clientData)473 deleteProc(clientData)
474     ClientData clientData;		/* The place to remove */
475 {
476     Tcl_HashEntry *entry;		/* Entry for delete tasks loop */
477     Tcl_HashSearch search;		/* Delete task loop parameter */
478     ClientData cd;			/* Clientdata for an delete proc */
479     Tclgeomap_PlaceDeleteProc *deleteProc;
480 					/* Procedure from deleteTasks table */
481     struct Tclgeomap_Place *plcPtr;	/* The place being deleted */
482 
483     plcPtr = (Tclgeomap_Place)clientData;
484     for (entry = Tcl_FirstHashEntry(&plcPtr->deleteTasks, &search);
485 	    entry != NULL; entry = Tcl_NextHashEntry(&search)) {
486 	cd = (ClientData)Tcl_GetHashKey(&plcPtr->deleteTasks, entry);
487 	deleteProc = (Tclgeomap_PlaceDeleteProc *)Tcl_GetHashValue(entry);
488 	(*deleteProc)(cd);
489     }
490     Tcl_DeleteHashEntry(Tcl_FindHashEntry(&places, (char *)plcPtr));
491     Tcl_DeleteHashTable(&plcPtr->updateTasks);
492     Tcl_DeleteHashTable(&plcPtr->deleteTasks);
493     CKFREE((char *)plcPtr);
494 }
495 
496 /*
497  *------------------------------------------------------------------------
498  *
499  * new --
500  *
501  *	This is the callback for the "geomap::place new ..." command.
502  *
503  * Results:
504  *	A standard Tcl result.
505  *
506  * Side effects:
507  *	This procedure allocates and initializes a new Tclgeomap_Place
508  *	structure, and adds it to the places table.
509  *	It creates a new Tcl command named for the place to access it.
510  *	The data structures and command associated with the place will be
511  *	destroyed when the place command is destroyed.
512  *
513  *------------------------------------------------------------------------
514  */
515 
516 int
new(clientData,interp,objc,objv)517 new(clientData, interp, objc, objv)
518     ClientData clientData;	/* Not used */
519     Tcl_Interp *interp;		/* The current interpreter */
520     int objc;			/* Number of arguments */
521     Tcl_Obj *const objv[];	/* Argument objects */
522 {
523     char *name;			/* Place name */
524     GeoPt geoPt;		/* Lat-lon of the new place */
525     Tcl_CmdInfo info;		/* Not used. */
526 
527     if (objc != 4) {
528 	Tcl_WrongNumArgs(interp, 2, objv, "placeName {lat lon}");
529 	return TCL_ERROR;
530     }
531     if (Tclgeomap_GetGeoPtFromObj(interp, objv[3], &geoPt) != TCL_OK) {
532 	return TCL_ERROR;
533     }
534     name = Tcl_GetString(objv[2]);
535     if (Tcl_GetCommandInfo(interp, name, &info)) {
536 	Tcl_AppendResult(interp, "Could not create place named ",
537 		name, " because a command by that name already exists.\n",
538 		NULL);
539 	return TCL_ERROR;
540     }
541     createPlace(interp, name, geoPt);
542     Tcl_SetObjResult(interp, Tcl_NewStringObj(name, -1));
543     return TCL_OK;
544 }
545 
546 /*
547  *------------------------------------------------------------------------
548  *
549  * set --
550  *
551  *	This is the callback for commands of form "geomap::place set ..." and
552  *	"placeName set ...".
553  *
554  * Results:
555  *	A standard Tcl result.
556  *
557  * Side effects:
558  * 	See the user documentation.
559  *
560  *------------------------------------------------------------------------
561  */
562 
563 int
set(clientData,interp,objc,objv)564 set(clientData, interp, objc, objv)
565     ClientData clientData;		/* If not NULL, a Tclgeomap_Place
566 					 * structure */
567     Tcl_Interp *interp;			/* The current interpreter */
568     int objc;				/* Number of arguments */
569     Tcl_Obj *const objv[];		/* Argument objects */
570 {
571 
572     char *name;				/* Fully qualified name of place */
573     Tcl_Obj *geoPtObj;			/* Lat-lon for the place */
574     GeoPt geoPt;			/* GeoPt from geoPtObj */
575     struct Tclgeomap_Place *plcPtr;		/* Place of interest */
576     Tcl_HashEntry *entry;		/* Entry for update loop */
577     Tcl_HashSearch search;		/* Update loop parameter */
578     ClientData cd;			/* Clientdata for an update proc */
579     Tclgeomap_PlaceUpdateProc *updateProc;
580     					/* Procedure from updateTasks table */
581 
582     if (clientData) {
583 	/*
584 	 * Command has form "placeName set" or "placeName set {lat lon}"
585 	 */
586 
587 	plcPtr = (Tclgeomap_Place)clientData;
588 	if (objc == 2) {
589 	    Tcl_SetObjResult(interp, Tclgeomap_NewGeoPtObj(plcPtr->geoPt));
590 	} else if (objc == 3) {
591 	    geoPtObj = objv[2];
592 	    if (Tclgeomap_GetGeoPtFromObj(interp, geoPtObj, &geoPt)
593 		    != TCL_OK)
594 		return TCL_ERROR;
595 	    plcPtr->geoPt = geoPt;
596 	    for (entry = Tcl_FirstHashEntry(&plcPtr->updateTasks, &search);
597 		    entry != NULL;
598 		    entry = Tcl_NextHashEntry(&search)) {
599 		cd = (ClientData)Tcl_GetHashKey(&plcPtr->updateTasks, entry);
600 		updateProc
601 		    = (Tclgeomap_PlaceUpdateProc *)Tcl_GetHashValue(entry);
602 		(*updateProc)(cd);
603 	    }
604 	    Tcl_SetObjResult(interp, objv[2]);
605 	} else {
606 	    Tcl_WrongNumArgs(interp, 2, objv, "?{lat lon}?");
607 	    return TCL_ERROR;
608 	}
609     } else {
610 	/*
611 	 * Command has form "geomap::place set placeName"
612 	 * or "geomap::place set placeName {lat lon}".
613 	 */
614 
615 	if (objc == 3) {
616 	    name = Tcl_GetString(objv[2]);
617 	    if ( !(plcPtr = Tclgeomap_GetPlace(interp, name)) ) {
618 		Tcl_AppendResult(interp, "No place named ", name, NULL);
619 		return TCL_ERROR;
620 	    } else {
621 		Tcl_SetObjResult(interp,
622 			Tclgeomap_NewGeoPtObj(plcPtr->geoPt));
623 	    }
624 	} else if (objc == 4) {
625 	    geoPtObj = objv[3];
626 	    if (Tclgeomap_GetGeoPtFromObj(interp, geoPtObj, &geoPt)
627 		    != TCL_OK) {
628 		return TCL_ERROR;
629 	    }
630 	    name = Tcl_GetString(objv[2]);
631 	    if ((plcPtr = Tclgeomap_GetPlace(interp, name))) {
632 		plcPtr->geoPt = geoPt;
633 	    } else {
634 		plcPtr = createPlace(interp, name, geoPt);
635 	    }
636 	    Tcl_SetObjResult(interp, objv[3]);
637 	} else {
638 	    Tcl_WrongNumArgs(interp, 2, objv, "placeName ?{lat lon}?");
639 	    return TCL_ERROR;
640 	}
641     }
642     return TCL_OK;
643 }
644 
645 /*
646  *------------------------------------------------------------------------
647  *
648  * distance --
649  *
650  *	This is the callback for the "geomap::place distance" command.
651  *
652  * Results:
653  *	A standard Tcl result.
654  *
655  * Side effects:
656  * 	See the user documentation.
657  *
658  *------------------------------------------------------------------------
659  */
660 
661 int
distance(clientData,interp,objc,objv)662 distance(clientData, interp, objc, objv)
663     ClientData clientData;			/* Not used */
664     Tcl_Interp *interp;				/* The current interpreter */
665     int objc;					/* Number of arguments */
666     Tcl_Obj *const objv[];			/* Argument objects */
667 {
668     char *plcNm;				/* Place name on command line */
669     Tclgeomap_Place plc1Ptr, plc2Ptr;		/* Input named places */
670     GeoPt geoPt1, geoPt2;			/* Input lat-lon's */
671     double dist;				/* Result */
672 
673     if (objc != 4 && objc != 5) {
674 	Tcl_WrongNumArgs(interp, 2, objv,
675 		"placeOR{lat lon} placeOR{lat lon} ?unit?");
676 	return TCL_ERROR;
677     }
678 
679     /*
680      * Get geoPt1 from objv[2], which is either a place name or
681      * a {lat lon} pair.
682      */
683 
684     if (Tclgeomap_GetGeoPtFromObj(NULL, objv[2], &geoPt1) != TCL_OK) {
685 	plcNm = Tcl_GetString(objv[2]);
686 	if ((plc1Ptr = Tclgeomap_GetPlace(interp, plcNm)) ) {
687 	    geoPt1 = plc1Ptr->geoPt;
688 	} else {
689 	    Tcl_AppendResult(interp, plcNm, " not a location", NULL);
690 	    return TCL_ERROR;
691 	}
692     }
693 
694     /*
695      * Get geoPt2 from objv[3], which is either a place name or
696      * a {lat lon} pair.
697      */
698 
699     if (Tclgeomap_GetGeoPtFromObj(NULL, objv[3], &geoPt2) != TCL_OK) {
700 	plcNm = Tcl_GetString(objv[3]);
701 	if ((plc2Ptr = Tclgeomap_GetPlace(interp, plcNm)) ) {
702 	    geoPt2 = plc2Ptr->geoPt;
703 	} else {
704 	    Tcl_AppendResult(interp, plcNm, " not a location", NULL);
705 	    return TCL_ERROR;
706 	}
707     }
708 
709     dist = AngleToDeg(GeoDistance(geoPt1, geoPt2));
710     if (objc == 5) {
711 	/*
712 	 * Apply optional distance unit.
713 	 */
714 
715 	Tcl_Obj *unit = objv[4];
716 	int idx;
717 	if (Tcl_GetIndexFromObj(interp, unit, units, "unit", 0, &idx)
718 		!= TCL_OK) {
719 	    return TCL_ERROR;
720 	}
721 	switch ((enum UnitIdx)idx) {
722 	    case NMILES:	dist *= NMIPERDEG;	break;
723 	    case SMILES:	dist *= SMIPERDEG;	break;
724 	    case KM:		dist *= KMPERDEG;	break;
725 	    case GSDEG:					break;
726 	}
727     }
728     Tcl_SetObjResult(interp, Tcl_NewDoubleObj(dist));
729     return TCL_OK;
730 }
731 
732 /*
733  *------------------------------------------------------------------------
734  *
735  * azrng --
736  *
737  *	This is the callback for the "geomap::place azrng ..." command.
738  *
739  * Results:
740  *	A standard Tcl result.
741  *
742  * Side effects:
743  * 	See the user documentation.
744  *
745  *------------------------------------------------------------------------
746  */
747 
748 int
azrng(clientData,interp,objc,objv)749 azrng(clientData, interp, objc, objv)
750     ClientData clientData;			/* Not used */
751     Tcl_Interp *interp;				/* The current interpreter */
752     int objc;					/* Number of arguments */
753     Tcl_Obj *const objv[];			/* Argument objects */
754 {
755     char *plcNm;				/* Place name on command line */
756     Tclgeomap_Place plc1Ptr, plc2Ptr;		/* Input named places */
757     GeoPt geoPt1, geoPt2;			/* Input lat-lon's */
758     double azm, dist;				/* Input azimuth and distance */
759     Tcl_Obj *rslt;				/* Hold result */
760 
761     rslt = Tcl_GetObjResult(interp);
762 
763     if (objc != 4 && objc != 5) {
764 	Tcl_WrongNumArgs(interp, 2, objv,
765 		"placeOR{lat lon} placeOR{lat lon} ?unit?");
766 	return TCL_ERROR;
767     }
768 
769     /*
770      * Get geoPt1 from objv[2], which is either a place name or
771      * a {lat lon} pair.
772      */
773 
774     if (Tclgeomap_GetGeoPtFromObj(NULL, objv[2], &geoPt1) != TCL_OK) {
775 	plcNm = Tcl_GetString(objv[2]);
776 	if ((plc1Ptr = Tclgeomap_GetPlace(interp, plcNm)) ) {
777 	    geoPt1 = plc1Ptr->geoPt;
778 	} else {
779 	    Tcl_AppendResult(interp, plcNm, " not a location", NULL);
780 	    return TCL_ERROR;
781 	}
782     }
783 
784     /*
785      * Get geoPt2 from objv[3], which is either a place name or
786      * a {lat lon} pair.
787      */
788 
789     if (Tclgeomap_GetGeoPtFromObj(NULL, objv[3], &geoPt2) != TCL_OK) {
790 	plcNm = Tcl_GetString(objv[3]);
791 	if ((plc2Ptr = Tclgeomap_GetPlace(interp, plcNm)) ) {
792 	    geoPt2 = plc2Ptr->geoPt;
793 	} else {
794 	    Tcl_AppendResult(interp, plcNm, " not a location", NULL);
795 	    return TCL_ERROR;
796 	}
797     }
798 
799     dist = AngleToDeg(GeoDistance(geoPt1, geoPt2));
800     if (objc == 5) {
801 	/*
802 	 * Apply optional distance unit.
803 	 */
804 
805 	Tcl_Obj *unit = objv[4];
806 	int idx;
807 	if (Tcl_GetIndexFromObj(interp, unit, units, "unit", 0, &idx)
808 		!= TCL_OK) {
809 	    return TCL_ERROR;
810 	}
811 	switch ((enum UnitIdx)idx) {
812 	    case NMILES:	dist *= NMIPERDEG;	break;
813 	    case SMILES:	dist *= SMIPERDEG;	break;
814 	    case KM:		dist *= KMPERDEG;	break;
815 	    case GSDEG:					break;
816 	}
817     }
818     azm = AngleToDeg(Azimuth(geoPt1, geoPt2));
819     Tcl_ListObjAppendElement(interp, rslt, Tcl_NewDoubleObj(azm));
820     Tcl_ListObjAppendElement(interp, rslt, Tcl_NewDoubleObj(dist));
821     Tcl_SetObjResult(interp, rslt);
822     return TCL_OK;
823 }
824 
825 /*
826  *------------------------------------------------------------------------
827  *
828  * nearest --
829  *
830  *	This is the callback for commands of form "geomap::place nearest ..."
831  *	and "placeName nearest ...".
832  *	usage details.
833  *
834  * Results:
835  *	A standard Tcl result.
836  *
837  * Side effects:
838  * 	See the user documentation.
839  *
840  *------------------------------------------------------------------------
841  */
842 
843 int
nearest(clientData,interp,objc,objv)844 nearest(clientData, interp, objc, objv)
845     ClientData clientData;	/* If not NULL, a Tclgeomap_Place struct */
846     Tcl_Interp *interp;		/* The current interpreter */
847     int objc;			/* Number of arguments */
848     Tcl_Obj *const objv[];	/* Argument objects */
849 {
850     char *plcNm;		/* Name of place of interest */
851     Tclgeomap_Place
852 	plcPtr,			/* Place of interest */
853 	skipPtr = NULL,		/* If place of interest is in database,
854 				 * store it here so that we do not compare it
855 				 * to itself during search */
856 	schPlcPtr;		/* Current place in search loop */
857     GeoPt geoPt;		/* Lat-lon of place of interest */
858     CONST char *nearPlcNm = NULL;/* Name of nearest place so far */
859     Tcl_Obj *cmdLnPList = NULL;	/* List of places to search on command line */
860     Angle d180 = AngleFmDeg(180.0);
861     Angle nearDist = d180 + 1000;/* Hold distance to nearest place so far.
862 				 * Note that GeoDistance returns great circle
863 				 * arc measured in microdegrees. */
864     Angle cDistance;		/* Distance to current place (microdegrees) */
865     Tcl_Obj **placeList;	/* Optional list of places on command line */
866     int placeCnt;		/* Number of places in placeList */
867     int np;			/* Loop index */
868     char *lsElemNm;		/* Name of place from list on command line */
869 
870     if (clientData) {
871 	/*
872 	 * Command is of form "placeName nearest {place place ...}"
873 	 */
874 
875 	if (objc != 3) {
876 	    Tcl_WrongNumArgs(interp, 2, objv, "{place place ...}");
877 	    return TCL_ERROR;
878 	}
879 	plcPtr = (Tclgeomap_Place)clientData;
880 	geoPt = plcPtr->geoPt;
881 	skipPtr = plcPtr;
882 	cmdLnPList = objv[2];
883 
884     } else {
885 	/*
886 	 * Command is of form
887 	 * "geomap::place nearest placeORgeoPt {place place ...}"
888 	 */
889 
890 	if (objc != 4) {
891 	    Tcl_WrongNumArgs(interp, 2, objv,
892 		    "placeOR{lat lon} {place place ...}");
893 	    return TCL_ERROR;
894 	}
895 	if (Tclgeomap_GetGeoPtFromObj(NULL, objv[2], &geoPt) != TCL_OK) {
896 	    plcNm = Tcl_GetString(objv[2]);
897 	    if ( !(plcPtr = Tclgeomap_GetPlace(interp, plcNm)) ) {
898 		Tcl_AppendResult(interp, plcNm, " not a location.", NULL);
899 		return TCL_ERROR;
900 	    }
901 	    geoPt = plcPtr->geoPt;
902 	    skipPtr = plcPtr;
903 	}
904 	if (objc == 4) {
905 	    cmdLnPList = objv[3];
906 	}
907 
908     }
909 
910     /*
911      * Search list given on command line
912      */
913 
914     if (Tcl_ListObjGetElements(interp, cmdLnPList, &placeCnt, &placeList)
915 	    != TCL_OK) {
916 	return TCL_ERROR;
917     }
918     for (np = 0, nearDist = d180 + 1000; np < placeCnt; np++) {
919 	lsElemNm = Tcl_GetString(placeList[np]);
920 	if ( !(schPlcPtr = Tclgeomap_GetPlace(interp, lsElemNm)) ) {
921 	    Tcl_AppendResult(interp, "No place named ", lsElemNm,
922 		    " in current namespace.", NULL);
923 	    return TCL_ERROR;
924 	}
925 	if (schPlcPtr == skipPtr) {
926 	    continue;
927 	}
928 	cDistance = GeoDistance(schPlcPtr->geoPt, geoPt);
929 	if (cDistance < nearDist) {
930 	    nearDist = cDistance;
931 	    nearPlcNm = Tcl_GetCommandName(interp, schPlcPtr->cmd);
932 	}
933     }
934 
935     if (nearPlcNm) {
936 	Tcl_SetObjResult(interp, Tcl_NewStringObj(nearPlcNm, -1));
937 	return TCL_OK;
938     } else {
939 	Tcl_AppendResult(interp, "No places to compare", NULL);
940 	return TCL_ERROR;
941     }
942 }
943 
944 /*
945  *------------------------------------------------------------------------
946  *
947  * step --
948  *
949  *	This is the callback for the "geomap::place step ..." and
950  *	"placeName step ..." commands.
951  *	for usage details.
952  *
953  * Results:
954  *	A standard Tcl result.
955  *
956  * Side effects:
957  * 	See the user documentation.
958  *
959  *------------------------------------------------------------------------
960  */
961 
962 int
step(clientData,interp,objc,objv)963 step(clientData, interp, objc, objv)
964     ClientData clientData;	/* Not used */
965     Tcl_Interp *interp;		/* The current interpreter */
966     int objc;			/* Number of arguments */
967     Tcl_Obj *const objv[];	/* Argument objects */
968 {
969     char *plcNm;		/* Place name */
970     GeoPt geoPt;		/* Starting location */
971     GeoPt endPt;		/* Point at bearing and range from geoPt */
972     struct Tclgeomap_Place *plcPtr;
973 				/* Current place */
974     double brg, rng;		/* Desired bearing and range */
975     Tcl_Obj
976 	*brgObj,		/* Bearing on command line */
977 	*rngObj;		/* Range on command line */
978     Tcl_Obj *unit = NULL;	/* Optional unit on command line */
979     int idx;			/* Index returned by Tcl_GetIndexFromObj */
980     static char *brgs[] = {
981 	"north",	"nneast",	"neast",	"eneast",
982 	"east",		"eseast",	"seast",	"sseast",
983 	"south",	"sswest",	"swest",	"wswest",
984 	"west",		"wnwest",	"nwest",	"nnwest",	NULL
985     };				/* Bearing names */
986     enum brgIdx {
987 	N,		NNE,		NE,		ENE,
988 	E,		ESE,		SE,		SSE,
989 	S,		SSW,		SW,		WSW,
990 	W,		WNW,		NW,		NNW
991     };
992     				/* Bearing indices */
993 
994     if (clientData) {
995 	/*
996 	 * Command is of form "placeName step az rng ?unit?"
997 	 */
998 
999 	if (objc != 4 && objc != 5) {
1000 	    Tcl_WrongNumArgs(interp, 2, objv,
1001 		    "bearing range ?unit?");
1002 	    return TCL_ERROR;
1003 	}
1004 	plcPtr = (Tclgeomap_Place)clientData;
1005 	geoPt = plcPtr->geoPt;
1006 	brgObj = objv[2];
1007 	rngObj = objv[3];
1008 	if (objc == 5) {
1009 	    unit = objv[4];
1010 	}
1011 
1012     } else {
1013 	/*
1014 	 * Command is of form
1015 	 * "geomap::place step placeNameOR{lat lon} az rng ?unit?"
1016 	 */
1017 
1018 	if (objc != 5 && objc != 6) {
1019 	Tcl_WrongNumArgs(interp, 2, objv,
1020 		"placeName bearing range ?unit?");
1021 	    return TCL_ERROR;
1022 	}
1023 	if (Tclgeomap_GetGeoPtFromObj(NULL, objv[2], &geoPt) != TCL_OK) {
1024 	    plcNm = Tcl_GetString(objv[2]);
1025 	    if ( !(plcPtr = Tclgeomap_GetPlace(interp, plcNm)) ) {
1026 		Tcl_AppendResult(interp, plcNm, " not a location.", NULL);
1027 		return TCL_ERROR;
1028 	    }
1029 	    geoPt = plcPtr->geoPt;
1030 	}
1031 	brgObj = objv[3];
1032 	rngObj = objv[4];
1033 	if (objc == 6) {
1034 	    unit = objv[5];
1035 	}
1036     }
1037 
1038     /*
1039      * Read bearing, which can be a number of degrees or a string
1040      * from the brgs array.
1041      */
1042 
1043     if (Tcl_GetDoubleFromObj(interp, brgObj, &brg) != TCL_OK) {
1044 	Tcl_ResetResult(interp);
1045 	if (Tcl_GetIndexFromObj(interp, brgObj, brgs, "bearing", 0, &idx)
1046 		!= TCL_OK) {
1047 	    Tcl_AppendResult(interp, ", or a double value", NULL);
1048 	    return TCL_ERROR;
1049 	}
1050 	switch ((enum brgIdx)idx) {
1051 	    case N:	brg =   0.0;	break;
1052 	    case NNE:	brg =  22.5;	break;
1053 	    case NE:	brg =  45.0;	break;
1054 	    case ENE:	brg =  67.5;	break;
1055 	    case E:	brg =  90.0;	break;
1056 	    case ESE:	brg = 112.5;	break;
1057 	    case SE:	brg = 135.0;	break;
1058 	    case SSE:	brg = 157.5;	break;
1059 	    case S:	brg = 180.0;	break;
1060 	    case SSW:	brg = 202.5;	break;
1061 	    case SW:	brg = 225.0;	break;
1062 	    case WSW:	brg = 247.5;	break;
1063 	    case W:	brg = 270.0;	break;
1064 	    case WNW:	brg = 292.5;	break;
1065 	    case NW:	brg = 315.0;	break;
1066 	    case NNW:	brg = 337.5;	break;
1067 	}
1068     }
1069 
1070     if (Tcl_GetDoubleFromObj(interp, rngObj, &rng) != TCL_OK) {
1071 	return TCL_ERROR;
1072     }
1073 
1074     if (unit) {
1075 	if (Tcl_GetIndexFromObj(interp, unit, units, "unit", 0, &idx)
1076 		!= TCL_OK) {
1077 	    return TCL_ERROR;
1078 	}
1079 	switch ((enum UnitIdx)idx) {
1080 	    case NMILES:	rng /= NMIPERDEG;	break;
1081 	    case SMILES:	rng /= SMIPERDEG;	break;
1082 	    case KM:		rng /= KMPERDEG;	break;
1083 	    case GSDEG:					break;
1084 	}
1085     }
1086 
1087     endPt = GeoStep(geoPt, AngleFmDeg(brg), AngleFmDeg(rng));
1088     Tcl_SetObjResult(interp, Tclgeomap_NewGeoPtObj(endPt));
1089     return TCL_OK;
1090 }
1091 
1092 /*
1093  *------------------------------------------------------------------------
1094  *
1095  * inrange --
1096  *
1097  *	This is the callback for the "geomap::place inrange ..." and
1098  *	"placeName inrange ..." commands.
1099  *
1100  * Results:
1101  *	A standard Tcl result.
1102  *
1103  * Side effects:
1104  * 	See the user documentation.
1105  *
1106  *------------------------------------------------------------------------
1107  */
1108 
1109 int
inrange(clientData,interp,objc,objv)1110 inrange(clientData, interp, objc, objv)
1111     ClientData clientData;	/* Not used */
1112     Tcl_Interp *interp;		/* The current interpreter */
1113     int objc;			/* Number of arguments */
1114     Tcl_Obj *const objv[];	/* Argument objects */
1115 {
1116     GeoPt
1117 	refPt,			/* References to use in units conversion */
1118 	origin = {0.0, 0.0};
1119     double rng;			/* Look for places within range of ctr */
1120     Tclgeomap_Place
1121 	schPlcPtr,		/* Current place in search loop */
1122 	ctrPtr = NULL;		/* Place we are measuring from */
1123     char *ctrNm;		/* Name of place we are measuring from */
1124     GeoPt ctr;			/* Location we are measuring from */
1125     int rngArgC;		/* Number of elements in rng argument */
1126     Tcl_Obj **rngArgv;		/* Range argument:  rng ?unit? */
1127     Tcl_Obj *rslt = NULL;
1128     Tcl_Obj *cmdLnRng;		/* Range term on command line */
1129     Tcl_Obj *cmdLnPList = NULL;	/* List of places to search on command line */
1130     int np, placeCnt;
1131     Tcl_Obj **placeList;
1132     char *lsElemNm;			/* Name of place from list */
1133 
1134     if (clientData) {
1135 	/*
1136 	 * Command has form "placeName inrange {rng ?unit?} ?list?"
1137 	 */
1138 
1139 	if (objc != 4) {
1140 	    Tcl_WrongNumArgs(interp, 2, objv,
1141 		    "{range ?unit?} {place place ...}");
1142 	    return TCL_ERROR;
1143 	}
1144 	ctrPtr = (Tclgeomap_Place)clientData;
1145 	ctr = ctrPtr->geoPt;
1146 	cmdLnRng = objv[2];
1147 	cmdLnPList = objv[3];
1148 
1149     } else {
1150 	/*
1151 	 * Command has form
1152 	 * "geomap::place inrange placeORgeoPt {rng ?unit?} ?list?"
1153 	 */
1154 
1155 	if (objc != 5) {
1156 	    Tcl_WrongNumArgs(interp, 2, objv,
1157 		    " placeNameOR{lat lon} {range ?unit?} {place place ...}");
1158 	    return TCL_ERROR;
1159 	}
1160 	if (Tclgeomap_GetGeoPtFromObj(NULL, objv[2], &ctr) != TCL_OK) {
1161 	    ctrNm = Tcl_GetString(objv[2]);
1162 	    if ( !(ctrPtr = Tclgeomap_GetPlace(interp, ctrNm)) ) {
1163 		Tcl_AppendResult(interp, ctrNm, " not a location.", NULL);
1164 		return TCL_ERROR;
1165 	    }
1166 	    ctr = ctrPtr->geoPt;
1167 	}
1168 	cmdLnRng = objv[3];
1169 	cmdLnPList = objv[4];
1170     }
1171 
1172     /*
1173      * Get range.  If range is a two element list, second element is optional
1174      * unit.
1175      */
1176 
1177     if (Tcl_ListObjGetElements(interp, cmdLnRng, &rngArgC, &rngArgv) != TCL_OK
1178 	    || Tcl_GetDoubleFromObj(interp, rngArgv[0], &rng) != TCL_OK) {
1179 	return TCL_ERROR;
1180     }
1181     if (rngArgC == 2) {
1182 	Tcl_Obj *unit = rngArgv[1];
1183 	int idx;
1184 
1185 	if (Tcl_GetIndexFromObj(interp, unit, units, "unit", 0, &idx)
1186 		!= TCL_OK) {
1187 	    return TCL_ERROR;
1188 	}
1189 	switch ((enum UnitIdx)idx) {
1190 	    case NMILES:	rng /= NMIPERDEG;	break;
1191 	    case SMILES:	rng /= SMIPERDEG;	break;
1192 	    case KM:		rng /= KMPERDEG;	break;
1193 	    case GSDEG:					break;
1194 	}
1195     }
1196 
1197     /*
1198      * This algorithm uses GeoQuickDistance for the comparisons, which
1199      * actually computes the Cartesian distance between two points.  To
1200      * get the distance in "GeoQuickDistance units" make a fictitious point
1201      * {rng 0} and use GeoQuickDistance to compute the distance from {0 0}
1202      * to {rng 0}.
1203      */
1204 
1205     refPt.lat = AngleFmDeg(rng);
1206     refPt.lon = 0;
1207     rng = GeoQuickDistance(refPt, origin);
1208 
1209     /*
1210      * Search list of places from command line.
1211      */
1212 
1213     rslt = Tcl_NewObj();
1214     if (Tcl_ListObjGetElements(interp, cmdLnPList, &placeCnt, &placeList)
1215 	    != TCL_OK) {
1216 	return TCL_ERROR;
1217     }
1218     for (np = 0; np < placeCnt; np++) {
1219 	lsElemNm = Tcl_GetString(placeList[np]);
1220 	if ( !(schPlcPtr = Tclgeomap_GetPlace(interp, lsElemNm)) ) {
1221 	    Tcl_AppendResult(interp, "No place named ", lsElemNm, NULL);
1222 	    return TCL_ERROR;
1223 	}
1224 	if (schPlcPtr == ctrPtr) {
1225 	    continue;
1226 	}
1227 	if (GeoQuickDistance(schPlcPtr->geoPt, ctr) < rng) {
1228 	    Tcl_ListObjAppendElement(interp, rslt,
1229 		    Tcl_NewStringObj(lsElemNm, -1));
1230 	}
1231     }
1232 
1233     Tcl_SetObjResult(interp, rslt);
1234     return TCL_OK;
1235 }
1236