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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
## Fetch tile images for maps based on the slippy scheme.
# ### ### ### ######### ######### #########
## Requisites
package require Tcl 8.4 ; # No {*}-expansion :(, no ** either, nor lassign
# Tk8.6 "image photo" supports PNG directly. Earlier versions requires
# the IMG extension, aka TkImg.
# See http://sourceforge.net/projects/tkimg
if {[catch {
package require Tk 8.6
}]} {
package require Tk;
package require img::png ; # Slippy tiles use the PNG image file format.
}
package require map::slippy ; # Slippy contants
package require http ; # Retrieval method
package require snit
# ### ### ### ######### ######### #########
## Implementation
snit::type map::slippy::fetcher {
# ### ### ### ######### ######### #########
## API
constructor {levels baseurl} {
set mybase $baseurl
set mylevels $levels
return
}
# ### ### ### ######### ######### #########
## Query API
method levels {} { return $mylevels }
method tileheight {} {map::slippy tile size}
method tilewidth {} {map::slippy tile size}
# ### ### ### ######### ######### #########
## Tile retrieval API
method get {tile donecmd} {
# tile = list (zoom, row, col)
if {![map::slippy tile valid $tile $mylevels msg]} {
return -code error $msg
}
# Compose the url for the requested tile
set url [urlOf $tile]
# Initiate tile download.
# Note however that a download is actually started if and only
# if there is no download of this tile already in progress. If
# there is we simply register the new request with that
# download. When the download is done we convert the data to
# an in-memory image and provide it to all the waiting requests.
lappend mypending($url) $donecmd
if {[llength $mypending($url)] > 1} return
# We keep the retrieved image data in memory, 256x256 is not
# that large for todays RAM sizes (Seen 124K max so far).
if {[catch {
set token [http::geturl $url -binary 1 -command [mymethod Done] \
-timeout 60000]
}]} {
puts $::errorInfo
# Some errors, like invalid urls, raise errors synchro-
# nously, even if a callback -command is specified.
after idle [linsert $donecmd end unset $tile]
return
}
# Remember the download settings.
set mytoken($token) [list $url $tile]
#puts "GET\t($url) = $token"
return
}
method Done {token} {
#puts GOT/$token
# We get the request settings and waiting callbacks first, and
# clean them up immediately, keeping the object state
# consistent even in the face of recursive calls. (Which
# should not be possible here).
foreach {url tile} $mytoken($token) break
set requests $mypending($url)
unset mytoken($token)
unset mypending($url)
# Then we get the request results, and clean them up as well.
set status [http::status $token]
set ncode [http::ncode $token]
set data [http::data $token]
http::cleanup $token
#puts URL|$url
#puts STT|$status
#puts COD|[http::code $token]
#puts NCO|[http::ncode $token]
#puts ERR|[http::error $token]
# Check whether the retrieval failed, bad url, server out,
# etc. or not, and report if yes.
if {($status ne "ok") || ($ncode != 200)} {
# error, eof, and other non-ok conditions.
foreach d $requests {
after idle [linsert $d end unset $tile]
}
return
}
# The request was ok. Note that we assume that the slippy
# server is not redirecting us to some other url. We expect
# the image at exactly this location. A redirection is treated
# as failure, see the check above.
#puts \t|[string length $data]|
if {[catch {
set tileimage [image create photo -data $data]
}]} {
# XXX AK: Here we need a better way to report internal
# problems. Maybe just throw the error?
#puts $::errorInfo
#puts $data
foreach d $requests {
after idle [linsert $d end unset $tile]
}
return
}
# Finally we have the image we seek, and can report it.
foreach d $requests {
after idle [linsert $d end set $tile $tileimage]
}
return
}
# ### ### ### ######### ######### #########
## Internal commands
proc urlOf {tile} {
upvar 1 mybase mybase
foreach {z r c} $tile break
return $mybase/$z/$c/$r.png
}
# ### ### ### ######### ######### #########
## State
variable mybase {} ; # Base url to the tiles.
variable mylevels 0 ; # Number of zoom levels (0...mylevels-1)
# State of all http requests currently in flight.
variable mypending -array {} ; # tile url -> list (done-cmd-prefix)
variable mytoken -array {} ; # http token -> list (tile url, tile id)
# ### ### ### ######### ######### #########
}
# ### ### ### ######### ######### #########
## Ready
package provide map::slippy::fetcher 0.4
|