File: area-map-display.tcl

package info (click to toggle)
tklib 0.9-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 23,156 kB
  • sloc: tcl: 105,088; sh: 2,573; ansic: 792; pascal: 359; makefile: 69; sed: 53; exp: 21
file content (392 lines) | stat: -rw-r--r-- 10,841 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
## -*- tcl -*-
# # ## ### ##### ######## ############# ######################
## (c) 2022 Andreas Kupries
##
## Originally developed within the AKIS project (c) Andreas Kupries

# @@ Meta Begin
# Package map::area::map-display 0.1
# Meta author      {Andreas Kupries}
# Meta location    https://core.tcl.tk/tklib
# Meta platform    tcl
# Meta summary	   Map Action Engine: Layer to display area definitions
# Meta description Attachment to map display widgets providing custom behaviour.
# Meta description Shows a set of area definitions. Areas geo area to ensure
# Meta description that only visible areas use canvas resources (items)
# Meta subject	   {addon, area display, map display}
# Meta subject	   {area display, map display, addon}
# Meta subject	   {map display, addon, area display}
# Meta require     {Tcl 8.6-}
# Meta require     {Tk  8.6-}
# Meta require     canvas::edit::polyline
# Meta require     debug
# Meta require     debug::caller
# Meta require     {map::slippy 0.8}
# Meta require     snit
# @@ Meta End

package provide map::area::map-display 0.1

# # ## ### ##### ######## ############# ######################
## API
#
##  <class> OBJ map-widget store
#
##  <obj> focus ID	-> VOID		Move map to area with ID
##  <obj> disable	-> VOID		Hide areas
##  <obj> enable	-> VOID		Show areas
#
##  -on-active		Command to report changes in the active area
#
##  -color		Visual options inherited from canvas::edit::polyline
##  -hilit-color	for full customization of the polyline appearance
##  -radius		.
##  -kind		.
##  -radius		.
##  -line-config	.
##  -create-cmd 	.
#
# TODO :: Can we get stuff like double-click handling to invoke a area action?
#
# # ## ### ##### ######## ############# ######################
## Requirements

package require Tcl 8.6-
#
package require debug                  ;# - Narrative Tracing
package require debug::caller          ;#
package require map::slippy 0.8        ;# - Map utilities
package require snit                   ;# - OO system
#                                      ;# Tklib.
package require canvas::edit::polyline ;# - Pixel level editor

# # ## ### ##### ######## ############# ######################
## Ensemble setup.

namespace eval map       { namespace export area        ; namespace ensemble create }
namespace eval map::area { namespace export map-display ; namespace ensemble create }

debug level  tklib/map/area/map-display
debug prefix tklib/map/area/map-display {<[pid]> [debug caller] | }

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

snit::type ::map::area::map-display {
    # ..................................................................
    # User configuration

    option -on-active   -default {} -readonly 1

    # Visual options passed to the low-level polyline engines
    option -color       -default {} -readonly 1
    option -hilit-color -default {} -readonly 1
    option -radius      -default {} -readonly 1
    option -kind        -default {} -readonly 1
    option -radius      -default {} -readonly 1
    option -line-config -default {} -readonly 1
    option -create-cmd  -default {} -readonly 1

    # ..................................................................
    ## State - Derived from configuration

    variable myactive    0      ;# Active layer? y/n
    variable myvisual	 {}	;# Visual configuration for the polyline engines
    variable mymap	 {}	;# Map the behaviour is attached to
    variable mycanvas	 {}	;# Canvas inside the map
    variable mystore     {}	;# Area store
    variable myviewchain {}	;# Old view reporting callback

    # ..................................................................
    # Map state (viewport)

    variable myzoom	 {}	;# Map zoom level
    variable mycanvasdim {}	;# Canvas viewport dimensions

    # ..................................................................
    # Display state

    variable myareas    {}	;# Cache of area information (area, box, pixels per level)
    #                           ;# dict (id -> 'level' -> level -> list(point...)
    #                           ;#          -> 'bbox'   -> geobox
    #                           ;#          -> 'center' -> geo
    variable myvisible  {}      ;# Set of the visible areas, map from id to manager
    #                           ;# dict (id -> canvas::edit::polyline instance)
    variable myrevers   {}      ;# dict (canvas::edit::polyline instance -> id)

    # ..................................................................
    # Object pool - Reusable polyline objects

    variable myfree {}	;# Set of reusable polyline instances
    variable myid   0	;# Id counter for new polyline instances

    # ..................................................................
    ## Lifecycle

    constructor {map store args} {
	debug.tklib/map/area/map-display {}

	$self configurelist $args

	set mystore  $store
	set mymap    $map
	set mycanvas [$map canvas]

	foreach o {
	    -color
	    -hilit-color
	    -radius
	    -kind
	    -radius
	    -line-config
	    -create-cmd
	} {
	    if {$options($o) eq {}} continue
	    lappend myvisual $o $options($o)
	}

	$self Attach
	return
    }

    destructor {
	debug.tklib/map/area/map-display {}

	if {![winfo exists $mycanvas]} return
	$self disable
	$self Detach

	# The low-level area managers are auto-destroyed because they are in this
	# object's namespace and deleted with it.
	return
    }

    # ..................................................................
    ## API

    method enable {} {
	debug.tklib/map/area/map-display {}

	if {$myactive} return
	set myactive yes

	# Force visibility processing
	$self ViewChanged {*}[$mymap view]
	return
    }

    method disable {} {
	debug.tklib/map/area/map-display {}

	if {!$myactive} return
	set myactive no

	# Remove all the visible areas
	dict for {id poly} $myvisible {
	    $self Close $id
	}
	return
    }

    method focus {id} {
	debug.tklib/map/area/map-display {}

	$self Load $id
	$self Fit  $id	;# The viewport change automatically triggers everything
	#                # needed to show the focus area, and whatever else is
	#                # visible.
	return
    }

    # ..................................................................
    ## Internal

    # ..................................................................
    ## Viewport interception

    method ViewChanged {zoom viewarea geobox} {
	debug.tklib/map/area/map-display {}

	# Note that the viewport is reported twice, as both pixel and geo coordinates.
	# We are only interested in the pixel coordinates, coming first.

	debug.tklib/map/area/map-display {}

	# Pass view change reporting to old callback, if any
	if {[llength $myviewchain]} {
	    uplevel 1 [list {*}$myviewchain $zoom $viewarea $geobox]
	}

	# Do nothing when disabled
	if {!$myactive} return

	set zoomchanged [expr {$zoom != $myzoom}]

	# Update map state (zoom, and canvas dimensions for fitting)
	set mycanvasdim [map slippy point box dimensions $viewarea]
	set myzoom      $zoom

	# Query store for visible areas
	set visible [DO visible $geobox]

	set new {}
	foreach v $visible { dict set new $v . }

	# Drop all areas which are not visible any longer
	dict for {id poly} $myvisible {
	    if {[dict exists $new $id]} continue
	    $self Close $id
	}

	# For all visible areas, get new, and move existing. move only for zoom changes.
	foreach id $visible {
	    if {[dict exists $myvisible $id]} {
		if {$zoomchanged} { $self Show $id }
		continue
	    }
	    $self Load $id
	    $self Open $id
	    $self Show $id
	}
	return
    }

    # ..................................................................

    method Fit {id} {
	debug.tklib/map/area/map-display {}
	# Already loaded.

	set center [dict get $myareas $id center]
	set gbox   [dict get $myareas $id bbox]
	set zoom   [map slippy geo box fit $gbox $mycanvasdim [expr {[$mymap levels]-1}]]

	#puts /area-box/$gbox
	#puts /dim/$mycanvasdim
	#puts /zom/$zoom

	# And this triggers display of the focused id, being fully visible
	$mymap center $center $zoom
	return
    }

    method Load {id} {
	debug.tklib/map/area/map-display {}

	if {[dict exists $myareas $id geo]} return

	set spec [DO get $id]
	dict with spec {}
	# names, geo, diameter, length, center, bbox, parts
	# => center, bbox

	dict set myareas $id bbox   $bbox
	dict set myareas $id center $center
	return
    }

    method Show {id} {
	debug.tklib/map/area/map-display {}

	# Note: point/marker radius is chosen for best visual appearance.
	# Single point    => extend size to make it visible
	# Multiple points => shrink to nothing so that line display is dominant

	set poly   [dict get $myvisible $id]
	set points [$self Pixels $id]
	set radius [expr { [llength $points] < 2 ? 3 : 0 }]

	$poly configure -radius $radius
	$poly set-line $points
	return
    }

    method Pixels {id} {
	debug.tklib/map/area/map-display {}

	if {![dict exists $myareas $id level $myzoom]} {
	    dict set myareas $id level $myzoom [DO pixels $id $myzoom]
	}
	return [dict get $myareas $id level $myzoom]
    }

    method Open {id} {
	debug.tklib/map/area/map-display {}

	if {[llength $myfree]} {
	    set poly   [lindex   $myfree end]
	    set myfree [lreplace $myfree end end]
	} else {
	    set obj  AREA_[incr myid]
	    set poly [canvas::edit polyline \
			  ${selfns}::$obj \
			  $mycanvas \
			  {*}$myvisual \
			  -closed yes \
			  -active-cmd [mymethod Active] \
			  -tag $self//$obj]
	    # starts disabled
	}

	dict set myvisible $id $poly
	dict set myrevers  $poly $id
	return
    }

    method Active {poly kind} {
	debug.tklib/map/area/map-display {}

	if {![llength $options(-on-active)]} return
	if {$kind ne "line"} return

	set id [dict get $myrevers $poly]
	uplevel #0 [list {*}$options(-on-active) $id]
	return
    }

    method Close {id} {
	debug.tklib/map/area/map-display {}

	set poly [dict get $myvisible $id]
	$poly clear

	dict unset myvisible $id
	dict unset myrevers  $poly
	lappend myfree $poly
	return
    }

    # ..................................................................
    ## Chain management

    method Attach {} {
	debug.tklib/map/area/map-display {}

	# Hook into viewport reporting
	set myviewchain [$mymap cget -on-view-change]
	$mymap configure -on-view-change [mymethod ViewChanged]
	return
    }

    method Detach {} {
	debug.tklib/map/area/map-display {}

	# Restore old view port reporting
	$mymap configure -on-view-change $myviewchain
	return
    }

    # ..................................................................
    ## Store access

    proc DO {args} {
	debug.tklib/map/area/map-display {}

	upvar 1 mystore mystore
	return [uplevel #0 [list {*}$mystore {*}$args]]
    }

    # ..................................................................
}

# # ## ### ##### ######## ############# ######################
return