File: osm.tcl

package info (click to toggle)
tklib 0.8~20230222-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 20,904 kB
  • sloc: tcl: 97,356; sh: 5,823; ansic: 792; pascal: 359; makefile: 70; sed: 53; exp: 21
file content (734 lines) | stat: -rwxr-xr-x 21,378 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
#!/usr/bin/env tclsh8.5
## -*- tcl -*-
# ### ### ### ######### ######### #########

## DEMO. Uses openstreetmap to show a tile-based world map.

## Call without arguments for a plain web-served map.
## Call with single argument (dir path) to use a tile cache.

## Syntax: osm ?cachedir?

## -- Note: The cache may not exist, it is automatically filled and/or
##    extended from the web-served data. This cache can grow very
##    large very quickly (I have currently seen ranging in size from
##    4K (water) to 124K (dense urban area)).

## Note: The editing of waypoints shows my inexperience with the
##       canvas. Adding points is with <1>, bound to the canvas
##       itself. Removing is with <3>, bound to the item
##       itself. However, often it doesn't work, or rather, only if a
##       add a new point X via <1> over the point of interest, and
##       then remove both X and the point of interest by using <3>
##       twice.
##
##       Oh, and removal via <1> bound the item works not at all,
##       because this triggers the global binding as well, re-adding
##       the point immediately after its removal. Found no way of
##       blocking that.
##
## Note: Currently new point can be added only at the end of the
##       trail. No insertion in the middle possible, although deletion
##       in the middle works. No moving points, yet.
##
## Note: This demo is reaching a size there it should be shifted to
##       tclapps for further development, and cleaned up, with many of
##       the messes encapsulated into snit types or other niceties,
##       separate packages, etc.


## Ideas:
## == DONE ==
## -- Add zoom-control to switch between zoom levels. This has to
##    adjust the scroll-region as well. The control can be something
##    using basic Tk widgets (scale, button), or maybe some constructed
##    from canvas items, to make the map look more like the web-based
##    map displays. For the latter we have to get viewport tracking
##    data out of the canvas::sqmap to move the item-group in sync
##    with scrolling, so that they appear to stay in place.
##
## == DONE ==
## -- Add a filesystem based tile cache to speed up their loading. The
##    pure http access is slow (*) OTOH, this makes the workings of
##    sqmap more observable, as things do not happen as fast as for
##    puzzle and city. (*) The xy store generates some output so you
##    can see that something is happening.
##
## -- Yes, it is possible to use google maps as well. Spying on a
##    browser easily shows the urls needed. But, they are commercial,
##    and some of the servers (sat image data) want some auth cookie.
##    Without they deliver a few proper tiles and then return errors.
##
##    Hence this demo uses the freely available openstreetmap(.org)
##    data instead.
##
## == DONE ==
## -- Select two locations, then compute the geo distance between
##    them. Or, select a series of location, like following a road,
##    and compute the partial and total distances.

## == DONE == (roughly)
## -- Mark, save, load series of points (gps tracks, own tracks).
##    Name point series. Name individual points (location marks).

# ### ### ### ######### ######### #########
## Other requirements for this example.

package require Tcl 8.5
package require Tk
package require widget::scrolledwindow
package require canvas::sqmap
package require canvas::zoom
package require crosshair
package require img::png
package require tooltip

package require map::slippy 0.8         ; # Slippy utilities
package require map::slippy::fetcher    ; # Slippy server access
package require map::slippy::cache      ; # Local slippy tile cache
#package require map::slippy::prefetcher ; # Agressive prefetch
package require map::geocode::nominatim ;# geo name resolution

package require snit             ; # canvas::sqmap dependency
package require uevent::onidle   ; # ditto
package require cache::async 0.2 ; # ditto

set defaultLocations {
}
set cities {
    "Aachen" {50.7764185111 6.086769104}
    "Anchorage" {61.218333 -149.899167}
    "Banff" {51.1653    -115.5322}
    "Beijing" {39.913889 116.391667}
    "Boston " {42.35 -71.066666}
    "Buenos Aires" {-34.603333 -58.381667}
    "Chicago" {41.8675 -87.6243}
    "Denver" {39.75 -104.98}
    "Honolulu" {21.31 -157.83}
    "Johannesburg" {-26.204444 28.045556}
    "London" {51.508056 -0.124722}
    "Los Angeles" {34.054 -118.245}
    "Mexico City" {19.433333 -99.133333}
    "Moscow" {55.751667 37.617778}
    "New York" {40.7563 -73.9865}
    "Palo Alto" {37.429167 -122.138056}
    "Paris" {48.856667 2.350833}
    "San Francisco" {37.77 -122.43}
    "Sydney" {-33.859972 151.211111}
    "Tokyo" {35.700556 139.715}
    "Vancouver (Lost Lagoon)" {49.30198   -123.13724}
    "Washington DC" {38.9136 -77.0132}
}

# ### ### ### ######### ######### #########

proc Main {} {
    InitModel
    GUI
    LoadInitialMarks

    # Hack to get display to show nicely while the initial maps are
    # loading
    set gridInfo [grid info .sw]
    grid forget .sw
    update
    grid .sw {*}$gridInfo

    SetRegion $::zoom ; # Force initial region as the zoom control
                        # will not call us initially, only on
                        # future changes.
    GetInitialMark
}

# ### ### ### ######### ######### #########

proc InitModel {} {
    global argv cachedir loaddir provider zoom

    set zoom     12
    set cachedir ""
    set loaddir  [pwd]

    # OpenStreetMap. Mapnik rendered tiles.
    # alternative  http://tah.openstreetmap.org/Tiles/tile

    if {"FETCH" in [info commands]} { rename FETCH {}} ;# KPV, allow re-loading
    set provider [map::slippy::fetcher FETCH 19 http://tile.openstreetmap.org]

    # Nothing to do if no cache is specified, and fail for wrong#args

    if {![llength $argv]} return
    if {[llength $argv] > 1} Usage

    # A cache is specified. Create the directory, if necessary, and
    # initialize the necessary objects.

    set cachedir [lindex $argv 0]
    set loaddir  $cachedir
    set provider [map::slippy::cache CACHE $cachedir FETCH]

    # Pre-filling the cache based on map requests. Half-baked. Takes
    # currently to much cycles from the main requests themselves.  set
    #provider [map::slippy::prefetcher PREFE CACHE]
    return
}

proc Usage {} {
    global argv0
    puts stderr "wrong\#args, expected: $argv0 ?cachedir?"
    exit 1
}

# ### ### ### ######### ######### #########

proc GUI {} {
    global provider
    # ---------------------------------------------------------
    # The gui elements, plus connections.

    widget::scrolledwindow .sw
    widget::scrolledwindow .sl

    set th [$provider tileheight]
    set tw [$provider tilewidth]

    canvas::sqmap          .map   -closeenough 3 \
        -viewport-command VPTRACK -grid-cell-command GET \
        -grid-cell-width $tw -grid-cell-height $th -bg yellow

    canvas::zoom           .z    -variable ::zoom -command ZOOM \
        -orient vertical -levels [$provider levels]

    label                  .loc  -textvariable ::location \
        -bd 2 -relief sunken -bg white -width 20 -anchor w
    label                  .dist  -textvariable ::distance \
        -bd 2 -relief sunken -bg white -width 20 -anchor w

    listbox                .lm   -listvariable ::locations \
        -selectmode single -exportselection 0

    button                 .srch -command {SearchLoc $::srchtext}     -text Search
    entry                  .srchtext                  -textvariable ::srchtext
    button                 .exit -command exit        -text Exit
    button                 .goto -command GotoMark    -text Goto
    button                 .clr  -command ClearPoints -text {Clear Points}
    button                 .ld   -command LoadPoints  -text {Load Points}
    button                 .sv   -command SavePoints  -text {Save Points}

    .sw setwidget .map
    .sl setwidget .lm

    # ---------------------------------------------------------
    # layout of the elements

    grid .sl   -row 2 -column 0 -sticky swen -columnspan 2
    #grid .z    -row 2 -column 2 -sticky wen
    grid .sw   -row 2 -column 3 -sticky swen -columnspan 6

    place .z -in .map -x .2i -y .2i -anchor nw

    grid .exit -row 0 -column 0 -sticky wen
    grid .goto -row 0 -column 1 -sticky wen
    grid .clr  -row 0 -column 3 -sticky wen
    grid .ld   -row 0 -column 4 -sticky wen
    grid .sv   -row 0 -column 5 -sticky wen
    grid .loc  -row 0 -column 6 -sticky wen
    grid .dist -row 0 -column 7 -sticky wen

    grid .srch     -row 1 -column 0 -sticky wen
    grid .srchtext -row 1 -column 1 -columnspan 7 -sticky wen

    grid rowconfigure . 0 -weight 0
    grid rowconfigure . 1 -weight 0
    grid rowconfigure . 2 -weight 1

    grid columnconfigure . 0 -weight 0
    grid columnconfigure . 1 -weight 0
    grid columnconfigure . 2 -weight 0
    grid columnconfigure . 3 -weight 0
    grid columnconfigure . 8 -weight 1

    # ---------------------------------------------------------
    # Behaviours

    # Panning via mouse
    bind .map <ButtonPress-2> {%W scan mark   %x %y}
    bind .map <B2-Motion>     {%W scan dragto %x %y}

    # Mark/unmark a point on the canvas
    bind .map <1> {RememberPoint %x %y}

    # Double clicking location selects it
    bind .lm <Double-Button-1> GotoMark

    # Double-clicking right button centers map to mouse location.
    bind .map <Double-Button-3> GotoMouse

    # Cross hairs ...
    .map configure -cursor tcross
    crosshair::crosshair .map -width 0 -fill \#999999 -dash {.}
    crosshair::track on  .map TRACK

    # ---------------------------------------------------------
    return
}

# ### ### ### ######### ######### #########

set location  "location" ; # geo location of the mouse in the canvas (crosshair)
set distance  "distance" ; # distance between marks

proc VPTRACK {xl yt xr yb} {
    # args = viewport, pixels, see also canvas::sqmap, SetPixelView.
    global viewport
    set viewport [list $xl $yt $xr $yb]
    #puts VP-TRACK($viewport)
    return
}

proc TRACK {win x y args} {
    # args = viewport, pixels, see also canvas::sqmap, SetPixelView.
    global location zoom clat clon

    # Convert pixels to geographic location.
    lassign [map slippy point 2geo $zoom [list $x $y]] clat clon

    # Update entry field.
    set location [PrettyLatLon $clat $clon]
    return
}

# ### ### ### ######### ######### #########
# Basic callback structure, log for logging, facade to transform the
# cache/tiles result into what xcanvas is expecting.

proc GET {__ at donecmd} {
    global provider zoom
    set tile [linsert $at 0 $zoom]

    if {![map slippy tile valid {*}$at [$provider levels]]} {
        GOT $donecmd unset $tile
        return
    }

    #puts "GET ($tile) ($donecmd)"
    $provider get $tile [list GOT $donecmd]
    return
}

proc GOT {donecmd what tile args} {
    #puts "\tGOT $donecmd $what ($tile) $args"
    set at [lrange $tile 1 end]
    if {[catch {
        uplevel #0 [eval [linsert $args 0 linsert $donecmd end $what $at]]
    }]} { puts $::errorInfo }
    return
}

# ### ### ### ######### ######### #########

proc ZOOM {w level} {
    # The variable 'zoom' is already set to level, as the -variable of
    # our zoom control .z

    #puts ".z = $level"

    if {$level<0} { set level 0 }

    SetRegion $level
    ShowPoints
    return
}

proc SetRegion {level} {
    set rlength [map slippy length $level]
    set region  [list 0 0 $rlength $rlength]

    .map configure -scrollregion $region
    return
}

# ### ### ### ######### ######### #########

proc Goto {geo} {
    global zoom

    #puts Jump($geo)

    # The geo location is converted to pixels, then to a fraction of
    # the scrollregion. This is adjusted so that the fraction
    # specifies the center of the viewed region, and not the upper
    # left corner. for this translation we need the viewport data of
    # VPTRACK.

    lassign [map slippy geo 2point $zoom $geo] x y
    after 200 [list Jigger $zoom $y $x]
    #.map xview moveto $ofx
    #.map yview moveto $ofy
    return
}

proc Jigger {z y x} {
    global viewport
    set len [map slippy length $z]
    lassign $viewport l t r b
    set ofy [expr {($y - ($b - $t)/2.0)/$len}]
    set ofx [expr {($x - ($r - $l)/2.0)/$len}]

    .map xview moveto $ofx
    .map yview moveto $ofy
    return
}

# ### ### ### ######### ######### #########

set points    {} ; # way-points loaded list (list (lat lon comment))
set locations {} ; # Location markers (locationmark.gps)
set lmarks    {} ; # Coordinates for items in location

proc SavePoints {} {
    global loaddir

    set chosen [tk_getSaveFile -defaultextension .gps \
                    -filetypes {
                        {GPS {.gps}}
                        {ALL {*}}
                    } \
                    -initialdir $loaddir \
                    -title   {Save waypoints} \
                    -parent .map]

    if {$chosen eq ""} return

    global points
    set lines {}
    foreach p $points {
	lassign $p lat lon comment
        lappend lines [list waypoint $lat $lon $comment]
    }

    fileutil::writeFile $chosen [join $lines \n]\n
    return
}

proc LoadPoints {} {
    global loaddir

    set chosen [tk_getOpenFile -defaultextension .gps \
                    -filetypes {
                        {GPS {.gps}}
                        {ALL {*}}
                    } \
                    -initialdir $loaddir \
                    -title   {Load waypoints} \
                    -parent .map]

    if {$chosen eq ""} return
    if {[catch {
        set waypoints [fileutil::cat $chosen]
    }]} {
        return
    }

    set loaddir [file dirname $chosen]

    ClearPoints
    # Content is TRUSTED. In a proper app this has to be isolated from
    # the main system through a safe interp.
    #eval $waypoints
    ProcessFile $waypoints
    ShowPoints
    return
}
##+##########################################################################
#
# Safer way of processing our GPS file data. Only two commands
# allowed: "poi lat lon comment" and "waypoint lat lon comment"
#
proc ProcessFile {data} {
    foreach line [split $data \n] {
        set line [string trim $line]
        if {$line eq "" || [string match "#*" $line]} continue

        set n [catch {set len [llength $line]}]
        if {$n || $len != 4} {
            puts "bad line: '$line'"
            continue
        }
        lassign $line cmd lat lon comment
        if {$cmd ne "poi" && $cmd ne "waypoint"} {
            puts "bad command: '$line'"
            continue
        }
        $cmd $lat $lon $comment
    }
}

proc waypoint {lat lon comment} {
    global  points
    lappend points [list $lat $lon $comment]
    return
}

proc ShowPoints {} {
    global points zoom distance

    if {![llength $points]} return

    set cmds {}
    set cmd [list .map create line]

    set dist  0
    set glast {}

    foreach point $points {
	lassign $point lat lon comment
	set geo [list $lat $lon]
	lassign [map slippy geo 2point $zoom $geo] x y
        lappend cmd  $x $y
        lappend cmds [list POI $y $x $lat $lon $comment -fill salmon -tags Series]

        if {$glast ne {}} {
            set leg  [map slippy geo distance $glast $geo]
            set dist [expr {$dist + $leg}]
        }
	set glast $geo
    }
    lappend cmd -width 2 -tags Series -capstyle round ;#-smooth 1

    if {[llength $points] > 1} {
        set cmds [linsert $cmds 0 $cmd]
    }

    .map delete Series
    #puts [join $cmds \n]
    eval [join $cmds \n]
    set distance [PrettyDistance $dist]
    return
}
proc PrettyLatLon {lat lon} {
    return [format "%.6f %.6f" $lat $lon]
}

global pcounter
set pcounter 0
proc RememberPoint {x y} {
    #puts REMEMBER///
    global pcounter zoom
    incr   pcounter

    set point [list [.map canvasy $y] [.map canvasx $x]]
    lassign [map slippy point 2geo $zoom $point] lat lon
    lassign [PrettyLatLon $lat $lon] lat lon

    set comment "$pcounter:<$lat,$lon>"
    #puts $x/$y/$lat/$lon/$comment/$pcounter

    global  points
    lappend points [list $lat $lon $comment $pcounter]
    ShowPoints

    # This is handled weird. Placing the mouse on top of a point
    # doesn't trigger, however when I create a new point <1> at the
    # position, and then immediately after use <3> I can remove the
    # new point, and the second click the point underneath triggers as
    # well. Could this be a stacking issue?
    .map bind T/$comment <3> "[list ForgetPoint $pcounter];break"

    # Alternative: Bind <3> and the top level and use 'find
    # overlapping'. In that case however either we, or the sqmap
    # should filter out the background items.

    return
}

proc ForgetPoint {pid} {

    #    puts [.map find overlapping $x $y $x $y]
    #return

    #puts //FORGET//$pid

    global points
    set pos -1
    foreach p $points {
        incr pos
	lassign $p lat lon comment id
        if {$id != $pid} continue
        #puts \tFound/$pos
        set points [lreplace $points $pos $pos]
        if {![llength $points]} {
            ClearPoints
        } else {
            ShowPoints
        }
        return
    }
    #puts Missed
    return
}
# See http://wiki.tcl.tk/8447
proc GreatCircleDistance {lat1 lon1 lat2 lon2} {
    set y1 $lat1
    set x1 $lon1
    set y2 $lat2
    set x2 $lon2

    set pi [expr {acos(-1)}]
    set x1 [expr {$x1 *2*$pi/360.0}]            ;# Convert degrees to radians
    set x2 [expr {$x2 *2*$pi/360.0}]
    set y1 [expr {$y1 *2*$pi/360.0}]
    set y2 [expr {$y2 *2*$pi/360.0}]
    # calculate distance:
    ##set d [expr {acos(sin($y1)*sin($y2)+cos($y1)*cos($y2)*cos($x1-$x2))}]
    set d [expr {sin($y1)*sin($y2)+cos($y1)*cos($y2)*cos($x1-$x2)}]
    if {abs($d) > 1.0} {                        ;# Rounding error
        set d [expr {$d > 0 ? 1.0 : -1.0}]
    }
    set d [expr {acos($d)}]

    set meters [expr {20001600/$pi*$d}]
    return $meters
}
proc PrettyDistance {dist} {
    if {$dist == 0} { return "distance" }
    set meters [expr {round($dist)}]
    if {$meters == 1} { return "1 meter"}
    if {$meters < 1000} { return "$meters meters"}
    return [format "%.1f km" [expr {$dist/1000.0}]]
}
proc POI {y x lat lon comment args} {
    set x1 [expr { $x + 6 }]
    set y1 [expr { $y + 6 }]
    set x  [expr { $x - 6 }]
    set y  [expr { $y - 6 }]

    set id [eval [linsert $args 0 .map create oval $x $y $x1 $y1]]
    if {$comment eq ""} return
    tooltip::tooltip .map -item $id $comment
    .map addtag T/$comment withtag $id
    return
}

proc ClearPoints {} {
    global points
    set points {}
    .map delete Series
    set ::distance "distance"
    return
}

proc LoadInitialMarks {} {
    foreach {name latlon} $::cities {
        lassign $latlon lat lon
        poi $lat $lon $name
    }
}

proc ClearMarks {} {
    global lmarks locations
    set lmarks {}
    set locations {}
    return
}

proc poi {lat lon comment args} {
    global lmarks locations
    lappend lmarks [list $lat $lon [lindex $args 0]]
    # first optional arg: an attributes dict:
    lappend locations $comment
    return
}

proc ShowMarks {} {
    # locations traced by .lm
    return
}

proc GotoMouse {} {
    global clat clon zoom
    Goto [list $zoom $clat $clon]
    return
}

proc GotoMark {} {
    global lmarks zoom provider
    set sel [.lm curselection]
    if {![llength $sel]} return
    set sel [lindex $sel 0]
    set sel [lindex $lmarks $sel]
    lassign $sel lat lon attrs

    if { [dict exists $attrs boundingbox] } {
        global viewport
	lassign $viewport x0 y0 x1 y1
        set z [map slippy geo box fit \
                   [dict get $attrs boundingbox] \
                   [list [expr {$x1 - $x0}] [expr {$y1 - $y0}]] \
                   [expr {[$provider levels] - 1}]]
        if {$z != $zoom} {
            set zoom $z
            ZOOM .map $zoom
        }
        # Debug: draw a red rectangle to show bbox:
	lassign [dict get $attrs boundingbox]                    lat0 lat1 lon0 lon1
	lassign [map slippy geo 2point $zoom [list $lat0 $lon0]] x0 y0
	lassign [map slippy geo 2point $zoom [list $lat1 $lon1]] x1 y1
        .map create rectangle $x0 $y0 $x1 $y1 -width 2 -outline red
        # End debug
    }

    Goto [list $zoom $lat $lon]
    return
}

proc GetInitialMark {} {
    set n [expr {int(rand()*[llength $::locations])}]
    .lm selection clear 0 end
    .lm selection set $n
    .lm selection anchor $n
    GotoMark
}

# ### ### ### ######### ######### #########

##+##########################################################################
#
# # Search: initiate a geo search; SearchDone picks up the results and
# # puts the results in the locations listbox
#

proc bgerror {err} {
    puts "BGERROR: $err"
}

proc SearchLoc {qry} {
    if { ! [info exists ::Searcher] } {
        set ::Searcher [::map::geocode::nominatim Searcher -callback SearchLocDone]
    }
    $::Searcher search $qry
}

proc SearchLocDone {result} {
    foreach loc $result {
        poi \
	    [dict get $loc lat] \
	    [dict get $loc lon] \
	    [dict get $loc display_name] \
	    $loc
    }
}

# ### ### ### ######### ######### #########

proc ShowGrid {} {
    # Activating the grid leaks items = memory
    .map configure -grid-show-borders 1
    .map flush
    return
}

# ### ### ### ######### ######### #########
# ### ### ### ######### ######### #########
Main