pax_global_header 0000666 0000000 0000000 00000000064 13042202720 0014502 g ustar 00root root 0000000 0000000 52 comment=19395e06a82321d6d9aae0617524d30f1fe0a01e
TileStache-1.51.5/ 0000775 0000000 0000000 00000000000 13042202720 0013620 5 ustar 00root root 0000000 0000000 TileStache-1.51.5/.gitignore 0000664 0000000 0000000 00000000204 13042202720 0015604 0 ustar 00root root 0000000 0000000 *.pyc
doc
dist
build
TileStache-*.*
TileStache-*.*.tar.gz
TileStache.egg-info/
*.dbf
*.prj
*.sbn
*.sbx
*.shp
*.shx
.coverage
.idea/
TileStache-1.51.5/.travis.yml 0000664 0000000 0000000 00000001671 13042202720 0015736 0 ustar 00root root 0000000 0000000 # This is the config file for building TileStache and running the test suite
# with Travis-ci.org
language: python
sudo: required
dist: trusty
python:
- "2.7"
- "3.4"
addons:
postgresql: "9.4"
apt:
packages:
- postgresql-9.4-postgis-2.3
- python-dev
- libgdal1-dev
- python-werkzeug
virtualenv:
system_site_packages: true
services:
- memcached
- postgresql
install:
- export CPLUS_INCLUDE_PATH=/usr/include/gdal
- export C_INCLUDE_PATH=/usr/include/gdal
- pip install gdal==1.10
- pip install nose coverage
- pip install -r requirements.txt
before_script:
- psql -U postgres -c "drop database if exists test_tilestache"
- psql -U postgres -c "create database test_tilestache"
- psql -U postgres -c "create extension postgis" -d test_tilestache
script:
- nosetests -v --with-coverage --cover-package TileStache
notifications:
email:
recipients:
- mike-travisci@teczno.com
TileStache-1.51.5/API.html 0000664 0000000 0000000 00000174234 13042202720 0015132 0 ustar 00root root 0000000 0000000
TileStache API
TileStache API
TileStache is a Python-based server application that can serve up map tiles
based on rendered geographic data. You might be familiar with
TileCache the venerable open source WMS
server from MetaCarta. TileStache is similar, but we hope simpler and
better-suited to the needs of designers and cartographers.
This document covers TileStache version N.N.N.
See also detailed module and class reference.
TileStache URLs are based on a Google Maps-like scheme:
/{layer name}/{zoom}/{column}/{row}.{extension}
An example tile URL might look like this:
http://example.org/path/tile.cgi/streets/12/656/1582.png
For JSON responses such as those from the Vector provider, URLs
can include an optional callback for JSONP support:
http://example.org/path/tile.cgi/streets/12/656/1582.json?callback=funcname
Interactive, slippy-map previews of tiles are also available:
/{layer name}/preview.html
Get a type string and tile binary for a given request layer tile.
Arguments to getTile
:
- layer
-
Instance of
Core.Layer
to render.
- coord
-
One
ModestMaps.Core.Coordinate
corresponding to a single tile.
- extension
-
Filename extension to choose response type, e.g. "png" or
"jpg".
- ignore_cached
-
Optional boolean: always re-render the tile, whether it's in the cache or
not. Default False.
Return value of getTile
is a tuple containing a mime-type string
such as "image/png" and a complete byte string representing the
rendered tile.
See
TileStache.getTile
documentation for more information.
Generate a mime-type and response body for a given request. This is the function
to use when creating new HTTP interfaces to TileStache.
Arguments to requestHandler
:
- config
-
Required file path string for a JSON configuration file or a configuration
object with cache, layers, and dirpath
properties, such as
TileStache.Config.Configuration
.
- path_info
-
Required end portion of a request URL including the layer name and tile
coordinate, e.g. "/roads/12/656/1582.png".
- query_string
-
Optional query string. Currently used only for JSONP callbacks.
- script_name
-
Optional script name corresponds to CGI environment variable SCRIPT_NAME, used to calculate correct 302 redirects.
Return value of requestHandler
is a tuple containing a mime-type string
such as "image/png" and a complete byte string representing the
rendered tile.
See
TileStache.requestHandler
documentation for more information.
We currently provide three scripts for serving tiles: one for a WSGI-based
webserver, one for a CGI-based webserver, and one for Apache mod_python
.
TileStache comes with a WSGI application and a
Werkzeug web server. To use the
built-in server, run tilestache-server.py,
which (by default) looks for a config file named tilestache.cfg
in the current directory and then serves tiles on
http://127.0.0.1:8080/. Check tilestache-server.py --help
to change these defaults.
Alternatively, any WSGI server can be pointed at an instance of
TileStache.WSGITileServer. Here’s how to use it with
gunicorn:
$ gunicorn "TileStache:WSGITileServer('/path/to/tilestache.cfg')"
The same configuration can be served with uWSGI like so. Note the
usage of the --eval option over --module as this latter
option does not support argument passing:
$ uwsgi --http :8080 --eval 'import TileStache; \
application = TileStache.WSGITileServer("/path/to/tilestache.cfg")'
See
TileStache.WSGITileServer
documentation for more information.
Using TileStache through CGI supports basic tile serving, and is useful for
simple testing and low-to-medium traffic websites. This is a complete, working
CGI script that looks for configuration in a local file called
tilestache.cfg:
#!/usr/bin/python
import os, TileStache
TileStache.cgiHandler(os.environ, 'tilestache.cfg', debug=True)
See
TileStache.cgiHandler
documentation for more information.
Using TileStache through mod_python
improves performance by
caching imported modules, but must be configured via the Apache webserver
config. This is a complete example configuration for a webserver publishing
tiles configured by a file in /etc
:
<Directory /var/www/tiles>
AddHandler mod_python .py
PythonHandler TileStache::modpythonHandler
PythonOption config /etc/tilestache.cfg
</Directory>
See
TileStache.modPythonHandler
documentation for more information.
TileStache configuration is stored in JSON files, and is composed of two main
top-level sections: "cache" and "layers". There are
examples of both in this minimal sample configuration:
{
"cache": {"name": "Test"},
"layers": {
"ex": {
"provider": {"name": "mapnik", "mapfile": "style.xml"},
"projection": "spherical mercator"
}
}
}
A Cache is the part of TileStache that stores static files to speed up future
requests. A few default caches are shown here, with additional cache classes
defined in
TileStache.Goodies.Caches
.
Jump to Test, Disk,
Multi, Memcache,
Redis,
or S3 cache.
Simple cache that doesn’t actually cache anything.
Activity is optionally logged, though.
Example configuration:
{
"cache": {
"name": "Test",
"verbose": true
},
"layers": { … }
}
Test cache parameters:
- verbose
-
Optional boolean flag to write cache activities to a logging function,
defaults to False if omitted.
See
TileStache.Caches.Test
documentation for more information.
Caches files to disk.
Example configuration:
{
"cache": {
"name": "Disk",
"path": "/tmp/stache",
"umask": "0000",
"dirs": "portable",
"gzip": ["xml", "json"]
},
"layers": { … }
}
Disk cache parameters:
- path
-
Required local directory path where files should be stored.
- umask
-
Optional string representation of octal permission mask for stored files.
Defaults to "0022".
- dirs
-
Optional string saying whether to create cache directories that are safe or
portable. For an example tile 12/656/1582.png,
"portable" creates matching directory trees while
"safe" guarantees directories with fewer files, e.g.
12/000/656/001/582.png. Defaults to "safe".
- gzip
-
Optional list of file formats that should be stored in a
compressed form. Defaults to ["txt", "text", "json", "xml"].
Provide an empty list in the configuration for no compression.
If your configuration file is loaded from a remote location, e.g.
http://example.com/tilestache.cfg, the path must
be an unambiguous filesystem path, e.g. "file:///tmp/cache".
See
TileStache.Caches.Disk
documentation for more information.
Caches tiles to multiple, ordered caches.
Multi cache is well-suited for a speed-to-capacity gradient, for example a
combination of Memcache and S3
to take advantage of the high speed of memcache and the high capacity of S3.
Each tier of caching is checked sequentially when reading from the cache, while
all tiers are used together for writing. Locks are only used with the first cache.
Example configuration:
{
"cache": {
"name": "Multi",
"tiers": [
{
"name": "Memcache",
"servers": ["127.0.0.1:11211"]
},
{
"name": "Disk",
"path": "/tmp/stache"
}
]
},
"layers": { … }
}
Multi cache parameters:
- tiers
-
Required list of cache configurations. The fastest, most local cache should
be at the beginning of the list while the slowest or most remote cache
should be at the end. Memcache and S3 together make a great pair.
See
TileStache.Caches.Multi
documentation for more information.
Caches tiles to Memcache,
requires python-memcached.
Example configuration:
{
"cache": {
"name": "Memcache",
"servers": ["127.0.0.1:11211"],
"revision": 0,
"key prefix": "unique-id"
},
"layers": { … }
}
Memcache cache parameters:
- servers
-
Optional array of servers, list of "{host}:{port}" pairs.
Defaults to ["127.0.0.1:11211"] if omitted.
- revision
-
Optional revision number for mass-expiry of cached tiles regardless of lifespan.
Defaults to 0.
- key prefix
-
Optional string to prepend to Memcache generated key.
Useful when running multiple instances of TileStache
that share the same Memcache instance to avoid key
collisions. The key prefix will be prepended to the
key name. Defaults to "".
See
TileStache.Memcache.Cache
documentation for more information.
Caches tiles to Redis,
requires redis-py and redis server.
Example configuration:
{
"cache": {
"name": "Redis",
"host": "localhost",
"port": 6379,
"db": 0,
"key prefix": "unique-id"
},
"layers": { … }
}
Redis cache parameters:
- host
-
Defaults to "localhost" if omitted.
- port
-
Integer; Defaults to 6379 if omitted.
- db
-
Integer; Redis database number, defaults to 0 if omitted.
- key prefix
-
Optional string to prepend to generated key.
Useful when running multiple instances of TileStache
that share the same Redis database to avoid key
collisions (though the prefered solution is to use a different
db number). The key prefix will be prepended to the
key name. Defaults to "".
See
TileStache.Redis.Cache
documentation for more information.
Caches tiles to Amazon S3,
requires boto (2.0+).
Example configuration:
{
"cache": {
"name": "S3",
"bucket": "<bucket name>",
"access": "<access key>",
"secret": "<secret key>"
"reduced_redundancy": False
},
"layers": { … }
}
S3 cache parameters:
- bucket
-
Required bucket name for S3. If it doesn’t exist, it will be created.
- access
-
Optional access key ID for your S3 account. You can find this under “Security
Credentials” at your AWS account page.
- secret
-
Optional secret access key for your S3 account. You can find this under “Security
Credentials” at your AWS account page.
- 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 additional price and time required for each
lock set in S3.
- path
-
Optional path under bucket to use as the cache directory. ex. 'path': 'cache' will
put tiles under {bucket}/cache/
- reduced_redundancy
-
Optional boolean specifying whether to use Reduced Redundancy Storage mode in S3.
Files stored with RRS incur less cost but have reduced redundancy in Amazon's storage
system.
When access or secret are not provided, the environment variables
AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY will be used.
See Boto documentation
for more information.
See
TileStache.S3.Cache
documentation for more information.
New caches with functionality that’s not strictly core to TileStache first appear in
TileStache.Goodies.Caches.
LimitedDisk
Cache that stores a limited amount of data. This is an example cache that uses
a SQLite database to track sizes and last-read times for cached tiles, and
removes least-recently-used tiles whenever the total size of the cache exceeds
a set limit. See
TileStache.Goodies.Caches.LimitedDisk
for more information.
A Layer represents a set of tiles in TileStache. It keeps references to
providers, projections, a Configuration instance, and other details required
for to the storage and rendering of a tile set.
Example layer configuration:
{
"cache": …,
"layers":
{
"example-name":
{
"provider": { … },
"metatile": { … },
"preview": { … },
"stale lock timeout": …,
"cache lifespan": …,
"projection": …,
"write cache": …,
"bounds": { … },
"allowed origin": …,
"maximum cache age": …,
"redirects": …,
"tile height": …,
"jpeg options": …,
"png options": …,
"pixel effect": { … }
}
}
}
The public-facing URL of a single tile for this layer might look like this:
http://example.com/tilestache.cgi/example-name/0/0/0.png
Shared layer parameters:
- provider
-
Refers to a Provider, explained in detail under
Providers.
- metatile
-
Optionally makes it possible for multiple individual tiles to be rendered
at one time, for greater speed and efficiency. This is commonly used for
bitmap providers such as Mapnik. See Metatiles
for more information.
- preview
-
Optionally overrides the starting point for the built-in per-layer slippy
map preview, useful for image-based layers where appropriate. See
Preview for more information.
- projection
-
Names a geographic projection, explained in
Projections. If omitted, defaults to
"spherical mercator".
- stale lock timeout
-
An optional number of seconds to wait before forcing a lock that might be
stuck. This is defined on a per-layer basis, rather than for an entire
cache at one time, because you may have different expectations for the
rendering speeds of different layer configurations. Defaults to
15.
- cache lifespan
-
An optional number of seconds that cached tiles should be stored. This is
defined on a per-layer basis. Defaults to forever if None,
0 or omitted.
- write cache
-
An optional boolean value to allow skipping cache write altogether.
This is defined on a per-layer basis. Defaults to true if omitted.
- bounds
-
An optional dictionary of six tile boundaries to limit the rendered area:
low (lowest zoom level), high (highest zoom level),
north, west, south, and east
(all in degrees). When any of these are omitted, default values are
north=89, west=-180, south=-89,
east=180, low=0, and high=31.
A list of dictionaries will also be accepted, indicating a set of possible
bounding boxes any one of which includes possible tiles.
- allowed origin
-
An optional string that shows up in the response HTTP header
Access-Control-Allow-Origin,
useful for when you need to provide javascript direct access to response
data such as GeoJSON or pixel values. The header is part of a
W3C working draft.
Pro-tip: if you want to allow maximum permissions and minimal
security headache, use a value of "*" for this.
- maximum cache age
-
An optional number of seconds used to control behavior of downstream caches.
Causes TileStache responses to include
Cache-Control
and Expires
HTTP response headers. Useful when TileStache is itself hosted behind an HTTP
cache such as Squid, Cloudfront, or Akamai.
- redirects
-
An optional dictionary of per-extension HTTP redirects, treated as
lowercase. Useful in cases where your tile provider can support many
formats but you want to enforce limits to save on cache usage. If a request
is made for a tile with an extension in the dictionary keys, a response can
be generated that redirects the client to the same tile with another
extension. For example, use the setting {"jpg": "png"}
to force all requests for JPEG tiles to be redirected to PNG tiles.
- tile height
-
An optional integer gives the height of the image tile in pixels. You
almost always want to leave this at the default value of 256,
but you can use a value of 512 to create double-size,
double-resolution tiles for high-density phone screens.
- jpeg options
-
An optional dictionary of JPEG creation options, passed through
to PIL.
Valid options include quality (integer), progressive
(boolean), and optimize (boolean).
- png options
-
An optional dictionary of PNG creation options, passed through
to PIL.
Valid options include palette (URL or filename), palette256
(boolean) and optimize (boolean).
- pixel effect
-
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.
A Provider is the part of TileStache that stores static files to speed up
future requests. A few default providers are shown here, with additional
provider classes defined in
TileStache.Goodies.Providers
Jump to Mapnik (image), Proxy,
Vector, URL Template,
MBTiles, Mapnik (grid),
or Pixel Sandwich provider.
Built-in Mapnik provider, renders map images from Mapnik XML files.
Example Mapnik provider configuration:
{
"cache": { … }.
"layers":
{
"roads":
{
"provider":
{
"name": "mapnik",
"mapfile": "style.xml"
}
}
}
}
Mapnik provider parameters:
- mapfile
-
Required local file path to Mapnik XML file.
- fonts
-
Optional relative directory path to *.ttf font files
See
TileStache.Mapnik.ImageProvider
for more information.
Proxy provider, to pass through and cache tiles from other places.
Example Proxy provider configuration:
{
"cache": { … }.
"layers":
{
"roads":
{
"provider":
{
"name": "proxy",
"url": "http://tile.openstreetmap.org/{Z}/{X}/{Y}.png"
}
}
}
}
Proxy provider parameters:
- url
-
Optional URL template for remote tiles, for example:
"http://tile.openstreetmap.org/{Z}/{X}/{Y}.png"
- provider
-
Optional provider name string from Modest Maps built-ins. See
ModestMaps.builtinProviders.keys()
for a list. Example:
"OPENSTREETMAP".
- timeout
Defines a timeout in seconds for the request.
If not defined, the global default timeout setting will be used.
See
TileStache.Providers.Proxy
for more information.
Provider that returns vector representation of features in a data source.
Currently two serializations and three encodings are supported for a total
of six possible kinds of output with these tile name extensions:
- GeoJSON (.geojson)
-
Conforms to the GeoJSON specification.
- Arc GeoServices JSON (.arcjson)
-
Conforms to ESRI’s GeoServices REST specification.
- GeoBSON (.geobson) and Arc GeoServices BSON (.arcbson)
-
BSON-encoded GeoJSON and Arc JSON.
- GeoAMF (.geoamf) and Arc GeoServices AMF (.arcamf)
-
AMF0-encoded GeoJSON and Arc JSON.
Example Vector provider configurations:
{
"cache": { … }.
"layers":
{
"vector-postgis-points":
{
"provider": {"name": "vector", "driver": "PostgreSQL",
"parameters": {"dbname": "geodata", "user": "geodata",
"table": "planet_osm_point"}}
},
"vector-shapefile-lines":
{
"provider": {"name": "vector", "driver": "shapefile",
"parameters": {"file": "oakland-uptown-line.latlon.shp"},
"properties": {"NAME": "name", "HIGHWAY": "highway"}}
},
"vector-sf-streets":
{
"provider": {"name": "vector", "driver": "GeoJSON",
"parameters": {"file": "stclines.json"},
"properties": ["STREETNAME"]}
},
{
"provider": {"name": "vector", "driver": "MySQL",
"parameters": {"dbname": "geodata", "port": "3306",
"user": "geotest", "table": "test"},
"properties": ["name"], "id_property": "oid"}
},
{
"provider": {"name": "vector", "driver": "Oracle",
"parameters": {"dbname": "ORCL", "port": "3306",
"user": "scott", "password": "tiger",
"table": "test"}}
},
{
"provider": {"name": "vector", "driver": "Spatialite",
"parameters": {"file": "test.sqlite", "layer": "test"}}
}
}
}
Vector provider parameters:
- driver
-
String used to identify an OGR driver. Currently, only
"ESRI Shapefile", "PostgreSQL", and
"GeoJSON" are supported as data source drivers, with
"postgis" and "shapefile" accepted as
synonyms. Not case-sensitive.
- parameters
-
Dictionary of parameters for each driver.
- PostgreSQL, MySQL and Oracle
-
"dbname" parameter is required, with name of database.
"host", "user", and "password"
are optional connection parameters. One of "table" or
"query" is required, with a table name in the first case
and a complete SQL query in the second.
- Shapefile and GeoJSON
-
"file" parameter is required, with filesystem path to
data file.
- Spatialite
-
"file" parameter is required, with filesystem path to
data file.
"layer" parameter is required, and is the name of
the SQLite table.
- properties
-
Optional list or dictionary of case-sensitive output property names.
If omitted, all fields from the data source will be included in response.
If a list, treated as a whitelist of field names to include in response.
If a dictionary, treated as a whitelist and re-mapping of field names.
- clipped
-
Default is true.
Boolean flag for optionally clipping the output geometries to the
bounds of the enclosing tile, or the string value "padded"
for clipping to the bounds of the tile plus 5%. This results in incomplete
geometries, dramatically smaller file sizes, and improves performance and
compatibility with Polymaps.
- projected
-
Default is false.
Boolean flag for optionally returning geometries in projected rather than
geographic coordinates. Typically this means EPSG:900913 a.k.a.
spherical mercator projection. Stylistically a poor fit for GeoJSON, but
useful when returning Arc GeoServices responses.
- precision
-
Default is 6.
Optional number of decimal places to use for floating point values.
- spacing
-
Optional number of tile pixels for spacing geometries in responses. Used
to cut down on the number of returned features by ensuring that only those
features at least this many pixels apart are returned. Order of features
in the data source matters: early features beat out later features.
- verbose
-
Default is false.
Boolean flag for optionally expanding output with additional whitespace
for readability. Results in larger but more readable GeoJSON responses.
- skip_empty_fields
-
Default is False.
Boolean flag for optionally skipping empty fields when assembling the GEOJSON
feature's properties dictionary.
See
TileStache.Vector
for more information.
Templated URL provider, to pass through and cache tiles from WMS servers.
Example UrlTemplate provider configuration:
{
"cache": { … }.
"layers":
{
"roads":
{
"provider":
{
"name": "url template",
"template": "http://example.com/?bbox=$xmin,$ymin,$xmax,$ymax"
}
}
}
}
UrlTemplate provider parameters:
- template
-
String with substitutions suitable for use in
string.Template.
The variables available for substitution are width,
height (in pixels), srs (in
PROJ.4 format),
xmin, ymin, xmax, ymax (in
projected map units), and zoom.
Example:
"http://example.com/?bbox=$xmin,$ymin,$xmax,$ymax&bboxSR=102113&size=$width,$height&imageSR=102113&format=jpg&f=image".
- referer
-
Optional string with HTTP Referer URL to send to WMS server.
Some WMS servers use the Referer request header to authenticate requests;
this parameter provides one.
- source projection
Names a geographic projection, explained in Projections, that
coordinates should be transformed to for requests.
- timeout
Defines a timeout in seconds for the request.
If not defined, the global default timeout setting will be used.
See
TileStache.Providers.UrlTemplate
for more information.
Provider that reads stored images from MBTiles tilesets.
Example MBTiles provider configuration:
{
"cache": { … }.
"layers":
{
"roads":
{
"provider":
{
"name": "mbtiles",
"tileset": "collection.mbtiles"
}
}
}
}
MBTiles provider parameters:
- tileset
-
Required local file path to MBTiles tileset file, a SQLite 3 database file.
See
TileStache.MBTiles.Provider
for more information.
Built-in Mapnik UTF Grid provider,
renders JSON raster objects from Mapnik 2.0+.
Example Mapnik Grid provider configurations:
{
"cache": { … }.
"layers":
{
"one-grid":
{
"provider":
{
"name": "mapnik grid",
"mapfile": "style.xml",
"layer_index": 1
},
}
"two-grids":
{
"provider":
{
"name": "mapnik grid",
"mapfile": "style.xml",
"layers":
[
[2, ["population"]],
[0, ["name", "population"]]
]
}
}
}
}
Mapnik Grid provider parameters:
- 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 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.
- scale
-
Optional scale factor of output raster, defaults to 4 (64×64).
- 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.
See
TileStache.Mapnik.GridProvider
for more information.
The Sandwich Provider supplies a Photoshop-like rendering pipeline, making it
possible to use the output of other configured tile layers as layers or masks
to create a combined output. Sandwich is modeled on Lars Ahlzen’s
TopOSM.
Sandwich require the external Blit library to function.
Example Sandwich provider configurations:
{
"cache": { … }.
"layers":
{
"sandwiches":
{
"provider":
{
"name": "Sandwich",
"stack":
[
{"src": "base"},
{"src": "outlines", "mask": "halos"},
{"src": "streets"}
]
}
},
"base":
{
"provider": {"name": "mapnik", "mapfile": "mapnik-base.xml"}
},
"halos":
{
"provider": {"name": "mapnik", "mapfile": "mapnik-halos.xml"},
"metatile": {"buffer": 128}
},
"outlines":
{
"provider": {"name": "mapnik", "mapfile": "mapnik-outlines.xml"},
"metatile": {"buffer": 16}
},
"streets":
{
"provider": {"name": "mapnik", "mapfile": "mapnik-streets.xml"},
"metatile": {"buffer": 128}
}
}
}
Sandwich provider parameters:
- stack
-
Required layer or stack of layers that can be combined to create output.
The stack is a list, with solid color or raster layers from elsewhere
in the configuration, and is described in detail in the dedicated
Sandwich documentation.
See
TileStache.Sandwich
for more information.
New providers with functionality that’s not strictly core to TileStache first appear in
TileStache.Goodies.Providers.
Grid
Grid rendering for TileStache. UTM provider draws gridlines in tiles,
in transparent images suitable for use as map overlays. See
TileStache.Goodies.Providers.Grid
for more information.
PostGeoJSON
Provider that returns GeoJSON data responses from PostGIS queries. This is an
example of a provider that does not return an image, but rather queries a
database for raw data and replies with a string of GeoJSON. For example, it’s
possible to retrieve data for locations of OpenStreetMap points of interest
based on a query with a bounding box intersection. See
TileStache.Goodies.Providers.PostGeoJSON
for more information.
SolrGeoJSON
Provider that returns GeoJSON data responses from Solr spatial queries. This is
an example of a provider that does not return an image, but rather queries a
Solr instance for raw data and replies with a string of GeoJSON. See
TileStache.Goodies.Providers.SolrGeoJSON
for more information.
Composite
Layered, composite rendering for TileStache. See
TileStache.Goodies.Providers.Composite
for more information.
MirrorOSM
Requests for tiles have the side effect of running
osm2pgsql to populate
a PostGIS database of OpenStreetMap data from a remote API source. It would be
normal to use this provider outside the regular confines of a web server,
perhaps with a call to tilestache-seed.py
governed by a cron job or
some other out-of-band process. See
TileStache.Goodies.Providers.MirrorOSM
for more information.
A Projection defines the relationship between the rendered tiles and the
underlying geographic data. Generally, just one popular projection is used for
most web maps, "spherical mercator".
Provided projections:
- spherical mercator
-
Projection for most commonly-used web map tile scheme, equivalent to
EPSG:900913
. The simplified projection used here is described
in greater detail at
openlayers.org.
- WGS84
-
Unprojected projection for the other commonly-used web map tile scheme,
equivalent to
EPSG:4326
.
You can define your own projection, with a module and object name as arguments:
"layer-name": {
...
"projection": "Module:Object",
}
The object must include methods that convert between coordinates, points, and
locations. See the included mercator and WGS84 implementations for example.
You can also instantiate a projection class using this syntax:
"layer-name": {
...
"projection": "Module:Object()"
}
See
TileStache.Geography
for more information.
Metatiles are larger areas to be rendered at one time, often used because it’s
more efficient to render a large number of contiguous tiles at once than each
one separately.
Example metatile configuration:
{
"cache": …,
"layers":
{
"example-name":
{
"provider": { … },
"metatile":
{
"rows": 4,
"columns": 4,
"buffer": 64
}
}
}
}
This example metatile is four rows tall and four columns wide with a buffer
of 64 pixels, for a total bitmap size of 4 × 256 + 64 × 2 = 1152.
Metatile parameters:
- rows
-
Height of the metatile measured in tiles.
- columns
-
Width of the metatile measured in tiles.
- buffer
-
Buffer area around the metatile, measured in pixels. This is useful for
providers with labels or icons, where it’s necessary to draw a bit extra
around the edges to ensure that text is not cut off.
TileStache includes a built-in slippy map preview, that can be viewed in a
browser using the URL /{layer name}/preview.html, e.g.
http://example.org/example-name/preview.html. The settings for
this preview are completely optional, but can be set on a per-layer basis
for control over starting location and file extension.
Example preview configuration:
{
"cache": …,
"layers":
{
"example-name":
{
"provider": { … },
"preview":
{
"lat": 37.80439,
"lon": -122.27127,
"zoom": 15,
"ext": "jpg"
}
}
}
}
This example preview displays JPG tiles, and is centered on
37.80439, -122.27127 at zoom 15.
Preview parameters:
- lat
-
Starting latitude in degrees.
- lon
-
Starting longitude in degrees.
- zoom
-
Starting zoom level.
- ext
-
Filename extension, e.g. "png".
TileStache supports configurable index pages for the front page of an instance.
A custom index can be specified as a filename relative to the configuration
location. Typically an HTML document would be given here, but other kinds of
files such as images can be used, with MIME content-type headers determined by
mimetypes.guess_type.
A simple text greeting is displayed if no index is provided.
Example index page configuration:
{
"cache": …,
"layers": …,
"index": "filename.html"
}
}
Example index page configuration using a remote image:
{
"cache": …,
"layers": …,
"index": "http://tilestache.org/mustaches.jpg"
}
}
TileStache includes basic support for Python’s built-in
logging system, with
a logging level settable in the main configuration file. Possible logging levels
include "debug", "info", "warning",
"error" and "critical", described in the
basic logging tutorial.
Example logging configuration:
{
"cache": …,
"layers": …,
"logging": "debug"
}
}
TileStache relies on duck typing
rather than inheritance for extensibility, so all guidelines for customization
below explain what methods and properties must be defined on objects for them
to be valid as providers, caches, and configurations.
Example external provider configuration:
{
"cache": …,
"layers":
{
"example-name":
{
"provider":
{
"class": "Module:Classname",
"kwargs": {"frob": "yes"}
}
}
}
}
The class value is split up into module and classname, and
dynamically included. If this doesn’t work for some reason, TileStache will
fail loudly to let you know. The kwargs value is fed to the class
constructor as a dictionary of keyword args. If your defined class doesn’t
accept any of these keyword arguments, TileStache will throw an exception.
A provider must offer at least one of two methods for rendering map areas:
renderTile
or renderArea
. A provider must also accept
an instance of Layer
as the first argument to its constructor.
Return value of both renderTile
and renderArea
is an
object with a save
method that can accept a file-like object and
a format name, typically an instance of the PIL.Image
object but
allowing for creation of providers that save text, raw data or other non-image
response.
A minimal provider stub class:
class ProviderStub:
def __init__(self, layer):
# create a new provider for a layer
raise NotImplementedError
def renderTile(self, width, height, srs, coord):
# return an object with a PIL-like save() method for a tile
raise NotImplementedError
def renderArea(self, width, height, srs, xmin, ymin, xmax, ymax, zoom):
# return an object with a PIL-like save() method for an area
raise NotImplementedError
In cases where a provider generates a response that should not be cached,
renderTile
and renderArea
may raise the
Core.NoTileLeftBehind
exception in lieu of a normal response. The exception is constructed using the
intended response object, but nothing will be written to cache. This feature
might useful in cases where a full tileset is being rendered for static
hosting, and you don’t want millions of identical ocean tiles.
See
TileStache.Providers
for more information on custom providers and
TileStache.Goodies.Providers
for examples of custom providers.
Draws a single tile at a time.
Arguments to renderTile
:
- width
-
Pixel width of tile, typically 256.
- height
-
Pixel height of tile, typically 256.
- srs
-
Projection as Proj4 string.
"+proj=longlat +ellps=WGS84 +datum=WGS84" is an example, see
TileStache.Geography
for actual values.
- coord
-
Coordinate object representing a single tile.
Return value of renderTile
is a
PIL.Image
or other saveable object, used like this:
provider.renderTile(…).save(file, "XML")
Draws a variably-sized area, and is used when drawing metatiles.
Non-image providers and metatiles do not mix. If your provider returns JSON,
plaintext, XML, or some other non-PIL format, implement only the
renderTile
method.
Arguments to renderArea
:
- width
-
Pixel width of tile, typically 256.
- height
-
Pixel height of tile, typically 256.
- srs
-
Projection as Proj4 string.
"+proj=longlat +ellps=WGS84 +datum=WGS84" is an example, see
TileStache.Geography
for actual values.
- xmin
-
Minimum x boundary of rendered area in projected coordinates.
- ymin
-
Minimum y boundary of rendered area in projected coordinates.
- xmax
-
Maximum x boundary of rendered area in projected coordinates.
- ymax
-
Maximum y boundary of rendered area in projected coordinates.
- zoom
-
Zoom level of final map. Technically this can be derived from the other
arguments, but that’s a hassle so we’ll pass it in explicitly.
Return value of renderArea
is a
PIL.Image
or other saveable object, used like this:
provider.renderArea(…).save(file, "PNG")
A provider may offer a method for custom response types,
getTypeByExtension
. This method returns a tuple with two strings:
a mime-type and a format.
Arguments to getTypeByExtension
:
- extension
-
Filename extension string, e.g. "png", "json", etc.
Example external provider configuration:
{
"cache":
{
"class": "Module:Classname",
"kwargs": {"frob": "yes"}
},
"layers": { … }
}
The class value is split up into module and classname, and
dynamically included. If this doesn’t work for some reason, TileStache will
fail loudly to let you know. The kwargs value is fed to the class
constructor as a dictionary of keyword args. If your defined class doesn’t
accept any of these keyword arguments, TileStache will throw an exception.
A cache must provide all of these five methods: lock
,
unlock
, remove
, read
, and save
.
Each method requires three arguments:
- layer
-
Instance of a layer.
- coord
-
Single Coordinate that represents a tile.
- format
-
String like "png" or "jpg" that is used as a
filename extension.
The save
method accepts an additional argument before the others:
- body
-
Raw content to save to the cache.
A minimal cache stub class:
class CacheStub:
def lock(self, layer, coord, format):
# lock a tile
raise NotImplementedError
def unlock(self, layer, coord, format):
# unlock a tile
raise NotImplementedError
def remove(self, layer, coord, format):
# remove a tile
raise NotImplementedError
def read(self, layer, coord, format):
# return raw tile content from cache
raise NotImplementedError
def save(self, body, layer, coord, format):
# save raw tile content to cache
raise NotImplementedError
See
TileStache.Caches
for more information on custom caches and
TileStache.Goodies.Caches
for examples of custom caches.
A complete configuration object includes cache,
layers, and dirpath properties and
optional index property:
- cache
-
Cache instance, e.g.
TileStache.Caches.Disk
etc. See
TileStache.Caches
for details on what makes a usable cache.
- layers
-
Dictionary of layers keyed by name.
- dirpath
-
Local filesystem path for this configuration, useful for expanding relative
paths.
- index
-
Two-element tuple with mime-type and content for installation index page.
When creating a custom layers dictionary, e.g. for dynamic layer
collections backed by some external configuration, these
dictionary methods
must be provided for a complete collection of layers:
- keys
-
Return list of layer name strings.
- items
-
Return list of (name, layer) pairs.
- __contains__
-
Return boolean true if given key is an existing layer.
- __getitem__
-
Return existing layer object for given key or raise
KeyError
.
A minimal layers dictionary stub class:
class LayersStub:
def keys(self):
# return a list of key strings
raise NotImplementedError
def items(self):
# return a list of (key, layer) tuples
raise NotImplementedError
def __contains__(self, key):
# return True if the key is here
raise NotImplementedError
def __getitem__(self, key):
# return the layer named by the key
raise NotImplementedError
TileStache-1.51.5/CHANGELOG 0000664 0000000 0000000 00000043245 13042202720 0015042 0 ustar 00root root 0000000 0000000 2017-01-25: 1.51.5
- Fix to be compliant with mapnik 3 grid renderer
- Fix of preview protocol
- Moving towards python 3 compliance
2016-12-21: 1.51.4
- Support for GDAL 2.x types OFTInteger64 and OFTInteger64 on Vector Tiles
2016-10-30: 1.51.3
- Fixes for debian packaging
2016-10-27: 1.51.2
- Adding support for .pbf format for Mapbox Vector Tiles
- Configuration can be passed as a dictionary in addition to path to JSON file.
2016-09-01: 1.51.1
- Bugfix on Mapnik image object
2016-09-01: 1.51
- Replacing PIL by Pillow
- Added example with raster
2015-01-30: 1.50.1
- Fixed missing ttf file
2015-01-21: 1.50.0
- Added 'scale factor' option to Mapnik image provider
- Added S3 policy specification to config
- Added dictionary config to VecTiles config
- Added pixel effects to all layer configurations
- Added remote configuration to MapnikGrid to match Mapnik
- Fixed UtfGridComposite to work with latest TileStache
- Fixed Pillow dependency
- Fixed "cache lifespan" in Redis cache
2014-06-07: 1.49.11
- Fixed 'allow-origin header' getting overwritten (JesseCrocker)
- Added 'source projection' parameter to UrlTemplate provider (JesseCrocker)
- Fixed minor AttributeError when palette256 argument is None (PerryGeo)
- Added Timeout feature to Proxy and UrlTemplate providers (Juliomalegria)
- Adjusted curl examples (Peter Richardson)
- More specific error message when SpatialRef is missing (probabble)
2014-05-10: 1.49.10
- Fixed Travis build.
- Fixed import errors for case-insensitive filesystems.
- Added TileStache Vagrant machine configuration.
- Fixed some memcache testing problems.
2014-05-10: 1.49.9
- Moved everything to PyPI and fixed VERSION kerfuffle.
2013-07-02: 1.49.8
- Dropped Proxy provider srs=900913 check.
- Updated most JSON mime-types from text/json to application/json.
- Fixed requestHandler() query_string argument to match existing API docs.
- Switched order of simplify and transform in VecTiles queries to handle GEOS bug.
- Made Rummy CORS-compliant.
2013-06-23: 1.49.7
- Added VecTiles:MultiProvider to merge TopoJSON and GeoJSON output layers.
2013-06-21: 1.49.6
- Switched to ST_SimplifyPreserveTopology in VecTiles clipping to calm GEOS.
2013-06-20: 1.49.5
- Tweaked VecTiles Provider documentation.
2013-06-20: 1.49.4
- Added TopoJSON support to VecTiles Provider.
2013-06-15: 1.49.3
- Fixed runaway query bug in VecTiles Provider with looped bounds in query_columns().
2013-06-14: 1.49.2
- Fixed SQL bug in VecTiles Provider for limiting column queries.
2013-06-14: 1.49.1
- Updated magic column names in VecTiles Provider to __geometry__ and __id__ for feature ID.
2013-06-05: 1.49.0
- Added option to use S3 reduced redundancy storage for cheaper caching.
- Made Postgres connections per-request instead of long term in VecTiles Provider.
2013-05-28: 1.48.2
- Fixed a serious documentation incompatibility with TileStache.requestHandler() introduced in 1.47.0.
2013-05-27: 1.48.1
- Fixed Werkzeug dependency in setup.py.
2013-05-26: 1.48.0
- Fixed a serious documentation incompatibility with TileStache.getTile() introduced in 1.47.0.
2013-04-24: 1.47.4
- Fixed from-mbtiles/output-directory use case in tilestache-seed, which I had broken.
2013-04-22: 1.47.3
- Fixed clobbered content from TheTileLeftANote exception.
2013-04-22: 1.47.2
- Fixed getTile bugs introduced in 1.47.0.
2013-04-20: 1.47.1
- Fixed typo in setup.py.
2013-04-20: 1.47.0
- Exposed mutable HTTP status codes and headers to TileStache.getTile() and caches via TheTileLeftANote exception.
- Removed old layer.redirects implementation via TheTileIsInAnotherCastle exception.
- Improved PIL dependency in setup requirements to include Pillow, with patch from Fabian Büchler.
- Fixed coordinate generator usage in tilestache-seed with patch from Seth Fitzsimmons.
- Added Redis cache provider with patch from Matthew Perry.
2013-04-12: 1.46.10
- Prefixed most Postgis functions in VecTile provider with "ST_" for forward compatibility.
2013-04-12: 1.46.9
- Added ogr.OFTDate and ogr.OFTTime to list of acceptable field types in Vector provider.
2013-04-12: 1.46.8
- Added ogr.OFTDateTime to list of acceptable field types in Vector provider, with patch from probabble.
2013-03-27: 1.46.7
- Fixed a stupid error in VecTiles.server discarding important Postgres connection details.
2013-03-26: 1.46.6
- Improved error tolerance in VecTiles client and server.
2013-03-25: 1.46.5
- Added new simplify_until parameter to VecTiles.Provider.
- Added some invalid geometry checks to VecTiles.server.
- Added !bbox! token to VecTiles query parser.
2013-03-19: 1.46.4
- Added new zoom_data parameter to TileStache.Goodies.VecTiles Datasource.
2013-03-19: 1.46.3
- Added new simplify parameter to TileStache.Goodies.VecTiles Provider.
2013-03-14: 1.46.2
- Added new parameters to TileStache.Goodies.VecTiles Datasource and Provider.
2013-03-14: 1.46.1
- Fixed missing TileStache.Goodies.VecTiles by adding it to setup.py.
2013-03-12: 1.46.0
- Added TileStache.Goodies.VecTiles to support efficient vector data tiles for use in Mapnik.
- Fixed alpha-channel issue in Sandwich provider with patch from Lars Ahlzen.
2013-02-26: 1.45.0
- Made S3 credentials optional since Boto allows them in via environment vars or config file.
- Added path parameter to S3 cache, to use a subdirectory rather than bucket root.
- Eased bitmap size checks in Sandwich provider for retina use.
- Added "quadtile" directory layout for disk cache provider.
- Added JSON support to MBTiles provider from Aaron Cope.
- Added Travis CI tests for Vector layer.
2013-02-22: 1.44.0
- Merged Ragi Burhum's Travis continuous integration tests with ~1/3rd test coverage.
- Modified Proxy provider to return untouched original image if size and type match.
- Updated Mapnik usage to reflect recently released versions.
- Fixed Mapnik provider deadlock bug with patch from Lee Shepherd.
- Added "key prefix" argument to Memcache cache provider.
2013-01-03: 1.43.0
- Added new tilestache-list.py script to feed tilestache-seed.py --tile-list.
- Added new "use_locks" option to S3 cache provider to optionally reduce write costs.
2012-10-01: 1.42.1
- Papered over a strange appearance bug in GDAL provider with using cubicspline interpolation.
2012-09-18: 1.42.0
- Added support for tiled bitmap files in Sandwich provider layer src attribute.
2012-09-14: 1.41.0
- Added TileStache.Goodies.Providers.Cascadenik to skip Mapnik XML where possible.
2012-09-09: 1.40.1
- Re-attempting Vector import to trigger a more-useful error than NameError in getProviderByName().
2012-09-04: 1.40.0
- Added TileStache.Sandwich provider based on previous Composite provider under Goodies.
- Modified import method for provider to reduce burden of required packages and improve error output.
2012-07-18: 1.39.0
- Fixed missing merge_grids() function in Mapnik Grid provider.
- Added TileStache.Goodies.ExternalConfigServer from Ryan Breen.
2012-07-05: 1.38.0
- Added Mapnik Grid provider "layer id key" parameter.
- Grid internals: response encodings to explicit UTF-8 for Chromium and using mapnik.render_layer().
2012-07-05: 1.37.0
- Added TileStache.Goodies.Proj4Projection for arbitrary PROJ.4 support.
- Added per-layer "tile height" option, for non-256x256 tiles.
2012-06-27: 1.36.1
- Added more Memcache disconnect_all() calls.
2012-05-22: 1.36.0
- Added --jsonp-callback option to tilestache-seed.py.
2012-05-22: 1.35.1
- Updated tilestache-seed.py manpage to reflect new options.
2012-05-22: 1.35.0
- Added direct-to-S3 cache option to tilestache-seed.py script.
- Changed tilestache-seed.py default extension behavior to cover JSON.
2012-05-21: 1.34.0
- Added TileStache.Goodies.StatusServer alternate WSGI server for redis-backed internal status checks.
- Added maskband parameter to TileStache.Goodies.Providers.GDAL provider.
2012-05-09: 1.33.1
- Fixed mapnik.Envelope/Box2d back-compatibility.
2012-05-09: 1.33.0
- Added Vector provider support for Spatialite, MySQL and Oracle from Andrew Semprebon.
2012-05-08: 1.32.3
- Fixed a dumb import error in TileStache.Providers.
2012-05-07: 1.32.2
- Disambiguated null vs. empty list behavior of Mapnik Grid fields.
2012-05-06: 1.32.1
- Fixed output of Mapnik Grid to be pure UTF-8 instead of ASCII-encoded for size.
2012-05-06: 1.32.0
- Added core Mapnik Grid provider based on providers under TileStache.Goodies with contributions from Alexander Clausen, Tom Nightingale, Dave Leaver and Paul Smith.
2012-04-09: 1.31.1
- Fixed broken MBTiles support.
2012-04-07: 1.31.0
- Added support for per-extension redirects in Layer configuration.
- Added new TileStache.Goodies.Provider.UtfGridComposite provider.
- Added new TileStache.Goodies.Cache.GoogleCloud cache.
- Added support for buffers around features in TileStache.Goodies.Provider.MapnikGrid.
2012-04-05: 1.30.0
- Added Goodies.AreaServer for serving static, non-tile images.
- Added TileStache.Goodies.Providers.Monkeycache for serving tiles out of caches.
- Documented JSONP callbacks in TileStache.requestHandler().
2012-03-27: 1.29.3
- Fixed a NameError in tilestache-seed.py.
2012-03-18: 1.29.2
- Ensured that in-process recent tiles features works with metatiles.
2012-03-15: 1.29.1
- Added debug logging to Mapnik provider.
2012-03-14: 1.29.0
- Added configurable HTTP referer header to URL Template provider.
- Added optional precision parameter to Vector provider for JSON output.
- Added basic logging facility, including getTile() logging at DEBUG level.
2012-02-26: 1.28.0
- Added new ways to define layer boundaries, including lists and partial limits.
2012-02-26: 1.27.3
- Fixed a forehead slapper in tilestache-seed.
2012-02-26: 1.27.2
- Fixed Mapnik fonts path bug.
2012-02-20: 1.27.1
- Fixed WSGITileServer issue in new Configuration.index support.
2012-02-20: 1.27.0
- Added new Configuration.index support for configurable index page responses.
- Cleaned up some unnecessary errors in preview and callback handling.
2012-02-17: 1.26.4
- Rewrote internals of tilestache-seed to support the no-config use case.
2012-02-16: 1.26.3
- Tweaked Vector mime-types and mapnik import details.
2012-02-15: 1.26.2
- Fixed os.rmdir() error handling in Caches.Disk.
2012-02-15: 1.26.1
- Changed lock in Providers.Mapnik to be a single global lock for the entire process instead of per-layer.
2012-02-13: 1.26.0
- Modified internals of Providers.Mapnik to ensure single-threaded access to mapnik.Map object.
- Fixed MapnikGrid so its documentation can be made even though no one has mapnik2.
- Added -i/--include-path flag to tilestache-compose.py script.
2012-02-06: 1.25.1
- Fixed tilestache-clean.py script to not complain on unsupported -e flag.
2012-02-01: 1.25.0
- Added "ALL" ("ALL LAYERS") option to tilestache-clean.py script.
2012-01-30: 1.24.1
- Fixed an error in WSGITileServer._response().
2012-01-25: 1.24.0
- Added support for HTTP Expires and Cache-Control headers with per-layer maximum cache age.
- Merged Per Liedman's fox for disk cache lock directory removal bug.
2012-01-23: 1.23.4
- Fixed a bug in Multi Cache provider remove().
2012-01-12: 1.23.3
- Changed import delay from 1.23.2 to a specific conditional around sqlite3 for Heroku.
2012-01-11: 1.23.2
- Delayed import of MBTiles and Vector providers until last minute.
2012-01-10: 1.23.1
- Added Nikolai's httplib patch in Proxy provider.
2012-01-10: 1.23.0
- Added new tilestache-clean.py script and required remove() method for caches.
2012-01-01: 1.22.0
- Fixed inconsistencies with cache-ignore setting when using tilestache-compose.py.
- Fixed use of write-cache setting in TileStache.Layer.
- Adding configuration file's directory to sys.path in Config.buildConfiguration().
- Stopped adding current working directory to sys.path for new classes.
- Added new TileStache.Goodies.Providers.MapnikGrid:Provider by github user danzel.
2011-12-25: 1.21.3
- Fixed a bug where I'm an idiot.
2011-12-25: 1.21.2
- Fixed a locking bug in tilestache-compose.py.
2011-12-25: 1.21.1
- Added current working directory to sys.path for new classes.
2011-12-18: 1.21.0
- Added new tilestache-compose.py script.
- Fixed preview body style to account for iOS viewport.
2011-12-12: 1.20.1
- Fixed body style to account for full-page map.
2011-12-07: 1.20.0
- Tile URLs can now contain negative integers.
- Now reading files in Disk Cache using 'rb' mode for Windows compatibility.
2011-11-23: 1.19.4
- Fixed a silly bug where I was comparing ints and strings.
2011-11-23: 1.19.3
- Merged llimllib's support for zoom limits in Composite provider.
2011-10-09: 1.19.2
- Added support for GCPs and cubicspline in GDAL provider.
2011-10-06: 1.19.1
- Fixed TypeError bug with Access-Control-Allow-Origin in WSGITileServer.
2011-09-04: 1.19.0
- Added new resampling option to GDAL provider in Goodies.
2011-08-30: 1.18.3
- Fixed incorrect handling of grayscale datasources in GDAL provider.
2011-08-24: 1.18.2
- Fixed alpha channel handling for mixed-alpha combinations in Composite provider.
2011-07-23: 1.18.1
- Fixed new spacing configuration option on vector provider.
2011-07-23: 1.18.0
- Added new configuration options to vector provider: clipped=padded for Polymaps and spacing.
2011-07-18: 1.17.0
- Added configuration for HTTP Access-Control-Allow-Origin response header for cross-origin resource sharing (CORS).
- Added --include-path option to tilestache-server.py.
2011-07-15: 1.16.0
- Added configuration for JPEG and PNG creation options, including 8-bit PNG output and Photoshop color table support.
2011-07-13: 1.15.0
- Added three new options to tilestache seed: --from-mbtiles, --enable-retries and --error-list.
2011-07-12: 1.14.0
- Added per-layer "write cache" option.
- Added --tile-list option to tilestache-seed.py, so that tiles to seed can be explicitly enumerated.
- Fixed S3 cache provider to work with boto 1.9 and 2.0 equally.
- Added the simplest GDAL provider that could possibly work.
2011-07-04: 1.13.0
- Added support for MBTiles format. Tilesets can be providers or destinations for seeded tiles.
2011-06-22: 1.12.1
- Added new adjustments and blend mode to Composite provider: threshold, curves2, and linear light.
2011-06-16: 1.12.0
- Changed argument structure of osm2pgsql in MirrorOSM provider to allow kept coastlines.
2011-06-14: 1.11.1
- Stopped using wasteful boto.s3.Connection.create_bucket() call in S3 cache provider.
- Mega-update from Dane Springmeyer with fixes to examples, documentation, and projections.
2011-05-13: 1.11.0
- Added ignore-cache parameter to tile rendering; renamed from Zain's cache-bust parameter.
2011-04-18: 1.10.6
- Added faint mapnik tile image to MirrorOSM image responses.
2011-04-18: 1.10.5
- Fixed Grid provider font path bug, again.
2011-04-18: 1.10.4
- Fixed Grid provider font path bug.
2011-04-15: 1.10.3
- Fixed cache lifespan flub in TileStache.Caches.Disk.
2011-04-14: 1.10.2
- Fixed TileStache.Vector in setup.py.
2011-04-13: 1.10.1
- Fixed cache lifespan flub in Memcache.
2011-04-13: 1.10.0
- Added new caches: Multi, Memcache, and S3.
- Added several new Vector response types: ESRI GeoServices JSON, ESRI AMF, and BSON encoding.
- Added new MirrorOSM provider for populating PostGIS with piecemeal OpenStreetMap data.
- Added "cache lifespan" to core Layer configuration.
2011-04-08: 1.9.1
- Bug fix from kaolin: Geography.py was missing import of ModestMaps.Geo Location
2011-03-28: 1.9.0
- Added new Vector provider based on old PostGeoJSON provider.
2011-03-28: 1.8.1
- Merged Zain Memon's proxy provider update.
2011-03-24: 1.8.0
- Added new Core.NoTileLeftBehind exception class to suppress result caching.
2011-03-17: 1.7.4
- Fixed use of "layers.get(name)" to "name in layers" in WSGI server.
- Made possible to build Composite docs without numpy or sympy.
2011-03-17: 1.7.3
- Made it possible to instantiate WSGI server with a configuration object instead of a file.
2011-01-31: 1.7.2
- Fixed json import to account for older Python versions, by checking for simplejson availability.
2011-01-29: 1.7.1
- Fixed PIL import to account for systems that can "import Image".
2011-01-26: 1.7.0
- Made geographic projections user-specifiable using module:class naming.
- Made all class specifications accept module:class syntax, hoping to deprecate module.class sometime.
- Added manpage fixes from David Paleino.
2011-01-26: 1.6.2
- Fixed WSGI content-length type error that was affecting mod_wsgi.
2011-01-21: 1.6.0
- Added "-d" flag for tilestache-seed.py to drop tiles in a directory, regardless of cache settings.
- Merged Zain Memon's WSGI support, supercedesprevious example gunicorn support.
2010-11-29: 1.5.0
- Expanded PostGeoJSON provider in extras collection to include built-in shape clipping for Polymaps.
- Added gzip option to Disk cache to reduce disk usage of text-based providers like XML or JSON.
2010-11-23: 1.4.1
- Being permissive of I/O errors when getting sublayers in Composite provider.
This is short-term necessary, may require configuration or fixing later.
2010-11-20: 1.4.0
- Completed substantial upgrades to Composite provider (TileStache.Goodies.Providers.Composite),
including multiple blend modes, curves adjustments, and layer opacity.
2010-11-18: 1.3.0
- Added new URL Template provider from Ian Dees to TileStache.Providers.
2010-11-15: 1.2.2
- Made it possible to use just a subset of layer preview arguments instead of the full four (lat, lon, zoom, ext).
2010-11-15: 1.2.1
- Previously neglected to include VERSION file in tarball.
2010-11-15: 1.2.0
- Made preview.html pages for tiles configurable with a starting location.
- Sharply improved TileStache.Goodies.Providers.Composite with JSON config, etc.
2010-11-09: 1.1.3
- Found another mapnik bug, removed unnecessary tempfile creation.
2010-11-09: 1.1.2
- Refixed a bug in mapnik provider and learned to test things before pushing them.
2010-11-09: 1.1.1
- Fixed a bug in mapnik provider to make fonts directory work.
2010-10-21: 1.1.0
- Made configuration objects customizable by defining properties in new API.html.
TileStache-1.51.5/LICENSE 0000664 0000000 0000000 00000002731 13042202720 0014630 0 ustar 00root root 0000000 0000000 Copyright (c) 2010, Michal Migurski, Aaron Cope
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
- Neither the name of the project nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
TileStache-1.51.5/MANIFEST.in 0000664 0000000 0000000 00000000104 13042202720 0015351 0 ustar 00root root 0000000 0000000 include TileStache/Goodies/Providers/DejaVuSansMono-alphanumeric.ttf TileStache-1.51.5/Makefile 0000664 0000000 0000000 00000005326 13042202720 0015266 0 ustar 00root root 0000000 0000000 VERSION:=$(shell cat TileStache/VERSION)
DOCROOT=tilestache.org:public_html/tilestache/www
live: doc
rsync -Cr doc/ $(DOCROOT)/doc/
python setup.py sdist upload
doc:
mkdir doc
python -m pydoc -w TileStache
python -m pydoc -w TileStache.Core
python -m pydoc -w TileStache.Caches
python -m pydoc -w TileStache.Memcache
python -m pydoc -w TileStache.Redis
python -m pydoc -w TileStache.S3
python -m pydoc -w TileStache.Config
python -m pydoc -w TileStache.Vector
python -m pydoc -w TileStache.Vector.Arc
python -m pydoc -w TileStache.Geography
python -m pydoc -w TileStache.Providers
python -m pydoc -w TileStache.Mapnik
python -m pydoc -w TileStache.MBTiles
python -m pydoc -w TileStache.Sandwich
python -m pydoc -w TileStache.Pixels
python -m pydoc -w TileStache.Goodies
python -m pydoc -w TileStache.Goodies.Caches
python -m pydoc -w TileStache.Goodies.Caches.LimitedDisk
python -m pydoc -w TileStache.Goodies.Caches.GoogleCloud
python -m pydoc -w TileStache.Goodies.Providers
python -m pydoc -w TileStache.Goodies.Providers.Composite
python -m pydoc -w TileStache.Goodies.Providers.Cascadenik
python -m pydoc -w TileStache.Goodies.Providers.PostGeoJSON
python -m pydoc -w TileStache.Goodies.Providers.SolrGeoJSON
python -m pydoc -w TileStache.Goodies.Providers.MapnikGrid
python -m pydoc -w TileStache.Goodies.Providers.MirrorOSM
python -m pydoc -w TileStache.Goodies.Providers.Monkeycache
python -m pydoc -w TileStache.Goodies.Providers.UtfGridComposite
python -m pydoc -w TileStache.Goodies.Providers.UtfGridCompositeOverlap
python -m pydoc -w TileStache.Goodies.Providers.TileDataOSM
python -m pydoc -w TileStache.Goodies.Providers.Grid
python -m pydoc -w TileStache.Goodies.Providers.GDAL
python -m pydoc -w TileStache.Goodies.AreaServer
python -m pydoc -w TileStache.Goodies.StatusServer
python -m pydoc -w TileStache.Goodies.Proj4Projection
python -m pydoc -w TileStache.Goodies.ExternalConfigServer
python -m pydoc -w TileStache.Goodies.VecTiles
python -m pydoc -w TileStache.Goodies.VecTiles.server
python -m pydoc -w TileStache.Goodies.VecTiles.client
python -m pydoc -w TileStache.Goodies.VecTiles.geojson
python -m pydoc -w TileStache.Goodies.VecTiles.topojson
python -m pydoc -w TileStache.Goodies.VecTiles.mvt
python -m pydoc -w TileStache.Goodies.VecTiles.wkb
python -m pydoc -w TileStache.Goodies.VecTiles.ops
python -m pydoc -w scripts/tilestache-*.py
mv TileStache.html doc/
mv TileStache.*.html doc/
mv tilestache-*.html doc/
perl -pi -e 's#
[^<]+##' doc/*.html
cp API.html doc/index.html
perl -pi -e 's#http://tilestache.org/doc/##' doc/index.html
perl -pi -e 's#\bN\.N\.N\b#$(VERSION)#' doc/index.html
clean:
find TileStache -name '*.pyc' -delete
rm -rf doc
TileStache-1.51.5/README.md 0000664 0000000 0000000 00000010440 13042202720 0015076 0 ustar 00root root 0000000 0000000 #TileStache
_a stylish alternative for caching your map tiles_
[](https://travis-ci.org/TileStache/TileStache)
**TileStache** is a Python-based server application that can serve up map tiles
based on rendered geographic data. You might be familiar with [TileCache](http://tilecache.org),
the venerable open source WMS server from MetaCarta. TileStache is similar, but we hope
simpler and better-suited to the needs of designers and cartographers.
##Synopsis
import TileStache
import ModestMaps
config = {
"cache": {"name": "Test"},
"layers": {
"example": {
"provider": {"name": "mapnik", "mapfile": "examples/style.xml"},
"projection": "spherical mercator"
}
}
}
# like http://tile.openstreetmap.org/1/0/0.png
coord = ModestMaps.Core.Coordinate(0, 0, 1)
config = TileStache.Config.buildConfiguration(config)
type, bytes = TileStache.getTile(config.layers['example'], coord, 'png')
open('tile.png', 'w').write(bytes)
##Dependencies
###Required:
- ModestMaps: http://modestmaps.com, http://github.com/migurski/modestmaps-py
- Python Imaging Library (Pillow): https://python-pillow.org
###Optional:
- Simplejson: https://github.com/simplejson/simplejson (optional if using >= python 2.6)
- mapnik: http://mapnik.org (optional)
- werkzeug: http://werkzeug.pocoo.org/ (optional)
Install the pure python modules with pip:
sudo pip install -U python-pil modestmaps simplejson werkzeug uuid
Install pip (http://www.pip-installer.org/) like:
curl -O -L https://raw.github.com/pypa/pip/master/contrib/get-pip.py
sudo python get-pip.py
Install Mapnik via instructions at:
http://mapnik.org/pages/downloads.html
##Installation
TileStache can be run from the download directory as is. For example the scripts:
tilestache-render.py tilestache-seed.py tilestache-server.py
Can all be run locally like:
./scripts/tilestache-server.py
To install globally do:
python setup.py install
* Note: you may need to prefix that command with 'sudo' to have permissions
to fully install TileStache.
##Quickstart
To make sure TileStache is working start the development server:
./scripts/tilestache-server.py
Then open a modern web browser and you should be able to preview tiles at:
http://localhost:8080/osm/preview.html
This is a previewer that uses ModestMaps and OpenStreetMap tiles from
http://tile.osm.org as defined in the default config file 'tilestache.cfg'
##Documentation
The next step is to learn how build custom layers and serve them.
See the [docs](http://tilestache.org/doc/) for details.
##Features
Rendering providers:
* Mapnik
* Proxy
* Vector
* Templated URLs
Caching backends:
* Local disk
* Test
* Memcache
* S3
##Design Goals
The design of TileStache focuses on approachability at the expense of
cleverness or completeness. Our hope is to make it easy for anyone to design
a new map of their city, publish a fresh view of their world, or even build
the next 8-Bit NYC (http://8bitnyc.com).
* Small
The core of TileStache is intended to have a small code footprint.
It should be quick and easy to to understand what the library is doing and
why, based on common entry points like included CGI scripts. Where possible,
dynamic programming "magic" is to be avoided, in favor of basic, procedural
and copiously-documented Python.
* Pluggable
We want to accept plug-ins and extensions from outside TileStache, and offer
TileStache itself as an extension for other systems. It must be possible to
write and use additional caches or renderers without having to modify the
core package itself, extend classes from inside the package, or navigate
chains of class dependencies. Duck typing and stable interfaces win.
* Sensible Defaults
The default action of a configured TileStache instance should permit the most
common form of interaction: a worldwide, spherical-mercator upper-left oriented
tile layout compatible with those used by OpenStreetMap, Google, Bing Maps,
Yahoo! and others. It should be possible to make TileStache do whatever is
necessary to support any external system, but we eschew complex, impenetrable
standards in favor of pragmatic, fast utility with basic web clients.
##License
BSD, see LICENSE file.
TileStache-1.51.5/TileStache/ 0000775 0000000 0000000 00000000000 13042202720 0015645 5 ustar 00root root 0000000 0000000 TileStache-1.51.5/TileStache/.gitignore 0000664 0000000 0000000 00000000006 13042202720 0017631 0 ustar 00root root 0000000 0000000 *.pyc
TileStache-1.51.5/TileStache/Caches.py 0000664 0000000 0000000 00000033265 13042202720 0017416 0 ustar 00root root 0000000 0000000 """ The cache bits of TileStache.
A Cache is the part of TileStache that stores static files to speed up future
requests. A few default caches are found here, but it's possible to define your
own and pull them into TileStache dynamically by class name.
Built-in providers:
- test
- disk
- multi
- memcache
- s3
Example built-in cache, for JSON configuration file:
"cache": {
"name": "Disk",
"path": "/tmp/stache",
"umask": "0000"
}
Example external cache, for JSON configuration file:
"cache": {
"class": "Module:Classname",
"kwargs": {"frob": "yes"}
}
- The "class" value is split up into module and classname, and dynamically
included. If this doesn't work for some reason, TileStache will fail loudly
to let you know.
- The "kwargs" value is fed to the class constructor as a dictionary of keyword
args. If your defined class doesn't accept any of these keyword arguments,
TileStache will throw an exception.
A cache must provide these methods: lock(), unlock(), read(), and save().
Each method accepts three arguments:
- layer: instance of a Layer.
- coord: single Coordinate that represents a tile.
- format: string like "png" or "jpg" that is used as a filename extension.
The save() method accepts an additional argument before the others:
- body: raw content to save to the cache.
TODO: add stale_lock_timeout and cache_lifespan to cache API in v2.
"""
import os
import sys
import time
import gzip
from tempfile import mkstemp
from os.path import isdir, exists, dirname, basename, join as pathjoin
from .Core import KnownUnknown
from . import Memcache
from . import Redis
from . import S3
def getCacheByName(name):
""" Retrieve a cache object by name.
Raise an exception if the name doesn't work out.
"""
if name.lower() == 'test':
return Test
elif name.lower() == 'disk':
return Disk
elif name.lower() == 'multi':
return Multi
elif name.lower() == 'memcache':
return Memcache.Cache
elif name.lower() == 'redis':
return Redis.Cache
elif name.lower() == 's3':
return S3.Cache
raise Exception('Unknown cache name: "%s"' % name)
class Test:
""" Simple cache that doesn't actually cache anything.
Activity is optionally logged, though.
Example configuration:
"cache": {
"name": "Test",
"verbose": true
}
Extra configuration parameters:
- verbose: optional boolean flag to write cache activities to a logging
function, defaults to False if omitted.
"""
def __init__(self, logfunc=None):
self.logfunc = logfunc
def _description(self, layer, coord, format):
"""
"""
name = layer.name()
tile = '%(zoom)d/%(column)d/%(row)d' % coord.__dict__
return ' '.join( (name, tile, format) )
def lock(self, layer, coord, format):
""" Pretend to acquire a cache lock for this tile.
"""
name = self._description(layer, coord, format)
if self.logfunc:
self.logfunc('Test cache lock: ' + name)
def unlock(self, layer, coord, format):
""" Pretend to release a cache lock for this tile.
"""
name = self._description(layer, coord, format)
if self.logfunc:
self.logfunc('Test cache unlock: ' + name)
def remove(self, layer, coord, format):
""" Pretend to remove a cached tile.
"""
name = self._description(layer, coord, format)
if self.logfunc:
self.logfunc('Test cache remove: ' + name)
def read(self, layer, coord, format):
""" Pretend to read a cached tile.
"""
name = self._description(layer, coord, format)
if self.logfunc:
self.logfunc('Test cache read: ' + name)
return None
def save(self, body, layer, coord, format):
""" Pretend to save a cached tile.
"""
name = self._description(layer, coord, format)
if self.logfunc:
self.logfunc('Test cache save: %d bytes to %s' % (len(body), name))
class Disk:
""" Caches files to disk.
Example configuration:
"cache": {
"name": "Disk",
"path": "/tmp/stache",
"umask": "0000",
"dirs": "portable"
}
Extra parameters:
- path: required local directory path where files should be stored.
- umask: optional string representation of octal permission mask
for stored files. Defaults to 0022.
- dirs: optional string saying whether to create cache directories that
are safe, portable or quadtile. For an example tile 12/656/1582.png,
"portable" creates matching directory trees while "safe" guarantees
directories with fewer files, e.g. 12/000/656/001/582.png.
Defaults to safe.
- gzip: optional list of file formats that should be stored in a
compressed form. Defaults to "txt", "text", "json", and "xml".
Provide an empty list in the configuration for no compression.
If your configuration file is loaded from a remote location, e.g.
"http://example.com/tilestache.cfg", the path *must* be an unambiguous
filesystem path, e.g. "file:///tmp/cache"
"""
def __init__(self, path, umask=0o022, dirs='safe', gzip='txt text json xml'.split()):
self.cachepath = path
self.umask = int(umask)
self.dirs = dirs
self.gzip = [format.lower() for format in gzip]
def _is_compressed(self, format):
return format.lower() in self.gzip
def _filepath(self, layer, coord, format):
"""
"""
l = layer.name()
z = '%d' % coord.zoom
e = format.lower()
e += self._is_compressed(format) and '.gz' or ''
if self.dirs == 'safe':
x = '%06d' % coord.column
y = '%06d' % coord.row
x1, x2 = x[:3], x[3:]
y1, y2 = y[:3], y[3:]
filepath = os.sep.join( (l, z, x1, x2, y1, y2 + '.' + e) )
elif self.dirs == 'portable':
x = '%d' % coord.column
y = '%d' % coord.row
filepath = os.sep.join( (l, z, x, y + '.' + e) )
elif self.dirs == 'quadtile':
pad, length = 1 << 31, 1 + coord.zoom
# two binary strings, one per dimension
xs = bin(pad + int(coord.column))[-length:]
ys = bin(pad + int(coord.row))[-length:]
# interleave binary bits into plain digits, 0-3.
# adapted from ModestMaps.Tiles.toMicrosoft()
dirpath = ''.join([str(int(y+x, 2)) for (x, y) in zip(xs, ys)])
# built a list of nested directory names and a file basename
parts = [dirpath[i:i+3] for i in range(0, len(dirpath), 3)]
filepath = os.sep.join([l] + parts[:-1] + [parts[-1] + '.' + e])
else:
raise KnownUnknown('Please provide a valid "dirs" parameter to the Disk cache, either "safe", "portable" or "quadtile" but not "%s"' % self.dirs)
return filepath
def _fullpath(self, layer, coord, format):
"""
"""
filepath = self._filepath(layer, coord, format)
fullpath = pathjoin(self.cachepath, filepath)
return fullpath
def _lockpath(self, layer, coord, format):
"""
"""
return self._fullpath(layer, coord, format) + '.lock'
def lock(self, layer, coord, format):
""" Acquire a cache lock for this tile.
Returns nothing, but blocks until the lock has been acquired.
Lock is implemented as an empty directory next to the tile file.
"""
lockpath = self._lockpath(layer, coord, format)
due = time.time() + layer.stale_lock_timeout
while True:
# try to acquire a directory lock, repeating if necessary.
try:
umask_old = os.umask(self.umask)
if time.time() > due:
# someone left the door locked.
try:
os.rmdir(lockpath)
except OSError:
# Oh - no they didn't.
pass
os.makedirs(lockpath, 0o777&~self.umask)
break
except OSError as e:
if e.errno != 17:
raise
time.sleep(.2)
finally:
os.umask(umask_old)
def unlock(self, layer, coord, format):
""" Release a cache lock for this tile.
Lock is implemented as an empty directory next to the tile file.
"""
lockpath = self._lockpath(layer, coord, format)
try:
os.rmdir(lockpath)
except OSError:
# Ok, someone else deleted it already
pass
def remove(self, layer, coord, format):
""" Remove a cached tile.
"""
fullpath = self._fullpath(layer, coord, format)
try:
os.remove(fullpath)
except OSError as e:
# errno=2 means that the file does not exist, which is fine
if e.errno != 2:
raise
def read(self, layer, coord, format):
""" Read a cached tile.
"""
fullpath = self._fullpath(layer, coord, format)
if not exists(fullpath):
return None
age = time.time() - os.stat(fullpath).st_mtime
if layer.cache_lifespan and age > layer.cache_lifespan:
return None
elif self._is_compressed(format):
return gzip.open(fullpath, 'r').read()
else:
body = open(fullpath, 'rb').read()
return body
def save(self, body, layer, coord, format):
""" Save a cached tile.
"""
fullpath = self._fullpath(layer, coord, format)
try:
umask_old = os.umask(self.umask)
os.makedirs(dirname(fullpath), 0o777&~self.umask)
except OSError as e:
if e.errno != 17:
raise
finally:
os.umask(umask_old)
suffix = '.' + format.lower()
suffix += self._is_compressed(format) and '.gz' or ''
fh, tmp_path = mkstemp(dir=self.cachepath, suffix=suffix)
if self._is_compressed(format):
os.close(fh)
tmp_file = gzip.open(tmp_path, 'w')
tmp_file.write(body)
tmp_file.close()
else:
os.write(fh, body)
os.close(fh)
try:
os.rename(tmp_path, fullpath)
except OSError:
os.unlink(fullpath)
os.rename(tmp_path, fullpath)
os.chmod(fullpath, 0o666&~self.umask)
class Multi:
""" Caches tiles to multiple, ordered caches.
Multi cache is well-suited for a speed-to-capacity gradient, for
example a combination of Memcache and S3 to take advantage of the high
speed of memcache and the high capacity of S3. Each tier of caching is
checked sequentially when reading from the cache, while all tiers are
used together for writing. Locks are only used with the first cache.
Example configuration:
"cache": {
"name": "Multi",
"tiers": [
{
"name": "Memcache",
"servers": ["127.0.0.1:11211"]
},
{
"name": "Disk",
"path": "/tmp/stache"
}
]
}
Multi cache parameters:
tiers
Required list of cache configurations. The fastest, most local
cache should be at the beginning of the list while the slowest or
most remote cache should be at the end. Memcache and S3 together
make a great pair.
"""
def __init__(self, tiers):
self.tiers = tiers
def lock(self, layer, coord, format):
""" Acquire a cache lock for this tile in the first tier.
Returns nothing, but blocks until the lock has been acquired.
"""
return self.tiers[0].lock(layer, coord, format)
def unlock(self, layer, coord, format):
""" Release a cache lock for this tile in the first tier.
"""
return self.tiers[0].unlock(layer, coord, format)
def remove(self, layer, coord, format):
""" Remove a cached tile from every tier.
"""
for (index, cache) in enumerate(self.tiers):
cache.remove(layer, coord, format)
def read(self, layer, coord, format):
""" Read a cached tile.
Start at the first tier and work forwards until a cached tile
is found. When found, save it back to the earlier tiers for faster
access on future requests.
"""
for (index, cache) in enumerate(self.tiers):
body = cache.read(layer, coord, format)
if body:
# save the body in earlier tiers for speedier access
for cache in self.tiers[:index]:
cache.save(body, layer, coord, format)
return body
return None
def save(self, body, layer, coord, format):
""" Save a cached tile.
Every tier gets a saved copy.
"""
for (index, cache) in enumerate(self.tiers):
cache.save(body, layer, coord, format)
TileStache-1.51.5/TileStache/Config.py 0000664 0000000 0000000 00000036722 13042202720 0017436 0 ustar 00root root 0000000 0000000 """ The configuration bits of TileStache.
TileStache configuration is stored in JSON files, and is composed of two main
top-level sections: "cache" and "layers". There are examples of both in this
minimal sample configuration:
{
"cache": {"name": "Test"},
"layers": {
"example": {
"provider": {"name": "mapnik", "mapfile": "examples/style.xml"},,
"projection": "spherical mercator"
}
}
}
The contents of the "cache" section are described in greater detail in the
TileStache.Caches module documentation. Here is a different sample:
"cache": {
"name": "Disk",
"path": "/tmp/stache",
"umask": "0000"
}
The "layers" section is a dictionary of layer names which are specified in the
URL of an individual tile. More detail on the configuration of individual layers
can be found in the TileStache.Core module documentation. Another sample:
{
"cache": ...,
"layers":
{
"example-name":
{
"provider": { ... },
"metatile": { ... },
"preview": { ... },
"stale lock timeout": ...,
"projection": ...
}
}
}
Configuration also supports these additional settings:
- "logging": one of "debug", "info", "warning", "error" or "critical", as
described in Python's logging module: http://docs.python.org/howto/logging.html
- "index": configurable index pages for the front page of an instance.
A custom index can be specified as a filename relative to the configuration
location. Typically an HTML document would be given here, but other kinds of
files such as images can be used, with MIME content-type headers determined
by mimetypes.guess_type. A simple text greeting is displayed if no index
is provided.
In-depth explanations of the layer components can be found in the module
documentation for TileStache.Providers, TileStache.Core, and TileStache.Geography.
"""
import sys
import logging
from sys import modules
from os.path import realpath, join as pathjoin
try:
from urllib.parse import urljoin, urlparse
except ImportError:
# Python 2
from urlparse import urljoin, urlparse
from mimetypes import guess_type
try:
from urllib.request import urlopen
except ImportError:
# Python 2
from urllib import urlopen
from json import dumps
try:
from json import dumps as json_dumps
except ImportError:
from simplejson import dumps as json_dumps
from ModestMaps.Geo import Location
from ModestMaps.Core import Coordinate
from . import Core
from . import Caches
from . import Providers
from . import Geography
from . import PixelEffects
class Configuration:
""" A complete site configuration, with a collection of Layer objects.
Attributes:
cache:
Cache instance, e.g. TileStache.Caches.Disk etc.
See TileStache.Caches for details on what makes
a usable cache.
layers:
Dictionary of layers keyed by name.
When creating a custom layers dictionary, e.g. for dynamic
layer collections backed by some external configuration,
these dictionary methods must be provided for a complete
collection of layers:
keys():
Return list of layer name strings.
items():
Return list of (name, layer) pairs.
__contains__(key):
Return boolean true if given key is an existing layer.
__getitem__(key):
Return existing layer object for given key or raise KeyError.
dirpath:
Local filesystem path for this configuration,
useful for expanding relative paths.
Optional attribute:
index:
Mimetype, content tuple for default index response.
"""
def __init__(self, cache, dirpath):
self.cache = cache
self.dirpath = dirpath
self.layers = {}
self.index = 'text/plain', 'TileStache bellows hello.'
class Bounds:
""" Coordinate bounding box for tiles.
"""
def __init__(self, upper_left_high, lower_right_low):
""" Two required Coordinate objects defining tile pyramid bounds.
Boundaries are inclusive: upper_left_high is the left-most column,
upper-most row, and highest zoom level; lower_right_low is the
right-most column, furthest-dwn row, and lowest zoom level.
"""
self.upper_left_high = upper_left_high
self.lower_right_low = lower_right_low
def excludes(self, tile):
""" Check a tile Coordinate against the bounds, return true/false.
"""
if tile.zoom > self.upper_left_high.zoom:
# too zoomed-in
return True
if tile.zoom < self.lower_right_low.zoom:
# too zoomed-out
return True
# check the top-left tile corner against the lower-right bound
_tile = tile.zoomTo(self.lower_right_low.zoom)
if _tile.column > self.lower_right_low.column:
# too far right
return True
if _tile.row > self.lower_right_low.row:
# too far down
return True
# check the bottom-right tile corner against the upper-left bound
__tile = tile.right().down().zoomTo(self.upper_left_high.zoom)
if __tile.column < self.upper_left_high.column:
# too far left
return True
if __tile.row < self.upper_left_high.row:
# too far up
return True
return False
def __str__(self):
return 'Bound %s - %s' % (self.upper_left_high, self.lower_right_low)
class BoundsList:
""" Multiple coordinate bounding boxes for tiles.
"""
def __init__(self, bounds):
""" Single argument is a list of Bounds objects.
"""
self.bounds = bounds
def excludes(self, tile):
""" Check a tile Coordinate against the bounds, return false if none match.
"""
for bound in self.bounds:
if not bound.excludes(tile):
return False
# Nothing worked.
return True
def buildConfiguration(config_dict, dirpath='.'):
""" Build a configuration dictionary into a Configuration object.
The second argument is an optional dirpath that specifies where in the
local filesystem the parsed dictionary originated, to make it possible
to resolve relative paths. It might be a path or more likely a full
URL including the "file://" prefix.
"""
scheme, h, path, p, q, f = urlparse(dirpath)
if scheme in ('', 'file'):
sys.path.insert(0, path)
cache_dict = config_dict.get('cache', {})
cache = _parseConfigCache(cache_dict, dirpath)
config = Configuration(cache, dirpath)
for (name, layer_dict) in config_dict.get('layers', {}).items():
config.layers[name] = _parseConfigLayer(layer_dict, config, dirpath)
if 'index' in config_dict:
index_href = urljoin(dirpath, config_dict['index'])
index_body = urlopen(index_href).read()
index_type = guess_type(index_href)
config.index = index_type[0], index_body
if 'logging' in config_dict:
level = config_dict['logging'].upper()
if hasattr(logging, level):
logging.basicConfig(level=getattr(logging, level))
return config
def enforcedLocalPath(relpath, dirpath, context='Path'):
""" Return a forced local path, relative to a directory.
Throw an error if the combination of path and directory seems to
specify a remote path, e.g. "/path" and "http://example.com".
Although a configuration file can be parsed from a remote URL, some
paths (e.g. the location of a disk cache) must be local to the server.
In cases where we mix a remote configuration location with a local
cache location, e.g. "http://example.com/tilestache.cfg", the disk path
must include the "file://" prefix instead of an ambiguous absolute
path such as "/tmp/tilestache".
"""
parsed_dir = urlparse(dirpath)
parsed_rel = urlparse(relpath)
if parsed_rel.scheme not in ('file', ''):
raise Core.KnownUnknown('%s path must be a local file path, absolute or "file://", not "%s".' % (context, relpath))
if parsed_dir.scheme not in ('file', '') and parsed_rel.scheme != 'file':
raise Core.KnownUnknown('%s path must start with "file://" in a remote configuration ("%s" relative to %s)' % (context, relpath, dirpath))
if parsed_rel.scheme == 'file':
# file:// is an absolute local reference for the disk cache.
return parsed_rel.path
if parsed_dir.scheme == 'file':
# file:// is an absolute local reference for the directory.
return urljoin(parsed_dir.path, parsed_rel.path)
# nothing has a scheme, it's probably just a bunch of
# dumb local paths, so let's see what happens next.
return pathjoin(dirpath, relpath)
def _parseConfigCache(cache_dict, dirpath):
""" Used by parseConfig() to parse just the cache parts of a config.
"""
if 'name' in cache_dict:
_class = Caches.getCacheByName(cache_dict['name'])
kwargs = {}
def add_kwargs(*keys):
""" Populate named keys in kwargs from cache_dict.
"""
for key in keys:
if key in cache_dict:
kwargs[key] = cache_dict[key]
if _class is Caches.Test:
if cache_dict.get('verbose', False):
kwargs['logfunc'] = lambda msg: stderr.write(msg + '\n')
elif _class is Caches.Disk:
kwargs['path'] = enforcedLocalPath(cache_dict['path'], dirpath, 'Disk cache path')
if 'umask' in cache_dict:
kwargs['umask'] = int(cache_dict['umask'], 8)
add_kwargs('dirs', 'gzip')
elif _class is Caches.Multi:
kwargs['tiers'] = [_parseConfigCache(tier_dict, dirpath)
for tier_dict in cache_dict['tiers']]
elif _class is Caches.Memcache.Cache:
if 'key prefix' in cache_dict:
kwargs['key_prefix'] = cache_dict['key prefix']
add_kwargs('servers', 'lifespan', 'revision')
elif _class is Caches.Redis.Cache:
if 'key prefix' in cache_dict:
kwargs['key_prefix'] = cache_dict['key prefix']
add_kwargs('host', 'port', 'db')
elif _class is Caches.S3.Cache:
add_kwargs('bucket', 'access', 'secret', 'use_locks', 'path', 'reduced_redundancy', 'policy')
else:
raise Exception('Unknown cache: %s' % cache_dict['name'])
elif 'class' in cache_dict:
_class = Core.loadClassPath(cache_dict['class'])
kwargs = cache_dict.get('kwargs', {})
kwargs = dict( [(str(k), v) for (k, v) in kwargs.items()] )
else:
raise Exception('Missing required cache name or class: %s' % json_dumps(cache_dict))
cache = _class(**kwargs)
return cache
def _parseLayerBounds(bounds_dict, projection):
"""
"""
north, west = bounds_dict.get('north', 89), bounds_dict.get('west', -180)
south, east = bounds_dict.get('south', -89), bounds_dict.get('east', 180)
high, low = bounds_dict.get('high', 31), bounds_dict.get('low', 0)
try:
ul_hi = projection.locationCoordinate(Location(north, west)).zoomTo(high)
lr_lo = projection.locationCoordinate(Location(south, east)).zoomTo(low)
except TypeError:
raise Core.KnownUnknown('Bad bounds for layer, need north, south, east, west, high, and low: ' + dumps(bounds_dict))
return Bounds(ul_hi, lr_lo)
def _parseConfigLayer(layer_dict, config, dirpath):
""" Used by parseConfig() to parse just the layer parts of a config.
"""
projection = layer_dict.get('projection', 'spherical mercator')
projection = Geography.getProjectionByName(projection)
#
# Add cache lock timeouts and preview arguments
#
layer_kwargs = {}
if 'cache lifespan' in layer_dict:
layer_kwargs['cache_lifespan'] = int(layer_dict['cache lifespan'])
if 'stale lock timeout' in layer_dict:
layer_kwargs['stale_lock_timeout'] = int(layer_dict['stale lock timeout'])
if 'write cache' in layer_dict:
layer_kwargs['write_cache'] = bool(layer_dict['write cache'])
if 'allowed origin' in layer_dict:
layer_kwargs['allowed_origin'] = str(layer_dict['allowed origin'])
if 'maximum cache age' in layer_dict:
layer_kwargs['max_cache_age'] = int(layer_dict['maximum cache age'])
if 'redirects' in layer_dict:
layer_kwargs['redirects'] = dict(layer_dict['redirects'])
if 'tile height' in layer_dict:
layer_kwargs['tile_height'] = int(layer_dict['tile height'])
if 'preview' in layer_dict:
preview_dict = layer_dict['preview']
for (key, func) in zip(('lat', 'lon', 'zoom', 'ext'), (float, float, int, str)):
if key in preview_dict:
layer_kwargs['preview_' + key] = func(preview_dict[key])
#
# Do the bounds
#
if 'bounds' in layer_dict:
if type(layer_dict['bounds']) is dict:
layer_kwargs['bounds'] = _parseLayerBounds(layer_dict['bounds'], projection)
elif type(layer_dict['bounds']) is list:
bounds = [_parseLayerBounds(b, projection) for b in layer_dict['bounds']]
layer_kwargs['bounds'] = BoundsList(bounds)
else:
raise Core.KnownUnknown('Layer bounds must be a dictionary, not: ' + dumps(layer_dict['bounds']))
#
# Do the metatile
#
meta_dict = layer_dict.get('metatile', {})
metatile_kwargs = {}
for k in ('buffer', 'rows', 'columns'):
if k in meta_dict:
metatile_kwargs[k] = int(meta_dict[k])
metatile = Core.Metatile(**metatile_kwargs)
#
# Do the per-format options
#
jpeg_kwargs = {}
png_kwargs = {}
if 'jpeg options' in layer_dict:
jpeg_kwargs = dict([(str(k), v) for (k, v) in layer_dict['jpeg options'].items()])
if 'png options' in layer_dict:
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
#
provider_dict = layer_dict['provider']
if 'name' in provider_dict:
_class = Providers.getProviderByName(provider_dict['name'])
provider_kwargs = _class.prepareKeywordArgs(provider_dict)
elif 'class' in provider_dict:
_class = Core.loadClassPath(provider_dict['class'])
provider_kwargs = provider_dict.get('kwargs', {})
provider_kwargs = dict( [(str(k), v) for (k, v) in provider_kwargs.items()] )
else:
raise Exception('Missing required provider name or class: %s' % json_dumps(provider_dict))
#
# Finish him!
#
layer = Core.Layer(config, projection, metatile, **layer_kwargs)
layer.provider = _class(layer, **provider_kwargs)
layer.setSaveOptionsJPEG(**jpeg_kwargs)
layer.setSaveOptionsPNG(**png_kwargs)
layer.pixel_effect = pixel_effect
return layer
TileStache-1.51.5/TileStache/Core.py 0000664 0000000 0000000 00000103745 13042202720 0017121 0 ustar 00root root 0000000 0000000 """ The core class bits of TileStache.
Two important classes can be found here.
Layer represents a set of tiles in TileStache. It keeps references to
providers, projections, a Configuration instance, and other details required
for to the storage and rendering of a tile set. Layers are represented in the
configuration file as a dictionary:
{
"cache": ...,
"layers":
{
"example-name":
{
"provider": { ... },
"metatile": { ... },
"preview": { ... },
"projection": ...,
"stale lock timeout": ...,
"cache lifespan": ...,
"write cache": ...,
"bounds": { ... },
"allowed origin": ...,
"maximum cache age": ...,
"redirects": ...,
"tile height": ...,
"jpeg options": ...,
"png options": ...
}
}
}
- "provider" refers to a Provider, explained in detail in TileStache.Providers.
- "metatile" optionally makes it possible for multiple individual tiles to be
rendered at one time, for greater speed and efficiency. This is commonly used
for the Mapnik provider. See below for more information on metatiles.
- "preview" optionally overrides the starting point for the built-in per-layer
slippy map preview, useful for image-based layers where appropriate.
See below for more information on the preview.
- "projection" names a geographic projection, explained in TileStache.Geography.
If omitted, defaults to spherical mercator.
- "stale lock timeout" is an optional number of seconds to wait before forcing
a lock that might be stuck. This is defined on a per-layer basis, rather than
for an entire cache at one time, because you may have different expectations
for the rendering speeds of different layer configurations. Defaults to 15.
- "cache lifespan" is an optional number of seconds that cached tiles should
be stored. This is defined on a per-layer basis. Defaults to forever if None,
0 or omitted.
- "write cache" is an optional boolean value to allow skipping cache write
altogether. This is defined on a per-layer basis. Defaults to true if omitted.
- "bounds" is an optional dictionary of six tile boundaries to limit the
rendered area: low (lowest zoom level), high (highest zoom level), north,
west, south, and east (all in degrees).
- "allowed origin" is an optional string that shows up in the response HTTP
header Access-Control-Allow-Origin, useful for when you need to provide
javascript direct access to response data such as GeoJSON or pixel values.
The header is part of a W3C working draft (http://www.w3.org/TR/cors/).
- "maximum cache age" is an optional number of seconds used to control behavior
of downstream caches. Causes TileStache responses to include Cache-Control
and Expires HTTP response headers. Useful when TileStache is itself hosted
behind an HTTP cache such as Squid, Cloudfront, or Akamai.
- "redirects" is an optional dictionary of per-extension HTTP redirects,
treated as lowercase. Useful in cases where your tile provider can support
many formats but you want to enforce limits to save on cache usage.
If a request is made for a tile with an extension in the dictionary keys,
a response can be generated that redirects the client to the same tile
with another extension.
- "tile height" gives the height of the image tile in pixels. You almost always
want to leave this at the default value of 256, but you can use a value of 512
to create double-size, double-resolution tiles for high-density phone screens.
- "jpeg options" is an optional dictionary of JPEG creation options, passed
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:
http://example.com/tilestache.cgi/example-name/0/0/0.png
Sample JPEG creation options:
{
"quality": 90,
"progressive": true,
"optimize": true
}
Sample PNG creation options:
{
"optimize": true,
"palette": "filename.act"
}
Sample pixel effect:
{
"name": "desaturate",
"factor": 0.85
}
Sample bounds:
{
"low": 9, "high": 15,
"south": 37.749, "west": -122.358,
"north": 37.860, "east": -122.113
}
Metatile represents a larger area to be rendered at one time. Metatiles are
represented in the configuration file as a dictionary:
{
"rows": 4,
"columns": 4,
"buffer": 64
}
- "rows" and "columns" are the height and width of the metatile measured in
tiles. This example metatile is four rows tall and four columns wide, so it
will render sixteen tiles simultaneously.
- "buffer" is a buffer area around the metatile, measured in pixels. This is
useful for providers with labels or icons, where it's necessary to draw a
bit extra around the edges to ensure that text is not cut off. This example
metatile has a buffer of 64 pixels, so the resulting metatile will be 1152
pixels square: 4 rows x 256 pixels + 2 x 64 pixel buffer.
The preview can be accessed through a URL like //preview.html:
{
"lat": 33.9901,
"lon": -116.1637,
"zoom": 16,
"ext": "jpg"
}
- "lat" and "lon" are the starting latitude and longitude in degrees.
- "zoom" is the starting zoom level.
- "ext" is the filename extension, e.g. "png".
"""
import logging
from sys import modules
from wsgiref.headers import Headers
try:
from io import BytesIO
except ImportError:
# Python 2
from StringIO import StringIO as BytesIO
try:
from urllib.parse import urljoin
except ImportError:
# Python 2
from urlparse import urljoin
from time import time
from .Pixels import load_palette, apply_palette, apply_palette256
try:
from PIL import Image
except ImportError:
import Image
from ModestMaps.Core import Coordinate
_recent_tiles = dict(hash={}, list=[])
def _addRecentTile(layer, coord, format, body, age=300):
""" Add the body of a tile to _recent_tiles with a timeout.
"""
key = (layer, coord, format)
due = time() + age
_recent_tiles['hash'][key] = body, due
_recent_tiles['list'].append((key, due))
logging.debug('TileStache.Core._addRecentTile() added tile to recent tiles: %s', key)
# now look at the oldest keys and remove them if needed
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:
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.
"""
key = (layer, coord, format)
body, use_by = _recent_tiles['hash'].get(key, (None, 0))
# non-existent?
if body is None:
return None
# new enough?
if time() < use_by:
logging.debug('TileStache.Core._addRecentTile() found tile in recent tiles: %s', key)
return body
# too old
try:
del _recent_tiles['hash'][key]
except KeyError:
pass
return None
class Metatile:
""" Some basic characteristics of a metatile.
Properties:
- rows: number of tile rows this metatile covers vertically.
- columns: number of tile columns this metatile covers horizontally.
- buffer: pixel width of outer edge.
"""
def __init__(self, buffer=0, rows=1, columns=1):
assert rows >= 1
assert columns >= 1
assert buffer >= 0
self.rows = rows
self.columns = columns
self.buffer = buffer
def isForReal(self):
""" Return True if this is really a metatile with a buffer or multiple tiles.
A default 1x1 metatile with buffer=0 is not for real.
"""
return self.buffer > 0 or self.rows > 1 or self.columns > 1
def firstCoord(self, coord):
""" Return a new coordinate for the upper-left corner of a metatile.
This is useful as a predictable way to refer to an entire metatile
by one of its sub-tiles, currently needed to do locking correctly.
"""
return self.allCoords(coord)[0]
def allCoords(self, coord):
""" Return a list of coordinates for a complete metatile.
Results are guaranteed to be ordered left-to-right, top-to-bottom.
"""
rows, columns = int(self.rows), int(self.columns)
# upper-left corner of coord's metatile
row = rows * (int(coord.row) / rows)
column = columns * (int(coord.column) / columns)
coords = []
for r in range(rows):
for c in range(columns):
coords.append(Coordinate(row + r, column + c, coord.zoom))
return coords
class Layer:
""" A Layer.
Required attributes:
provider:
Render provider, see Providers module.
config:
Configuration instance, see Config module.
projection:
Geographic projection, see Geography module.
metatile:
Some information for drawing many tiles at once.
Optional attributes:
stale_lock_timeout:
Number of seconds until a cache lock is forced, default 15.
cache_lifespan:
Number of seconds that cached tiles should be stored, default 15.
write_cache:
Allow skipping cache write altogether, default true.
bounds:
Instance of Config.Bounds for limiting rendered tiles.
allowed_origin:
Value for the Access-Control-Allow-Origin HTTP response header.
max_cache_age:
Number of seconds that tiles from this layer may be cached by downstream clients.
redirects:
Dictionary of per-extension HTTP redirects, treated as lowercase.
preview_lat:
Starting latitude for slippy map layer preview, default 37.80.
preview_lon:
Starting longitude for slippy map layer preview, default -122.26.
preview_zoom:
Starting zoom for slippy map layer preview, default 10.
preview_ext:
Tile name extension for slippy map layer preview, default "png".
tile_height:
Height of tile in pixels, as a single integer. Tiles are generally
assumed to be square, and Layer.render() will respond with an error
if the rendered image is not this height.
"""
def __init__(self, config, projection, metatile, stale_lock_timeout=15, cache_lifespan=None, write_cache=True, allowed_origin=None, max_cache_age=None, redirects=None, preview_lat=37.80, preview_lon=-122.26, preview_zoom=10, preview_ext='png', bounds=None, tile_height=256):
self.provider = None
self.config = config
self.projection = projection
self.metatile = metatile
self.stale_lock_timeout = stale_lock_timeout
self.cache_lifespan = cache_lifespan
self.write_cache = write_cache
self.allowed_origin = allowed_origin
self.max_cache_age = max_cache_age
self.redirects = redirects or dict()
self.preview_lat = preview_lat
self.preview_lon = preview_lon
self.preview_zoom = preview_zoom
self.preview_ext = preview_ext
self.bounds = bounds
self.dim = tile_height
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.
Layer names are stored in the Configuration object, so
config.layers must be inspected to find a matching name.
"""
for (name, layer) in self.config.layers.items():
if layer is self:
return name
return None
def getTileResponse(self, coord, extension, ignore_cached=False):
""" Get status code, headers, and a tile binary for a given request layer tile.
Arguments:
- coord: one ModestMaps.Core.Coordinate corresponding to a single tile.
- extension: filename extension to choose response type, e.g. "png" or "jpg".
- ignore_cached: always re-render the tile, whether it's in the cache or not.
This is the main entry point, after site configuration has been loaded
and individual tiles need to be rendered.
"""
start_time = time()
mimetype, format = self.getTypeByExtension(extension)
# default response values
status_code = 200
headers = Headers([('Content-Type', mimetype)])
body = None
cache = self.config.cache
if not ignore_cached:
# Start by checking for a tile in the cache.
try:
body = cache.read(self, coord, format)
except TheTileLeftANote as e:
headers = e.headers
status_code = e.status_code
body = e.content
if e.emit_content_type:
headers.setdefault('Content-Type', mimetype)
tile_from = 'cache'
else:
# Then look in the bag of recent tiles.
body = _getRecentTile(self, coord, format)
tile_from = 'recent tiles'
# If no tile was found, dig deeper
if body is None:
try:
lockCoord = None
if self.write_cache:
# this is the coordinate that actually gets locked.
lockCoord = self.metatile.firstCoord(coord)
# We may need to write a new tile, so acquire a lock.
cache.lock(self, lockCoord, format)
if not ignore_cached:
# There's a chance that some other process has
# written the tile while the lock was being acquired.
body = cache.read(self, coord, format)
tile_from = 'cache after all'
if body is None:
# No one else wrote the tile, do it here.
buff = BytesIO()
try:
tile = self.render(coord, format)
save = True
except NoTileLeftBehind as e:
tile = e.tile
save = False
status_code = 404
if not self.write_cache:
save = False
if format.lower() == 'jpeg':
save_kwargs = self.jpeg_options
elif format.lower() == 'png':
save_kwargs = self.png_options
else:
save_kwargs = {}
tile.save(buff, format, **save_kwargs)
body = buff.getvalue()
if save:
cache.save(body, self, coord, format)
tile_from = 'layer.render()'
except TheTileLeftANote as e:
headers = e.headers
status_code = e.status_code
body = e.content
if e.emit_content_type:
headers.setdefault('Content-Type', mimetype)
finally:
if lockCoord:
# Always clean up a lock when it's no longer being used.
cache.unlock(self, lockCoord, format)
_addRecentTile(self, coord, format, body)
logging.info('TileStache.Core.Layer.getTileResponse() %s/%d/%d/%d.%s via %s in %.3f', self.name(), coord.zoom, coord.column, coord.row, extension, tile_from, time() - start_time)
return status_code, headers, body
def doMetatile(self):
""" Return True if we have a real metatile and the provider is OK with it.
"""
return self.metatile.isForReal() and hasattr(self.provider, 'renderArea')
def render(self, coord, format):
""" Render a tile for a coordinate, return PIL Image-like object.
Perform metatile slicing here as well, if required, writing the
full set of rendered tiles to cache as we go.
Note that metatiling and pass-through mode of a Provider
are mutually exclusive options
"""
if self.bounds and self.bounds.excludes(coord):
raise NoTileLeftBehind(Image.new('RGBA', (self.dim, self.dim), (0, 0, 0, 0)))
srs = self.projection.srs
xmin, ymin, xmax, ymax = self.envelope(coord)
width, height = self.dim, self.dim
provider = self.provider
metatile = self.metatile
pass_through = provider.pass_through if hasattr(provider, 'pass_through') else False
if self.doMetatile():
if pass_through:
raise KnownUnknown('Your provider is configured for metatiling and pass_through mode. That does not work')
# adjust render size and coverage for metatile
xmin, ymin, xmax, ymax = self.metaEnvelope(coord)
width, height = self.metaSize(coord)
subtiles = self.metaSubtiles(coord)
if self.doMetatile() or hasattr(provider, 'renderArea'):
# draw an area, defined in projected coordinates
tile = provider.renderArea(width, height, srs, xmin, ymin, xmax, ymax, coord.zoom)
elif hasattr(provider, 'renderTile'):
# draw a single tile
width, height = self.dim, self.dim
tile = provider.renderTile(width, height, srs, coord)
else:
raise KnownUnknown('Your provider lacks renderTile and renderArea methods.')
if not hasattr(tile, 'save'):
raise KnownUnknown('Return value of provider.renderArea() must act like an image; e.g. have a "save" method.')
if hasattr(tile, 'size') and tile.size[1] != height:
raise KnownUnknown('Your provider returned the wrong image size: %s instead of %d pixels tall.' % (repr(tile.size), self.dim))
if self.bitmap_palette:
# this is where we apply the palette if there is one
if pass_through:
raise KnownUnknown('Cannot apply palette in pass_through mode')
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
tile, surtile = None, tile
for (other, x, y) in subtiles:
buff = StringIO()
bbox = (x, y, x + self.dim, y + self.dim)
subtile = surtile.crop(bbox)
if self.palette256:
# this is where we have PIL optimally palette our image
subtile = apply_palette256(subtile)
subtile.save(buff, format)
body = buff.getvalue()
if self.write_cache:
self.config.cache.save(body, self, other, format)
if other == coord:
# the one that actually gets returned
tile = subtile
_addRecentTile(self, other, format, body)
return tile
def envelope(self, coord):
""" Projected rendering envelope (xmin, ymin, xmax, ymax) for a Coordinate.
"""
ul = self.projection.coordinateProj(coord)
lr = self.projection.coordinateProj(coord.down().right())
return min(ul.x, lr.x), min(ul.y, lr.y), max(ul.x, lr.x), max(ul.y, lr.y)
def metaEnvelope(self, coord):
""" Projected rendering envelope (xmin, ymin, xmax, ymax) for a metatile.
"""
# size of buffer expressed as fraction of tile size
buffer = float(self.metatile.buffer) / self.dim
# full set of metatile coordinates
coords = self.metatile.allCoords(coord)
# upper-left and lower-right expressed as fractional coordinates
ul = coords[0].left(buffer).up(buffer)
lr = coords[-1].right(1 + buffer).down(1 + buffer)
# upper-left and lower-right expressed as projected coordinates
ul = self.projection.coordinateProj(ul)
lr = self.projection.coordinateProj(lr)
# new render area coverage in projected coordinates
return min(ul.x, lr.x), min(ul.y, lr.y), max(ul.x, lr.x), max(ul.y, lr.y)
def metaSize(self, coord):
""" Pixel width and height of full rendered image for a metatile.
"""
# size of buffer expressed as fraction of tile size
buffer = float(self.metatile.buffer) / self.dim
# new master image render size
width = int(self.dim * (buffer * 2 + self.metatile.columns))
height = int(self.dim * (buffer * 2 + self.metatile.rows))
return width, height
def metaSubtiles(self, coord):
""" List of all coords in a metatile and their x, y offsets in a parent image.
"""
subtiles = []
coords = self.metatile.allCoords(coord)
for other in coords:
r = other.row - coords[0].row
c = other.column - coords[0].column
x = c * self.dim + self.metatile.buffer
y = r * self.dim + self.metatile.buffer
subtiles.append((other, x, y))
return subtiles
def getTypeByExtension(self, extension):
""" Get mime-type and PIL format by file extension.
"""
if hasattr(self.provider, 'getTypeByExtension'):
return self.provider.getTypeByExtension(extension)
elif extension.lower() == 'png':
return 'image/png', 'PNG'
elif extension.lower() == 'jpg':
return 'image/jpeg', 'JPEG'
else:
raise KnownUnknown('Unknown extension in configuration: "%s"' % extension)
def setSaveOptionsJPEG(self, quality=None, optimize=None, progressive=None):
""" Optional arguments are added to self.jpeg_options for pickup when saving.
More information about options:
http://effbot.org/imagingbook/format-jpeg.htm
"""
if quality is not None:
self.jpeg_options['quality'] = int(quality)
if optimize is not None:
self.jpeg_options['optimize'] = bool(optimize)
if progressive is not None:
self.jpeg_options['progressive'] = bool(progressive)
def setSaveOptionsPNG(self, optimize=None, palette=None, palette256=None):
""" Optional arguments are added to self.png_options for pickup when saving.
Palette argument is a URL relative to the configuration file,
and it implies bits and optional transparency options.
More information about options:
http://effbot.org/imagingbook/format-png.htm
"""
if optimize is not None:
self.png_options['optimize'] = bool(optimize)
if palette is not None:
palette = urljoin(self.config.dirpath, palette)
palette, bits, t_index = load_palette(palette)
self.bitmap_palette, self.png_options['bits'] = palette, bits
if t_index is not None:
self.png_options['transparency'] = t_index
if palette256 is not None:
self.palette256 = bool(palette256)
else:
self.palette256 = None
class KnownUnknown(Exception):
""" There are known unknowns. That is to say, there are things that we now know we don't know.
This exception gets thrown in a couple places where common mistakes are made.
"""
pass
class NoTileLeftBehind(Exception):
""" Leave no tile in the cache.
This exception can be thrown in a provider to signal to
TileStache.getTile() that the result tile should be returned,
but not saved in a cache. Useful in cases where a full tileset
is being rendered for static hosting, and you don't want millions
of identical ocean tiles.
The one constructor argument is an instance of PIL.Image or
some other object with a save() method, as would be returned
by provider renderArea() or renderTile() methods.
"""
def __init__(self, tile):
self.tile = tile
Exception.__init__(self, tile)
class TheTileLeftANote(Exception):
""" A tile exists, but it shouldn't be returned to the client. Headers
and/or a status code are provided in its stead.
This exception can be thrown in a provider or a cache to signal to
upstream servers where a tile can be found or to clients that a tile
is empty (or solid).
"""
def __init__(self, headers=None, status_code=200, content='', emit_content_type=True):
self.headers = headers or Headers([])
self.status_code = status_code
self.content = content
self.emit_content_type = bool(emit_content_type)
Exception.__init__(self, self.headers, self.status_code,
self.content, self.emit_content_type)
def _preview(layer):
""" Get an HTML response for a given named layer.
"""
layername = layer.name()
lat, lon = layer.preview_lat, layer.preview_lon
zoom = layer.preview_zoom
ext = layer.preview_ext
return """
TileStache Preview: %(layername)s
""" % locals()
def _rummy():
""" Draw Him.
"""
return ['------------------------------------------------------------------------------------------------------------',
'MB###BHHHBBMBBBB#####MBBHHHHBBBBHHAAA&GG&AAAHB###MHAAAAAAAAAHHAGh&&&AAAAH#@As;;shM@@@@@@@@@@@@@@@@@@@@@@@@@@',
'MGBMHAGG&&AAA&&AAM##MHAGG&GG&&GGGG93X5SS2XX9hh3255X2issii5X3h9X22555XXXX9H@A. rA@@@@@@@@@@@@@@@@@@@@@@@@@@',
'BAM#BAAAAAAHHAAAHM##MBHAAAAAAAAAAAAG9X2X3hGXiii5X9hG3X9Xisi29B##BA33hGGhGB@@r ;9@@@@@@@@@@@@@@@@@@@@@@@@@@',
'BAM#MHAAAHHHAAAAHM###BHAAAAAAAAAAAAGhXX3h2iSX&A&&AAHAGGAGs;rrri2r;rSiXGA&B@@9. ,2#@@@@@@@@@@@@@@@@@@@@@@@@@',
'B&B#MHAAAAHHHAAAHM##MBHAAAAAAAAAAHAG93XSrs5Xh93h3XXX93529Xr;:,,:;;s25223AB@@@; sB@@@@@@@@@@@@@@@@@@@@@@@@@',
'B&B#BAAAAAHHHAAAHB##MBAAAAAAAAAAAHHAh5rs2AGGAhXisiissSsr;r;::,:riiiisrr,s#@@@9. ,2#@@@@@@@@@@@@@@@@@@@@@@@@',
'B&B#BAAAAAAHAAAAHM###BHA&AAAAAA&AAHA2S@MBHGX22s;;;;r;;:,:,,:;;rrr:,,:,.X@@@@r :9@@@@@@@@@@@@@@@@@@@@@@@@',
'BAM#MAAAAAAAAAAAAB##MBAA&AAAAAAA&AH929AHA9XhXirrir::;r;;:::,:,,:,;rsr;,.,;2@@@#, :G@@@@@@@@@@@@@@@@@@@@@@B',
'B&B#MAAAAAAHAAAAABM#MHAA&&&&&&&&&H&ss3AXisisisr;;r;::;::::,..,,,,::;rir;,;,A@@@G. ;9@@@@@@@@@@@@@@@@@@@@@#',
'B&B#MHAAAAHHAAAAABM#MHAAA&G&A&&&AG2rr2X; .:;;;;::::::::::,,,,,:,.,;::;;,;rr:@@@@X :2#@@@@@@@@@@@@@@@@@@@@',
'B&B##HAAAAHHAAAAABMMMHAA&&&&&AAA&h2:r2r..:,,,,,,,,,,,,:;:,,,,,,. ,;;;::, ;2rr@@@@2 :SB@@@@@@@@@@@@@@@@@@@',
'BGB##HAAAAAAAAAAABMMMBAA&&&&&&&&AHr ir:;;;;:,,,,,,::::,,:,:,,,,...;:;:,:,:2Xr&@@@@3. .rG@@@@@@@@@@@@@@@@@@',
'B&B@#B&&AAAAAA&&AHMMMBAA&&&&&&&&AH,.i;;rrr;::,,:::::::,,::::::,,..;,:;.;;iXGSs#@@@@A, :5#@@@@@@@@@@@@@@@@',
'B&M@@B&&AAAHAA&&AHMMMBAA&&&&&&&&AA;,;rrrrr;;::::::::::::::::::::.:;.::,:5A9r,.9@@@@@M; .;G@@@@@@@@@@@@@@@',
'B&M@@B&&AAHAAA&&AHMMMBAA&G&GG&&&AM3;rrr;rr;;;;;;:::::;;,:,::,,,..,:;;:,;2r:.:;r@@##@@@i .sH@@@@@@@@@@@@@',
'BGM@@B&&AAAHAA&&AHMMMBHAGGGG&&&&AMHs;srrr;r:;;;;::::::,..,,,,,,...,;rrrsi, . :,#@####@@A; ,iB@@@@@@@@@@@',
'B@@B&&AAAAAA&&AHMMMBAA&GGGGG&&&BHr,rirr;;;::::::::::,,,,,::,,::,.,SS;r:.;r .,A#HHMBB#@@2, :iA@@@@@@@@@',
'B@@B&&AAAAAA&&AHBMBBAAGGGGGGG&&H#2:sis;;;::,,:::r;rsrr23HMAXr:::,:;...,,,5s,,#BGGAAAAB@@#i. ,rG@@@@@@@',
'B@@BG&AAAAAA&&AHHBMHAAGGhhGGGGGA#Hrs9s;;;;r;:;s5Xrrh@@@@@@@@&5rr;. .,,;. ;;.;@Bh39hhhAM#@@Ar. ,rG#@@@@',
'BA#@@BG&AAAAAA&&AHBMMBA&GGGGGGGGGAM#3r5SsiSSX@@@#@@i. 2h5ir;;:;r;:...,,:,.,;,,3@HG99XX23&H#MMBAS, .;2H@@',
'BA#@@B&&AAAAAA&&&AHBMBAA&GGGGGGGhABMhsrirrS9#@Mh5iG&::r;..:;:,,.,...,::,,,...,A@A&h9X255XGAA93B#MX; .:X',
'BH@@@B&&AAAAAA&G&ABM#BHAGGGGGGGGG&HBAXiir;s2r;;:rrsi.,,. .....,,,,::,.,,:: :2@H&Gh9X2523AG253AM@@Ai, ,',
'MB@@@B&&AAAAAAGGAA###@#H&GGGGGGG&AHBAXXi;,. .:,,, .;:,.,;:;..,::::;;;:,,,:,srs5@B&hhh32229AG2S29GAB#@#A2; .',
'MB@@@BGGAAAAA&&GAHr ,sH#AGGhhGGG&AH&X22s:..,. . ;S:,. .,i9r;::,,:;:::,:::,,5A#BAhhhX22X9AG2i2X9hG&AB#@@B3r',
'MB@@@B&&AAAAAA&AM#;.. ;AAGhhGGG&AHGX2XXis::,,,,,Xi,.:.ri;Xir;:,...,:::;::,.:S9#AGh9X2229A&2i52X39hhG&AM@@&',
'MM@@@B&GAAAHBHBhsiGhhGi. 3MGGhGGG&HH&X52GXshh2r;;rXiB25sX2r;;:ii;,...:;:;:;:.., r#G33X2223AG2i52XX3339hGAA&&',
'#M@@@B&GAM#A3hr .;S5;:, ;MAGhGGG&ABAX55X9rS93s::i::i52X;,::,,,;5r:,,,::;;;:,.i @@AXX222X&G2S52XXXX3399hhh&',
'#M@@@BAB&S; .:, .,,;,;;. rBGhhGG&ABAXSS29G5issrrS,,,,,:,...,;i;rr:,:,,::;::,,r #@@B25523&G2iS2XXX3X33999h&',
'#M@@@MH; ,. .;i::::;rr;, ,M&GGGh&AHAXSS2X3hXirss5;r;:;;;2#@@H9Ai;::,,,,:;:;:: ,@@@#Xi23&G2iS2XXX3X33339h&',
'#M#@@#i .:;,.,::,::;ⅈ.;#AGhGG&AHAXSS2XX3&hir;;s9GG@@@@@h;,,riirr;:,.:;;;. i@##@@AS2hh5iS222XXXX3999hG',
'#M@@@@:.;,,:r,,;r,,..h#sr: rHAGhG&AHAXSi52X39AAir::is;::,,. .::,sssrr;,,;r: ,@@MM#@@#HBA2iiSS5522XX39hhG',
'#M@@@@r.sr,:rr::r;,, ,As:, :B&hh&ABAXSiSS5229HHS3r;rSSsiiSSr;:,,,:;;r;;; @@#BMM#@@@@@@@@#MH&93XXXXX3G',
'#M@@@@A,:r:,:i,,rr,,. ;;;,. ;BGhhGAHAX5529hAAAM#AH#2i25Ss;;;:.....,rSi2r M@@MMMM##@#@@@@@@@@@@@@@@#MHA',
'#M@@@@M::rr::SS,;r;::.:;;r:rHAh9h&ABM##@@@@@@@@ABAAA25i;::;;;:,,,,:r32: H@@#MM######@@@@@@@@@@@@@@@@@#',
'#M@@@@@5:;sr;;9r:i;,.,sr;;iMHhGABM#####@@@@@@@BHH&H@#AXr;;r;rsr;;ssS; H@@##########@@@##@@@@@@@@@@@@#',
'#M@@@@##r;;s;:3&;rsSrrisr:h#AHM#######BM#@@@#HHH9hM@@@X&92XX9&&G2i, .,:,@@@##M########@@@####@@@@@@@@@##',
'#M#@@@M@2,:;s;;2s:rAX5SirS#BB##@@@##MAAHB#@#BBH93GA@@@2 2@@@MAAHA .,,:,,. G@@#M#################@@@@@@#####',
'#M#@@#M@;,;:,,,;h52iX33sX@@#@@@@@@@#Ah&&H####HhA@@@@@@@;s@@@@H5@@ . r@@##M###########@###@@@@@@#######',
'#M#@@@#r.:;;;;rrrrrri5iA@@#@@@@@@@@#HHAH##MBA@@@@@@@@3i@@@@@3:, ,@@#M############@@###@@@@@########',
'#M@@@@r r::::;;;;;;rirA@#@@@@@@@@@@@#MGAMMHBAB@@@@@@@@@#2@@@@#i .. #@##M#####@###@@@@###@@@@##########',
'#M#@@@ 2;;;;;;rr;rish@@#@#@@@@@@@@@@B&hGM#MH#@@@@@@@@@@3;,h@. .. :@@MM#######@@@@#####@@@@###########',
'#M@@#A ;r;riirrrr;:2S@###@@@@@@@@@@@#AH#@#HB#@@@@@@@@@@@@2A9 @@#BMMM############@#@@@####M#######',
'#M@MM# ,:,:;;,5ir@B#@@@@@@@@@@@@@@@@@#MMH#@@@@@@@@@@@@r Ms B@#MMMMMM####@###@@#@@@@#####M######@',
'##Mh@M . ...:;;,:@A#@@@@@@@@@@@#@@@@@@#MMHAB@@@@#G#@@#: i@@ r@@#MMM#######@@@@#@@@@@@#####M#####@@',
'#H3#@3. ,. ... :@@&@@@@@@@@@@@@@#@@#@@@MMBHGA@H&;:@@i :B@@@B .@@#MM####@@@##@@@#@@@@@#######M##M#@@@',
'M&AM5i;.,. ..,,rA@@MH@@@@@@@@@@@@@##@@@@@MMMBB#@h9hH#s;3######, .A@#MMM#####@@@@@##@@@#@@#####M#####M39B']
def loadClassPath(classpath):
""" Load external class based on a path.
Example classpath: "Module.Submodule:Classname".
Equivalent soon-to-be-deprecated classpath: "Module.Submodule.Classname".
"""
if ':' in classpath:
#
# Just-added support for "foo:blah"-style classpaths.
#
modname, objname = classpath.split(':', 1)
try:
__import__(modname)
module = modules[modname]
_class = eval(objname, module.__dict__)
if _class is None:
raise Exception('eval(%(objname)s) in %(modname)s came up None' % locals())
except Exception as e:
raise KnownUnknown('Tried to import %s, but: %s' % (classpath, e))
else:
#
# Support for "foo.blah"-style classpaths, TODO: deprecate this in v2.
#
classpath = classpath.split('.')
try:
module = __import__('.'.join(classpath[:-1]), fromlist=str(classpath[-1]))
except ImportError as e:
raise KnownUnknown('Tried to import %s, but: %s' % ('.'.join(classpath), e))
try:
_class = getattr(module, classpath[-1])
except AttributeError as e:
raise KnownUnknown('Tried to import %s, but: %s' % ('.'.join(classpath), e))
return _class
TileStache-1.51.5/TileStache/Geography.py 0000664 0000000 0000000 00000011427 13042202720 0020151 0 ustar 00root root 0000000 0000000 """ The geography bits of TileStache.
A Projection defines the relationship between the rendered tiles and the
underlying geographic data. Generally, just one popular projection is used
for most web maps, "spherical mercator".
Built-in projections:
- spherical mercator
- WGS84
Example use projection in a layer definition:
"layer-name": {
"projection": "spherical mercator",
...
}
You can define your own projection, with a module and object name as arguments:
"layer-name": {
...
"projection": "Module:Object"
}
The object must include methods that convert between coordinates, points, and
locations. See the included mercator and WGS84 implementations for example.
You can also instantiate a projection class using this syntax:
"layer-name": {
...
"projection": "Module:Object()"
}
"""
from ModestMaps.Core import Point, Coordinate
from ModestMaps.Geo import deriveTransformation, MercatorProjection, LinearProjection, Location
from math import log as _log, pi as _pi
from . import Core
class SphericalMercator(MercatorProjection):
""" Spherical mercator projection for most commonly-used web map tile scheme.
This projection is identified by the name "spherical mercator" in the
TileStache config. The simplified projection used here is described in
greater detail at: http://trac.openlayers.org/wiki/SphericalMercator
"""
srs = '+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over'
def __init__(self):
pi = _pi
# Transform from raw mercator projection to tile coordinates
t = deriveTransformation(-pi, pi, 0, 0, pi, pi, 1, 0, -pi, -pi, 0, 1)
MercatorProjection.__init__(self, 0, t)
def coordinateProj(self, coord):
""" Convert from Coordinate object to a Point object in EPSG:900913
"""
# the zoom at which we're dealing with meters on the ground
diameter = 2 * _pi * 6378137
zoom = _log(diameter) / _log(2)
coord = coord.zoomTo(zoom)
# global offsets
point = Point(coord.column, coord.row)
point.x = point.x - diameter/2
point.y = diameter/2 - point.y
return point
def projCoordinate(self, point):
""" Convert from Point object in EPSG:900913 to a Coordinate object
"""
# the zoom at which we're dealing with meters on the ground
diameter = 2 * _pi * 6378137
zoom = _log(diameter) / _log(2)
# global offsets
coord = Coordinate(point.y, point.x, zoom)
coord.column = coord.column + diameter/2
coord.row = diameter/2 - coord.row
return coord
def locationProj(self, location):
""" Convert from Location object to a Point object in EPSG:900913
"""
return self.coordinateProj(self.locationCoordinate(location))
def projLocation(self, point):
""" Convert from Point object in EPSG:900913 to a Location object
"""
return self.coordinateLocation(self.projCoordinate(point))
class WGS84(LinearProjection):
""" Unprojected projection for the other commonly-used web map tile scheme.
This projection is identified by the name "WGS84" in the TileStache config.
"""
srs = '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs'
def __init__(self):
p = _pi
# Transform from geography in radians to tile coordinates
t = deriveTransformation(-p, p/2, 0, 0, p, p/2, 2, 0, -p, -p/2, 0, 1)
LinearProjection.__init__(self, 0, t)
def coordinateProj(self, coord):
""" Convert from Coordinate object to a Point object in EPSG:4326
"""
return self.locationProj(self.coordinateLocation(coord))
def projCoordinate(self, point):
""" Convert from Point object in EPSG:4326 to a Coordinate object
"""
return self.locationCoordinate(self.projLocation(point))
def locationProj(self, location):
""" Convert from Location object to a Point object in EPSG:4326
"""
return Point(location.lon, location.lat)
def projLocation(self, point):
""" Convert from Point object in EPSG:4326 to a Location object
"""
return Location(point.y, point.x)
def getProjectionByName(name):
""" Retrieve a projection object by name.
Raise an exception if the name doesn't work out.
"""
if name.lower() == 'spherical mercator':
return SphericalMercator()
elif name.lower() == 'wgs84':
return WGS84()
else:
try:
return Core.loadClassPath(name)
except Exception as e:
raise Core.KnownUnknown('Failed projection in configuration: "%s" - %s' % (name, e))
TileStache-1.51.5/TileStache/Goodies/ 0000775 0000000 0000000 00000000000 13042202720 0017236 5 ustar 00root root 0000000 0000000 TileStache-1.51.5/TileStache/Goodies/AreaServer.py 0000664 0000000 0000000 00000007632 13042202720 0021657 0 ustar 00root root 0000000 0000000 """ AreaServer supplies a tiny image server for use with TileStache providers
that implement renderArea() (http://tilestache.org/doc/#custom-providers).
The built-in Mapnik provider (http://tilestache.org/doc/#mapnik-provider)
is one example.
There are no tiles here, just a quick & dirty way of getting variously-sized
images out of a codebase that's ordinarily oriented toward tile generation.
Example usage, with gunicorn (http://gunicorn.org):
gunicorn --bind localhost:8888 "TileStache.Goodies.AreaServer:WSGIServer('tilestache.cfg')"
AreaServer URLs are compatible with the built-in URL Template provider
(http://tilestache.org/doc/#url-template-provider) and implement a generic
kind of WMS (http://en.wikipedia.org/wiki/Web_Map_Service).
All six URL parameters shown in this example are required; any other
URL parameter is ignored:
http://localhost:8888/layer-name?width=600&height=600&xmin=-100&ymin=-100&xmax=100&ymax=100
"""
from urlparse import parse_qsl
from datetime import timedelta
from datetime import datetime
from StringIO import StringIO
from TileStache import WSGITileServer
from TileStache.Core import KnownUnknown
class WSGIServer (WSGITileServer):
""" WSGI Application that can handle WMS-style requests for static images.
Inherits the constructor from TileStache WSGI, which just loads
a TileStache configuration file into self.config.
WSGITileServer autoreload argument is ignored, though. For now.
"""
def __call__(self, environ, start_response):
""" Handle a request, using PATH_INFO and QUERY_STRING from environ.
There are six required query string parameters: width, height,
xmin, ymin, xmax and ymax. Layer name must be supplied in PATH_INFO.
"""
try:
for var in 'QUERY_STRING PATH_INFO'.split():
if var not in environ:
raise KnownUnknown('Missing "%s" environment variable' % var)
query = dict(parse_qsl(environ['QUERY_STRING']))
for param in 'width height xmin ymin xmax ymax'.split():
if param not in query:
raise KnownUnknown('Missing "%s" parameter' % param)
layer = environ['PATH_INFO'].strip('/')
layer = self.config.layers[layer]
provider = layer.provider
if not hasattr(provider, 'renderArea'):
raise KnownUnknown('Layer "%s" provider %s has no renderArea() method' % (layer.name(), provider.__class__))
width, height = [int(query[p]) for p in 'width height'.split()]
xmin, ymin, xmax, ymax = [float(query[p]) for p in 'xmin ymin xmax ymax'.split()]
#
# Don't supply srs or zoom parameters, which may cause problems for
# some providers. TODO: add optional support for these two parameters.
#
output = StringIO()
image = provider.renderArea(width, height, None, xmin, ymin, xmax, ymax, None)
image.save(output, format='PNG')
headers = [('Content-Type', 'image/png')]
if layer.allowed_origin:
headers.append(('Access-Control-Allow-Origin', layer.allowed_origin))
if layer.max_cache_age is not None:
expires = datetime.utcnow() + timedelta(seconds=layer.max_cache_age)
headers.append(('Expires', expires.strftime('%a %d %b %Y %H:%M:%S GMT')))
headers.append(('Cache-Control', 'public, max-age=%d' % layer.max_cache_age))
start_response('200 OK', headers)
return output.getvalue()
except KnownUnknown, e:
start_response('400 Bad Request', [('Content-Type', 'text/plain')])
return str(e)
TileStache-1.51.5/TileStache/Goodies/Caches/ 0000775 0000000 0000000 00000000000 13042202720 0020424 5 ustar 00root root 0000000 0000000 TileStache-1.51.5/TileStache/Goodies/Caches/GoogleCloud.py 0000664 0000000 0000000 00000006662 13042202720 0023213 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
""" Caches tiles to Google Cloud Storage.
Requires boto (2.0+):
http://pypi.python.org/pypi/boto
Example configuration:
"cache": {
"name": "TileStache.Goodies.Caches.GoogleCloud:Cache",
"kwargs": {
"bucket": "",
"access": "",
"secret": ""
}
}
cache parameters:
bucket
Required bucket name for GS. If it doesn't exist, it will be created.
access
Required access key ID for your GS account.
secret
Required secret access key for your GS account.
"""
from time import time
from mimetypes import guess_type
# URI scheme for Google Cloud Storage.
GOOGLE_STORAGE = 'gs'
# URI scheme for accessing local files.
LOCAL_FILE = 'file'
try:
import boto
except ImportError:
# at least we can build the documentation
pass
def tile_key(layer, coord, format):
""" Return a tile key string.
"""
name = layer.name()
tile = '%(zoom)d/%(column)d/%(row)d' % coord.__dict__
ext = format.lower()
return str('%(name)s/%(tile)s.%(ext)s' % locals())
class Cache:
"""
"""
def __init__(self, bucket, access, secret):
config = boto.config
config.add_section('Credentials')
config.set('Credentials', 'gs_access_key_id', access)
config.set('Credentials', 'gs_secret_access_key', secret)
uri = boto.storage_uri('', GOOGLE_STORAGE)
for b in uri.get_all_buckets():
if b.name == bucket:
self.bucket = b
#TODO: create bucket if not found
def lock(self, layer, coord, format):
""" Acquire a cache lock for this tile.
Returns nothing, but blocks until the lock has been acquired.
"""
key_name = tile_key(layer, coord, format)
due = time() + layer.stale_lock_timeout
while time() < due:
if not self.bucket.get_key(key_name+'-lock'):
break
_sleep(.2)
key = self.bucket.new_key(key_name+'-lock')
key.set_contents_from_string('locked.', {'Content-Type': 'text/plain'})
def unlock(self, layer, coord, format):
""" Release a cache lock for this tile.
"""
key_name = tile_key(layer, coord, format)
try:
self.bucket.delete_key(key_name+'-lock')
except:
pass
def remove(self, layer, coord, format):
""" Remove a cached tile.
"""
key_name = tile_key(layer, coord, format)
self.bucket.delete_key(key_name)
def read(self, layer, coord, format):
""" Read a cached tile.
"""
key_name = tile_key(layer, coord, format)
key = self.bucket.get_key(key_name)
if key is None:
return None
if layer.cache_lifespan:
t = timegm(strptime(key.last_modified, '%a, %d %b %Y %H:%M:%S %Z'))
if (time() - t) > layer.cache_lifespan:
return None
return key.get_contents_as_string()
def save(self, body, layer, coord, format):
""" Save a cached tile.
"""
key_name = tile_key(layer, coord, format)
key = self.bucket.new_key(key_name)
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')
TileStache-1.51.5/TileStache/Goodies/Caches/LimitedDisk.py 0000664 0000000 0000000 00000016721 13042202720 0023207 0 ustar 00root root 0000000 0000000 """ Cache that stores a limited amount of data.
This is an example cache that uses a SQLite database to track sizes and last-read
times for cached tiles, and removes least-recently-used tiles whenever the total
size of the cache exceeds a set limit.
Example TileStache cache configuration, with a 16MB limit:
"cache":
{
"class": "TileStache.Goodies.Caches.LimitedDisk.Cache",
"kwargs": {
"path": "/tmp/limited-cache",
"limit": 16777216
}
}
"""
import os
import sys
import time
from math import ceil as _ceil
from tempfile import mkstemp
from os.path import isdir, exists, dirname, basename, join as pathjoin
from sqlite3 import connect, OperationalError, IntegrityError
_create_tables = """
CREATE TABLE IF NOT EXISTS locks (
row INTEGER,
column INTEGER,
zoom INTEGER,
format TEXT,
PRIMARY KEY (row, column, zoom, format)
)
""", """
CREATE TABLE IF NOT EXISTS tiles (
path TEXT PRIMARY KEY,
used INTEGER,
size INTEGER
)
""", """
CREATE INDEX IF NOT EXISTS tiles_used ON tiles (used)
"""
class Cache:
def __init__(self, path, limit, umask=0022):
self.cachepath = path
self.dbpath = pathjoin(self.cachepath, 'stache.db')
self.umask = umask
self.limit = limit
db = connect(self.dbpath).cursor()
for create_table in _create_tables:
db.execute(create_table)
db.connection.close()
def _filepath(self, layer, coord, format):
"""
"""
l = layer.name()
z = '%d' % coord.zoom
e = format.lower()
x = '%06d' % coord.column
y = '%06d' % coord.row
x1, x2 = x[:3], x[3:]
y1, y2 = y[:3], y[3:]
filepath = os.sep.join( (l, z, x1, x2, y1, y2 + '.' + e) )
return filepath
def lock(self, layer, coord, format):
""" Acquire a cache lock for this tile.
Returns nothing, but (TODO) blocks until the lock has been acquired.
Lock is implemented as a row in the "locks" table.
"""
sys.stderr.write('lock %d/%d/%d, %s' % (coord.zoom, coord.column, coord.row, format))
due = time.time() + layer.stale_lock_timeout
while True:
if time.time() > due:
# someone left the door locked.
sys.stderr.write('...force %d/%d/%d, %s' % (coord.zoom, coord.column, coord.row, format))
self.unlock(layer, coord, format)
# try to acquire a lock, repeating if necessary.
db = connect(self.dbpath, isolation_level='EXCLUSIVE').cursor()
try:
db.execute("""INSERT INTO locks
(row, column, zoom, format)
VALUES (?, ?, ?, ?)""",
(coord.row, coord.column, coord.zoom, format))
except IntegrityError:
db.connection.close()
time.sleep(.2)
continue
else:
db.connection.commit()
db.connection.close()
break
def unlock(self, layer, coord, format):
""" Release a cache lock for this tile.
Lock is implemented as a row in the "locks" table.
"""
sys.stderr.write('unlock %d/%d/%d, %s' % (coord.zoom, coord.column, coord.row, format))
db = connect(self.dbpath, isolation_level='EXCLUSIVE').cursor()
db.execute("""DELETE FROM locks
WHERE row=? AND column=? AND zoom=? AND format=?""",
(coord.row, coord.column, coord.zoom, format))
db.connection.commit()
db.connection.close()
def remove(self, layer, coord, format):
""" Remove a cached tile.
"""
# TODO: write me
raise NotImplementedError('LimitedDisk Cache does not yet implement the .remove() method.')
def read(self, layer, coord, format):
""" Read a cached tile.
If found, update the used column in the tiles table with current time.
"""
sys.stderr.write('read %d/%d/%d, %s' % (coord.zoom, coord.column, coord.row, format))
path = self._filepath(layer, coord, format)
fullpath = pathjoin(self.cachepath, path)
if exists(fullpath):
body = open(fullpath, 'r').read()
sys.stderr.write('...hit %s, set used=%d' % (path, time.time()))
db = connect(self.dbpath).cursor()
db.execute("""UPDATE tiles
SET used=?
WHERE path=?""",
(int(time.time()), path))
db.connection.commit()
db.connection.close()
else:
sys.stderr.write('...miss')
body = None
return body
def _write(self, body, path, format):
""" Actually write the file to the cache directory, return its size.
If filesystem block size is known, try to return actual disk space used.
"""
fullpath = pathjoin(self.cachepath, path)
try:
umask_old = os.umask(self.umask)
os.makedirs(dirname(fullpath), 0777&~self.umask)
except OSError, e:
if e.errno != 17:
raise
finally:
os.umask(umask_old)
fh, tmp_path = mkstemp(dir=self.cachepath, suffix='.' + format.lower())
os.write(fh, body)
os.close(fh)
try:
os.rename(tmp_path, fullpath)
except OSError:
os.unlink(fullpath)
os.rename(tmp_path, fullpath)
os.chmod(fullpath, 0666&~self.umask)
stat = os.stat(fullpath)
size = stat.st_size
if hasattr(stat, 'st_blksize'):
blocks = _ceil(size / float(stat.st_blksize))
size = int(blocks * stat.st_blksize)
return size
def _remove(self, path):
"""
"""
fullpath = pathjoin(self.cachepath, path)
os.unlink(fullpath)
def save(self, body, layer, coord, format):
"""
"""
sys.stderr.write('save %d/%d/%d, %s' % (coord.zoom, coord.column, coord.row, format))
path = self._filepath(layer, coord, format)
size = self._write(body, path, format)
db = connect(self.dbpath).cursor()
try:
db.execute("""INSERT INTO tiles
(size, used, path)
VALUES (?, ?, ?)""",
(size, int(time.time()), path))
except IntegrityError:
db.execute("""UPDATE tiles
SET size=?, used=?
WHERE path=?""",
(size, int(time.time()), path))
row = db.execute('SELECT SUM(size) FROM tiles').fetchone()
if row and (row[0] > self.limit):
over = row[0] - self.limit
while over > 0:
row = db.execute('SELECT path, size FROM tiles ORDER BY used ASC LIMIT 1').fetchone()
if row is None:
break
path, size = row
db.execute('DELETE FROM tiles WHERE path=?', (path, ))
self._remove(path)
over -= size
sys.stderr.write('delete ' + path)
db.connection.commit()
db.connection.close()
TileStache-1.51.5/TileStache/Goodies/Caches/__init__.py 0000664 0000000 0000000 00000000051 13042202720 0022531 0 ustar 00root root 0000000 0000000 """ Additional cache classes go here.
""" TileStache-1.51.5/TileStache/Goodies/ExternalConfigServer.py 0000664 0000000 0000000 00000011145 13042202720 0023711 0 ustar 00root root 0000000 0000000 """ ExternalConfigServer is a replacement for WSGITileServer that uses external
configuration fetched via HTTP to service all config requests.
Example usage, with gunicorn (http://gunicorn.org):
gunicorn --bind localhost:8888 "TileStache.Goodies.ExternalConfigServer:WSGIServer(url)"
"""
from urllib import urlopen
import logging
try:
from json import load as json_load
except ImportError:
from simplejson import load as json_load
import TileStache
class DynamicLayers:
def __init__(self, config, url_root, cache_responses, dirpath):
self.config = config
self.url_root = url_root
self.dirpath = dirpath
self.cache_responses = cache_responses;
self.seen_layers = {}
self.lookup_failures = set()
def keys(self):
return self.seen_layers.keys()
def items(self):
return self.seen_layers.items()
def parse_layer(self, layer_json):
layer_dict = json_load(layer_json)
return TileStache.Config._parseConfigLayer(layer_dict, self.config, self.dirpath)
def __contains__(self, key):
# If caching is enabled and we've seen a request for this layer before, return True unless
# the prior lookup failed to find this layer.
if self.cache_responses:
if key in self.seen_layers:
return True
elif key in self.lookup_failures:
return False
res = urlopen(self.url_root + "/layer/" + key)
if self.cache_responses:
if res.getcode() != 200:
# Cache a failed lookup
self.lookup_failures.add(key)
else :
# If lookup succeeded and we are caching, parse the layer now so that a subsequent
# call to __getitem__ doesn't require a call to the config server. If we aren't
# caching, we skip this step to avoid an unnecessary json parse.
try:
self.seen_layers[key] = self.parse_layer(res)
except ValueError:
# The JSON received by the config server was invalid. Treat this layer as a
# failure. We don't want to raise ValueError from here because other parts
# of TileStache are just expecting a boolean response from __contains__
logging.error("Invalid JSON response seen for %s", key)
self.lookup_failures.add(key)
return False
if res.getcode() != 200:
logging.info("Config response code %s for %s", res.getcode(), key)
return res.getcode() == 200
def __getitem__(self, key):
if self.cache_responses:
if key in self.seen_layers:
return self.seen_layers[key]
elif key in self.lookup_failures:
# If we are caching, raise KnownUnknown if we have previously failed to find this layer
raise TileStache.KnownUnknown("Layer %s previously not found", key)
logging.debug("Requesting layer %s", self.url_root + "/layer/" + key)
res = urlopen(self.url_root + "/layer/" + key)
if (res.getcode() != 200) :
logging.info("Config response code %s for %s", res.getcode(), key)
if (self.cache_responses) :
self.lookup_failures.add(key)
raise TileStache.KnownUnknown("Layer %s not found", key)
try :
layer = self.parse_layer(res)
self.seen_layers[key] = layer
return layer
except ValueError:
logging.error("Invalid JSON response seen for %s", key)
if (self.cache_responses) :
# If caching responses, cache this failure
self.lookup_failures.add(key)
# KnownUnknown seems like the appropriate thing to raise here since this is akin
# to a missing configuration.
raise TileStache.KnownUnknown("Failed to parse JSON configuration for %s", key)
class ExternalConfiguration:
def __init__(self, url_root, cache_dict, cache_responses, dirpath):
self.cache = TileStache.Config._parseConfigCache(cache_dict, dirpath)
self.dirpath = dirpath
self.layers = DynamicLayers(self, url_root, cache_responses, dirpath)
class WSGIServer (TileStache.WSGITileServer):
"""
Wrap WSGI application, passing it a custom configuration.
The WSGI application is an instance of TileStache:WSGITileServer.
This method is initiated with a url_root that contains the scheme, host, port
and path that must prefix the API calls on our local server. Any valid http
or https urls should work.
The cache_responses parameter tells TileStache to cache all responses from
the configuration server.
"""
def __init__(self, url_root, cache_responses=True, debug_level="DEBUG"):
logging.basicConfig(level=debug_level)
# Call API server at url to grab cache_dict
cache_dict = json_load(urlopen(url_root + "/cache"))
dirpath = '/tmp/stache'
config = ExternalConfiguration(url_root, cache_dict, cache_responses, dirpath)
TileStache.WSGITileServer.__init__(self, config, False)
def __call__(self, environ, start_response):
response = TileStache.WSGITileServer.__call__(self, environ, start_response)
return response
TileStache-1.51.5/TileStache/Goodies/Proj4Projection.py 0000664 0000000 0000000 00000015015 13042202720 0022645 0 ustar 00root root 0000000 0000000 """ Projection that supports any projection that can be expressed in Proj.4 format.
The projection is configured by a projection definition in the Proj.4
format, the resolution of the zoom levels that the projection should
support, the tile size, and a transformation that defines how to tile
coordinates are calculated.
An example, instantiating a projection for EPSG:2400 (RT90 2.5 gon W):
Proj4Projection('+proj=tmerc +lat_0=0 +lon_0=15.80827777777778 +k=1'
+' +x_0=1500000 +y_0=0 +ellps=bessel +units=m +no_defs',
[8192, 4096, 2048, 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1],
transformation=Transformation(1, 0, 0, 0, -1, 0))
This example defines 14 zoom levels, where each level doubles the
resolution, where the most zoomed out level uses 8192 projected units
(meters, in this case) per pixel. The tiles are adressed using XYZ scheme,
with the origin at (0, 0): the x component of the transformation is 1, the
y component is -1 (tile rows increase from north to south). Tile size
defaults to 256x256 pixels.
The same projection, included in a TileStache configuration file:
"example":
{
"provider": {"name": "mapnik", "mapfile": "examples/style.xml"},
"projection": "TileStache.Goodies.Proj4Projection:Proj4Projection('+proj=tmerc +lat_0=0 +lon_0=15.80827777777778 +k=1 +x_0=1500000 +y_0=0 +ellps=bessel +units=m +no_defs', [8192, 4096, 2048, 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1], transformation=Transformation(1, 0, 0, 0, -1, 0))"
}
"Module:Class()" syntax described in http://tilestache.org/doc/#projections.
For more details about tiling, projections, zoom levels and transformations,
see http://blog.kartena.se/local-projections-in-a-world-of-spherical-mercator/
"""
import TileStache
from pyproj import Proj
from ModestMaps.Core import Point, Coordinate
from ModestMaps.Geo import Location, LinearProjection, Transformation
_grid_threshold = 1e-3
class Proj4Projection(LinearProjection):
""" Projection that supports any projection that can be expressed in Proj.4 format.
Required attributes:
srs:
The Proj.4 definition of the projection to use, as a string
resolutions:
An array of the zoom levels' resolutions, expressed as the number
of projected units per pixel on each zoom level. The array is ordered
with outermost zoom level first (0 is most zoomed out).
Optional attributes:
tile_size:
The size of a tile in pixels, default is 256.
transformation:
Transformation to apply to the projected coordinates to convert them
to tile coordinates. Defaults to Transformation(1, 0, 0, 1, 0), which
gives row = projected_y * scale, column = projected_x * scale
"""
def __init__(self, srs, resolutions, tile_size=256, transformation=Transformation(1, 0, 0, 0, 1, 0)):
"""
Creates a new instance with the projection specified in srs, which is in Proj4
format.
"""
self.resolutions = resolutions
self.tile_size = tile_size
self.proj = Proj(srs)
self.srs = srs
self.tile_dimensions = \
[self.tile_size * r for r in self.resolutions]
try:
self.base_zoom = self.resolutions.index(1.0)
except ValueError:
raise TileStache.Core.KnownUnknown('No zoom level with resolution 1.0')
LinearProjection.__init__(self, self.base_zoom, transformation)
def project(self, point, scale):
p = LinearProjection.project(self, point)
p.x = p.x * scale
p.y = p.y * scale
return p
def unproject(self, point, scale):
p = LinearProjection.unproject(self, point)
p.x = p.x / scale
p.y = p.y / scale
return p
def locationCoordinate(self, location):
point = self.locationProj(location)
point = self.project(point, 1.0 / self.tile_dimensions[self.zoom])
return Coordinate(point.y, point.x, self.zoom)
def coordinateLocation(self, coord):
''' TODO: write me.
'''
raise NotImplementedError('Missing Proj4Projection.coordinateLocation(), see https://github.com/migurski/TileStache/pull/127')
def coordinateProj(self, coord):
"""Convert from Coordinate object to a Point object in the defined projection"""
if coord.zoom >= len(self.tile_dimensions):
raise TileStache.Core.KnownUnknown('Requested zoom level %d outside defined resolutions.' % coord.zoom)
p = self.unproject(Point(coord.column, coord.row), 1.0 / self.tile_dimensions[coord.zoom])
return p
def locationProj(self, location):
"""Convert from Location object to a Point object in the defined projection"""
x,y = self.proj(location.lon, location.lat)
return Point(x, y)
def projCoordinate(self, point, zoom=None):
"""Convert from Point object in the defined projection to a Coordinate object"""
if zoom == None:
zoom = self.base_zoom
if zoom >= len(self.tile_dimensions):
raise TileStache.Core.KnownUnknown('Requested zoom level %d outside defined resolutions.' % zoom)
td = self.tile_dimensions[zoom]
p = self.project(point, 1.0 / td)
row = round(p.y)
col = round(p.x)
if abs(p.y - row) > _grid_threshold \
or abs(p.x - col) > _grid_threshold:
raise TileStache.Core.KnownUnknown(('Point(%f, %f) does not align with grid '
+ 'for zoom level %d '
+ '(resolution=%f, difference: %f, %f).') %
(point.x, point.y, zoom, self.resolutions[zoom],
p.y - row, p.x - col))
c = Coordinate(int(row), int(col), zoom)
return c
def projLocation(self, point):
"""Convert from Point object in the defined projection to a Location object"""
x,y = self.proj(point.x, point.y, inverse=True)
return Location(y, x)
def findZoom(self, resolution):
try:
return self.resolutions.index(resolution)
except ValueError:
raise TileStache.Core.KnownUnknown("No zoom level with resolution %f defined." % resolution)
TileStache-1.51.5/TileStache/Goodies/Providers/ 0000775 0000000 0000000 00000000000 13042202720 0021213 5 ustar 00root root 0000000 0000000 TileStache-1.51.5/TileStache/Goodies/Providers/Cascadenik.py 0000664 0000000 0000000 00000003067 13042202720 0023620 0 ustar 00root root 0000000 0000000 ''' Cascadenik Provider.
Simple wrapper for TileStache Mapnik provider that parses Cascadenik MML files
directly, skipping the typical compilation to XML step.
More information on Cascadenik:
- https://github.com/mapnik/Cascadenik/wiki/Cascadenik
Requires Cascadenik 2.x+.
'''
from tempfile import gettempdir
try:
from ...Mapnik import ImageProvider, mapnik
from cascadenik import load_map
except ImportError:
# can still build documentation
pass
class Provider (ImageProvider):
""" Renders map images from Cascadenik MML files.
Arguments:
- mapfile (required)
Local file path to Mapnik XML file.
- fonts (optional)
Local directory path to *.ttf font files.
- workdir (optional)
Directory path for working files, tempfile.gettempdir() by default.
"""
def __init__(self, layer, mapfile, fonts=None, workdir=None):
""" Initialize Cascadenik provider with layer and mapfile.
"""
self.workdir = workdir or gettempdir()
self.mapnik = None
ImageProvider.__init__(self, layer, mapfile, fonts)
def renderArea(self, width, height, srs, xmin, ymin, xmax, ymax, zoom):
""" Mostly hand off functionality to Mapnik.ImageProvider.renderArea()
"""
if self.mapnik is None:
self.mapnik = mapnik.Map(0, 0)
load_map(self.mapnik, str(self.mapfile), self.workdir, cache_dir=self.workdir)
return ImageProvider.renderArea(self, width, height, srs, xmin, ymin, xmax, ymax, zoom)
TileStache-1.51.5/TileStache/Goodies/Providers/Composite.py 0000664 0000000 0000000 00000126674 13042202720 0023547 0 ustar 00root root 0000000 0000000 """ Layered, composite rendering for TileStache.
NOTE: This code is currently in heavy progress. I'm finishing the addition
of the new JSON style of layer configuration, while the original XML form
is *deprecated* and will be removed in the future TileStache 2.0.
The Composite Provider provides a Photoshop-like rendering pipeline, making it
possible to use the output of other configured tile layers as layers or masks
to create a combined output. Composite is modeled on Lars Ahlzen's TopOSM.
The "stack" configuration parameter describes a layer or stack of layers that
can be combined to create output. A simple stack that merely outputs a single
color orange tile looks like this:
{"color" "#ff9900"}
Other layers in the current TileStache configuration can be reference by name,
as in this example stack that simply echoes another layer:
{"src": "layer-name"}
Layers can be limited to appear at certain zoom levels, given either as a range
or as a single number:
{"src": "layer-name", "zoom": "12"}
{"src": "layer-name", "zoom": "12-18"}
Layers can also be used as masks, as in this example that uses one layer
to mask another layer:
{"mask": "layer-name", "src": "other-layer"}
Many combinations of "src", "mask", and "color" can be used together, but it's
an error to provide all three.
Layers can be combined through the use of opacity and blend modes. Opacity is
specified as a value from 0.0-1.0, and blend mode is specified as a string.
This example layer is blended using the "hard light" mode at 50% opacity:
{"src": "hillshading", "mode": "hard light", "opacity": 0.5}
Currently-supported blend modes include "screen", "multiply", "linear light",
and "hard light".
Layers can also be affected by adjustments. Adjustments are specified as an
array of names and parameters. This example layer has been slightly darkened
using the "curves" adjustment, moving the input value of 181 (light gray)
to 50% gray while leaving black and white alone:
{"src": "hillshading", "adjustments": [ ["curves", [0, 181, 255]] ]}
Available adjustments:
"threshold" - apply_threshold_adjustment()
"curves" - apply_curves_adjustment()
"curves2" - apply_curves2_adjustment()
Finally, the stacking feature allows layers to combined in more complex ways.
This example stack combines a background color and foreground layer:
[
{"color": "#ff9900"},
{"src": "layer-name"}
]
Stacks can be nested as well, such as this combination of two background layers
and two foreground layers:
[
[
{"color"" "#0066ff"},
{"src": "continents"}
],
[
{"src": "streets"},
{"src": "labels"}
]
]
A complete example configuration might look like this:
{
"cache":
{
"name": "Test"
},
"layers":
{
"base":
{
"provider": {"name": "mapnik", "mapfile": "mapnik-base.xml"}
},
"halos":
{
"provider": {"name": "mapnik", "mapfile": "mapnik-halos.xml"},
"metatile": {"buffer": 128}
},
"outlines":
{
"provider": {"name": "mapnik", "mapfile": "mapnik-outlines.xml"},
"metatile": {"buffer": 16}
},
"streets":
{
"provider": {"name": "mapnik", "mapfile": "mapnik-streets.xml"},
"metatile": {"buffer": 128}
},
"composite":
{
"provider":
{
"class": "TileStache.Goodies.Providers.Composite:Provider",
"kwargs":
{
"stack":
[
{"src": "base"},
[
{"src": "outlines", "mask": "halos"},
{"src": "streets"}
]
]
}
}
}
}
}
It's also possible to provide an equivalent "stackfile" argument that refers to
an XML file, but this feature is *deprecated* and will be removed in the future
release of TileStache 2.0.
Corresponding example stackfile XML:
Note that each layer in this file refers to a TileStache layer by name.
This complete example can be found in the included examples directory.
"""
import sys
import re
from urllib import urlopen
from urlparse import urljoin
from os.path import join as pathjoin
from xml.dom.minidom import parse as parseXML
from StringIO import StringIO
try:
from json import loads as jsonload
except ImportError:
from simplejson import loads as jsonload
import TileStache
try:
import numpy
import sympy
except ImportError:
# At least we can build the docs
pass
try:
from PIL import Image
except ImportError:
# On some systems, PIL.Image is known as Image.
import Image
from TileStache.Core import KnownUnknown
class Provider:
""" Provides a Photoshop-like rendering pipeline, making it possible to use
the output of other configured tile layers as layers or masks to create
a combined output.
"""
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)
elif stack is None and stackfile:
#
# The stackfile argument is super-deprecated.
#
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):
""" An old name for the Provider class, deprecated for the next version.
"""
pass
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'),
('mode', 'blendmode'), ('adjustments', 'adjustments'),
('zoom', 'zoom'))
args = [(arg, obj[key]) for (key, arg) in keys if key in obj]
return Layer(**dict(args))
else:
raise Exception('Uh oh')
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.
"""
def __init__(self, layername=None, colorname=None, maskname=None, opacity=1.0,
blendmode=None, adjustments=None, zoom=""):
""" 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.
"""
self.layername = layername
self.colorname = colorname
self.maskname = maskname
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')
def in_zoom(self, zoom):
""" 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.
Given a configuration object, starting image, and coordinate,
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')
layer_img = Image.open(StringIO(body)).convert('RGBA')
layer_rgba = _img2rgba(layer_img)
has_layer = True
if self.maskname:
layer = config.layers[self.maskname]
mime, body = TileStache.getTile(layer, coord, 'png')
mask_img = Image.open(StringIO(body)).convert('L')
mask_chan = _img2arr(mask_img).astype(numpy.float32) / 255.
has_mask = True
if self.colorname:
color = make_color(self.colorname)
color_rgba = [numpy.zeros(output_rgba[0].shape, numpy.float32) + band/255.0 for band in color]
has_color = True
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)
output_rgba = blend_images(output_rgba, layer_rgba[:3], layer_rgba[3], self.opacity, self.blendmode)
elif has_layer and has_mask:
# need to combine the masks here
layermask_chan = layer_rgba[3] * mask_chan
output_rgba = blend_images(output_rgba, layer_rgba[:3], layermask_chan, self.opacity, self.blendmode)
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)
elif has_mask:
raise KnownUnknown("You have to provide more than just a mask to Composite Layer: %s" % repr(self.maskname))
else:
raise KnownUnknown("You have to provide at least some combination of src, color and mask to Composite Layer")
return output_rgba
def __str__(self):
return self.layername
class Stack:
""" A stack of image layers.
"""
def __init__(self, layers):
""" A new image stack.
Argument:
layers:
List of Layer instances.
"""
self.layers = layers
def in_zoom(self, level):
"""
"""
return True
def render(self, config, input_rgba, coord):
""" Render this image stack.
Given a configuration object, starting image, and coordinate,
return an output image with the results of all the layers in
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):
stack_rgba = layer.render(config, stack_rgba, coord)
except IOError:
# Be permissive of I/O errors getting sub-layers, for example if a
# proxy layer referenced here doesn't have an image for a zoom level.
# TODO: regret this later.
pass
return blend_images(input_rgba, stack_rgba[:3], stack_rgba[3], 1, None)
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"
null: "#0000", "#00000000"
orange: "#f90", "#ff9900", "#ff9900ff"
transparent orange: "#f908", "#ff990088"
"""
if type(color) not in (str, unicode):
raise KnownUnknown('Color must be a string: %s' % repr(color))
if color[0] != '#':
raise KnownUnknown('Color must start with hash: "%s"' % color)
if len(color) not in (4, 5, 7, 9):
raise KnownUnknown('Color must have three, four, six or seven hex chars: "%s"' % color)
if len(color) == 4:
color = ''.join([color[i] for i in (0, 1, 1, 2, 2, 3, 3)])
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)
b = int(color[5:7], 16)
a = len(color) == 7 and 0xFF or int(color[7:9], 16)
except ValueError:
raise KnownUnknown('Color must be made up of valid hex chars: "%s"' % color)
return r, g, b, a
def _arr2img(ar):
""" Convert Numeric array to PIL Image.
"""
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.tobytes(), numpy.ubyte), (im.size[1], im.size[0]))
def _rgba2img(rgba):
""" Convert four Numeric array objects to PIL Image.
"""
assert type(rgba) is list
return Image.merge('RGBA', [_arr2img(numpy.round(band * 255.0).astype(numpy.ubyte)) for band in rgba])
def _img2rgba(im):
""" Convert PIL Image to four Numeric array objects.
"""
assert im.mode == 'RGBA'
return [_img2arr(band).astype(numpy.float32) / 255.0 for band in im.split()]
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()
"""
if not adjustments:
return rgba
for adjustment in adjustments:
name, args = adjustment[0], adjustment[1:]
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):
"""
"""
if green_value is None or blue_value is None:
# if there aren't three provided, use the one
green_value, blue_value = red_value, red_value
# 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]
]
"""
# 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:
[
"curves2",
[[0, 22], [128, 128], [255, 255]],
[[0, 29], [128, 128], [255, 255]],
[[0, 65], [128, 128], [255, 228]]
]
"""
if map_green is None or map_blue is None:
# if there aren't three provided, use the one
map_green, map_blue = map_red, map_red
# 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]
else:
blend_functions = {'screen': blend_channels_screen,
'multiply': blend_channels_multiply,
'linear light': blend_channels_linear_light,
'hard light': blend_channels_hard_light}
if blendmode in blend_functions:
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):
#
# 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)
return output_rgba
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)
else:
raise Exception('Unknown element "%s"' % child.tagName)
print >> sys.stderr, 'Making a stack with %d layers' % len(layers)
return Stack(layers)
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.frombytes('RGBA', (3, 3), string)
def renderTile(self, *args, **kwargs):
return SizelessImage(self.img)
def tinybitmap_layer(config, string):
""" Gin up a fake layer with a TinyBitmap provider.
"""
meta = TileStache.Core.Metatile()
proj = TileStache.Geography.SphericalMercator()
layer = TileStache.Core.Layer(config, proj, meta)
layer.provider = TinyBitmap(string)
return layer
def minimal_stack_layer(config, stack):
"""
"""
meta = TileStache.Core.Metatile()
proj = TileStache.Geography.SphericalMercator()
layer = TileStache.Core.Layer(config, proj, meta)
layer.provider = Provider(layer, stack=stack)
return layer
class ColorTests(unittest.TestCase):
"""
"""
def testColors(self):
assert make_color('#ffffff') == (0xFF, 0xFF, 0xFF, 0xFF), 'white'
assert make_color('#fff') == (0xFF, 0xFF, 0xFF, 0xFF), 'white again'
assert make_color('#ffff') == (0xFF, 0xFF, 0xFF, 0xFF), 'white again again'
assert make_color('#ffffffff') == (0xFF, 0xFF, 0xFF, 0xFF), 'white again again again'
assert make_color('#000000') == (0x00, 0x00, 0x00, 0xFF), 'black'
assert make_color('#000') == (0x00, 0x00, 0x00, 0xFF), 'black again'
assert make_color('#000f') == (0x00, 0x00, 0x00, 0xFF), 'black again'
assert make_color('#000000ff') == (0x00, 0x00, 0x00, 0xFF), 'black again again'
assert make_color('#0000') == (0x00, 0x00, 0x00, 0x00), 'null'
assert make_color('#00000000') == (0x00, 0x00, 0x00, 0x00), 'null again'
assert make_color('#f90') == (0xFF, 0x99, 0x00, 0xFF), 'orange'
assert make_color('#ff9900') == (0xFF, 0x99, 0x00, 0xFF), 'orange again'
assert make_color('#ff9900ff') == (0xFF, 0x99, 0x00, 0xFF), 'orange again again'
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
self.assertRaises(KnownUnknown, make_color, True)
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:
#
# +------+ +------+ +------+ +------+ +------+
# |\\\\\\| |++++--| | ////| | ''| |\\//''|
# |\\\\\\| + |++++--| + |//////| + | '' | > |//''\\|
# |\\\\\\| |------| |//// | |'' | |''\\\\|
# +------+ +------+ +------+ +------+ +------+
# base halos outlines streets output
#
# 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),
'halos': tinybitmap_layer(self.config, _fff + _fff + _000 + _fff + _fff + (_000 * 4)),
'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"},
[
{"src": "outlines"},
{"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'
assert img.getpixel((0, 1)) == (0x99, 0x99, 0x99, 0xFF), 'center left pixel'
assert img.getpixel((1, 1)) == (0xFF, 0xFF, 0xFF, 0xFF), 'middle pixel'
assert img.getpixel((2, 1)) == (0x99, 0x99, 0x99, 0xFF), 'center right pixel'
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"},
[
{"src": "outlines", "mask": "halos"},
{"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'
assert img.getpixel((0, 1)) == (0x99, 0x99, 0x99, 0xFF), 'center left pixel'
assert img.getpixel((1, 1)) == (0xFF, 0xFF, 0xFF, 0xFF), 'middle pixel'
assert img.getpixel((2, 1)) == (0xCC, 0xCC, 0xCC, 0xFF), 'center right pixel'
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"},
[
{"src": "outlines", "mask": "halos"},
{"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'
assert img.getpixel((0, 1)) == (0x99, 0x99, 0x99, 0xFF), 'center left pixel'
assert img.getpixel((1, 1)) == (0xFF, 0xFF, 0xFF, 0xFF), 'middle pixel'
assert img.getpixel((2, 1)) == (0xCC, 0xCC, 0xCC, 0xFF), 'center right pixel'
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"},
[
{"color": "#999", "mask": "halos"},
{"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'
assert img.getpixel((0, 1)) == (0x99, 0x99, 0x99, 0xFF), 'center left pixel'
assert img.getpixel((1, 1)) == (0xFF, 0xFF, 0xFF, 0xFF), 'middle pixel'
assert img.getpixel((2, 1)) == (0xCC, 0xCC, 0xCC, 0xFF), 'center right pixel'
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 = \
[
[
{"color": "#999", "mask": "halos"},
{"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'
assert img.getpixel((0, 1)) == (0x99, 0x99, 0x99, 0xFF), 'center left pixel'
assert img.getpixel((1, 1)) == (0xFF, 0xFF, 0xFF, 0xFF), 'middle pixel'
assert img.getpixel((2, 1)) == (0x00, 0x00, 0x00, 0x00), 'center right pixel'
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))
class AlphaTests(unittest.TestCase):
"""
"""
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 = \
[
[
{"src": "gray"},
{"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'
assert img.getpixel((0, 1)) == (0xC0, 0xC0, 0xC0, 0xFF), 'center left pixel'
assert img.getpixel((1, 1)) == (0xC0, 0xC0, 0xC0, 0xFF), 'middle pixel'
assert img.getpixel((2, 1)) == (0xC0, 0xC0, 0xC0, 0xFF), 'center right pixel'
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 = \
[
[
{"src": "gray"},
{"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'
assert img.getpixel((0, 1)) == (0x40, 0x40, 0x40, 0xFF), 'center left pixel'
assert img.getpixel((1, 1)) == (0x40, 0x40, 0x40, 0xFF), 'middle pixel'
assert img.getpixel((2, 1)) == (0x40, 0x40, 0x40, 0xFF), 'center right pixel'
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 = \
[
[
{"src": "gray"},
{"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'
assert img.getpixel((0, 1)) == (0x80, 0x80, 0x80, 0xFF), 'center left pixel'
assert img.getpixel((1, 1)) == (0xA0, 0xA0, 0xA0, 0xFF), 'middle pixel'
assert img.getpixel((2, 1)) == (0xC0, 0xC0, 0xC0, 0xFF), 'center right pixel'
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 = \
[
[
{"src": "gray"},
{"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'
assert img.getpixel((0, 1)) == (0x80, 0x80, 0x80, 0xFF), 'center left pixel'
assert img.getpixel((1, 1)) == (0x60, 0x60, 0x60, 0xFF), 'middle pixel'
assert img.getpixel((2, 1)) == (0x40, 0x40, 0x40, 0xFF), 'center right pixel'
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 = \
[
[
{"src": "nothing"},
{"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'
assert img.getpixel((0, 1)) == (0xFF, 0xFF, 0xFF, 0x80), 'center left pixel'
assert img.getpixel((1, 1)) == (0xFF, 0xFF, 0xFF, 0x80), 'middle pixel'
assert img.getpixel((2, 1)) == (0xFF, 0xFF, 0xFF, 0x80), 'center right pixel'
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()
TileStache-1.51.5/TileStache/Goodies/Providers/DejaVuSansMono-alphanumeric.ttf 0000664 0000000 0000000 00000063770 13042202720 0027251 0 ustar 00root root 0000000 0000000 FFTMS g GDEF ) h f GPOS5N g @GSUBRs f OS/2jB Vcmap̑ Bcvt 4fpgm[k gasp f glyf&