[tilestache] 06/22: Imported Upstream version 1.51+ds

Bas Couwenberg sebastic at debian.org
Thu Oct 27 21:40:29 UTC 2016


This is an automated email from the git hooks/post-receive script.

sebastic pushed a commit to branch master
in repository tilestache.

commit 1d7ed3dc221bfc59a1ee94b73043794dac615842
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Mon Oct 17 08:30:34 2016 +0200

    Imported Upstream version 1.51+ds
---
 PKG-INFO                                  |   2 +-
 TileStache/Core.py                        |  11 +-
 TileStache/Goodies/Providers/Composite.py | 346 +++++++++++++++---------------
 TileStache/Goodies/Providers/GDAL.py      |  50 ++---
 TileStache/Mapnik.py                      | 134 ++++++------
 TileStache/Pixels.py                      |  35 +--
 TileStache/S3.py                          |   3 +
 TileStache/VERSION                        |   2 +-
 TileStache/Vector/__init__.py             |  23 +-
 scripts/tilestache-seed.py                |  10 +
 setup.py                                  |   8 +-
 11 files changed, 317 insertions(+), 307 deletions(-)

diff --git a/PKG-INFO b/PKG-INFO
index a75117a..06d4c36 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.0
 Name: TileStache
-Version: 1.50.1
+Version: 1.51
 Summary: A stylish alternative for caching your map tiles.
 Home-page: http://tilestache.org
 Author: Michal Migurski
diff --git a/TileStache/Core.py b/TileStache/Core.py
index 416d787..d325283 100644
--- a/TileStache/Core.py
+++ b/TileStache/Core.py
@@ -172,22 +172,20 @@ def _addRecentTile(layer, coord, format, body, age=300):
     logging.debug('TileStache.Core._addRecentTile() added tile to recent tiles: %s', key)
     
     # now look at the oldest keys and remove them if needed
-    for (key, due_by) in _recent_tiles['list']:
+    cutoff = 0
+    for i, (key, due_by) in enumerate(_recent_tiles['list']):
         # new enough?
         if time() < due_by:
+            cutoff = i
             break
         
         logging.debug('TileStache.Core._addRecentTile() removed tile from recent tiles: %s', key)
         
         try:
-            _recent_tiles['list'].remove((key, due_by))
-        except ValueError:
-            pass
-        
-        try:
             del _recent_tiles['hash'][key]
         except KeyError:
             pass
+    del _recent_tiles['list'][:cutoff]
 
 def _getRecentTile(layer, coord, format):
     """ Return the body of a recent tile, or None if it's not there.
@@ -427,6 +425,7 @@ class Layer:
                     except NoTileLeftBehind, e:
                         tile = e.tile
                         save = False
+                        status_code = 404
 
                     if not self.write_cache:
                         save = False
diff --git a/TileStache/Goodies/Providers/Composite.py b/TileStache/Goodies/Providers/Composite.py
index 659329a..5209af5 100644
--- a/TileStache/Goodies/Providers/Composite.py
+++ b/TileStache/Goodies/Providers/Composite.py
@@ -83,7 +83,7 @@ A complete example configuration might look like this:
       {
         "name": "Test"
       },
-      "layers": 
+      "layers":
       {
         "base":
         {
@@ -134,7 +134,7 @@ Corresponding example stackfile XML:
   <?xml version="1.0"?>
   <stack>
     <layer src="base" />
-  
+
     <stack>
       <layer src="outlines">
         <mask src="halos" />
@@ -185,24 +185,24 @@ class Provider:
     """
     def __init__(self, layer, stack=None, stackfile=None):
         """ Make a new Composite.Provider.
-            
+
             Arguments:
-            
+
               layer:
                 The current TileStache.Core.Layer
-                
+
               stack:
                 A list or dictionary with configuration for the image stack, parsed
                 by build_stack(). Also acceptable is a URL to a JSON file.
-              
+
               stackfile:
                 *Deprecated* filename for an XML representation of the image stack.
         """
         self.layer = layer
-        
+
         if type(stack) in (str, unicode):
             stack = jsonload(urlopen(urljoin(layer.config.dirpath, stack)).read())
-        
+
         if type(stack) in (list, dict):
             self.stack = build_stack(stack)
 
@@ -212,21 +212,21 @@ class Provider:
             #
             stackfile = pathjoin(self.layer.config.dirpath, stackfile)
             stack = parseXML(stackfile).firstChild
-            
+
             assert stack.tagName == 'stack', \
                    'Expecting root element "stack" but got "%s"' % stack.tagName
-    
+
             self.stack = makeStack(stack)
-        
+
         else:
             raise Exception('Note sure what to do with this stack argument: %s' % repr(stack))
-        
+
     def renderTile(self, width, height, srs, coord):
-    
+
         rgba = [numpy.zeros((width, height), float) for chan in range(4)]
-        
+
         rgba = self.stack.render(self.layer.config, rgba, coord)
-        
+
         return _rgba2img(rgba)
 
 class Composite(Provider):
@@ -236,13 +236,13 @@ class Composite(Provider):
 
 def build_stack(obj):
     """ Build up a data structure of Stack and Layer objects from lists of dictionaries.
-    
+
         Normally, this is applied to the "stack" parameter to Composite.Provider.
     """
     if type(obj) is list:
         layers = map(build_stack, obj)
         return Stack(layers)
-    
+
     elif type(obj) is dict:
         keys = (('src', 'layername'), ('color', 'colorname'),
                 ('mask', 'maskname'), ('opacity', 'opacity'),
@@ -250,7 +250,7 @@ def build_stack(obj):
                 ('zoom', 'zoom'))
 
         args = [(arg, obj[key]) for (key, arg) in keys if key in obj]
-        
+
         return Layer(**dict(args))
 
     else:
@@ -258,7 +258,7 @@ def build_stack(obj):
 
 class Layer:
     """ A single image layer in a stack.
-    
+
         Can include a reference to another layer for the source image, a second
         reference to another layer for the mask, and a color name for the fill.
     """
@@ -267,13 +267,13 @@ class Layer:
         """ A new image layer.
 
             Arguments:
-            
+
               layername:
                 Name of the primary source image layer.
-              
+
               colorname:
                 Fill color, passed to make_color().
-              
+
               maskname:
                 Name of the mask image layer.
         """
@@ -283,17 +283,17 @@ class Layer:
         self.opacity = opacity
         self.blendmode = blendmode
         self.adjustments = adjustments
-        
+
         zooms = re.search("^(\d+)-(\d+)$|^(\d+)$", zoom) if zoom else None
-        
+
         if zooms:
             min_zoom, max_zoom, at_zoom = zooms.groups()
-            
+
             if min_zoom is not None and max_zoom is not None:
                 self.min_zoom, self.max_zoom = int(min_zoom), int(max_zoom)
             elif at_zoom is not None:
                 self.min_zoom, self.max_zoom = int(at_zoom), int(at_zoom)
-        
+
         else:
             self.min_zoom, self.max_zoom = 0, float('inf')
 
@@ -301,7 +301,7 @@ class Layer:
         """ Return true if the requested zoom level is valid for this layer.
         """
         return self.min_zoom <= zoom and zoom <= self.max_zoom
-    
+
     def render(self, config, input_rgba, coord):
         """ Render this image layer.
 
@@ -309,9 +309,9 @@ class Layer:
             return an output image with the contents of this image layer.
         """
         has_layer, has_color, has_mask = False, False, False
-        
+
         output_rgba = [chan.copy() for chan in input_rgba]
-    
+
         if self.layername:
             layer = config.layers[self.layername]
             mime, body = TileStache.getTile(layer, coord, 'png')
@@ -319,7 +319,7 @@ class Layer:
             layer_rgba = _img2rgba(layer_img)
 
             has_layer = True
-        
+
         if self.maskname:
             layer = config.layers[self.maskname]
             mime, body = TileStache.getTile(layer, coord, 'png')
@@ -336,10 +336,10 @@ class Layer:
 
         if has_layer:
             layer_rgba = apply_adjustments(layer_rgba, self.adjustments)
-        
+
         if has_layer and has_color and has_mask:
             raise KnownUnknown("You can't specify src, color and mask together in a Composite Layer: %s, %s, %s" % (repr(self.layername), repr(self.colorname), repr(self.maskname)))
-        
+
         elif has_layer and has_color:
             # color first, then layer
             output_rgba = blend_images(output_rgba, color_rgba[:3], color_rgba[3], self.opacity, self.blendmode)
@@ -352,10 +352,10 @@ class Layer:
 
         elif has_color and has_mask:
             output_rgba = blend_images(output_rgba, color_rgba[:3], mask_chan, self.opacity, self.blendmode)
-        
+
         elif has_layer:
             output_rgba = blend_images(output_rgba, layer_rgba[:3], layer_rgba[3], self.opacity, self.blendmode)
-        
+
         elif has_color:
             output_rgba = blend_images(output_rgba, color_rgba[:3], color_rgba[3], self.opacity, self.blendmode)
 
@@ -375,9 +375,9 @@ class Stack:
     """
     def __init__(self, layers):
         """ A new image stack.
-        
+
             Argument:
-            
+
               layers:
                 List of Layer instances.
         """
@@ -387,7 +387,7 @@ class Stack:
         """
         """
         return True
-    
+
     def render(self, config, input_rgba, coord):
         """ Render this image stack.
 
@@ -396,7 +396,7 @@ class Stack:
             this stack pasted on in turn.
         """
         stack_rgba = [numpy.zeros(chan.shape, chan.dtype) for chan in input_rgba]
-        
+
         for layer in self.layers:
             try:
                 if layer.in_zoom(coord.zoom):
@@ -412,9 +412,9 @@ class Stack:
 
 def make_color(color):
     """ Convert colors expressed as HTML-style RGB(A) strings to tuples.
-        
+
         Returns four-element RGBA tuple, e.g. (0xFF, 0x99, 0x00, 0xFF).
-    
+
         Examples:
           white: "#ffffff", "#fff", "#ffff", "#ffffffff"
           black: "#000000", "#000", "#000f", "#000000ff"
@@ -436,7 +436,7 @@ def make_color(color):
 
     elif len(color) == 5:
         color = ''.join([color[i] for i in (0, 1, 1, 2, 2, 3, 3, 4, 4)])
-    
+
     try:
         r = int(color[1:3], 16)
         g = int(color[3:5], 16)
@@ -451,13 +451,13 @@ def make_color(color):
 def _arr2img(ar):
     """ Convert Numeric array to PIL Image.
     """
-    return Image.fromstring('L', (ar.shape[1], ar.shape[0]), ar.astype(numpy.ubyte).tostring())
+    return Image.frombytes('L', (ar.shape[1], ar.shape[0]), ar.astype(numpy.ubyte).tostring())
 
 def _img2arr(im):
     """ Convert PIL Image to Numeric array.
     """
     assert im.mode == 'L'
-    return numpy.reshape(numpy.fromstring(im.tostring(), numpy.ubyte), (im.size[1], im.size[0]))
+    return numpy.reshape(numpy.fromstring(im.tobytes(), numpy.ubyte), (im.size[1], im.size[0]))
 
 def _rgba2img(rgba):
     """ Convert four Numeric array objects to PIL Image.
@@ -473,15 +473,15 @@ def _img2rgba(im):
 
 def apply_adjustments(rgba, adjustments):
     """ Apply image adjustments one by one and return a modified image.
-    
+
         Working adjustments:
-        
+
           threshold:
             Calls apply_threshold_adjustment()
-        
+
           curves:
             Calls apply_curves_adjustment()
-        
+
           curves2:
             Calls apply_curves2_adjustment()
     """
@@ -493,16 +493,16 @@ def apply_adjustments(rgba, adjustments):
 
         if name == 'threshold':
             rgba = apply_threshold_adjustment(rgba, *args)
-        
+
         elif name == 'curves':
             rgba = apply_curves_adjustment(rgba, *args)
-        
+
         elif name == 'curves2':
             rgba = apply_curves2_adjustment(rgba, *args)
-        
+
         else:
             raise KnownUnknown('Unrecognized composite adjustment: "%s" with args %s' % (name, repr(args)))
-    
+
     return rgba
 
 def apply_threshold_adjustment(rgba, red_value, green_value=None, blue_value=None):
@@ -514,30 +514,30 @@ def apply_threshold_adjustment(rgba, red_value, green_value=None, blue_value=Non
 
     # channels
     red, green, blue, alpha = rgba
-    
+
     # knowns are given in 0-255 range, need to be converted to floats
     red_value, green_value, blue_value = red_value / 255.0, green_value / 255.0, blue_value / 255.0
-    
+
     red[red > red_value] = 1
     red[red <= red_value] = 0
-    
+
     green[green > green_value] = 1
     green[green <= green_value] = 0
-    
+
     blue[blue > blue_value] = 1
     blue[blue <= blue_value] = 0
-    
+
     return red, green, blue, alpha
 
 def apply_curves_adjustment(rgba, black_grey_white):
     """ Adjustment inspired by Photoshop "Curves" feature.
-    
+
         Arguments are three integers that are intended to be mapped to black,
         grey, and white outputs. Curves2 offers more flexibility, see
         apply_curves2_adjustment().
-        
+
         Darken a light image by pushing light grey to 50% grey, 0xCC to 0x80:
-    
+
           [
             "curves",
             [0, 204, 255]
@@ -546,54 +546,54 @@ def apply_curves_adjustment(rgba, black_grey_white):
     # channels
     red, green, blue, alpha = rgba
     black, grey, white = black_grey_white
-    
+
     # coefficients
     a, b, c = [sympy.Symbol(n) for n in 'abc']
-    
+
     # knowns are given in 0-255 range, need to be converted to floats
     black, grey, white = black / 255.0, grey / 255.0, white / 255.0
-    
+
     # black, gray, white
     eqs = [a * black**2 + b * black + c - 0.0,
            a *  grey**2 + b *  grey + c - 0.5,
            a * white**2 + b * white + c - 1.0]
-    
+
     co = sympy.solve(eqs, a, b, c)
-    
+
     # arrays for each coefficient
     a, b, c = [float(co[n]) * numpy.ones(red.shape, numpy.float32) for n in (a, b, c)]
-    
+
     # arithmetic
     red   = numpy.clip(a * red**2   + b * red   + c, 0, 1)
     green = numpy.clip(a * green**2 + b * green + c, 0, 1)
     blue  = numpy.clip(a * blue**2  + b * blue  + c, 0, 1)
-    
+
     return red, green, blue, alpha
 
 def apply_curves2_adjustment(rgba, map_red, map_green=None, map_blue=None):
     """ Adjustment inspired by Photoshop "Curves" feature.
-    
+
         Arguments are given in the form of three value mappings, typically
         mapping black, grey and white input and output values. One argument
         indicates an effect applicable to all channels, three arguments apply
         effects to each channel separately.
-    
+
         Simple monochrome inversion:
-    
+
           [
             "curves2",
             [[0, 255], [128, 128], [255, 0]]
           ]
-    
+
         Darken a light image by pushing light grey down by 50%, 0x99 to 0x66:
-    
+
           [
             "curves2",
             [[0, 255], [153, 102], [255, 0]]
           ]
-    
-        Shaded hills, with Imhof-style purple-blue shadows and warm highlights: 
-        
+
+        Shaded hills, with Imhof-style purple-blue shadows and warm highlights:
+
           [
             "curves2",
             [[0, 22], [128, 128], [255, 255]],
@@ -608,43 +608,43 @@ def apply_curves2_adjustment(rgba, map_red, map_green=None, map_blue=None):
     # channels
     red, green, blue, alpha = rgba
     out = []
-    
+
     for (chan, input) in ((red, map_red), (green, map_green), (blue, map_blue)):
         # coefficients
         a, b, c = [sympy.Symbol(n) for n in 'abc']
-        
+
         # parameters given in 0-255 range, need to be converted to floats
         (in_1, out_1), (in_2, out_2), (in_3, out_3) \
             = [(in_ / 255.0, out_ / 255.0) for (in_, out_) in input]
-        
+
         # quadratic function
         eqs = [a * in_1**2 + b * in_1 + c - out_1,
                a * in_2**2 + b * in_2 + c - out_2,
                a * in_3**2 + b * in_3 + c - out_3]
-        
+
         co = sympy.solve(eqs, a, b, c)
-        
+
         # arrays for each coefficient
         a, b, c = [float(co[n]) * numpy.ones(chan.shape, numpy.float32) for n in (a, b, c)]
-        
+
         # arithmetic
         out.append(numpy.clip(a * chan**2 + b * chan + c, 0, 1))
-    
+
     return out + [alpha]
 
 def blend_images(bottom_rgba, top_rgb, mask_chan, opacity, blendmode):
     """ Blend images using a given mask, opacity, and blend mode.
-    
+
         Working blend modes:
         None for plain pass-through, "screen", "multiply", "linear light", and "hard light".
     """
     if opacity == 0 or not mask_chan.any():
         # no-op for zero opacity or empty mask
         return [numpy.copy(chan) for chan in bottom_rgba]
-    
+
     # prepare unitialized output arrays
     output_rgba = [numpy.empty_like(chan) for chan in bottom_rgba]
-    
+
     if not blendmode:
         # plain old paste
         output_rgba[:3] = [numpy.copy(chan) for chan in top_rgb]
@@ -659,17 +659,17 @@ def blend_images(bottom_rgba, top_rgb, mask_chan, opacity, blendmode):
             for c in (0, 1, 2):
                 blend_function = blend_functions[blendmode]
                 output_rgba[c] = blend_function(bottom_rgba[c], top_rgb[c])
-        
+
         else:
             raise KnownUnknown('Unrecognized blend mode: "%s"' % blendmode)
-    
+
     # comined effective mask channel
     if opacity < 1:
         mask_chan = mask_chan * opacity
 
     # pixels from mask that aren't full-white
     gr = mask_chan < 1
-    
+
     if gr.any():
         # we have some shades of gray to take care of
         for c in (0, 1, 2):
@@ -677,18 +677,18 @@ def blend_images(bottom_rgba, top_rgb, mask_chan, opacity, blendmode):
             # Math borrowed from Wikipedia; C0 is the variable alpha_denom:
             # http://en.wikipedia.org/wiki/Alpha_compositing#Analytical_derivation_of_the_over_operator
             #
-            
+
             alpha_denom = 1 - (1 - mask_chan) * (1 - bottom_rgba[3])
             nz = alpha_denom > 0 # non-zero alpha denominator
-            
+
             alpha_ratio = mask_chan[nz] / alpha_denom[nz]
-            
+
             output_rgba[c][nz] = output_rgba[c][nz] * alpha_ratio \
                                + bottom_rgba[c][nz] * (1 - alpha_ratio)
-            
+
             # let the zeros perish
             output_rgba[c][~nz] = 0
-    
+
     # output mask is the screen of the existing and overlaid alphas
     output_rgba[3] = blend_channels_screen(bottom_rgba[3], mask_chan)
 
@@ -696,75 +696,75 @@ def blend_images(bottom_rgba, top_rgb, mask_chan, opacity, blendmode):
 
 def blend_channels_screen(bottom_chan, top_chan):
     """ Return combination of bottom and top channels.
-    
+
         Math from http://illusions.hu/effectwiki/doku.php?id=screen_blending
     """
     return 1 - (1 - bottom_chan[:,:]) * (1 - top_chan[:,:])
 
 def blend_channels_multiply(bottom_chan, top_chan):
     """ Return combination of bottom and top channels.
-    
+
         Math from http://illusions.hu/effectwiki/doku.php?id=multiply_blending
     """
     return bottom_chan[:,:] * top_chan[:,:]
 
 def blend_channels_linear_light(bottom_chan, top_chan):
     """ Return combination of bottom and top channels.
-    
+
         Math from http://illusions.hu/effectwiki/doku.php?id=linear_light_blending
     """
     return numpy.clip(bottom_chan[:,:] + 2 * top_chan[:,:] - 1, 0, 1)
 
 def blend_channels_hard_light(bottom_chan, top_chan):
     """ Return combination of bottom and top channels.
-    
+
         Math from http://illusions.hu/effectwiki/doku.php?id=hard_light_blending
     """
     # different pixel subsets for dark and light parts of overlay
     dk, lt = top_chan < .5, top_chan >= .5
-    
+
     output_chan = numpy.empty(bottom_chan.shape, bottom_chan.dtype)
     output_chan[dk] = 2 * bottom_chan[dk] * top_chan[dk]
     output_chan[lt] = 1 - 2 * (1 - bottom_chan[lt]) * (1 - top_chan[lt])
-    
+
     return output_chan
 
 def makeColor(color):
     """ An old name for the make_color function, deprecated for the next version.
     """
     return make_color(color)
-    
+
 def makeLayer(element):
     """ Build a Layer object from an XML element, deprecated for the next version.
     """
     kwargs = {}
-    
+
     if element.hasAttribute('src'):
         kwargs['layername'] = element.getAttribute('src')
 
     if element.hasAttribute('color'):
         kwargs['colorname'] = element.getAttribute('color')
-    
+
     for child in element.childNodes:
         if child.nodeType == child.ELEMENT_NODE:
             if child.tagName == 'mask' and child.hasAttribute('src'):
                 kwargs['maskname'] = child.getAttribute('src')
 
     print >> sys.stderr, 'Making a layer from', kwargs
-    
+
     return Layer(**kwargs)
 
 def makeStack(element):
     """ Build a Stack object from an XML element, deprecated for the next version.
     """
     layers = []
-    
+
     for child in element.childNodes:
         if child.nodeType == child.ELEMENT_NODE:
             if child.tagName == 'stack':
                 stack = makeStack(child)
                 layers.append(stack)
-            
+
             elif child.tagName == 'layer':
                 layer = makeLayer(child)
                 layers.append(layer)
@@ -779,27 +779,27 @@ def makeStack(element):
 if __name__ == '__main__':
 
     import unittest
-    
+
     import TileStache.Core
     import TileStache.Caches
     import TileStache.Geography
     import TileStache.Config
     import ModestMaps.Core
-    
+
     class SizelessImage:
         """ Wrap an image without wrapping the size() method, for Layer.render().
         """
         def __init__(self, img):
             self.img = img
-        
+
         def save(self, out, format):
             self.img.save(out, format)
-    
+
     class TinyBitmap:
         """ A minimal provider that only returns 3x3 bitmaps from strings.
         """
         def __init__(self, string):
-            self.img = Image.fromstring('RGBA', (3, 3), string)
+            self.img = Image.frombytes('RGBA', (3, 3), string)
 
         def renderTile(self, *args, **kwargs):
             return SizelessImage(self.img)
@@ -823,7 +823,7 @@ if __name__ == '__main__':
         layer.provider = Provider(layer, stack=stack)
 
         return layer
-    
+
     class ColorTests(unittest.TestCase):
         """
         """
@@ -847,7 +847,7 @@ if __name__ == '__main__':
 
             assert make_color('#f908') == (0xFF, 0x99, 0x00, 0x88), 'transparent orange'
             assert make_color('#ff990088') == (0xFF, 0x99, 0x00, 0x88), 'transparent orange again'
-        
+
         def testErrors(self):
 
             # it has to be a string
@@ -855,32 +855,32 @@ if __name__ == '__main__':
             self.assertRaises(KnownUnknown, make_color, None)
             self.assertRaises(KnownUnknown, make_color, 1337)
             self.assertRaises(KnownUnknown, make_color, [93])
-            
+
             # it has to start with a hash
             self.assertRaises(KnownUnknown, make_color, 'hello')
-            
+
             # it has to have 3, 4, 6 or 7 hex chars
             self.assertRaises(KnownUnknown, make_color, '#00')
             self.assertRaises(KnownUnknown, make_color, '#00000')
             self.assertRaises(KnownUnknown, make_color, '#0000000')
             self.assertRaises(KnownUnknown, make_color, '#000000000')
-            
+
             # they have to actually hex chars
             self.assertRaises(KnownUnknown, make_color, '#foo')
             self.assertRaises(KnownUnknown, make_color, '#bear')
             self.assertRaises(KnownUnknown, make_color, '#monkey')
             self.assertRaises(KnownUnknown, make_color, '#dedboeuf')
-    
+
     class CompositeTests(unittest.TestCase):
         """
         """
         def setUp(self):
-    
+
             cache = TileStache.Caches.Test()
             self.config = TileStache.Config.Configuration(cache, '.')
-            
+
             # Sort of a sw/ne diagonal street, with a top-left corner halo:
-            # 
+            #
             # +------+   +------+   +------+   +------+   +------+
             # |\\\\\\|   |++++--|   |  ////|   |    ''|   |\\//''|
             # |\\\\\\| + |++++--| + |//////| + |  ''  | > |//''\\|
@@ -891,7 +891,7 @@ if __name__ == '__main__':
             # Just trust the tests.
             #
             _fff, _ccc, _999, _000, _nil = '\xFF\xFF\xFF\xFF', '\xCC\xCC\xCC\xFF', '\x99\x99\x99\xFF', '\x00\x00\x00\xFF', '\x00\x00\x00\x00'
-            
+
             self.config.layers = \
             {
                 'base':     tinybitmap_layer(self.config, _ccc * 9),
@@ -899,11 +899,11 @@ if __name__ == '__main__':
                 'outlines': tinybitmap_layer(self.config, _nil + (_999 * 7) + _nil),
                 'streets':  tinybitmap_layer(self.config, _nil + _nil + _fff + _nil + _fff + _nil + _fff + _nil + _nil)
             }
-            
+
             self.start_img = Image.new('RGBA', (3, 3), (0x00, 0x00, 0x00, 0x00))
-        
+
         def test0(self):
-    
+
             stack = \
                 [
                     {"src": "base"},
@@ -912,10 +912,10 @@ if __name__ == '__main__':
                         {"src": "streets"}
                     ]
                 ]
-            
+
             layer = minimal_stack_layer(self.config, stack)
             img = layer.provider.renderTile(3, 3, None, ModestMaps.Core.Coordinate(0, 0, 0))
-            
+
             assert img.getpixel((0, 0)) == (0xCC, 0xCC, 0xCC, 0xFF), 'top left pixel'
             assert img.getpixel((1, 0)) == (0x99, 0x99, 0x99, 0xFF), 'top center pixel'
             assert img.getpixel((2, 0)) == (0xFF, 0xFF, 0xFF, 0xFF), 'top right pixel'
@@ -925,9 +925,9 @@ if __name__ == '__main__':
             assert img.getpixel((0, 2)) == (0xFF, 0xFF, 0xFF, 0xFF), 'bottom left pixel'
             assert img.getpixel((1, 2)) == (0x99, 0x99, 0x99, 0xFF), 'bottom center pixel'
             assert img.getpixel((2, 2)) == (0xCC, 0xCC, 0xCC, 0xFF), 'bottom right pixel'
-        
+
         def test1(self):
-    
+
             stack = \
                 [
                     {"src": "base"},
@@ -936,10 +936,10 @@ if __name__ == '__main__':
                         {"src": "streets"}
                     ]
                 ]
-            
+
             layer = minimal_stack_layer(self.config, stack)
             img = layer.provider.renderTile(3, 3, None, ModestMaps.Core.Coordinate(0, 0, 0))
-            
+
             assert img.getpixel((0, 0)) == (0xCC, 0xCC, 0xCC, 0xFF), 'top left pixel'
             assert img.getpixel((1, 0)) == (0x99, 0x99, 0x99, 0xFF), 'top center pixel'
             assert img.getpixel((2, 0)) == (0xFF, 0xFF, 0xFF, 0xFF), 'top right pixel'
@@ -949,9 +949,9 @@ if __name__ == '__main__':
             assert img.getpixel((0, 2)) == (0xFF, 0xFF, 0xFF, 0xFF), 'bottom left pixel'
             assert img.getpixel((1, 2)) == (0xCC, 0xCC, 0xCC, 0xFF), 'bottom center pixel'
             assert img.getpixel((2, 2)) == (0xCC, 0xCC, 0xCC, 0xFF), 'bottom right pixel'
-        
+
         def test2(self):
-    
+
             stack = \
                 [
                     {"color": "#ccc"},
@@ -960,10 +960,10 @@ if __name__ == '__main__':
                         {"src": "streets"}
                     ]
                 ]
-            
+
             layer = minimal_stack_layer(self.config, stack)
             img = layer.provider.renderTile(3, 3, None, ModestMaps.Core.Coordinate(0, 0, 0))
-            
+
             assert img.getpixel((0, 0)) == (0xCC, 0xCC, 0xCC, 0xFF), 'top left pixel'
             assert img.getpixel((1, 0)) == (0x99, 0x99, 0x99, 0xFF), 'top center pixel'
             assert img.getpixel((2, 0)) == (0xFF, 0xFF, 0xFF, 0xFF), 'top right pixel'
@@ -973,9 +973,9 @@ if __name__ == '__main__':
             assert img.getpixel((0, 2)) == (0xFF, 0xFF, 0xFF, 0xFF), 'bottom left pixel'
             assert img.getpixel((1, 2)) == (0xCC, 0xCC, 0xCC, 0xFF), 'bottom center pixel'
             assert img.getpixel((2, 2)) == (0xCC, 0xCC, 0xCC, 0xFF), 'bottom right pixel'
-        
+
         def test3(self):
-            
+
             stack = \
                 [
                     {"color": "#ccc"},
@@ -984,10 +984,10 @@ if __name__ == '__main__':
                         {"src": "streets"}
                     ]
                 ]
-            
+
             layer = minimal_stack_layer(self.config, stack)
             img = layer.provider.renderTile(3, 3, None, ModestMaps.Core.Coordinate(0, 0, 0))
-            
+
             assert img.getpixel((0, 0)) == (0x99, 0x99, 0x99, 0xFF), 'top left pixel'
             assert img.getpixel((1, 0)) == (0x99, 0x99, 0x99, 0xFF), 'top center pixel'
             assert img.getpixel((2, 0)) == (0xFF, 0xFF, 0xFF, 0xFF), 'top right pixel'
@@ -997,9 +997,9 @@ if __name__ == '__main__':
             assert img.getpixel((0, 2)) == (0xFF, 0xFF, 0xFF, 0xFF), 'bottom left pixel'
             assert img.getpixel((1, 2)) == (0xCC, 0xCC, 0xCC, 0xFF), 'bottom center pixel'
             assert img.getpixel((2, 2)) == (0xCC, 0xCC, 0xCC, 0xFF), 'bottom right pixel'
-        
+
         def test4(self):
-    
+
             stack = \
                 [
                     [
@@ -1007,10 +1007,10 @@ if __name__ == '__main__':
                         {"src": "streets"}
                     ]
                 ]
-            
+
             layer = minimal_stack_layer(self.config, stack)
             img = layer.provider.renderTile(3, 3, None, ModestMaps.Core.Coordinate(0, 0, 0))
-            
+
             assert img.getpixel((0, 0)) == (0x99, 0x99, 0x99, 0xFF), 'top left pixel'
             assert img.getpixel((1, 0)) == (0x99, 0x99, 0x99, 0xFF), 'top center pixel'
             assert img.getpixel((2, 0)) == (0xFF, 0xFF, 0xFF, 0xFF), 'top right pixel'
@@ -1020,24 +1020,24 @@ if __name__ == '__main__':
             assert img.getpixel((0, 2)) == (0xFF, 0xFF, 0xFF, 0xFF), 'bottom left pixel'
             assert img.getpixel((1, 2)) == (0x00, 0x00, 0x00, 0x00), 'bottom center pixel'
             assert img.getpixel((2, 2)) == (0x00, 0x00, 0x00, 0x00), 'bottom right pixel'
-        
+
         def test5(self):
 
             stack = {"src": "streets", "color": "#999", "mask": "halos"}
             layer = minimal_stack_layer(self.config, stack)
-            
+
             # it's an error to specify scr, color, and mask all together
             self.assertRaises(KnownUnknown, layer.provider.renderTile, 3, 3, None, ModestMaps.Core.Coordinate(0, 0, 0))
 
             stack = {"mask": "halos"}
             layer = minimal_stack_layer(self.config, stack)
-            
+
             # it's also an error to specify just a mask
             self.assertRaises(KnownUnknown, layer.provider.renderTile, 3, 3, None, ModestMaps.Core.Coordinate(0, 0, 0))
 
             stack = {}
             layer = minimal_stack_layer(self.config, stack)
-            
+
             # an empty stack is not so great
             self.assertRaises(KnownUnknown, layer.provider.renderTile, 3, 3, None, ModestMaps.Core.Coordinate(0, 0, 0))
 
@@ -1045,36 +1045,36 @@ if __name__ == '__main__':
         """
         """
         def setUp(self):
-    
+
             cache = TileStache.Caches.Test()
             self.config = TileStache.Config.Configuration(cache, '.')
-            
+
             _808f = '\x80\x80\x80\xFF'
             _fff0, _fff8, _ffff = '\xFF\xFF\xFF\x00', '\xFF\xFF\xFF\x80', '\xFF\xFF\xFF\xFF'
             _0000, _0008, _000f = '\x00\x00\x00\x00', '\x00\x00\x00\x80', '\x00\x00\x00\xFF'
-            
+
             self.config.layers = \
             {
                 # 50% gray all over
                 'gray':       tinybitmap_layer(self.config, _808f * 9),
-                
+
                 # nothing anywhere
                 'nothing':    tinybitmap_layer(self.config, _0000 * 9),
-                
+
                 # opaque horizontal gradient, black to white
                 'h gradient': tinybitmap_layer(self.config, (_000f + _808f + _ffff) * 3),
-                
+
                 # transparent white at top to opaque white at bottom
                 'white wipe': tinybitmap_layer(self.config, _fff0 * 3 + _fff8 * 3 + _ffff * 3),
-                
+
                 # transparent black at top to opaque black at bottom
                 'black wipe': tinybitmap_layer(self.config, _0000 * 3 + _0008 * 3 + _000f * 3)
             }
-            
+
             self.start_img = Image.new('RGBA', (3, 3), (0x00, 0x00, 0x00, 0x00))
-        
+
         def test0(self):
-            
+
             stack = \
                 [
                     [
@@ -1082,10 +1082,10 @@ if __name__ == '__main__':
                         {"src": "white wipe"}
                     ]
                 ]
-            
+
             layer = minimal_stack_layer(self.config, stack)
             img = layer.provider.renderTile(3, 3, None, ModestMaps.Core.Coordinate(0, 0, 0))
-            
+
             assert img.getpixel((0, 0)) == (0x80, 0x80, 0x80, 0xFF), 'top left pixel'
             assert img.getpixel((1, 0)) == (0x80, 0x80, 0x80, 0xFF), 'top center pixel'
             assert img.getpixel((2, 0)) == (0x80, 0x80, 0x80, 0xFF), 'top right pixel'
@@ -1095,9 +1095,9 @@ if __name__ == '__main__':
             assert img.getpixel((0, 2)) == (0xFF, 0xFF, 0xFF, 0xFF), 'bottom left pixel'
             assert img.getpixel((1, 2)) == (0xFF, 0xFF, 0xFF, 0xFF), 'bottom center pixel'
             assert img.getpixel((2, 2)) == (0xFF, 0xFF, 0xFF, 0xFF), 'bottom right pixel'
-        
+
         def test1(self):
-            
+
             stack = \
                 [
                     [
@@ -1105,10 +1105,10 @@ if __name__ == '__main__':
                         {"src": "black wipe"}
                     ]
                 ]
-            
+
             layer = minimal_stack_layer(self.config, stack)
             img = layer.provider.renderTile(3, 3, None, ModestMaps.Core.Coordinate(0, 0, 0))
-            
+
             assert img.getpixel((0, 0)) == (0x80, 0x80, 0x80, 0xFF), 'top left pixel'
             assert img.getpixel((1, 0)) == (0x80, 0x80, 0x80, 0xFF), 'top center pixel'
             assert img.getpixel((2, 0)) == (0x80, 0x80, 0x80, 0xFF), 'top right pixel'
@@ -1118,9 +1118,9 @@ if __name__ == '__main__':
             assert img.getpixel((0, 2)) == (0x00, 0x00, 0x00, 0xFF), 'bottom left pixel'
             assert img.getpixel((1, 2)) == (0x00, 0x00, 0x00, 0xFF), 'bottom center pixel'
             assert img.getpixel((2, 2)) == (0x00, 0x00, 0x00, 0xFF), 'bottom right pixel'
-        
+
         def test2(self):
-        
+
             stack = \
                 [
                     [
@@ -1128,10 +1128,10 @@ if __name__ == '__main__':
                         {"src": "white wipe", "mask": "h gradient"}
                     ]
                 ]
-            
+
             layer = minimal_stack_layer(self.config, stack)
             img = layer.provider.renderTile(3, 3, None, ModestMaps.Core.Coordinate(0, 0, 0))
-            
+
             assert img.getpixel((0, 0)) == (0x80, 0x80, 0x80, 0xFF), 'top left pixel'
             assert img.getpixel((1, 0)) == (0x80, 0x80, 0x80, 0xFF), 'top center pixel'
             assert img.getpixel((2, 0)) == (0x80, 0x80, 0x80, 0xFF), 'top right pixel'
@@ -1141,9 +1141,9 @@ if __name__ == '__main__':
             assert img.getpixel((0, 2)) == (0x80, 0x80, 0x80, 0xFF), 'bottom left pixel'
             assert img.getpixel((1, 2)) == (0xC0, 0xC0, 0xC0, 0xFF), 'bottom center pixel'
             assert img.getpixel((2, 2)) == (0xFF, 0xFF, 0xFF, 0xFF), 'bottom right pixel'
-        
+
         def test3(self):
-            
+
             stack = \
                 [
                     [
@@ -1151,10 +1151,10 @@ if __name__ == '__main__':
                         {"src": "black wipe", "mask": "h gradient"}
                     ]
                 ]
-            
+
             layer = minimal_stack_layer(self.config, stack)
             img = layer.provider.renderTile(3, 3, None, ModestMaps.Core.Coordinate(0, 0, 0))
-            
+
             assert img.getpixel((0, 0)) == (0x80, 0x80, 0x80, 0xFF), 'top left pixel'
             assert img.getpixel((1, 0)) == (0x80, 0x80, 0x80, 0xFF), 'top center pixel'
             assert img.getpixel((2, 0)) == (0x80, 0x80, 0x80, 0xFF), 'top right pixel'
@@ -1164,9 +1164,9 @@ if __name__ == '__main__':
             assert img.getpixel((0, 2)) == (0x80, 0x80, 0x80, 0xFF), 'bottom left pixel'
             assert img.getpixel((1, 2)) == (0x40, 0x40, 0x40, 0xFF), 'bottom center pixel'
             assert img.getpixel((2, 2)) == (0x00, 0x00, 0x00, 0xFF), 'bottom right pixel'
-        
+
         def test4(self):
-            
+
             stack = \
                 [
                     [
@@ -1174,10 +1174,10 @@ if __name__ == '__main__':
                         {"src": "white wipe"}
                     ]
                 ]
-            
+
             layer = minimal_stack_layer(self.config, stack)
             img = layer.provider.renderTile(3, 3, None, ModestMaps.Core.Coordinate(0, 0, 0))
-            
+
             assert img.getpixel((0, 0)) == (0x00, 0x00, 0x00, 0x00), 'top left pixel'
             assert img.getpixel((1, 0)) == (0x00, 0x00, 0x00, 0x00), 'top center pixel'
             assert img.getpixel((2, 0)) == (0x00, 0x00, 0x00, 0x00), 'top right pixel'
@@ -1187,5 +1187,5 @@ if __name__ == '__main__':
             assert img.getpixel((0, 2)) == (0xFF, 0xFF, 0xFF, 0xFF), 'bottom left pixel'
             assert img.getpixel((1, 2)) == (0xFF, 0xFF, 0xFF, 0xFF), 'bottom center pixel'
             assert img.getpixel((2, 2)) == (0xFF, 0xFF, 0xFF, 0xFF), 'bottom right pixel'
-    
+
     unittest.main()
diff --git a/TileStache/Goodies/Providers/GDAL.py b/TileStache/Goodies/Providers/GDAL.py
index b09afcd..f5d89ec 100644
--- a/TileStache/Goodies/Providers/GDAL.py
+++ b/TileStache/Goodies/Providers/GDAL.py
@@ -42,39 +42,39 @@ class Provider:
         """
         """
         self.layer = layer
-        
+
         fileurl = urljoin(layer.config.dirpath, filename)
         scheme, h, file_path, p, q, f = urlparse(fileurl)
-        
+
         if scheme not in ('', 'file'):
             raise Exception('GDAL file must be on the local filesystem, not: '+fileurl)
-        
+
         if resample not in resamplings:
             raise Exception('Resample must be "cubic", "linear", or "nearest", not: '+resample)
-        
+
         self.filename = file_path
         self.resample = resamplings[resample]
         self.maskband = maskband
-    
+
     def renderArea(self, width, height, srs, xmin, ymin, xmax, ymax, zoom):
         """
         """
         src_ds = gdal.Open(str(self.filename))
         driver = gdal.GetDriverByName('GTiff')
-        
+
         if src_ds.GetGCPs():
             src_ds.SetProjection(src_ds.GetGCPProjection())
-        
+
         grayscale_src = (src_ds.RasterCount == 1)
 
         try:
             # Prepare output gdal datasource -----------------------------------
-            
+
             area_ds = driver.Create('/vsimem/output', width, height, 3)
-            
+
             if area_ds is None:
                 raise Exception('uh oh.')
-            
+
             # If we are using a mask band, create a data set which possesses a 'NoData' value enabling us to create a
             # mask for validity.
             mask_ds = None
@@ -83,31 +83,31 @@ class Provider:
                 # efficient way to extract a single band from a dataset which doesn't risk attempting to copy the entire
                 # dataset.
                 mask_ds = driver.Create('/vsimem/alpha', width, height, src_ds.RasterCount, gdal.GDT_Float32)
-            
+
                 if mask_ds is None:
                     raise Exception('Failed to create dataset mask.')
 
                 [mask_ds.GetRasterBand(i).SetNoDataValue(float('nan')) for i in xrange(1, src_ds.RasterCount+1)]
-            
+
             merc = osr.SpatialReference()
             merc.ImportFromProj4(srs)
             area_ds.SetProjection(merc.ExportToWkt())
             if mask_ds is not None:
                 mask_ds.SetProjection(merc.ExportToWkt())
-            
+
             # note that 900913 points north and east
             x, y = xmin, ymax
             w, h = xmax - xmin, ymin - ymax
-            
+
             gtx = [x, w/width, 0, y, 0, h/height]
             area_ds.SetGeoTransform(gtx)
             if mask_ds is not None:
                 mask_ds.SetGeoTransform(gtx)
-            
+
             # Adjust resampling method -----------------------------------------
-            
+
             resample = self.resample
-            
+
             if resample == gdal.GRA_CubicSpline:
                 #
                 # I've found through testing that when ReprojectImage is used
@@ -118,35 +118,35 @@ class Provider:
                 xscale = area_ds.GetGeoTransform()[1] / src_ds.GetGeoTransform()[1]
                 yscale = area_ds.GetGeoTransform()[5] / src_ds.GetGeoTransform()[5]
                 diff = max(abs(xscale - 1), abs(yscale - 1))
-                
+
                 if diff < .001:
                     resample = gdal.GRA_Cubic
-            
+
             # Create rendered area ---------------------------------------------
-            
+
             src_sref = osr.SpatialReference()
             src_sref.ImportFromWkt(src_ds.GetProjection())
-            
+
             gdal.ReprojectImage(src_ds, area_ds, src_ds.GetProjection(), area_ds.GetProjection(), resample)
             if mask_ds is not None:
                 # Interpolating validity makes no sense and so we can use nearest neighbour resampling here no matter
                 # what is requested.
                 gdal.ReprojectImage(src_ds, mask_ds, src_ds.GetProjection(), mask_ds.GetProjection(), gdal.GRA_NearestNeighbour)
-            
+
             channel = grayscale_src and (1, 1, 1) or (1, 2, 3)
             r, g, b = [area_ds.GetRasterBand(i).ReadRaster(0, 0, width, height) for i in channel]
 
             if mask_ds is None:
                 data = ''.join([''.join(pixel) for pixel in zip(r, g, b)])
-                area = Image.fromstring('RGB', (width, height), data)
+                area = Image.frombytes('RGB', (width, height), data)
             else:
                 a = mask_ds.GetRasterBand(self.maskband).GetMaskBand().ReadRaster(0, 0, width, height)
                 data = ''.join([''.join(pixel) for pixel in zip(r, g, b, a)])
-                area = Image.fromstring('RGBA', (width, height), data)
+                area = Image.frombytes('RGBA', (width, height), data)
 
         finally:
             driver.Delete('/vsimem/output')
             if self.maskband > 0:
                 driver.Delete('/vsimem/alpha')
-        
+
         return area
diff --git a/TileStache/Mapnik.py b/TileStache/Mapnik.py
index 76ad779..2a6530c 100644
--- a/TileStache/Mapnik.py
+++ b/TileStache/Mapnik.py
@@ -40,7 +40,7 @@ except ImportError:
 
 if 'mapnik' in locals():
     _version = hasattr(mapnik, 'mapnik_version') and mapnik.mapnik_version() or 701
-    
+
     if _version >= 20000:
         Box2d = mapnik.Box2d
     else:
@@ -50,14 +50,14 @@ global_mapnik_lock = allocate_lock()
 
 class ImageProvider:
     """ Built-in Mapnik provider. Renders map images from Mapnik XML files.
-    
+
         This provider is identified by the name "mapnik" in the TileStache config.
-        
+
         Arguments:
-        
+
         - mapfile (required)
             Local file path to Mapnik XML file.
-    
+
         - fonts (optional)
             Local directory path to *.ttf font files.
 
@@ -65,41 +65,41 @@ class ImageProvider:
             Scale multiplier used for Mapnik rendering pipeline. Used for
             supporting retina resolution.
 
-            For more information about the scale factor, see: 
+            For more information about the scale factor, see:
             https://github.com/mapnik/mapnik/wiki/Scale-factor
-    
+
         More information on Mapnik and Mapnik XML:
         - http://mapnik.org
         - http://trac.mapnik.org/wiki/XMLGettingStarted
         - http://trac.mapnik.org/wiki/XMLConfigReference
     """
-    
+
     def __init__(self, layer, mapfile, fonts=None, scale_factor=None):
         """ Initialize Mapnik provider with layer and mapfile.
-            
+
             XML mapfile keyword arg comes from TileStache config,
             and is an absolute path by the time it gets here.
         """
         maphref = urljoin(layer.config.dirpath, mapfile)
         scheme, h, path, q, p, f = urlparse(maphref)
-        
+
         if scheme in ('file', ''):
             self.mapfile = path
         else:
             self.mapfile = maphref
-        
+
         self.layer = layer
         self.mapnik = None
-        
+
         engine = mapnik.FontEngine.instance()
-        
+
         if fonts:
             fontshref = urljoin(layer.config.dirpath, fonts)
             scheme, h, path, q, p, f = urlparse(fontshref)
-            
+
             if scheme not in ('file', ''):
                 raise Exception('Fonts from "%s" can\'t be used by Mapnik' % fontshref)
-        
+
             for font in glob(path.rstrip('/') + '/*.ttf'):
                 engine.register_font(str(font))
 
@@ -116,14 +116,14 @@ class ImageProvider:
 
         if 'scale factor' in config_dict:
             kwargs['scale_factor'] = int(config_dict['scale factor'])
-        
+
         return kwargs
-    
+
     def renderArea(self, width, height, srs, xmin, ymin, xmax, ymax, zoom):
         """
         """
         start_time = time()
-        
+
         #
         # Mapnik can behave strangely when run in threads, so place a lock on the instance.
         #
@@ -136,14 +136,14 @@ class ImageProvider:
                 self.mapnik.width = width
                 self.mapnik.height = height
                 self.mapnik.zoom_to_box(Box2d(xmin, ymin, xmax, ymax))
-            
+
                 img = mapnik.Image(width, height)
                 # Don't even call render with scale factor if it's not
                 # defined. Plays safe with older versions.
                 if self.scale_factor is None:
-                    mapnik.render(self.mapnik, img) 
+                    mapnik.render(self.mapnik, img)
                 else:
-                    mapnik.render(self.mapnik, img, self.scale_factor) 
+                    mapnik.render(self.mapnik, img, self.scale_factor)
             except:
                 self.mapnik = None
                 raise
@@ -151,28 +151,28 @@ class ImageProvider:
                 # always release the lock
                 global_mapnik_lock.release()
 
-        img = Image.fromstring('RGBA', (width, height), img.tostring())
-        
+        img = Image.frombytes('RGBA', (width, height), img.tobytes())
+
         logging.debug('TileStache.Mapnik.ImageProvider.renderArea() %dx%d in %.3f from %s', width, height, time() - start_time, self.mapfile)
-    
+
         return img
 
 class GridProvider:
     """ Built-in UTF Grid provider. Renders JSON raster objects from Mapnik.
-    
+
         This provider is identified by the name "mapnik grid" in the
         Tilestache config, and uses Mapnik 2.0 (and above) to generate
         JSON UTF grid responses.
-        
+
         Sample configuration for a single grid layer:
 
           "provider":
           {
             "name": "mapnik grid",
-            "mapfile": "world_merc.xml", 
+            "mapfile": "world_merc.xml",
             "fields": ["NAME", "POP2005"]
           }
-    
+
         Sample configuration for multiple overlaid grid layers:
 
           "provider":
@@ -187,41 +187,41 @@ class GridProvider:
               [0, []]
             ]
           }
-    
+
         Arguments:
-        
+
         - mapfile (required)
           Local file path to Mapnik XML file.
-        
+
         - fields (optional)
           Array of field names to return in the response, defaults to all.
           An empty list will return no field names, while a value of null is
           equivalent to all.
-        
+
         - layer_index (optional)
           Which layer from the mapfile to render, defaults to 0 (first layer).
-        
+
         - layers (optional)
           Ordered list of (layer_index, fields) to combine; if provided
           layers overrides both layer_index and fields arguments.
-          An empty fields list will return no field names, while a value of null 
+          An empty fields list will return no field names, while a value of null
           is equivalent to all fields.
- 
+
         - scale (optional)
           Scale factor of output raster, defaults to 4 (64x64).
-        
+
         - layer_id_key (optional)
           If set, each item in the 'data' property will have its source mapnik
           layer name added, keyed by this value. Useful for distingushing
           between data items.
-        
+
         Information and examples for UTF Grid:
         - https://github.com/mapbox/utfgrid-spec/blob/master/1.2/utfgrid.md
         - http://mapbox.github.com/wax/interaction-leaf.html
     """
     def __init__(self, layer, mapfile, fields=None, layers=None, layer_index=0, scale=4, layer_id_key=None):
         """ Initialize Mapnik grid provider with layer and mapfile.
-            
+
             XML mapfile keyword arg comes from TileStache config,
             and is an absolute path by the time it gets here.
         """
@@ -238,7 +238,7 @@ class GridProvider:
 
         self.scale = scale
         self.layer_id_key = layer_id_key
-        
+
         if layers:
             self.layers = layers
         else:
@@ -253,14 +253,14 @@ class GridProvider:
         for key in ('fields', 'layers', 'layer_index', 'scale', 'layer_id_key'):
             if key in config_dict:
                 kwargs[key] = config_dict[key]
-        
+
         return kwargs
-    
+
     def renderArea(self, width, height, srs, xmin, ymin, xmax, ymax, zoom):
         """
         """
         start_time = time()
-        
+
         #
         # Mapnik can behave strangely when run in threads, so place a lock on the instance.
         #
@@ -273,33 +273,33 @@ class GridProvider:
                 self.mapnik.width = width
                 self.mapnik.height = height
                 self.mapnik.zoom_to_box(Box2d(xmin, ymin, xmax, ymax))
-            
+
                 if self.layer_id_key is not None:
                     grids = []
-    
+
                     for (index, fields) in self.layers:
                         datasource = self.mapnik.layers[index].datasource
                         fields = (type(fields) is list) and map(str, fields) or datasource.fields()
-                    
+
                         grid = mapnik.render_grid(self.mapnik, index, resolution=self.scale, fields=fields)
-    
+
                         for key in grid['data']:
                             grid['data'][key][self.layer_id_key] = self.mapnik.layers[index].name
-    
+
                         grids.append(grid)
-        
+
                     # global_mapnik_lock.release()
                     outgrid = reduce(merge_grids, grids)
-           
+
                 else:
                     grid = mapnik.Grid(width, height)
-    
+
                     for (index, fields) in self.layers:
                         datasource = self.mapnik.layers[index].datasource
                         fields = (type(fields) is list) and map(str, fields) or datasource.fields()
-    
+
                         mapnik.render_layer(self.mapnik, grid, layer=index, fields=fields)
-    
+
                     # global_mapnik_lock.release()
                     outgrid = grid.encode('utf', resolution=self.scale, features=True)
             except:
@@ -337,7 +337,7 @@ class SaveableResponse:
 
         bytes = json.dumps(self.content, ensure_ascii=False).encode('utf-8')
         out.write(bytes)
-    
+
     def crop(self, bbox):
         """ Return a cropped grid response.
         """
@@ -345,7 +345,7 @@ class SaveableResponse:
 
         keys, data = self.content['keys'], self.content.get('data', None)
         grid = [row[minchar:maxchar] for row in self.content['grid'][minrow:maxrow]]
-        
+
         cropped = dict(keys=keys, data=data, grid=grid)
         return SaveableResponse(cropped, self.scale)
 
@@ -357,42 +357,42 @@ def merge_grids(grid1, grid2):
     #
 
     keygen, outkeys, outdata = count(1), [], dict()
-    
+
     for ingrid in [grid1, grid2]:
         for (index, key) in enumerate(ingrid['keys']):
             if key not in ingrid['data']:
                 outkeys.append('')
                 continue
-        
+
             outkey = '%d' % keygen.next()
             outkeys.append(outkey)
-    
+
             datum = ingrid['data'][key]
             outdata[outkey] = datum
-    
+
     #
     # Merge the two grids, one on top of the other.
     #
-    
+
     offset, outgrid = len(grid1['keys']), []
-    
+
     def newchar(char1, char2):
         """ Return a new encoded character based on two inputs.
         """
         id1, id2 = decode_char(char1), decode_char(char2)
-        
+
         if grid2['keys'][id2] == '':
             # transparent pixel, use the bottom character
             return encode_id(id1)
-        
+
         else:
             # opaque pixel, use the top character
             return encode_id(id2 + offset)
-    
+
     for (row1, row2) in zip(grid1['grid'], grid2['grid']):
         outrow = [newchar(c1, c2) for (c1, c2) in zip(row1, row2)]
         outgrid.append(''.join(outrow))
-    
+
     return dict(keys=outkeys, data=outdata, grid=outgrid)
 
 def encode_id(id):
@@ -417,10 +417,10 @@ def get_mapnikMap(mapfile):
     """ Get a new mapnik.Map instance for a mapfile
     """
     mmap = mapnik.Map(0, 0)
-    
+
     if exists(mapfile):
         mapnik.load_map(mmap, str(mapfile))
-    
+
     else:
         handle, filename = mkstemp()
         os.write(handle, urlopen(mapfile).read())
@@ -428,5 +428,5 @@ def get_mapnikMap(mapfile):
 
         mapnik.load_map(mmap, filename)
         os.unlink(filename)
-    
+
     return mmap
diff --git a/TileStache/Pixels.py b/TileStache/Pixels.py
index de7f208..b3c74fa 100644
--- a/TileStache/Pixels.py
+++ b/TileStache/Pixels.py
@@ -34,7 +34,7 @@ except ImportError:
 
 def load_palette(file_href):
     """ Load colors from a Photoshop .act file, return palette info.
-    
+
         Return tuple is an array of [ (r, g, b), (r, g, b), ... ],
         bit depth of the palette, and a numeric transparency index
         or None if not defined.
@@ -42,19 +42,19 @@ def load_palette(file_href):
     bytes = urlopen(file_href).read()
     count, t_index = unpack('!HH', bytes[768:768+4])
     t_index = (t_index <= 0xff) and t_index or None
-    
+
     palette = []
-    
+
     for offset in range(0, count):
         if offset == t_index:
             rgb = 0xff, 0x99, 0x00
         else:
             rgb = unpack('!BBB', bytes[offset*3:(offset + 1)*3])
-        
+
         palette.append(rgb)
-    
+
     bits = int(ceil(log(len(palette)) / log(2)))
-    
+
     return palette, bits, t_index
 
 def palette_color(r, g, b, palette, t_index):
@@ -65,48 +65,49 @@ def palette_color(r, g, b, palette, t_index):
     """
     distances = [(r - _r)**2 + (g - _g)**2 + (b - _b)**2 for (_r, _g, _b) in palette]
     distances = map(sqrt, distances)
-    
+
     if t_index is not None:
         distances = distances[:t_index] + distances[t_index+1:]
-    
+
     return distances.index(min(distances))
 
 def apply_palette(image, palette, t_index):
     """ Apply a palette array to an image, return a new image.
     """
     image = image.convert('RGBA')
-    pixels = image.tostring()
+    pixels = image.tobytes()
+
     t_value = (t_index in range(256)) and pack('!B', t_index) or None
     mapping = {}
     indexes = []
-    
+
     for offset in range(0, len(pixels), 4):
         r, g, b, a = unpack('!BBBB', pixels[offset:offset+4])
-        
+
         if a < 0x80 and t_value is not None:
             # Sufficiently transparent
             indexes.append(t_value)
             continue
-        
+
         try:
             indexes.append(mapping[(r, g, b)])
 
         except KeyError:
             # Never seen this color
             mapping[(r, g, b)] = pack('!B', palette_color(r, g, b, palette, t_index))
-        
+
         else:
             continue
-        
+
         indexes.append(mapping[(r, g, b)])
 
-    output = Image.fromstring('P', image.size, ''.join(indexes))
+    output = Image.frombytes('P', image.size, ''.join(indexes))
     bits = int(ceil(log(len(palette)) / log(2)))
-    
+
     palette += [(0, 0, 0)] * (256 - len(palette))
     palette = reduce(add, palette)
     output.putpalette(palette)
-    
+
     return output
 
 def apply_palette256(image):
diff --git a/TileStache/S3.py b/TileStache/S3.py
index 6c98ef3..601a494 100644
--- a/TileStache/S3.py
+++ b/TileStache/S3.py
@@ -102,6 +102,9 @@ class Cache:
     def unlock(self, layer, coord, format):
         """ Release a cache lock for this tile.
         """
+        if not self.use_locks:
+            return
+
         key_name = tile_key(layer, coord, format, self.path)
         self.bucket.delete_key(key_name+'-lock')
         
diff --git a/TileStache/VERSION b/TileStache/VERSION
index daf515c..08369aa 100644
--- a/TileStache/VERSION
+++ b/TileStache/VERSION
@@ -1 +1 @@
-1.50.1
\ No newline at end of file
+1.51
diff --git a/TileStache/Vector/__init__.py b/TileStache/Vector/__init__.py
index 2ff987c..72bda34 100644
--- a/TileStache/Vector/__init__.py
+++ b/TileStache/Vector/__init__.py
@@ -312,7 +312,7 @@ def _tile_perimeter_geom(coord, projection, padded):
     
     return geom
 
-def _feature_properties(feature, layer_definition, whitelist=None):
+def _feature_properties(feature, layer_definition, whitelist=None, skip_empty_fields=False):
     """ Returns a dictionary of feature properties for a feature in a layer.
     
         Third argument is an optional list or dictionary of properties to
@@ -343,10 +343,11 @@ def _feature_properties(feature, layer_definition, whitelist=None):
                 raise KnownUnknown("Found an OGR field type I've never even seen: %d" % field_type)
             else:
                 raise KnownUnknown("Found an OGR field type I don't know what to do with: ogr.%s" % name)
-       
-        property = type(whitelist) is dict and whitelist[name] or name
-        properties[property] = feature.GetField(name)
-    
+
+        if not skip_empty_fields or feature.IsFieldSet(name):
+            property = type(whitelist) is dict and whitelist[name] or name
+            properties[property] = feature.GetField(name)
+
     return properties
 
 def _append_with_delim(s, delim, data, key):
@@ -419,7 +420,7 @@ def _open_layer(driver_name, parameters, dirpath):
         
     elif driver_name in ('ESRI Shapefile', 'GeoJSON', 'SQLite'):
         if 'file' not in parameters:
-            raise KnownUnknown('Need at least a "file" parameter for a shapefile')
+            raise KnownUnknown('Need a "file" parameter')
     
         file_href = urljoin(dirpath, parameters['file'])
         scheme, h, file_path, q, p, f = urlparse(file_href)
@@ -460,7 +461,7 @@ def _open_layer(driver_name, parameters, dirpath):
     #
     return layer, datasource
 
-def _get_features(coord, properties, projection, layer, clipped, projected, spacing, id_property):
+def _get_features(coord, properties, projection, layer, clipped, projected, spacing, id_property, skip_empty_fields=False):
     """ Return a list of features in an OGR layer with properties in GeoJSON form.
     
         Optionally clip features to coordinate bounding box, and optionally
@@ -524,7 +525,7 @@ def _get_features(coord, properties, projection, layer, clipped, projected, spac
         geometry.TransformTo(output_sref)
 
         geom = json_loads(geometry.ExportToJson())
-        prop = _feature_properties(feature, definition, properties)
+        prop = _feature_properties(feature, definition, properties, skip_empty_fields)
 
         geojson_feature = {'type': 'Feature', 'properties': prop, 'geometry': geom}
         if id_property != None and id_property in prop:
@@ -539,7 +540,7 @@ class Provider:
         See module documentation for explanation of constructor arguments.
     """
     
-    def __init__(self, layer, driver, parameters, clipped, verbose, projected, spacing, properties, precision, id_property):
+    def __init__(self, layer, driver, parameters, clipped, verbose, projected, spacing, properties, precision, id_property, skip_empty_fields=False):
         self.layer      = layer
         self.driver     = driver
         self.clipped    = clipped
@@ -550,6 +551,7 @@ class Provider:
         self.properties = properties
         self.precision  = precision
         self.id_property = id_property
+        self.skip_empty_fields = skip_empty_fields
 
     @staticmethod
     def prepareKeywordArgs(config_dict):
@@ -564,6 +566,7 @@ class Provider:
         kwargs['projected'] = bool(config_dict.get('projected', False))
         kwargs['verbose'] = bool(config_dict.get('verbose', False))
         kwargs['precision'] = int(config_dict.get('precision', 6))
+        kwargs['skip_empty_fields'] = bool(config_dict.get('skip_empty_fields', False))
         
         if 'spacing' in config_dict:
             kwargs['spacing'] = float(config_dict.get('spacing', 0.0))
@@ -581,7 +584,7 @@ class Provider:
         """ Render a single tile, return a VectorResponse instance.
         """
         layer, ds = _open_layer(self.driver, self.parameters, self.layer.config.dirpath)
-        features = _get_features(coord, self.properties, self.layer.projection, layer, self.clipped, self.projected, self.spacing, self.id_property)
+        features = _get_features(coord, self.properties, self.layer.projection, layer, self.clipped, self.projected, self.spacing, self.id_property, self.skip_empty_fields)
         response = {'type': 'FeatureCollection', 'features': features}
         
         if self.projected:
diff --git a/scripts/tilestache-seed.py b/scripts/tilestache-seed.py
index d62dfaf..f19814f 100644
--- a/scripts/tilestache-seed.py
+++ b/scripts/tilestache-seed.py
@@ -280,6 +280,16 @@ if __name__ == '__main__':
         south, west = min(lat1, lat2), min(lon1, lon2)
         north, east = max(lat1, lat2), max(lon1, lon2)
 
+        if not (-90.0 < south < 90.0) or not (-90.0 < north < 90.0):
+            raise KnownUnknown(
+                'Latitude must be a value between -90 and 90 '
+                '(Hint: Maybe you did long/lat instead of lat/long?).'
+            )
+        if not (-180.0 < west < 180.0) or not (-180.0 < east < 180.0):
+            raise KnownUnknown(
+                'Longitude must be a value between -180 and 180.'
+            )
+
         northwest = Location(north, west)
         southeast = Location(south, east)
 
diff --git a/setup.py b/setup.py
index f4f74fe..385984f 100644
--- a/setup.py
+++ b/setup.py
@@ -20,13 +20,7 @@ def is_installed(name):
         return False
 
 
-requires = ['ModestMaps >=1.3.0','simplejson', 'Werkzeug']
-
-# Soft dependency on PIL or Pillow
-if is_installed('PIL'):
-    requires.append('PIL')
-else:
-    requires.append('Pillow')
+requires = ['ModestMaps >=1.3.0','simplejson', 'Werkzeug', 'Pillow']
 
 
 setup(name='TileStache',

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/tilestache.git



More information about the Pkg-grass-devel mailing list