[tilestache] 04/22: Imported Upstream version 1.50.0+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 e98046c48ef0a9ab61348202705f81ec8d741b49
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Mon Oct 17 08:30:30 2016 +0200

    Imported Upstream version 1.50.0+ds
---
 PKG-INFO                                           |   2 +-
 TileStache/Config.py                               |  20 ++-
 TileStache/Core.py                                 |  25 ++-
 .../Providers/DejaVuSansMono-alphanumeric.ttf      | Bin 26616 -> 0 bytes
 TileStache/Goodies/Providers/MapnikGrid.py         |  25 ++-
 TileStache/Goodies/Providers/UtfGridComposite.py   |  49 +++---
 TileStache/Goodies/VecTiles/server.py              |  34 +++-
 TileStache/Mapnik.py                               |  29 +++-
 TileStache/PixelEffects.py                         | 172 +++++++++++++++++++++
 TileStache/Providers.py                            |   4 +-
 TileStache/Redis.py                                |   8 +-
 TileStache/S3.py                                   |   8 +-
 TileStache/VERSION                                 |   2 +-
 TileStache/Vector/__init__.py                      |   2 +-
 doc/TileStache.Config.html                         |   9 +-
 doc/TileStache.Core.html                           |  10 ++
 doc/TileStache.Goodies.Providers.MapnikGrid.html   |  13 +-
 ...eStache.Goodies.Providers.UtfGridComposite.html |   4 +-
 doc/TileStache.Goodies.VecTiles.server.html        |  16 +-
 doc/TileStache.Mapnik.html                         |  17 +-
 doc/TileStache.S3.html                             |   5 +-
 doc/TileStache.html                                |  11 +-
 doc/index.html                                     |  23 ++-
 scripts/tilestache-list.py                         |   2 +-
 scripts/tilestache-seed.py                         |   2 +-
 setup.cfg                                          |   5 +
 setup.py                                           |  12 +-
 27 files changed, 429 insertions(+), 80 deletions(-)

diff --git a/PKG-INFO b/PKG-INFO
index cf8cbe7..0ed0267 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.0
 Name: TileStache
-Version: 1.49.11
+Version: 1.50.0
 Summary: A stylish alternative for caching your map tiles.
 Home-page: http://tilestache.org
 Author: Michal Migurski
diff --git a/TileStache/Config.py b/TileStache/Config.py
index 456ceae..e241d24 100644
--- a/TileStache/Config.py
+++ b/TileStache/Config.py
@@ -79,6 +79,7 @@ import Core
 import Caches
 import Providers
 import Geography
+import PixelEffects
 
 class Configuration:
     """ A complete site configuration, with a collection of Layer objects.
@@ -308,7 +309,7 @@ def _parseConfigfileCache(cache_dict, dirpath):
             add_kwargs('host', 'port', 'db')
     
         elif _class is Caches.S3.Cache:
-            add_kwargs('bucket', 'access', 'secret', 'use_locks', 'path', 'reduced_redundancy')
+            add_kwargs('bucket', 'access', 'secret', 'use_locks', 'path', 'reduced_redundancy', 'policy')
     
         else:
             raise Exception('Unknown cache: %s' % cache_dict['name'])
@@ -422,6 +423,22 @@ def _parseConfigfileLayer(layer_dict, config, dirpath):
         png_kwargs = dict([(str(k), v) for (k, v) in layer_dict['png options'].items()])
 
     #
+    # Do pixel effect
+    #
+
+    pixel_effect = None
+
+    if 'pixel effect' in layer_dict:
+        pixel_effect_dict = layer_dict['pixel effect']
+        pixel_effect_name = pixel_effect_dict.get('name')
+        if pixel_effect_name in PixelEffects.all:
+            pixel_effect_kwargs = {}
+            for k, v in pixel_effect_dict.items():
+                if k != 'name':
+                    pixel_effect_kwargs[str(k)] = float(v)
+            PixelEffectClass = PixelEffects.all[pixel_effect_name]
+            pixel_effect = PixelEffectClass(**pixel_effect_kwargs)
+    #
     # Do the provider
     #
 
@@ -447,6 +464,7 @@ def _parseConfigfileLayer(layer_dict, config, dirpath):
     layer.provider = _class(layer, **provider_kwargs)
     layer.setSaveOptionsJPEG(**jpeg_kwargs)
     layer.setSaveOptionsPNG(**png_kwargs)
+    layer.pixel_effect = pixel_effect
     
     return layer
 
diff --git a/TileStache/Core.py b/TileStache/Core.py
index c89eee7..416d787 100644
--- a/TileStache/Core.py
+++ b/TileStache/Core.py
@@ -73,6 +73,9 @@ configuration file as a dictionary:
   through to PIL: http://effbot.org/imagingbook/format-jpeg.htm.
 - "png options" is an optional dictionary of PNG creation options, passed
   through to PIL: http://effbot.org/imagingbook/format-png.htm.
+- "pixel effect" is an optional dictionary that defines an effect to be applied
+   for all tiles of this layer. Pixel effect can be any of these: blackwhite,
+  greyscale, desaturate, pixelate, halftone, or blur.
 
 The public-facing URL of a single tile for this layer might look like this:
 
@@ -93,6 +96,13 @@ Sample PNG creation options:
       "palette": "filename.act"
     }
 
+Sample pixel effect:
+
+    {
+        "name": "desaturate",
+        "factor": 0.85
+    }
+
 Sample bounds:
 
     {
@@ -334,6 +344,7 @@ class Layer:
         self.bitmap_palette = None
         self.jpeg_options = {}
         self.png_options = {}
+        self.pixel_effect = None
 
     def name(self):
         """ Figure out what I'm called, return a name if there is one.
@@ -468,7 +479,7 @@ class Layer:
             are mutually exclusive options
         """
         if self.bounds and self.bounds.excludes(coord):
-            raise NoTileLeftBehind(Image.new('RGB', (self.dim, self.dim), (0x99, 0x99, 0x99)))
+            raise NoTileLeftBehind(Image.new('RGBA', (self.dim, self.dim), (0, 0, 0, 0)))
         
         srs = self.projection.srs
         xmin, ymin, xmax, ymax = self.envelope(coord)
@@ -517,6 +528,18 @@ class Layer:
             if format.lower() == 'png':
                 t_index = self.png_options.get('transparency', None)
                 tile = apply_palette(tile, self.bitmap_palette, t_index)
+
+        if self.pixel_effect:
+            # this is where we apply the pixel effect if there is one
+
+            if pass_through:
+                raise KnownUnknown(
+                    'Cannot apply pixel effect in pass_through mode'
+                )
+
+            # if tile is an image
+            if format.lower() in ('png', 'jpeg', 'tiff', 'bmp', 'gif'):
+                tile = self.pixel_effect.apply(tile)
         
         if self.doMetatile():
             # tile will be set again later
diff --git a/TileStache/Goodies/Providers/DejaVuSansMono-alphanumeric.ttf b/TileStache/Goodies/Providers/DejaVuSansMono-alphanumeric.ttf
deleted file mode 100644
index 24b9a7d..0000000
Binary files a/TileStache/Goodies/Providers/DejaVuSansMono-alphanumeric.ttf and /dev/null differ
diff --git a/TileStache/Goodies/Providers/MapnikGrid.py b/TileStache/Goodies/Providers/MapnikGrid.py
index 05c1b22..361596b 100644
--- a/TileStache/Goodies/Providers/MapnikGrid.py
+++ b/TileStache/Goodies/Providers/MapnikGrid.py
@@ -31,9 +31,13 @@ scale: What to divide the tile pixel size by to get the resulting grid size. Usu
 buffer: buffer around the queried features, in px, default 0. Use this to prevent problems on tile boundaries.
 """
 import json
+from os.path import exists
 from TileStache.Core import KnownUnknown
 from TileStache.Geography import getProjectionByName
 from urlparse import urlparse, urljoin
+from tempfile import mkstemp
+from urllib import urlopen
+import os
 
 try:
     import mapnik
@@ -69,8 +73,7 @@ class Provider:
         """
         """
         if self.mapnik is None:
-            self.mapnik = mapnik.Map(0, 0)
-            mapnik.load_map(self.mapnik, str(self.mapfile))
+            self.mapnik = get_mapnikMap(self.mapfile)
 
         # buffer as fraction of tile size
         buffer = float(self.buffer) / 256
@@ -121,3 +124,21 @@ class SaveableResponse:
             raise KnownUnknown('MapnikGrid only saves .json tiles, not "%s"' % format)
 
         out.write(self.content)
+
+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())
+        os.close(handle)
+
+        mapnik.load_map(mmap, filename)
+        os.unlink(filename)
+
+    return mmap
diff --git a/TileStache/Goodies/Providers/UtfGridComposite.py b/TileStache/Goodies/Providers/UtfGridComposite.py
index 6f3a16f..480cfe7 100644
--- a/TileStache/Goodies/Providers/UtfGridComposite.py
+++ b/TileStache/Goodies/Providers/UtfGridComposite.py
@@ -40,9 +40,6 @@ class Provider:
 	def __init__(self, layer, stack, layer_id=None, wrapper=None):
 
 		#Set up result storage
-		self.resultGrid = []
-		self.gridKeys = []
-		self.gridData = {}
 		
 		self.layer = layer
 		self.stack = stack
@@ -50,10 +47,14 @@ class Provider:
 		self.wrapper = wrapper
 	
 	def renderTile(self, width, height, srs, coord):
-	
+
+		resultGrid = []
+		gridKeys = []
+		gridData = {}
+		
 		for l in self.stack:
-			self.addLayer(l, coord)
-		return SaveableResponse(self.writeResult())
+			self.addLayer(resultGrid, gridKeys, gridData, l, coord)
+		return SaveableResponse(self.writeResult(resultGrid, gridKeys, gridData))
 
 	def getTypeByExtension(self, extension):
 		""" Get mime-type and format by file extension.
@@ -64,9 +65,9 @@ class Provider:
 		
 		return 'text/json', 'JSON'
 
-	def addLayer( self, layerDef, coord ):
+	def addLayer( self, resultGrid, gridKeys, gridData, layerDef, coord ):
 		
-		mime, layer = TileStache.getTile(self.layer.config.layers[layerDef['src']], coord, 'JSON')[1]
+		mime, layer = TileStache.getTile(self.layer.config.layers[layerDef['src']], coord, 'JSON')
 #		raise KnownUnknown(layer)
 		if layerDef['wrapper'] == None:
 			layer = json.loads(layer)
@@ -76,19 +77,19 @@ class Provider:
 		gridSize = len(layer['grid'])
 
 		#init resultGrid based on given layers (if required)
-		if len(self.resultGrid) == 0:
+		if len(resultGrid) == 0:
 			for i in xrange(gridSize):
-				self.resultGrid.append([])
+				resultGrid.append([])
 				for j in xrange(gridSize):
-					self.resultGrid[i].append(-1)
+					resultGrid[i].append(-1)
 	
 		keys = layer['keys']
 		
 		keyRemap = {}
 		for k in keys:
-			if k in self.gridKeys:
+			if k in gridKeys:
 				for ext in xrange(ord('a'), ord('z')+1):
-					if not k+chr(ext) in self.gridKeys:
+					if not k+chr(ext) in gridKeys:
 						keyRemap[k] = (k+chr(ext))
 						break
 				if not k in keyRemap:
@@ -109,19 +110,19 @@ class Provider:
 					key = keyRemap[keys[idNo]]
 				
 				if not key in addedKeys:
-					self.gridKeys.append(key)
+					gridKeys.append(key)
 					addedKeys.append(key)
 					if layerDef['layer_id'] != None and self.layer_id != None: #Add layer name attribute
 						layer['data'][keys[idNo]][self.layer_id] = layerDef['layer_id']
-					self.gridData[key] = layer['data'][keys[idNo]]
+					gridData[key] = layer['data'][keys[idNo]]
 						
 						
-				newId = self.gridKeys.index(key)
+				newId = gridKeys.index(key)
 				
-				self.resultGrid[x][y] = newId
+				resultGrid[x][y] = newId
 
-	def writeResult( self ):
-		gridSize = len(self.resultGrid)
+	def writeResult( self, resultGrid, gridKeys, gridData ):
+		gridSize = len(resultGrid)
 	
 		finalKeys = []
 		finalData = {}
@@ -134,7 +135,7 @@ class Provider:
 		
 		for y in xrange(gridSize):
 			for x in xrange(gridSize):
-				id = self.resultGrid[x][y]
+				id = resultGrid[x][y]
 				
 				if not id in idToFinalId:
 					idToFinalId[id] = finalIdCounter
@@ -143,8 +144,8 @@ class Provider:
 					if id == -1:
 						finalKeys.append("")
 					else:
-						finalKeys.append(self.gridKeys[id])
-						finalData[self.gridKeys[id]] = self.gridData[self.gridKeys[id]]
+						finalKeys.append(gridKeys[id])
+						finalData[gridKeys[id]] = gridData[gridKeys[id]]
 				
 				finalId = idToFinalId[id]
 				finalGrid[y] = finalGrid[y] + self.encodeId(finalId)
@@ -158,11 +159,11 @@ class Provider:
 		result += "], \"data\": { "
 		
 		first = True
-		for entry in self.gridData:
+		for entry in gridData:
 			if not first:
 				result += ","
 			first = False
-			result += "\"" + entry + "\": " + json.dumps(self.gridData[entry]) + ""
+			result += "\"" + entry + "\": " + json.dumps(gridData[entry]) + ""
 		
 		result += "}, \"grid\": ["
 		
diff --git a/TileStache/Goodies/VecTiles/server.py b/TileStache/Goodies/VecTiles/server.py
index c7f90c5..f36bc41 100644
--- a/TileStache/Goodies/VecTiles/server.py
+++ b/TileStache/Goodies/VecTiles/server.py
@@ -99,6 +99,20 @@ class Provider:
               ]
             }
           }
+
+        The queries field has an alternate dictionary-like syntax which maps
+        zoom levels to their associated query.  Zoom levels for which there is
+        no query may be omitted and are assumed null.  This is equivalent to
+        the queries defined above:
+
+              "queries": {
+                "10": "SELECT way AS __geometry__, highway, name FROM planet_osm_line -- zoom 10+ ",
+                "11": "http://example.com/query-z11.pgsql",
+                "12": "query-z12-plus.pgsql"
+              }
+
+        Note that JSON requires keys to be strings, therefore the zoom levels
+        must be enclosed in quotes.
     '''
     def __init__(self, layer, dbinfo, queries, clip=True, srid=900913, simplify=1.0, simplify_until=16):
         '''
@@ -113,12 +127,22 @@ class Provider:
         self.simplify = float(simplify)
         self.simplify_until = int(simplify_until)
         
-        self.queries = []
         self.columns = {}
-        
-        for query in queries:
+
+        # Each type creates an iterator yielding tuples of:
+        # (zoom level (int), query (string))
+        if isinstance(queries, dict):
+            # Add 1 to include space for zoom level 0
+            n_zooms = max(int(z) for z in queries) + 1
+            queryiter = ((int(z), q) for z, q in queries.iteritems())
+        else:  # specified as array
+            n_zooms = len(queries)
+            queryiter = enumerate(queries)
+
+        # For the dict case, unspecified zoom levels are assumed to be null.
+        self.queries = [None] * n_zooms
+        for z, query in queryiter:
             if query is None:
-                self.queries.append(None)
                 continue
         
             #
@@ -133,7 +157,7 @@ class Provider:
             elif scheme == 'http' and ' ' not in url:
                 query = urlopen(url).read()
         
-            self.queries.append(query)
+            self.queries[z] = query
         
     def renderTile(self, width, height, srs, coord):
         ''' Render a single tile, return a Response instance.
diff --git a/TileStache/Mapnik.py b/TileStache/Mapnik.py
index de148f3..76ad779 100644
--- a/TileStache/Mapnik.py
+++ b/TileStache/Mapnik.py
@@ -60,6 +60,13 @@ class ImageProvider:
     
         - fonts (optional)
             Local directory path to *.ttf font files.
+
+        - "scale factor" (optional)
+            Scale multiplier used for Mapnik rendering pipeline. Used for
+            supporting retina resolution.
+
+            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
@@ -67,7 +74,7 @@ class ImageProvider:
         - http://trac.mapnik.org/wiki/XMLConfigReference
     """
     
-    def __init__(self, layer, mapfile, fonts=None):
+    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,
@@ -96,6 +103,8 @@ class ImageProvider:
             for font in glob(path.rstrip('/') + '/*.ttf'):
                 engine.register_font(str(font))
 
+        self.scale_factor = scale_factor
+
     @staticmethod
     def prepareKeywordArgs(config_dict):
         """ Convert configured parameters to keyword args for __init__().
@@ -104,6 +113,9 @@ class ImageProvider:
 
         if 'fonts' in config_dict:
             kwargs['fonts'] = config_dict['fonts']
+
+        if 'scale factor' in config_dict:
+            kwargs['scale_factor'] = int(config_dict['scale factor'])
         
         return kwargs
     
@@ -126,7 +138,12 @@ class ImageProvider:
                 self.mapnik.zoom_to_box(Box2d(xmin, ymin, xmax, ymax))
             
                 img = mapnik.Image(width, height)
-                mapnik.render(self.mapnik, img) 
+                # 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) 
+                else:
+                    mapnik.render(self.mapnik, img, self.scale_factor) 
             except:
                 self.mapnik = None
                 raise
@@ -181,19 +198,19 @@ class GridProvider:
           An empty list will return no field names, while a value of null is
           equivalent to all.
         
-        - layer index (optional)
+        - 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.
+          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 
           is equivalent to all fields.
  
         - scale (optional)
           Scale factor of output raster, defaults to 4 (64x64).
         
-        - layer id key (optional)
+        - 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.
diff --git a/TileStache/PixelEffects.py b/TileStache/PixelEffects.py
new file mode 100644
index 0000000..1b49a68
--- /dev/null
+++ b/TileStache/PixelEffects.py
@@ -0,0 +1,172 @@
+""" Different effects that can be applied to tiles.
+
+Options are:
+
+- blackwhite:
+
+    "effect":
+    {
+        "name": "blackwhite"
+    }
+
+- greyscale:
+
+    "effect":
+    {
+        "name": "greyscale"
+    }
+
+- desaturate:
+  Has an optional parameter "factor" that defines the saturation of the image.
+  Defaults to 0.85.
+
+    "effect":
+    {
+        "name": "desaturate",
+        "factor": 0.85
+    }
+
+- pixelate:
+  Has an optional parameter "reduction" that defines how pixelated the image
+  will be (size of pixel). Defaults to 5.
+
+    "effect":
+    {
+        "name": "pixelate",
+        "factor": 5
+    }
+
+- halftone:
+
+    "effect":
+    {
+        "name": "halftone"
+    }
+
+- blur:
+  Has an optional parameter "radius" that defines the blurriness of an image.
+  Larger radius means more blurry. Defaults to 5.
+
+    "effect":
+    {
+        "name": "blur",
+        "radius": 5
+    }
+"""
+
+from PIL import Image, ImageFilter
+
+
+def put_original_alpha(original_image, new_image):
+    """ Put alpha channel of original image (if any) in the new image.
+    """
+
+    try:
+        alpha_idx = original_image.mode.index('A')
+        alpha_channel = original_image.split()[alpha_idx]
+        new_image.putalpha(alpha_channel)
+    except ValueError:
+        pass
+    return new_image
+
+
+class PixelEffect:
+    """ Base class for all pixel effects.
+        Subclasses must implement method `apply_effect`.
+    """
+
+    def __init__(self):
+        pass
+
+    def apply(self, image):
+        try:
+            image = image.image()  # Handle Providers.Verbatim tiles
+        except (AttributeError, TypeError):
+            pass
+        return self.apply_effect(image)
+
+    def apply_effect(self, image):
+        raise NotImplementedError(
+            'PixelEffect subclasses must implement method `apply_effect`.'
+        )
+
+
+class Blackwhite(PixelEffect):
+    """ Returns a black and white version of the original image.
+    """
+
+    def apply_effect(self, image):
+        new_image = image.convert('1').convert(image.mode)
+        return put_original_alpha(image, new_image)
+
+
+class Greyscale(PixelEffect):
+    """ Returns a grescale version of the original image.
+    """
+
+    def apply_effect(self, image):
+        return image.convert('LA').convert(image.mode)
+
+
+class Desaturate(PixelEffect):
+    """ Returns a desaturated version of the original image.
+        `factor` is a number between 0 and 1, where 1 results in a
+        greyscale image (no color), and 0 results in the original image.
+    """
+
+    def __init__(self, factor=0.85):
+        self.factor = min(max(factor, 0.0), 1.0)  # 0.0 <= factor <= 1.0
+
+    def apply_effect(self, image):
+        avg = image.convert('LA').convert(image.mode)
+        return Image.blend(image, avg, self.factor)
+
+
+class Pixelate(PixelEffect):
+    """ Returns a pixelated version of the original image.
+        `reduction` defines how pixelated the image will be (size of pixels).
+    """
+
+    def __init__(self, reduction=5):
+        self.reduction = max(reduction, 1)  # 1 <= reduction
+
+    def apply_effect(self, image):
+        tmp_size = (int(image.size[0] / self.reduction),
+                    int(image.size[1] / self.reduction))
+        pixelated = image.resize(tmp_size, Image.NEAREST)
+        return pixelated.resize(image.size, Image.NEAREST)
+
+
+class Halftone(PixelEffect):
+    """ Returns a halftone version of the original image.
+    """
+
+    def apply_effect(self, image):
+        cmyk = []
+        for band in image.convert('CMYK').split():
+            cmyk.append(band.convert('1').convert('L'))
+        new_image = Image.merge('CMYK', cmyk).convert(image.mode)
+        return put_original_alpha(image, new_image)
+
+
+class Blur(PixelEffect):
+    """ Returns a blurred version of the original image.
+        `radius` defines the blurriness of an image. Larger radius means more
+        blurry.
+    """
+
+    def __init__(self, radius=5):
+        self.radius = max(radius, 0)  # 0 <= radius
+
+    def apply_effect(self, image):
+        return image.filter(ImageFilter.GaussianBlur(self.radius))
+
+
+all = {
+    'blackwhite': Blackwhite,
+    'greyscale': Greyscale,
+    'desaturate': Desaturate,
+    'pixelate': Pixelate,
+    'halftone': Halftone,
+    'blur': Blur,
+}
diff --git a/TileStache/Providers.py b/TileStache/Providers.py
index 0d309e7..1f9c7a1 100644
--- a/TileStache/Providers.py
+++ b/TileStache/Providers.py
@@ -346,8 +346,8 @@ class UrlTemplate:
             xmin = sw_point.x
             srs = self.source_projection.srs
 
-        mapping = {'width': width, 'height': height, 'srs': srs, 'zoom': zoom}
-        mapping.update({'xmin': xmin, 'ymin': ymin, 'xmax': xmax, 'ymax': ymax})
+        mapping = {'width': width, 'height': height, 'srs': srs, 'zoom': zoom,
+                   'xmin': xmin, 'ymin': ymin, 'xmax': xmax, 'ymax': ymax}
 
         href = self.template.safe_substitute(mapping)
         req = urllib2.Request(href)
diff --git a/TileStache/Redis.py b/TileStache/Redis.py
index a8a7483..dc3fd94 100644
--- a/TileStache/Redis.py
+++ b/TileStache/Redis.py
@@ -113,4 +113,10 @@ class Cache:
         """ Save a cached tile.
         """
         key = tile_key(layer, coord, format, self.key_prefix)
-        self.conn.set(key, body)
+
+        # note: setting ex=0 will raise an error
+        cache_lifespan = layer.cache_lifespan
+        if cache_lifespan == 0:
+            cache_lifespan = None
+
+        self.conn.set(key, body, ex=cache_lifespan)
diff --git a/TileStache/S3.py b/TileStache/S3.py
index 92a9ebb..6c98ef3 100644
--- a/TileStache/S3.py
+++ b/TileStache/S3.py
@@ -23,6 +23,9 @@ S3 cache parameters:
   secret
     Optional secret access key for your S3 account.
 
+  policy
+    Optional S3 ACL policy for uploaded tiles. Default is 'public-read'.
+
   use_locks
     Optional boolean flag for whether to use the locking feature on S3.
     True by default. A good reason to set this to false would be the
@@ -68,11 +71,12 @@ def tile_key(layer, coord, format, path = ''):
 class Cache:
     """
     """
-    def __init__(self, bucket, access=None, secret=None, use_locks=True, path='', reduced_redundancy=False):
+    def __init__(self, bucket, access=None, secret=None, use_locks=True, path='', reduced_redundancy=False, policy='public-read'):
         self.bucket = S3Bucket(S3Connection(access, secret), bucket)
         self.use_locks = bool(use_locks)
         self.path = path
         self.reduced_redundancy = reduced_redundancy
+        self.policy = policy
 
     def lock(self, layer, coord, format):
         """ Acquire a cache lock for this tile.
@@ -133,4 +137,4 @@ class Cache:
         content_type, encoding = guess_type('example.'+format)
         headers = content_type and {'Content-Type': content_type} or {}
         
-        key.set_contents_from_string(body, headers, policy='public-read', reduced_redundancy=self.reduced_redundancy)
+        key.set_contents_from_string(body, headers, policy=self.policy, reduced_redundancy=self.reduced_redundancy)
diff --git a/TileStache/VERSION b/TileStache/VERSION
index 041c636..5a5c721 100644
--- a/TileStache/VERSION
+++ b/TileStache/VERSION
@@ -1 +1 @@
-1.49.11
+1.50.0
diff --git a/TileStache/Vector/__init__.py b/TileStache/Vector/__init__.py
index 6ab0b08..2ff987c 100644
--- a/TileStache/Vector/__init__.py
+++ b/TileStache/Vector/__init__.py
@@ -303,7 +303,7 @@ def _tile_perimeter_geom(coord, projection, padded):
         Uses _tile_perimeter().
     """
     perimeter = _tile_perimeter(coord, projection, padded)
-    wkt = 'POLYGON((%s))' % ', '.join(['%.3f %.3f' % xy for xy in perimeter])
+    wkt = 'POLYGON((%s))' % ', '.join(['%.7f %.7f' % xy for xy in perimeter])
     geom = ogr.CreateGeometryFromWkt(wkt)
     
     ref = osr.SpatialReference()
diff --git a/doc/TileStache.Config.html b/doc/TileStache.Config.html
index aebd19a..5232d0c 100644
--- a/doc/TileStache.Config.html
+++ b/doc/TileStache.Config.html
@@ -77,10 +77,11 @@ documentation for TileStache.Providers, TileStache.Core, and
 <td width="100%"><table width="100%" summary="list"><tr><td width="25%" valign=top><a href="TileStache.Caches.html">TileStache.Caches</a><br>
 <a href="TileStache.Core.html">TileStache.Core</a><br>
 </td><td width="25%" valign=top><a href="TileStache.Geography.html">TileStache.Geography</a><br>
-<a href="TileStache.Providers.html">TileStache.Providers</a><br>
-</td><td width="25%" valign=top><a href="logging.html">logging</a><br>
-<a href="sys.html">sys</a><br>
-</td><td width="25%" valign=top></td></tr></table></td></tr></table><p>
+<a href="TileStache.PixelEffects.html">TileStache.PixelEffects</a><br>
+</td><td width="25%" valign=top><a href="TileStache.Providers.html">TileStache.Providers</a><br>
+<a href="logging.html">logging</a><br>
+</td><td width="25%" valign=top><a href="sys.html">sys</a><br>
+</td></tr></table></td></tr></table><p>
 <table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
 <tr bgcolor="#ee77aa">
 <td colspan=3 valign=bottom> <br>
diff --git a/doc/TileStache.Core.html b/doc/TileStache.Core.html
index 717d982..c4c71d5 100644
--- a/doc/TileStache.Core.html
+++ b/doc/TileStache.Core.html
@@ -84,6 +84,9 @@ configuration file as a dictionary:<br>
   through to PIL: <a href="http://effbot.org/imagingbook/format-jpeg.htm">http://effbot.org/imagingbook/format-jpeg.htm</a>.<br>
 - "png options" is an optional dictionary of PNG creation options, passed<br>
   through to PIL: <a href="http://effbot.org/imagingbook/format-png.htm">http://effbot.org/imagingbook/format-png.htm</a>.<br>
+- "pixel effect" is an optional dictionary that defines an effect to be applied<br>
+   for all tiles of this layer. Pixel effect can be any of these: blackwhite,<br>
+  greyscale, desaturate, pixelate, halftone, or blur.<br>
  <br>
 The public-facing URL of a single tile for this layer might look like this:<br>
  <br>
@@ -104,6 +107,13 @@ Sample PNG creation options:<br>
       "palette": "filename.act"<br>
     }<br>
  <br>
+Sample pixel effect:<br>
+ <br>
+    {<br>
+        "name": "desaturate",<br>
+        "factor": 0.85<br>
+    }<br>
+ <br>
 Sample bounds:<br>
  <br>
     {<br>
diff --git a/doc/TileStache.Goodies.Providers.MapnikGrid.html b/doc/TileStache.Goodies.Providers.MapnikGrid.html
index ff762ad..91273e6 100644
--- a/doc/TileStache.Goodies.Providers.MapnikGrid.html
+++ b/doc/TileStache.Goodies.Providers.MapnikGrid.html
@@ -48,7 +48,8 @@ buffer: buffer around the queried features, in&nbs
     
 <tr><td bgcolor="#aa55cc"><tt>      </tt></td><td> </td>
 <td width="100%"><table width="100%" summary="list"><tr><td width="25%" valign=top><a href="json.html">json</a><br>
-</td><td width="25%" valign=top></td><td width="25%" valign=top></td><td width="25%" valign=top></td></tr></table></td></tr></table><p>
+</td><td width="25%" valign=top><a href="os.html">os</a><br>
+</td><td width="25%" valign=top></td><td width="25%" valign=top></td></tr></table></td></tr></table><p>
 <table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
 <tr bgcolor="#ee77aa">
 <td colspan=3 valign=bottom> <br>
@@ -91,5 +92,13 @@ TileStache.getTile() expects to be able to save&nb
 
 <dl><dt><a name="SaveableResponse-save"><strong>save</strong></a>(self, out, format)</dt></dl>
 
-</td></tr></table></td></tr></table>
+</td></tr></table></td></tr></table><p>
+<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
+<tr bgcolor="#eeaa77">
+<td colspan=3 valign=bottom> <br>
+<font color="#ffffff" face="helvetica, arial"><big><strong>Functions</strong></big></font></td></tr>
+    
+<tr><td bgcolor="#eeaa77"><tt>      </tt></td><td> </td>
+<td width="100%"><dl><dt><a name="-get_mapnikMap"><strong>get_mapnikMap</strong></a>(mapfile)</dt><dd><tt>Get a new mapnik.Map instance for a mapfile</tt></dd></dl>
+</td></tr></table>
 </body></html>
\ No newline at end of file
diff --git a/doc/TileStache.Goodies.Providers.UtfGridComposite.html b/doc/TileStache.Goodies.Providers.UtfGridComposite.html
index e8cd6b9..4490c72 100644
--- a/doc/TileStache.Goodies.Providers.UtfGridComposite.html
+++ b/doc/TileStache.Goodies.Providers.UtfGridComposite.html
@@ -70,7 +70,7 @@ if layer_id is not set in the layer or&n
 <td width="100%">Methods defined here:<br>
 <dl><dt><a name="Provider-__init__"><strong>__init__</strong></a>(self, layer, stack, layer_id<font color="#909090">=None</font>, wrapper<font color="#909090">=None</font>)</dt></dl>
 
-<dl><dt><a name="Provider-addLayer"><strong>addLayer</strong></a>(self, layerDef, coord)</dt></dl>
+<dl><dt><a name="Provider-addLayer"><strong>addLayer</strong></a>(self, resultGrid, gridKeys, gridData, layerDef, coord)</dt></dl>
 
 <dl><dt><a name="Provider-decodeId"><strong>decodeId</strong></a>(self, id)</dt></dl>
 
@@ -81,7 +81,7 @@ This only accepts "json".</tt></dd></dl>
 
 <dl><dt><a name="Provider-renderTile"><strong>renderTile</strong></a>(self, width, height, srs, coord)</dt></dl>
 
-<dl><dt><a name="Provider-writeResult"><strong>writeResult</strong></a>(self)</dt></dl>
+<dl><dt><a name="Provider-writeResult"><strong>writeResult</strong></a>(self, resultGrid, gridKeys, gridData)</dt></dl>
 
 </td></tr></table> <p>
 <table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
diff --git a/doc/TileStache.Goodies.VecTiles.server.html b/doc/TileStache.Goodies.VecTiles.server.html
index 9f1199f..3e1c7c5 100644
--- a/doc/TileStache.Goodies.VecTiles.server.html
+++ b/doc/TileStache.Goodies.VecTiles.server.html
@@ -196,7 +196,21 @@ URL containing a query for zoom 11, and 
         "query-z12-plus.pgsql"<br>
       ]<br>
     }<br>
-  }<br> </tt></td></tr>
+  }<br>
+ <br>
+The queries field has an alternate dictionary-like syntax which maps<br>
+zoom levels to their associated query.  Zoom levels for which there is<br>
+no query may be omitted and are assumed null.  This is equivalent to<br>
+the queries defined above:<br>
+ <br>
+      "queries": {<br>
+        "10": "SELECT way AS __geometry__, highway, name FROM planet_osm_line -- zoom 10+ ",<br>
+        "11": "<a href="http://example.com/query-z11.pgsql">http://example.com/query-z11.pgsql</a>",<br>
+        "12": "query-z12-plus.pgsql"<br>
+      }<br>
+ <br>
+Note that JSON requires keys to be strings, therefore the zoom levels<br>
+must be enclosed in quotes.<br> </tt></td></tr>
 <tr><td> </td>
 <td width="100%">Methods defined here:<br>
 <dl><dt><a name="Provider-__init__"><strong>__init__</strong></a>(self, layer, dbinfo, queries, clip<font color="#909090">=True</font>, srid<font color="#909090">=900913</font>, simplify<font color="#909090">=1.0</font>, simplify_until<font color="#909090">=16</font>)</dt></dl>
diff --git a/doc/TileStache.Mapnik.html b/doc/TileStache.Mapnik.html
index b818411..d1a859d 100644
--- a/doc/TileStache.Mapnik.html
+++ b/doc/TileStache.Mapnik.html
@@ -84,19 +84,19 @@ Arguments:<br>
   An empty list will return no field names, while a value of null is<br>
   equivalent to all.<br>
  <br>
-- layer index (optional)<br>
+- layer_index (optional)<br>
   Which layer from the mapfile to render, defaults to 0 (first layer).<br>
  <br>
 - layers (optional)<br>
-  Ordered list of (layer index, fields) to combine; if provided<br>
-  layers overrides both layer index and fields arguments.<br>
+  Ordered list of (layer_index, fields) to combine; if provided<br>
+  layers overrides both layer_index and fields arguments.<br>
   An empty fields list will return no field names, while a value of null <br>
   is equivalent to all fields.<br>
  <br>
 - scale (optional)<br>
   Scale factor of output raster, defaults to 4 (64x64).<br>
  <br>
-- layer id key (optional)<br>
+- layer_id_key (optional)<br>
   If set, each item in the 'data' property will have its source mapnik<br>
   layer name added, keyed by this value. Useful for distingushing<br>
   between data items.<br>
@@ -140,13 +140,20 @@ Arguments:<br>
 - fonts (optional)<br>
     Local directory path to *.ttf font files.<br>
  <br>
+- "scale factor" (optional)<br>
+    Scale multiplier used for Mapnik rendering pipeline. Used for<br>
+    supporting retina resolution.<br>
+ <br>
+    For more information about the scale factor, see: <br>
+    https://github.com/mapnik/mapnik/wiki/Scale-factor<br>
+ <br>
 More information on Mapnik and Mapnik XML:<br>
 - <a href="http://mapnik.org">http://mapnik.org</a><br>
 - <a href="http://trac.mapnik.org/wiki/XMLGettingStarted">http://trac.mapnik.org/wiki/XMLGettingStarted</a><br>
 - <a href="http://trac.mapnik.org/wiki/XMLConfigReference">http://trac.mapnik.org/wiki/XMLConfigReference</a><br> </tt></td></tr>
 <tr><td> </td>
 <td width="100%">Methods defined here:<br>
-<dl><dt><a name="ImageProvider-__init__"><strong>__init__</strong></a>(self, layer, mapfile, fonts<font color="#909090">=None</font>)</dt><dd><tt>Initialize Mapnik provider with layer and mapfile.<br>
+<dl><dt><a name="ImageProvider-__init__"><strong>__init__</strong></a>(self, layer, mapfile, fonts<font color="#909090">=None</font>, scale_factor<font color="#909090">=None</font>)</dt><dd><tt>Initialize Mapnik provider with layer and mapfile.<br>
  <br>
 XML mapfile keyword arg comes from TileStache config,<br>
 and is an absolute path by the time it gets here.</tt></dd></dl>
diff --git a/doc/TileStache.S3.html b/doc/TileStache.S3.html
index c016ce2..aae6e87 100644
--- a/doc/TileStache.S3.html
+++ b/doc/TileStache.S3.html
@@ -34,6 +34,9 @@ S3 cache parameters:<br>
   secret<br>
     Optional secret access key for your S3 account.<br>
  <br>
+  policy<br>
+    Optional S3 ACL policy for uploaded tiles. Default is 'public-read'.<br>
+ <br>
   use_locks<br>
     Optional boolean flag for whether to use the locking feature on S3.<br>
     True by default. A good reason to set this to false would be the<br>
@@ -71,7 +74,7 @@ AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY will be use
     
 <tr><td bgcolor="#ffc8d8"><tt>   </tt></td><td> </td>
 <td width="100%">Methods defined here:<br>
-<dl><dt><a name="Cache-__init__"><strong>__init__</strong></a>(self, bucket, access<font color="#909090">=None</font>, secret<font color="#909090">=None</font>, use_locks<font color="#909090">=True</font>, path<font color="#909090">=''</font>, reduced_redundancy<font color="#909090">=False</font>)</dt></dl>
+<dl><dt><a name="Cache-__init__"><strong>__init__</strong></a>(self, bucket, access<font color="#909090">=None</font>, secret<font color="#909090">=None</font>, use_locks<font color="#909090">=True</font>, path<font color="#909090">=''</font>, reduced_redundancy<font color="#909090">=False</font>, policy<font color="#909090">='public-read'</font>)</dt></dl>
 
 <dl><dt><a name="Cache-lock"><strong>lock</strong></a>(self, layer, coord, format)</dt><dd><tt>Acquire a cache lock for this tile.<br>
  <br>
diff --git a/doc/TileStache.html b/doc/TileStache.html
index 0561bf0..df6b847 100644
--- a/doc/TileStache.html
+++ b/doc/TileStache.html
@@ -6,7 +6,7 @@
 <table width="100%" cellspacing=0 cellpadding=2 border=0 summary="heading">
 <tr bgcolor="#7799ee">
 <td valign=bottom> <br>
-<font color="#ffffff" face="helvetica, arial"> <br><big><big><strong>TileStache</strong></big></big> (version 1.49.11)</font></td
+<font color="#ffffff" face="helvetica, arial"> <br><big><big><strong>TileStache</strong></big></big> (version 1.50.0)</font></td
 ><td align=right valign=bottom
 ><font color="#ffffff" face="helvetica, arial"><a href=".">index</a></font></td></tr></table>
     <p><tt>A stylish alternative for caching your map tiles.<br>
@@ -33,11 +33,12 @@ Documentation available at <a href="http://tilestache.org/doc/">h
 <a href="TileStache.MBTiles.html">MBTiles</a><br>
 <a href="TileStache.Mapnik.html">Mapnik</a><br>
 <a href="TileStache.Memcache.html">Memcache</a><br>
-</td><td width="25%" valign=top><a href="TileStache.Pixels.html">Pixels</a><br>
+</td><td width="25%" valign=top><a href="TileStache.PixelEffects.html">PixelEffects</a><br>
+<a href="TileStache.Pixels.html">Pixels</a><br>
 <a href="TileStache.Providers.html">Providers</a><br>
 <a href="TileStache.Redis.html">Redis</a><br>
-<a href="TileStache.S3.html">S3</a><br>
-</td><td width="25%" valign=top><a href="TileStache.Sandwich.html">Sandwich</a><br>
+</td><td width="25%" valign=top><a href="TileStache.S3.html">S3</a><br>
+<a href="TileStache.Sandwich.html">Sandwich</a><br>
 <a href="TileStache.Vector.html"><strong>Vector</strong> (package)</a><br>
 </td></tr></table></td></tr></table><p>
 <table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
@@ -190,6 +191,6 @@ Fractions of a second may be present if 
 <font color="#ffffff" face="helvetica, arial"><big><strong>Data</strong></big></font></td></tr>
     
 <tr><td bgcolor="#55aa55"><tt>      </tt></td><td> </td>
-<td width="100%"><strong>__version__</strong> = '1.49.11'<br>
+<td width="100%"><strong>__version__</strong> = '1.50.0'<br>
 <strong>stdout</strong> = <open file '<stdout>', mode 'w'></td></tr></table>
 </body></html>
\ No newline at end of file
diff --git a/doc/index.html b/doc/index.html
index 1472da6..b00319c 100644
--- a/doc/index.html
+++ b/doc/index.html
@@ -76,7 +76,7 @@
     </p>
 
     <p>
-    <strong>This document covers TileStache version 1.49.11</strong>.
+    <strong>This document covers TileStache version 1.50.0</strong>.
     </p>
 
     <p>
@@ -837,7 +837,8 @@ Example layer configuration:
       "redirects": …,
       "tile height": …,
       "jpeg options": …,
-      "png options": …
+      "png options": …,
+      "pixel effect": { … }
     }
   <span class="bg">}
 }</span>
@@ -973,6 +974,14 @@ Shared layer parameters:
     Valid options include <var>palette</var> (URL or filename), <var>palette256</var>
 	(boolean) and <var>optimize</var> (boolean).
     </dd>
+
+    <dt>pixel effect</dt>
+    <dd>
+    An optional dictionary that defines an effect to be applied for all tiles
+    of this layer. Pixel effect can be any of these: <samp>blackwhite</samp>,
+    <samp>greyscale</samp>, <samp>desaturate</samp>, <samp>pixelate</samp>,
+    <samp>halftone</samp>, or <samp>blur</samp>.
+    </dd>
 </dl>
 
 <h3><a id="providers" name="providers">Providers</a> <a href="#providers" class="permalink">¶</a></h3>
@@ -1398,7 +1407,7 @@ Example Mapnik Grid provider configurations:
       {
         "name": "mapnik grid", 
         "mapfile": "style.xml",
-        "layer index": 1
+        "layer_index": 1
       },
     <span class="bg">}
     "two-grids":
@@ -1433,21 +1442,21 @@ Mapnik Grid provider parameters:
     An empty list will return no field names, while a value of <samp>null</samp>
     is equivalent to all.
     </dd>
-    <dt>layer index</dt>
+    <dt>layer_index</dt>
     <dd>
     Optional layer from the mapfile to render, defaults to <samp>0</samp> (first layer).
     </dd>
     <dt>layers</dt>
     <dd>
-    Optional ordered list of (layer index, fields) to combine; if provided
-    <var>layers</var> overrides both <var>layer index</var> and <var>fields</var>
+    Optional ordered list of (layer_index, fields) to combine; if provided
+    <var>layers</var> overrides both <var>layer_index</var> and <var>fields</var>
     arguments.
     </dd>
     <dt>scale</dt>
     <dd>
     Optional scale factor of output raster, defaults to <samp>4</samp> (64×64).
     </dd>
-    <dt>layer id key</dt>
+    <dt>layer_id_key</dt>
     <dd>
     Optional. If set, each item in the <samp>"data"</samp> property will have
     its source mapnik layer name added, keyed by this value. Useful for
diff --git a/scripts/tilestache-list.py b/scripts/tilestache-list.py
index 71f771c..9488a63 100644
--- a/scripts/tilestache-list.py
+++ b/scripts/tilestache-list.py
@@ -41,7 +41,7 @@ defaults = dict(padding=0, bbox=(37.777, -122.352, 37.839, -122.226))
 parser.set_defaults(**defaults)
 
 parser.add_option('-b', '--bbox', dest='bbox',
-                  help='Bounding box in floating point geographic coordinates: south west north east. Default value is %.3f, %.3f, %.3f, %.3f.' % defaults['bbox'],
+                  help='Bounding box in floating point geographic coordinates: south west north east. Default value is %.7f, %.7f, %.7f, %.7f.' % defaults['bbox'],
                   type='float', nargs=4)
 
 parser.add_option('-p', '--padding', dest='padding',
diff --git a/scripts/tilestache-seed.py b/scripts/tilestache-seed.py
index ca43bc7..d62dfaf 100644
--- a/scripts/tilestache-seed.py
+++ b/scripts/tilestache-seed.py
@@ -54,7 +54,7 @@ parser.add_option('-l', '--layer', dest='layer',
                   help='Layer name from configuration, typically required.')
 
 parser.add_option('-b', '--bbox', dest='bbox',
-                  help='Bounding box in floating point geographic coordinates: south west north east. Default value is %.3f, %.3f, %.3f, %.3f.' % defaults['bbox'],
+                  help='Bounding box in floating point geographic coordinates: south west north east. Default value is %.7f, %.7f, %.7f, %.7f.' % defaults['bbox'],
                   type='float', nargs=4)
 
 parser.add_option('-p', '--padding', dest='padding',
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..861a9f5
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,5 @@
+[egg_info]
+tag_build = 
+tag_date = 0
+tag_svn_revision = 0
+
diff --git a/setup.py b/setup.py
index 963c7da..f4f74fe 100644
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,10 @@
 #!/usr/bin/env python
 
-from distutils.core import setup
+try:
+    from setuptools import setup
+except ImportError:
+    from distutils.core import setup
+
 import pkg_resources
 import sys
 
@@ -19,10 +23,10 @@ def is_installed(name):
 requires = ['ModestMaps >=1.3.0','simplejson', 'Werkzeug']
 
 # Soft dependency on PIL or Pillow
-if is_installed('Pillow') or sys.platform == 'win32':
-    requires.append('Pillow')
-else:
+if is_installed('PIL'):
     requires.append('PIL')
+else:
+    requires.append('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