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