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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
## A cache we put on top of a slippy fetcher, to satisfy requests for
## tiles from the local filesystem first, if possible.
# ### ### ### ######### ######### #########
## Requisites
package require Tcl 8.4 ; # No {*}-expansion :(, no ** either, nor lassign
package require Tk ; # image photo
package require map::slippy ; # Slippy constants
package require fileutil ; # Testing paths
package require img::png ; # We write tile images using the PNG image file format.
package require snit
# ### ### ### ######### ######### #########
## Implementation
snit::type map::slippy::cache {
# ### ### ### ######### ######### #########
## API
constructor {cachedir provider} {
if {![fileutil::test $cachedir edrw msg]} {
return -code error "$type constructor: $msg"
}
set mycachedir $cachedir
set myprovider $provider
set mylevels [uplevel \#0 [linsert $myprovider end levels]]
return
}
delegate method * to myprovider
delegate option * to myprovider
method valid {tile {msgv {}}} {
if {$msgv ne ""} { upvar 1 $msgv msg }
return [map::slippy tile valid $tile $mylevels msg]
}
method exists {tile} {
if {![map::slippy tile valid $tile $mylevels msg]} {
return -code error $msg
}
return [file exists [FileOf $tile]]
}
method get {tile donecmd} {
if {![map::slippy tile valid $tile $mylevels msg]} {
return -code error $msg
}
# Query the filesystem for a cached tile and return
# immediately if such was found.
set tilefile [FileOf $tile]
if {[file exists $tilefile]} {
set tileimage [image create photo -file $tilefile]
after 0 [linsert $donecmd end set $tile $tileimage]
return
}
# The requested tile is not known to the cache, so we forward
# the request to our provider and intercept the result to
# update the cache. Only one retrieval request will be issued
# if multiple arrive from above.
lappend mypending($tile) $donecmd
if {[llength $mypending($tile)] > 1} return
uplevel \#0 [linsert $myprovider end get $tile [mymethod Done]]
return
}
method {Done set} {tile tileimage} {
# The requested tile was known to the provider, we can cache
# the image we got and then hand it over to the original
# requestor.
set tilefile [FileOf $tile]
file mkdir [file dirname $tilefile]
$tileimage write $tilefile -format png
set requests $mypending($tile)
unset mypending($tile)
# Note. The cache accepts empty callbacks for requests, and if
# no actual callback 'took' the image it is assumed to be not
# wanted and destroyed. This allows higher layers to request
# tiles before needng them without leaking imagas and yet also
# not throwing them away when a prefetch and regular fetch
# collide.
set taken 0
foreach d $requests {
if {![llength $d]} continue
uplevel \#0 [linsert $d end set $tile $tileimage]
set taken 1
}
if {!$taken} {
image delete $tileimage
}
return
}
method {Do unset} {donecmd tile} {
# The requested tile is not known. Nothing has to change in
# the cache (it did not know the tile either), the result can
# be directly handed over to the original requestor.
uplevel \#0 [linsert $donecmd end unset $tile]
return
}
# ### ### ### ######### ######### #########
## Internal commands
proc FileOf {tile} {
upvar 1 mycachedir mycachedir
foreach {z r c} $tile break
return [file join $mycachedir $z $c $r.png]
}
# ### ### ### ######### ######### #########
## State
variable mycachedir {} ; # Directory to cache tiles in.
variable myprovider {} ; # Command prefix, provider of tiles to cache.
variable mylevels {} ; # Zoom-levels, retrieved from provider.
variable mypending -array {} ; # tile -> list (done-cmd-prefix)
# ### ### ### ######### ######### #########
}
# ### ### ### ######### ######### #########
## Ready
package provide map::slippy::cache 0.2
|