Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 12 additions & 49 deletions TileStache/Goodies/VecTiles/geojson.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
from shapely.wkb import loads
from shapely.geometry import asShape

from ... import getTile
from ...Core import KnownUnknown
from .ops import transform

float_pat = compile(r'^-?\d+\.\d+(e-?\d+)?$')
Expand All @@ -16,32 +14,6 @@
# floating point lat/lon precision for each zoom level, good to ~1/4 pixel.
precisions = [int(ceil(log(1<<zoom + 8+2) / log(10)) - 2) for zoom in range(23)]

def get_tiles(names, config, coord):
''' Retrieve a list of named GeoJSON layer tiles from a TileStache config.

Check integrity and compatibility of each, looking at known layers,
correct JSON mime-types and "FeatureCollection" in the type attributes.
'''
unknown_layers = set(names) - set(config.layers.keys())

if unknown_layers:
raise KnownUnknown("%s.get_tiles didn't recognize %s when trying to load %s." % (__name__, ', '.join(unknown_layers), ', '.join(names)))

layers = [config.layers[name] for name in names]
mimes, bodies = zip(*[getTile(layer, coord, 'json') for layer in layers])
bad_mimes = [(name, mime) for (mime, name) in zip(mimes, names) if not mime.endswith('/json')]

if bad_mimes:
raise KnownUnknown('%s.get_tiles encountered a non-JSON mime-type in %s sub-layer: "%s"' % ((__name__, ) + bad_mimes[0]))

geojsons = map(json.loads, bodies)
bad_types = [(name, topo['type']) for (topo, name) in zip(geojsons, names) if topo['type'] != 'FeatureCollection']

if bad_types:
raise KnownUnknown('%s.get_tiles encountered a non-FeatureCollection type in %s sub-layer: "%s"' % ((__name__, ) + bad_types[0]))

return geojsons

def mercator((x, y)):
''' Project an (x, y) tuple to spherical mercator.
'''
Expand Down Expand Up @@ -91,33 +63,24 @@ def encode(file, features, zoom, is_clipped):
feature.update(dict(clipped=True))

geojson = dict(type='FeatureCollection', features=features)
encoder = json.JSONEncoder(separators=(',', ':'))
encoded = encoder.iterencode(geojson)
flt_fmt = '%%.%df' % precisions[zoom]

for token in encoded:
if charfloat_pat.match(token):
# in python 2.7, we see a character followed by a float literal
file.write(token[0] + flt_fmt % float(token[1:]))

elif float_pat.match(token):
# in python 2.6, we see a simple float literal
file.write(flt_fmt % float(token))

else:
file.write(token)

def merge(file, names, config, coord):
write_to_file(file, geojson, zoom)

def merge(file, names, tiles, config, coord):
''' Retrieve a list of GeoJSON tile responses and merge them into one.

get_tiles() retrieves data and performs basic integrity checks.
'''
inputs = get_tiles(names, config, coord)
output = dict(zip(names, inputs))
output = dict(zip(names, tiles))
write_to_file(file, output, coord.zoom)

def write_to_file(file, geojson, zoom):
''' Write GeoJSON stream to a file

'''
encoder = json.JSONEncoder(separators=(',', ':'))
encoded = encoder.iterencode(output)
flt_fmt = '%%.%df' % precisions[coord.zoom]
encoded = encoder.iterencode(geojson)
flt_fmt = '%%.%df' % precisions[zoom]

for token in encoded:
if charfloat_pat.match(token):
Expand All @@ -129,4 +92,4 @@ def merge(file, names, config, coord):
file.write(flt_fmt % float(token))

else:
file.write(token)
file.write(token)
30 changes: 28 additions & 2 deletions TileStache/Goodies/VecTiles/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
from urllib import urlopen
from os.path import exists

import json
from ... import getTile
from ...Core import KnownUnknown

try:
from psycopg2.extras import RealDictCursor
from psycopg2 import connect
Expand Down Expand Up @@ -320,14 +324,36 @@ def save(self, out, format):
'''
'''
if format == 'TopoJSON':
topojson.merge(out, self.names, self.config, self.coord)
topojson.merge(out, self.names, self.get_tiles(format), self.config, self.coord)

elif format == 'JSON':
geojson.merge(out, self.names, self.config, self.coord)
geojson.merge(out, self.names, self.get_tiles(format), self.config, self.coord)

else:
raise ValueError(format)

def get_tiles(self, format):
unknown_layers = set(self.names) - set(self.config.layers.keys())

if unknown_layers:
raise KnownUnknown("%s.get_tiles didn't recognize %s when trying to load %s." % (__name__, ', '.join(unknown_layers), ', '.join(self.names)))

layers = [self.config.layers[name] for name in self.names]
mimes, bodies = zip(*[getTile(layer, self.coord, format.lower()) for layer in layers])
bad_mimes = [(name, mime) for (mime, name) in zip(mimes, self.names) if not mime.endswith('/json')]

if bad_mimes:
raise KnownUnknown('%s.get_tiles encountered a non-JSON mime-type in %s sub-layer: "%s"' % ((__name__, ) + bad_mimes[0]))

tiles = map(json.loads, bodies)
bad_types = [(name, topo['type']) for (topo, name) in zip(tiles, self.names) if topo['type'] != ('FeatureCollection' if (format.lower()=='json') else 'Topology')]

if bad_types:
raise KnownUnknown('%s.get_tiles encountered a non-%sCollection type in %s sub-layer: "%s"' % ((__name__, ('Feature' if (format.lower()=='json') else 'Topology'), ) + bad_types[0]))

return tiles


def query_columns(dbinfo, srid, subquery, bounds):
''' Get information about the columns returned for a subquery.
'''
Expand Down
42 changes: 6 additions & 36 deletions TileStache/Goodies/VecTiles/topojson.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,8 @@
from shapely.wkb import loads
import json

from ... import getTile
from ...Core import KnownUnknown

def get_tiles(names, config, coord):
''' Retrieve a list of named TopoJSON layer tiles from a TileStache config.

Check integrity and compatibility of each, looking at known layers,
correct JSON mime-types, "Topology" in the type attributes, and
matching affine transformations.
'''
unknown_layers = set(names) - set(config.layers.keys())

if unknown_layers:
raise KnownUnknown("%s.get_tiles didn't recognize %s when trying to load %s." % (__name__, ', '.join(unknown_layers), ', '.join(names)))

layers = [config.layers[name] for name in names]
mimes, bodies = zip(*[getTile(layer, coord, 'topojson') for layer in layers])
bad_mimes = [(name, mime) for (mime, name) in zip(mimes, names) if not mime.endswith('/json')]

if bad_mimes:
raise KnownUnknown('%s.get_tiles encountered a non-JSON mime-type in %s sub-layer: "%s"' % ((__name__, ) + bad_mimes[0]))

topojsons = map(json.loads, bodies)
bad_types = [(name, topo['type']) for (topo, name) in zip(topojsons, names) if topo['type'] != 'Topology']

if bad_types:
raise KnownUnknown('%s.get_tiles encountered a non-Topology type in %s sub-layer: "%s"' % ((__name__, ) + bad_types[0]))

transforms = [topo['transform'] for topo in topojsons]
unique_xforms = set([tuple(xform['scale'] + xform['translate']) for xform in transforms])

if len(unique_xforms) > 1:
raise KnownUnknown('%s.get_tiles encountered incompatible transforms: %s' % (__name__, list(unique_xforms)))

return topojsons

def update_arc_indexes(geometry, merged_arcs, old_arcs):
''' Updated geometry arc indexes, and add arcs to merged_arcs along the way.

Expand Down Expand Up @@ -190,12 +156,16 @@ def encode(file, features, bounds, is_clipped):

json.dump(result, file, separators=(',', ':'))

def merge(file, names, config, coord):
def merge(file, names, inputs, config, coord):
''' Retrieve a list of TopoJSON tile responses and merge them into one.

get_tiles() retrieves data and performs basic integrity checks.
'''
inputs = get_tiles(names, config, coord)
transforms = [topo['transform'] for topo in inputs]
unique_xforms = set([tuple(xform['scale'] + xform['translate']) for xform in transforms])

if len(unique_xforms) > 1:
raise KnownUnknown('%s.merge encountered incompatible transforms: %s' % (__name__, list(unique_xforms)))

output = {
'type': 'Topology',
Expand Down