/
tilestache-seed.py
executable file
·190 lines (132 loc) · 7.27 KB
/
tilestache-seed.py
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
#!/usr/bin/env python
"""tilestache-seed.py will warm your cache.
This script is intended to be run directly. This example seeds the area around
West Oakland (http://sta.mn/ck) in the "osm" layer, for zoom levels 12-15:
tilestache-seed.py -c ./config.json -l osm -b 37.79 -122.35 37.83 -122.25 -e png 12 13 14 15
See `tilestache-seed.py --help` for more information.
"""
from sys import stderr, path
from optparse import OptionParser
try:
from json import dump as json_dump
except ImportError:
from simplejson import dump as json_dump
#
# Most imports can be found below, after the --include-path option is known.
#
parser = OptionParser(usage="""%prog [options] [zoom...]
Seeds a single layer in your TileStache configuration - no images are returned,
but TileStache ends up with a pre-filled cache. Bounding box is given as a pair
of lat/lon coordinates, e.g. "37.788 -122.349 37.833 -122.246". Output is a list
of tile paths as they are created.
Configuration, bbox, and layer options are required; see `%prog --help` for info.""")
defaults = dict(extension='png', padding=0, verbose=True, bbox=(37.777, -122.352, 37.839, -122.226))
parser.set_defaults(**defaults)
parser.add_option('-c', '--config', dest='config',
help='Path to configuration file.')
parser.add_option('-l', '--layer', dest='layer',
help='Layer name from configuration.')
parser.add_option('-b', '--bbox', dest='bbox',
help='Bounding box in floating point geographic coordinates: south west north east.',
type='float', nargs=4)
parser.add_option('-p', '--padding', dest='padding',
help='Extra margin of tiles to add around bounded area. Default value is %s (no extra tiles).' % repr(defaults['padding']),
type='int')
parser.add_option('-e', '--extension', dest='extension',
help='Optional file type for rendered tiles. Default value is %s.' % repr(defaults['extension']))
parser.add_option('-f', '--progress-file', dest='progressfile',
help="Optional JSON progress file that gets written on each iteration, so you don't have to pay close attention.")
parser.add_option('-q', action='store_false', dest='verbose',
help='Suppress chatty output, --progress-file works well with this.')
parser.add_option('-i', '--include-path', dest='include',
help="Add the following colon-separated list of paths to Python's include path (aka sys.path)")
parser.add_option('-d', '--output-directory', dest='outputdirectory',
help='Optional output directory for tiles, to override configured cache with the equivalent of: {"name": "Disk", "path": <output directory>, "dirs": "portable", "gzip": []}. More information in http://tilestache.org/doc/#caches.')
parser.add_option('--to-mbtiles', dest='mbtiles_output',
help='Optional output file for tiles, will be created as an MBTiles 1.1 tileset. See http://mbtiles.org for more information.')
parser.add_option('-x', '--ignore-cached', action='store_true', dest='ignore_cached',
help='Re-render every tile, whether it is in the cache already or not.')
def generateCoordinates(ul, lr, zooms, padding):
""" Generate a stream of (offset, count, coordinate) tuples for seeding.
"""
# start with a simple total of all the coordinates we will need.
count = 0
for zoom in zooms:
ul_ = ul.zoomTo(zoom).container().left(padding).up(padding)
lr_ = lr.zoomTo(zoom).container().right(padding).down(padding)
rows = lr_.row + 1 - ul_.row
cols = lr_.column + 1 - ul_.column
count += int(rows * cols)
# now generate the actual coordinates.
# offset starts at zero
offset = 0
for zoom in zooms:
ul_ = ul.zoomTo(zoom).container().left(padding).up(padding)
lr_ = lr.zoomTo(zoom).container().right(padding).down(padding)
for row in range(int(ul_.row), int(lr_.row + 1)):
for column in range(int(ul_.column), int(lr_.column + 1)):
coord = Coordinate(row, column, zoom)
yield (offset, count, coord)
offset += 1
if __name__ == '__main__':
options, zooms = parser.parse_args()
if options.include:
for p in options.include.split(':'):
path.insert(0, p)
from TileStache import parseConfigfile, getTile
from TileStache.Core import KnownUnknown
from TileStache.Caches import Disk, Multi
from TileStache import MBTiles
from ModestMaps.Core import Coordinate
from ModestMaps.Geo import Location
try:
if options.config is None:
raise KnownUnknown('Missing required configuration (--config) parameter.')
if options.layer is None:
raise KnownUnknown('Missing required layer (--layer) parameter.')
config = parseConfigfile(options.config)
if options.layer not in config.layers:
raise KnownUnknown('"%s" is not a layer I know about. Here are some that I do know about: %s.' % (options.layer, ', '.join(sorted(config.layers.keys()))))
layer = config.layers[options.layer]
verbose = options.verbose
extension = options.extension
progressfile = options.progressfile
if options.outputdirectory and options.mbtiles_output:
cache1 = Disk(options.outputdirectory, dirs='portable', gzip=[])
cache2 = MBTiles.Cache(options.mbtiles_output, extension, options.layer)
config.cache = Multi([cache1, cache2])
elif options.outputdirectory:
config.cache = Disk(options.outputdirectory, dirs='portable', gzip=[])
elif options.mbtiles_output:
config.cache = MBTiles.Cache(options.mbtiles_output, extension, options.layer)
lat1, lon1, lat2, lon2 = options.bbox
south, west = min(lat1, lat2), min(lon1, lon2)
north, east = max(lat1, lat2), max(lon1, lon2)
northwest = Location(north, west)
southeast = Location(south, east)
ul = layer.projection.locationCoordinate(northwest)
lr = layer.projection.locationCoordinate(southeast)
for (i, zoom) in enumerate(zooms):
if not zoom.isdigit():
raise KnownUnknown('"%s" is not a valid numeric zoom level.' % zoom)
zooms[i] = int(zoom)
if options.padding < 0:
raise KnownUnknown('A negative padding will not work.')
padding = options.padding
except KnownUnknown, e:
parser.error(str(e))
for (offset, count, coord) in generateCoordinates(ul, lr, zooms, padding):
path = '%s/%d/%d/%d.%s' % (layer.name(), coord.zoom, coord.column, coord.row, extension)
progress = {"tile": path,
"offset": offset + 1,
"total": count}
if options.verbose:
print >> stderr, '%(offset)d of %(total)d...' % progress,
mimetype, content = getTile(layer, coord, extension, options.ignore_cached)
progress['size'] = '%dKB' % (len(content) / 1024)
if options.verbose:
print >> stderr, '%(tile)s (%(size)s)' % progress
if progressfile:
fp = open(progressfile, 'w')
json_dump(progress, fp)
fp.close()