mapbox-vector-tile-0.5.0/0000775000175000017500000000000012766777174015223 5ustar mattmatt00000000000000mapbox-vector-tile-0.5.0/mapbox_vector_tile/0000775000175000017500000000000012766777174021110 5ustar mattmatt00000000000000mapbox-vector-tile-0.5.0/mapbox_vector_tile/encoder.py0000664000175000017500000004330012766776523023076 0ustar mattmatt00000000000000from math import fabs from numbers import Number from past.builtins import long from past.builtins import unicode from past.builtins import xrange from shapely.geometry.base import BaseGeometry from shapely.geometry.multipolygon import MultiPolygon from shapely.geometry.polygon import orient from shapely.geometry.polygon import Polygon from shapely.geometry.polygon import LinearRing from shapely.ops import transform from shapely.wkb import loads as load_wkb from shapely.wkt import loads as load_wkt import decimal from .compat import PY3, vector_tile, apply_map # tiles are padded by this number of pixels for the current zoom level padding = 0 cmd_bits = 3 tolerance = 0 CMD_MOVE_TO = 1 CMD_LINE_TO = 2 CMD_SEG_END = 7 def on_invalid_geometry_raise(shape): raise ValueError('Invalid geometry: %s' % shape.wkt) def on_invalid_geometry_ignore(shape): return None def reverse_ring(shape): assert shape.type == 'LinearRing' return LinearRing(list(shape.coords)[::-1]) def reverse_polygon(shape): assert shape.type == 'Polygon' exterior = reverse_ring(shape.exterior) interiors = [reverse_ring(r) for r in shape.interiors] return Polygon(exterior, interiors) def make_valid_polygon_flip(shape): assert shape.type == 'Polygon' # to handle cases where the area of the polygon is zero, we need to # manually reverse the coords in the polygon, then buffer(0) it to make it # valid in reverse, then reverse them again to get back to the original, # intended orientation. flipped = reverse_polygon(shape) fixed = flipped.buffer(0) if fixed.is_empty: return None else: return reverse_polygon(fixed) def area_bounds(shape): if shape.is_empty: return 0 minx, miny, maxx, maxy = shape.bounds return (maxx - minx) * (maxy - miny) def make_valid_polygon(shape): prev_area = area_bounds(shape) new_shape = shape.buffer(0) assert new_shape.is_valid, \ 'buffer(0) did not make geometry valid. old shape: %s' % shape.wkt new_area = area_bounds(new_shape) if new_area < 0.9 * prev_area: alt_shape = make_valid_polygon_flip(shape) if alt_shape: new_shape = new_shape.union(alt_shape) return new_shape def make_valid_multipolygon(shape): new_g = [] for g in shape.geoms: if g.is_empty: continue valid_g = on_invalid_geometry_make_valid(g) if valid_g.type == 'MultiPolygon': new_g.extend(valid_g.geoms) else: new_g.append(valid_g) return MultiPolygon(new_g) def on_invalid_geometry_make_valid(shape): if shape.is_empty: return shape elif shape.type == 'MultiPolygon': shape = make_valid_multipolygon(shape) elif shape.type == 'Polygon': shape = make_valid_polygon(shape) return shape class VectorTile: """ """ def __init__(self, extents, on_invalid_geometry=None, max_geometry_validate_tries=5, round_fn=None): self.tile = vector_tile.tile() self.extents = extents self.on_invalid_geometry = on_invalid_geometry self.max_geometry_validate_tries = max_geometry_validate_tries self.round_fn = round_fn def _round(self, val): # Prefer provided round function. if self.round_fn: return self.round_fn(val) # round() has different behavior in python 2/3 # For consistency between 2 and 3 we use quantize, however # it is slower than the built in round function. d = decimal.Decimal(val) rounded = d.quantize(1, rounding=decimal.ROUND_HALF_EVEN) return float(rounded) def addFeatures(self, features, layer_name='', quantize_bounds=None, y_coord_down=False): self.layer = self.tile.layers.add() self.layer.name = layer_name self.layer.version = 1 self.layer.extent = self.extents self.key_idx = 0 self.val_idx = 0 self.seen_keys_idx = {} self.seen_values_idx = {} for feature in features: # skip missing or empty geometries geometry_spec = feature.get('geometry') if geometry_spec is None: continue shape = self._load_geometry(geometry_spec) if shape is None: raise NotImplementedError( 'Can\'t do geometries that are not wkt, wkb, or shapely ' 'geometries') if shape.is_empty: continue if quantize_bounds: shape = self.quantize(shape, quantize_bounds) shape = self.enforce_winding_order(shape, y_coord_down) if shape is not None and not shape.is_empty: self.addFeature(feature, shape, y_coord_down) def enforce_winding_order(self, shape, y_coord_down, n_try=1): if shape.type == 'MultiPolygon': # If we are a multipolygon, we need to ensure that the # winding orders of the consituent polygons are # correct. In particular, the winding order of the # interior rings need to be the opposite of the # exterior ones, and all interior rings need to follow # the exterior one. This is how the end of one polygon # and the beginning of another are signaled. shape = self.enforce_multipolygon_winding_order( shape, y_coord_down, n_try) elif shape.type == 'Polygon': # Ensure that polygons are also oriented with the # appropriate winding order. Their exterior rings must # have a clockwise order, which is translated into a # clockwise order in MVT's tile-local coordinates with # the Y axis in "screen" (i.e: +ve down) configuration. # Note that while the Y axis flips, we also invert the # Y coordinate to get the tile-local value, which means # the clockwise orientation is unchanged. shape = self.enforce_polygon_winding_order( shape, y_coord_down, n_try) # other shapes just get passed through return shape def quantize(self, shape, bounds): minx, miny, maxx, maxy = bounds def fn(x, y, z=None): xfac = self.extents / (maxx - minx) yfac = self.extents / (maxy - miny) x = xfac * (x - minx) y = yfac * (y - miny) return self._round(x), self._round(y) return transform(fn, shape) def handle_shape_validity(self, shape, y_coord_down, n_try): if shape.is_valid: return shape if n_try >= self.max_geometry_validate_tries: # ensure that we don't recurse indefinitely with an # invalid geometry handler that doesn't validate # geometries return None if self.on_invalid_geometry: shape = self.on_invalid_geometry(shape) if shape is not None and not shape.is_empty: # this means that we have a handler that might have # altered the geometry. We'll run through the process # again, but keep track of which attempt we are on to # terminate the recursion. shape = self.enforce_winding_order( shape, y_coord_down, n_try + 1) return shape def enforce_multipolygon_winding_order(self, shape, y_coord_down, n_try): assert shape.type == 'MultiPolygon' parts = [] for part in shape.geoms: part = self.enforce_polygon_winding_order( part, y_coord_down, n_try) if part is not None and not part.is_empty: parts.append(part) if not parts: return None if len(parts) == 1: oriented_shape = parts[0] else: oriented_shape = MultiPolygon(parts) oriented_shape = self.handle_shape_validity( oriented_shape, y_coord_down, n_try) return oriented_shape def enforce_polygon_winding_order(self, shape, y_coord_down, n_try): assert shape.type == 'Polygon' def fn(point): x, y = point return self._round(x), self._round(y) exterior = apply_map(fn, shape.exterior.coords) rings = None if len(shape.interiors) > 0: rings = [apply_map(fn, ring.coords) for ring in shape.interiors] sign = 1.0 if y_coord_down else -1.0 oriented_shape = orient(Polygon(exterior, rings), sign=sign) oriented_shape = self.handle_shape_validity( oriented_shape, y_coord_down, n_try) return oriented_shape def _load_geometry(self, geometry_spec): if isinstance(geometry_spec, BaseGeometry): return geometry_spec try: return load_wkb(geometry_spec) except: try: return load_wkt(geometry_spec) except: return None def addFeature(self, feature, shape, y_coord_down): f = self.layer.features.add() fid = feature.get('id') if fid is not None: if isinstance(fid, Number) and fid >= 0: f.id = fid # properties properties = feature.get('properties') if properties is not None: self._handle_attr(self.layer, f, properties) f.type = self._get_feature_type(shape) self._geo_encode(f, shape, y_coord_down) def _get_feature_type(self, shape): if shape.type == 'Point' or shape.type == 'MultiPoint': return self.tile.Point elif shape.type == 'LineString' or shape.type == 'MultiLineString': return self.tile.LineString elif shape.type == 'Polygon' or shape.type == 'MultiPolygon': return self.tile.Polygon elif shape.type == 'GeometryCollection': raise ValueError('Encoding geometry collections not supported') else: raise ValueError('Cannot encode unknown geometry type: %s' % shape.type) def _encode_cmd_length(self, cmd, length): return (length << cmd_bits) | (cmd & ((1 << cmd_bits) - 1)) def _chunker(self, seq, size): return [seq[pos:pos + size] for pos in xrange(0, len(seq), size)] def _can_handle_key(self, k): return isinstance(k, (str, unicode)) def _can_handle_val(self, v): if isinstance(v, (str, unicode)): return True elif isinstance(v, bool): return True elif isinstance(v, (int, long)): return True elif isinstance(v, float): return True return False def _can_handle_attr(self, k, v): return self._can_handle_key(k) and \ self._can_handle_val(v) def _handle_attr(self, layer, feature, props): for k, v in props.items(): if self._can_handle_attr(k, v): if not PY3 and isinstance(k, str): k = k.decode('utf-8') if k not in self.seen_keys_idx: layer.keys.append(k) self.seen_keys_idx[k] = self.key_idx self.key_idx += 1 feature.tags.append(self.seen_keys_idx[k]) if v not in self.seen_values_idx: self.seen_values_idx[v] = self.val_idx self.val_idx += 1 val = layer.values.add() if isinstance(v, bool): val.bool_value = v elif isinstance(v, str): if PY3: val.string_value = v else: val.string_value = unicode(v, 'utf-8') elif isinstance(v, unicode): val.string_value = v elif isinstance(v, (int, long)): val.int_value = v elif isinstance(v, float): val.double_value = v feature.tags.append(self.seen_values_idx[v]) def _handle_skipped_last(self, f, skipped_index, cur_x, cur_y, x_, y_): last_x = f.geometry[skipped_index - 2] last_y = f.geometry[skipped_index - 1] last_dx = ((last_x >> 1) ^ (-(last_x & 1))) last_dy = ((last_y >> 1) ^ (-(last_y & 1))) dx = cur_x - x_ + last_dx dy = cur_y - y_ + last_dy x_ = cur_x y_ = cur_y f.geometry.__setitem__(skipped_index - 2, ((dx << 1) ^ (dx >> 31))) f.geometry.__setitem__(skipped_index - 1, ((dy << 1) ^ (dy >> 31))) def _parseGeometry(self, shape): coordinates = [] lineType = "line" polygonType = "polygon" def _get_arc_obj(arc, type): cmd = CMD_MOVE_TO length = len(arc.coords) for i, (x, y) in enumerate(arc.coords): if i == 0: cmd = CMD_MOVE_TO elif i == length - 1 and type == polygonType: cmd = CMD_SEG_END else: cmd = CMD_LINE_TO coordinates.append((x, y, cmd)) if shape.type == 'GeometryCollection': # do nothing coordinates = [] elif shape.type == 'Point': coordinates.append((shape.x, shape.y, CMD_MOVE_TO)) elif shape.type == 'LineString': _get_arc_obj(shape, lineType) elif shape.type == 'Polygon': rings = [shape.exterior] + list(shape.interiors) for ring in rings: _get_arc_obj(ring, polygonType) elif shape.type == 'MultiPoint': coordinates += [(point.x, point.y, CMD_MOVE_TO) for point in shape.geoms] elif shape.type == 'MultiLineString': for arc in shape.geoms: _get_arc_obj(arc, lineType) elif shape.type == 'MultiPolygon': for polygon in shape.geoms: rings = [polygon.exterior] + list(polygon.interiors) for ring in rings: _get_arc_obj(ring, polygonType) else: raise NotImplementedError("Can't do %s geometries" % shape.type) return coordinates def _geo_encode(self, f, shape, y_coord_down): x_, y_ = 0, 0 cmd = -1 cmd_idx = -1 vtx_cmd = -1 prev_cmd = -1 skipped_index = -1 skipped_last = False cur_x = 0 cur_y = 0 it = 0 length = 0 coordinates = self._parseGeometry(shape) while it < len(coordinates): x, y, vtx_cmd = coordinates[it] if vtx_cmd != cmd: if cmd_idx >= 0: f.geometry[cmd_idx] = self._encode_cmd_length(cmd, length) cmd = vtx_cmd length = 0 cmd_idx = len(f.geometry) f.geometry.append(0) # placeholder added in first pass if (vtx_cmd == CMD_MOVE_TO or vtx_cmd == CMD_LINE_TO): if cmd == CMD_MOVE_TO and skipped_last and skipped_index > 1: self._handle_skipped_last( f, skipped_index, cur_x, cur_y, x_, y_) # ensure that floating point values don't get truncated if isinstance(x, float): x = self._round(x) if isinstance(y, float): y = self._round(y) x = int(x) y = int(y) if not y_coord_down: y = self.extents - y # Compute delta to the previous coordinate. cur_x = int(x) cur_y = int(y) dx = cur_x - x_ dy = cur_y - y_ sharp_turn_ahead = False if (it + 2 <= len(coordinates)): next_x, next_y, next_cmd = coordinates[it + 1] if next_cmd == CMD_LINE_TO: next_dx = fabs(cur_x - int(next_x)) next_dy = fabs(cur_y - int(next_y)) if ((next_dx == 0 and next_dy >= tolerance) or (next_dy == 0 and next_dx >= tolerance)): sharp_turn_ahead = True # Keep all move_to commands, but omit other movements # that are not >= the tolerance threshold and should # be considered no-ops. # NOTE: length == 0 indicates the command has changed and will # preserve any non duplicate move_to or line_to if (length == 0 or sharp_turn_ahead or fabs(dx) >= tolerance or fabs(dy) >= tolerance): # Manual zigzag encoding. f.geometry.append((dx << 1) ^ (dx >> 31)) f.geometry.append((dy << 1) ^ (dy >> 31)) x_ = cur_x y_ = cur_y skipped_last = False length = length + 1 else: skipped_last = True skipped_index = len(f.geometry) elif vtx_cmd == CMD_SEG_END: if prev_cmd != CMD_SEG_END: length = length + 1 else: raise Exception("Unknown command type: '%s'" % vtx_cmd) it = it + 1 prev_cmd = cmd # at least one vertex + cmd/length if skipped_last and skipped_index > 1: # if we skipped previous vertex we just update it to the # last one here. self._handle_skipped_last(f, skipped_index, cur_x, cur_y, x_, y_) # Update the last length/command value. if cmd_idx >= 0: f.geometry[cmd_idx] = self._encode_cmd_length(cmd, length) mapbox-vector-tile-0.5.0/mapbox_vector_tile/compat.py0000664000175000017500000000044112722020760022712 0ustar mattmatt00000000000000import sys from builtins import map PY3 = sys.version_info[0] == 3 if PY3: from .Mapbox import vector_tile_pb2_p3 vector_tile = vector_tile_pb2_p3 else: from .Mapbox import vector_tile_pb2 vector_tile = vector_tile_pb2 def apply_map(fn, x): return list(map(fn, x)) mapbox-vector-tile-0.5.0/mapbox_vector_tile/decoder.py0000664000175000017500000001271312722020760023041 0ustar mattmatt00000000000000from past.builtins import xrange from .compat import vector_tile cmd_bits = 3 CMD_MOVE_TO = 1 CMD_LINE_TO = 2 CMD_SEG_END = 7 UNKNOWN = 0 POINT = 1 LINESTRING = 2 POLYGON = 3 class TileData: """ """ def __init__(self): self.tile = vector_tile.tile() def getMessage(self, pbf_data, y_coord_down=False): self.tile.ParseFromString(pbf_data) tile = {} for layer in self.tile.layers: keys = layer.keys vals = layer.values features = [] for feature in layer.features: tags = feature.tags props = {} assert len(tags) % 2 == 0, 'Unexpected number of tags' for key_idx, val_idx in zip(tags[::2], tags[1::2]): key = keys[key_idx] val = vals[val_idx] value = self.parse_value(val) props[key] = value geometry = self.parse_geometry(feature.geometry, feature.type, layer.extent, y_coord_down) new_feature = { "geometry": geometry, "properties": props, "id": feature.id, "type": feature.type } features.append(new_feature) tile[layer.name] = { "extent": layer.extent, "version": layer.version, "features": features, } return tile def zero_pad(self, val): return '0' + val if val[0] == 'b' else val def parse_value(self, val): for candidate in ('bool_value', 'double_value', 'float_value', 'int_value', 'sint_value', 'string_value', 'uint_value'): if val.HasField(candidate): return getattr(val, candidate) raise ValueError('%s is an unknown value') def zig_zag_decode(self, n): return (n >> 1) ^ (-(n & 1)) def parse_geometry(self, geom, ftype, extent, y_coord_down): # [9 0 8192 26 0 10 2 0 0 2 15] i = 0 coords = [] dx = 0 dy = 0 parts = [] # for multi linestrings and polygons while i != len(geom): item = bin(geom[i]) ilen = len(item) cmd = int(self.zero_pad(item[(ilen - cmd_bits):ilen]), 2) cmd_len = int(self.zero_pad(item[:ilen - cmd_bits]), 2) i = i + 1 def _ensure_polygon_closed(coords): if coords and coords[0] != coords[-1]: coords.append(coords[0]) if cmd == CMD_SEG_END: if ftype == POLYGON: _ensure_polygon_closed(coords) parts.append(coords) coords = [] elif cmd == CMD_MOVE_TO or cmd == CMD_LINE_TO: if coords and cmd == CMD_MOVE_TO: if ftype in (LINESTRING, POLYGON): # multi line string or polygon # our encoder includes CMD_SEG_END to denote # the end of a polygon ring, but this path # would also handle the case where we receive # a move without a previous close on polygons # for polygons, we want to ensure that it is # closed if ftype == POLYGON: _ensure_polygon_closed(coords) parts.append(coords) coords = [] for point in xrange(0, cmd_len): x = geom[i] i = i + 1 y = geom[i] i = i + 1 # zipzag decode x = self.zig_zag_decode(x) y = self.zig_zag_decode(y) x = x + dx y = y + dy dx = x dy = y if not y_coord_down: y = extent - y coords.append([x, y]) if ftype == POINT: return coords elif ftype == LINESTRING: if parts: if coords: parts.append(coords) return parts[0] if len(parts) == 1 else parts else: return coords elif ftype == POLYGON: if coords: parts.append(coords) def _area_sign(ring): a = sum(ring[i][0]*ring[i+1][1] - ring[i+1][0]*ring[i][1] for i in range(0, len(ring)-1)) # noqa return -1 if a < 0 else 1 if a > 0 else 0 polygon = [] polygons = [] winding = 0 for ring in parts: a = _area_sign(ring) if a == 0: continue if winding == 0: winding = a if winding == a: if polygon: polygons.append(polygon) polygon = [ring] else: polygon.append(ring) if polygon: polygons.append(polygon) return polygons[0] if len(polygons) == 1 else polygons else: raise ValueError('Unknown geometry type: %s' % ftype) mapbox-vector-tile-0.5.0/mapbox_vector_tile/__init__.py0000664000175000017500000000151112741674325023202 0ustar mattmatt00000000000000from . import encoder from . import decoder def decode(tile, y_coord_down=False): vector_tile = decoder.TileData() message = vector_tile.getMessage(tile, y_coord_down) return message def encode(layers, quantize_bounds=None, y_coord_down=False, extents=4096, on_invalid_geometry=None, round_fn=None): vector_tile = encoder.VectorTile(extents, on_invalid_geometry, round_fn=round_fn) if (isinstance(layers, list)): for layer in layers: vector_tile.addFeatures(layer['features'], layer['name'], quantize_bounds, y_coord_down) else: vector_tile.addFeatures(layers['features'], layers['name'], quantize_bounds, y_coord_down) return vector_tile.tile.SerializeToString() mapbox-vector-tile-0.5.0/mapbox_vector_tile/Mapbox/0000775000175000017500000000000012766777174022336 5ustar mattmatt00000000000000mapbox-vector-tile-0.5.0/mapbox_vector_tile/Mapbox/vector_tile_pb2_p3.py0000664000175000017500000003033112624663731026356 0ustar mattmatt00000000000000# Generated by the protocol buffer compiler. DO NOT EDIT! # source: vector_tile.proto from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database from google.protobuf import descriptor_pb2 # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor.FileDescriptor( name='vector_tile.proto', package='mapnik.vector', syntax='proto2', serialized_pb=b'\n\x11vector_tile.proto\x12\rmapnik.vector\"\xc5\x04\n\x04tile\x12)\n\x06layers\x18\x03 \x03(\x0b\x32\x19.mapnik.vector.tile.layer\x1a\xa1\x01\n\x05value\x12\x14\n\x0cstring_value\x18\x01 \x01(\t\x12\x13\n\x0b\x66loat_value\x18\x02 \x01(\x02\x12\x14\n\x0c\x64ouble_value\x18\x03 \x01(\x01\x12\x11\n\tint_value\x18\x04 \x01(\x03\x12\x12\n\nuint_value\x18\x05 \x01(\x04\x12\x12\n\nsint_value\x18\x06 \x01(\x12\x12\x12\n\nbool_value\x18\x07 \x01(\x08*\x08\x08\x08\x10\x80\x80\x80\x80\x02\x1ar\n\x07\x66\x65\x61ture\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x10\n\x04tags\x18\x02 \x03(\rB\x02\x10\x01\x12\x33\n\x04type\x18\x03 \x01(\x0e\x32\x1c.mapnik.vector.tile.GeomType:\x07Unknown\x12\x14\n\x08geometry\x18\x04 \x03(\rB\x02\x10\x01\x1a\xb1\x01\n\x05layer\x12\x12\n\x07version\x18\x0f \x02(\r:\x01\x31\x12\x0c\n\x04name\x18\x01 \x02(\t\x12-\n\x08\x66\x65\x61tures\x18\x02 \x03(\x0b\x32\x1b.mapnik.vector.tile.feature\x12\x0c\n\x04keys\x18\x03 \x03(\t\x12)\n\x06values\x18\x04 \x03(\x0b\x32\x19.mapnik.vector.tile.value\x12\x14\n\x06\x65xtent\x18\x05 \x01(\r:\x04\x34\x30\x39\x36*\x08\x08\x10\x10\x80\x80\x80\x80\x02\"?\n\x08GeomType\x12\x0b\n\x07Unknown\x10\x00\x12\t\n\x05Point\x10\x01\x12\x0e\n\nLineString\x10\x02\x12\x0b\n\x07Polygon\x10\x03*\x05\x08\x10\x10\x80@B\x02H\x03' ) _sym_db.RegisterFileDescriptor(DESCRIPTOR) _TILE_GEOMTYPE = _descriptor.EnumDescriptor( name='GeomType', full_name='mapnik.vector.tile.GeomType', filename=None, file=DESCRIPTOR, values=[ _descriptor.EnumValueDescriptor( name='Unknown', index=0, number=0, options=None, type=None), _descriptor.EnumValueDescriptor( name='Point', index=1, number=1, options=None, type=None), _descriptor.EnumValueDescriptor( name='LineString', index=2, number=2, options=None, type=None), _descriptor.EnumValueDescriptor( name='Polygon', index=3, number=3, options=None, type=None), ], containing_type=None, options=None, serialized_start=548, serialized_end=611, ) _sym_db.RegisterEnumDescriptor(_TILE_GEOMTYPE) _TILE_VALUE = _descriptor.Descriptor( name='value', full_name='mapnik.vector.tile.value', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( name='string_value', full_name='mapnik.vector.tile.value.string_value', index=0, number=1, type=9, cpp_type=9, label=1, has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( name='float_value', full_name='mapnik.vector.tile.value.float_value', index=1, number=2, type=2, cpp_type=6, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( name='double_value', full_name='mapnik.vector.tile.value.double_value', index=2, number=3, type=1, cpp_type=5, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( name='int_value', full_name='mapnik.vector.tile.value.int_value', index=3, number=4, type=3, cpp_type=2, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( name='uint_value', full_name='mapnik.vector.tile.value.uint_value', index=4, number=5, type=4, cpp_type=4, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( name='sint_value', full_name='mapnik.vector.tile.value.sint_value', index=5, number=6, type=18, cpp_type=2, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( name='bool_value', full_name='mapnik.vector.tile.value.bool_value', index=6, number=7, type=8, cpp_type=7, label=1, has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), ], extensions=[ ], nested_types=[], enum_types=[ ], options=None, is_extendable=True, syntax='proto2', extension_ranges=[(8, 536870912), ], oneofs=[ ], serialized_start=89, serialized_end=250, ) _TILE_FEATURE = _descriptor.Descriptor( name='feature', full_name='mapnik.vector.tile.feature', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( name='id', full_name='mapnik.vector.tile.feature.id', index=0, number=1, type=4, cpp_type=4, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( name='tags', full_name='mapnik.vector.tile.feature.tags', index=1, number=2, type=13, cpp_type=3, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), b'\020\001')), _descriptor.FieldDescriptor( name='type', full_name='mapnik.vector.tile.feature.type', index=2, number=3, type=14, cpp_type=8, label=1, has_default_value=True, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( name='geometry', full_name='mapnik.vector.tile.feature.geometry', index=3, number=4, type=13, cpp_type=3, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), b'\020\001')), ], extensions=[ ], nested_types=[], enum_types=[ ], options=None, is_extendable=False, syntax='proto2', extension_ranges=[], oneofs=[ ], serialized_start=252, serialized_end=366, ) _TILE_LAYER = _descriptor.Descriptor( name='layer', full_name='mapnik.vector.tile.layer', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( name='version', full_name='mapnik.vector.tile.layer.version', index=0, number=15, type=13, cpp_type=3, label=2, has_default_value=True, default_value=1, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( name='name', full_name='mapnik.vector.tile.layer.name', index=1, number=1, type=9, cpp_type=9, label=2, has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( name='features', full_name='mapnik.vector.tile.layer.features', index=2, number=2, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( name='keys', full_name='mapnik.vector.tile.layer.keys', index=3, number=3, type=9, cpp_type=9, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( name='values', full_name='mapnik.vector.tile.layer.values', index=4, number=4, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( name='extent', full_name='mapnik.vector.tile.layer.extent', index=5, number=5, type=13, cpp_type=3, label=1, has_default_value=True, default_value=4096, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), ], extensions=[ ], nested_types=[], enum_types=[ ], options=None, is_extendable=True, syntax='proto2', extension_ranges=[(16, 536870912), ], oneofs=[ ], serialized_start=369, serialized_end=546, ) _TILE = _descriptor.Descriptor( name='tile', full_name='mapnik.vector.tile', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( name='layers', full_name='mapnik.vector.tile.layers', index=0, number=3, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), ], extensions=[ ], nested_types=[_TILE_VALUE, _TILE_FEATURE, _TILE_LAYER, ], enum_types=[ _TILE_GEOMTYPE, ], options=None, is_extendable=True, syntax='proto2', extension_ranges=[(16, 8192), ], oneofs=[ ], serialized_start=37, serialized_end=618, ) _TILE_VALUE.containing_type = _TILE _TILE_FEATURE.fields_by_name['type'].enum_type = _TILE_GEOMTYPE _TILE_FEATURE.containing_type = _TILE _TILE_LAYER.fields_by_name['features'].message_type = _TILE_FEATURE _TILE_LAYER.fields_by_name['values'].message_type = _TILE_VALUE _TILE_LAYER.containing_type = _TILE _TILE.fields_by_name['layers'].message_type = _TILE_LAYER _TILE_GEOMTYPE.containing_type = _TILE DESCRIPTOR.message_types_by_name['tile'] = _TILE tile = _reflection.GeneratedProtocolMessageType('tile', (_message.Message,), dict( value = _reflection.GeneratedProtocolMessageType('value', (_message.Message,), dict( DESCRIPTOR = _TILE_VALUE, __module__ = 'vector_tile_pb2_p3' # @@protoc_insertion_point(class_scope:mapnik.vector.tile.value) )) , feature = _reflection.GeneratedProtocolMessageType('feature', (_message.Message,), dict( DESCRIPTOR = _TILE_FEATURE, __module__ = 'vector_tile_pb2_p3' # @@protoc_insertion_point(class_scope:mapnik.vector.tile.feature) )) , layer = _reflection.GeneratedProtocolMessageType('layer', (_message.Message,), dict( DESCRIPTOR = _TILE_LAYER, __module__ = 'vector_tile_pb2_p3' # @@protoc_insertion_point(class_scope:mapnik.vector.tile.layer) )) , DESCRIPTOR = _TILE, __module__ = 'vector_tile_pb2_p3' # @@protoc_insertion_point(class_scope:mapnik.vector.tile) )) _sym_db.RegisterMessage(tile) _sym_db.RegisterMessage(tile.value) _sym_db.RegisterMessage(tile.feature) _sym_db.RegisterMessage(tile.layer) DESCRIPTOR.has_options = True DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), b'H\003') _TILE_FEATURE.fields_by_name['tags'].has_options = True _TILE_FEATURE.fields_by_name['tags']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), b'\020\001') _TILE_FEATURE.fields_by_name['geometry'].has_options = True _TILE_FEATURE.fields_by_name['geometry']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), b'\020\001') # @@protoc_insertion_point(module_scope) mapbox-vector-tile-0.5.0/mapbox_vector_tile/Mapbox/vector_tile_pb2.py0000664000175000017500000002711212620627605025753 0ustar mattmatt00000000000000# Generated by the protocol buffer compiler. DO NOT EDIT! # source: vector_tile.proto from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message from google.protobuf import reflection as _reflection from google.protobuf import descriptor_pb2 # @@protoc_insertion_point(imports) DESCRIPTOR = _descriptor.FileDescriptor( name='vector_tile.proto', package='mapnik.vector', serialized_pb='\n\x11vector_tile.proto\x12\rmapnik.vector\"\xc5\x04\n\x04tile\x12)\n\x06layers\x18\x03 \x03(\x0b\x32\x19.mapnik.vector.tile.layer\x1a\xa1\x01\n\x05value\x12\x14\n\x0cstring_value\x18\x01 \x01(\t\x12\x13\n\x0b\x66loat_value\x18\x02 \x01(\x02\x12\x14\n\x0c\x64ouble_value\x18\x03 \x01(\x01\x12\x11\n\tint_value\x18\x04 \x01(\x03\x12\x12\n\nuint_value\x18\x05 \x01(\x04\x12\x12\n\nsint_value\x18\x06 \x01(\x12\x12\x12\n\nbool_value\x18\x07 \x01(\x08*\x08\x08\x08\x10\x80\x80\x80\x80\x02\x1ar\n\x07\x66\x65\x61ture\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x10\n\x04tags\x18\x02 \x03(\rB\x02\x10\x01\x12\x33\n\x04type\x18\x03 \x01(\x0e\x32\x1c.mapnik.vector.tile.GeomType:\x07Unknown\x12\x14\n\x08geometry\x18\x04 \x03(\rB\x02\x10\x01\x1a\xb1\x01\n\x05layer\x12\x12\n\x07version\x18\x0f \x02(\r:\x01\x31\x12\x0c\n\x04name\x18\x01 \x02(\t\x12-\n\x08\x66\x65\x61tures\x18\x02 \x03(\x0b\x32\x1b.mapnik.vector.tile.feature\x12\x0c\n\x04keys\x18\x03 \x03(\t\x12)\n\x06values\x18\x04 \x03(\x0b\x32\x19.mapnik.vector.tile.value\x12\x14\n\x06\x65xtent\x18\x05 \x01(\r:\x04\x34\x30\x39\x36*\x08\x08\x10\x10\x80\x80\x80\x80\x02\"?\n\x08GeomType\x12\x0b\n\x07Unknown\x10\x00\x12\t\n\x05Point\x10\x01\x12\x0e\n\nLineString\x10\x02\x12\x0b\n\x07Polygon\x10\x03*\x05\x08\x10\x10\x80@B\x02H\x03') _TILE_GEOMTYPE = _descriptor.EnumDescriptor( name='GeomType', full_name='mapnik.vector.tile.GeomType', filename=None, file=DESCRIPTOR, values=[ _descriptor.EnumValueDescriptor( name='Unknown', index=0, number=0, options=None, type=None), _descriptor.EnumValueDescriptor( name='Point', index=1, number=1, options=None, type=None), _descriptor.EnumValueDescriptor( name='LineString', index=2, number=2, options=None, type=None), _descriptor.EnumValueDescriptor( name='Polygon', index=3, number=3, options=None, type=None), ], containing_type=None, options=None, serialized_start=548, serialized_end=611, ) _TILE_VALUE = _descriptor.Descriptor( name='value', full_name='mapnik.vector.tile.value', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( name='string_value', full_name='mapnik.vector.tile.value.string_value', index=0, number=1, type=9, cpp_type=9, label=1, has_default_value=False, default_value=unicode("", "utf-8"), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( name='float_value', full_name='mapnik.vector.tile.value.float_value', index=1, number=2, type=2, cpp_type=6, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( name='double_value', full_name='mapnik.vector.tile.value.double_value', index=2, number=3, type=1, cpp_type=5, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( name='int_value', full_name='mapnik.vector.tile.value.int_value', index=3, number=4, type=3, cpp_type=2, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( name='uint_value', full_name='mapnik.vector.tile.value.uint_value', index=4, number=5, type=4, cpp_type=4, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( name='sint_value', full_name='mapnik.vector.tile.value.sint_value', index=5, number=6, type=18, cpp_type=2, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( name='bool_value', full_name='mapnik.vector.tile.value.bool_value', index=6, number=7, type=8, cpp_type=7, label=1, has_default_value=False, default_value=False, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), ], extensions=[ ], nested_types=[], enum_types=[ ], options=None, is_extendable=True, extension_ranges=[(8, 536870912), ], serialized_start=89, serialized_end=250, ) _TILE_FEATURE = _descriptor.Descriptor( name='feature', full_name='mapnik.vector.tile.feature', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( name='id', full_name='mapnik.vector.tile.feature.id', index=0, number=1, type=4, cpp_type=4, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( name='tags', full_name='mapnik.vector.tile.feature.tags', index=1, number=2, type=13, cpp_type=3, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), '\020\001')), _descriptor.FieldDescriptor( name='type', full_name='mapnik.vector.tile.feature.type', index=2, number=3, type=14, cpp_type=8, label=1, has_default_value=True, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( name='geometry', full_name='mapnik.vector.tile.feature.geometry', index=3, number=4, type=13, cpp_type=3, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), '\020\001')), ], extensions=[ ], nested_types=[], enum_types=[ ], options=None, is_extendable=False, extension_ranges=[], serialized_start=252, serialized_end=366, ) _TILE_LAYER = _descriptor.Descriptor( name='layer', full_name='mapnik.vector.tile.layer', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( name='version', full_name='mapnik.vector.tile.layer.version', index=0, number=15, type=13, cpp_type=3, label=2, has_default_value=True, default_value=1, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( name='name', full_name='mapnik.vector.tile.layer.name', index=1, number=1, type=9, cpp_type=9, label=2, has_default_value=False, default_value=unicode("", "utf-8"), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( name='features', full_name='mapnik.vector.tile.layer.features', index=2, number=2, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( name='keys', full_name='mapnik.vector.tile.layer.keys', index=3, number=3, type=9, cpp_type=9, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( name='values', full_name='mapnik.vector.tile.layer.values', index=4, number=4, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( name='extent', full_name='mapnik.vector.tile.layer.extent', index=5, number=5, type=13, cpp_type=3, label=1, has_default_value=True, default_value=4096, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), ], extensions=[ ], nested_types=[], enum_types=[ ], options=None, is_extendable=True, extension_ranges=[(16, 536870912), ], serialized_start=369, serialized_end=546, ) _TILE = _descriptor.Descriptor( name='tile', full_name='mapnik.vector.tile', filename=None, file=DESCRIPTOR, containing_type=None, fields=[ _descriptor.FieldDescriptor( name='layers', full_name='mapnik.vector.tile.layers', index=0, number=3, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), ], extensions=[ ], nested_types=[_TILE_VALUE, _TILE_FEATURE, _TILE_LAYER, ], enum_types=[ _TILE_GEOMTYPE, ], options=None, is_extendable=True, extension_ranges=[(16, 8192), ], serialized_start=37, serialized_end=618, ) _TILE_VALUE.containing_type = _TILE; _TILE_FEATURE.fields_by_name['type'].enum_type = _TILE_GEOMTYPE _TILE_FEATURE.containing_type = _TILE; _TILE_LAYER.fields_by_name['features'].message_type = _TILE_FEATURE _TILE_LAYER.fields_by_name['values'].message_type = _TILE_VALUE _TILE_LAYER.containing_type = _TILE; _TILE.fields_by_name['layers'].message_type = _TILE_LAYER _TILE_GEOMTYPE.containing_type = _TILE; DESCRIPTOR.message_types_by_name['tile'] = _TILE class tile(_message.Message): __metaclass__ = _reflection.GeneratedProtocolMessageType class value(_message.Message): __metaclass__ = _reflection.GeneratedProtocolMessageType DESCRIPTOR = _TILE_VALUE # @@protoc_insertion_point(class_scope:mapnik.vector.tile.value) class feature(_message.Message): __metaclass__ = _reflection.GeneratedProtocolMessageType DESCRIPTOR = _TILE_FEATURE # @@protoc_insertion_point(class_scope:mapnik.vector.tile.feature) class layer(_message.Message): __metaclass__ = _reflection.GeneratedProtocolMessageType DESCRIPTOR = _TILE_LAYER # @@protoc_insertion_point(class_scope:mapnik.vector.tile.layer) DESCRIPTOR = _TILE # @@protoc_insertion_point(class_scope:mapnik.vector.tile) DESCRIPTOR.has_options = True DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), 'H\003') _TILE_FEATURE.fields_by_name['tags'].has_options = True _TILE_FEATURE.fields_by_name['tags']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), '\020\001') _TILE_FEATURE.fields_by_name['geometry'].has_options = True _TILE_FEATURE.fields_by_name['geometry']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), '\020\001') # @@protoc_insertion_point(module_scope) mapbox-vector-tile-0.5.0/mapbox_vector_tile/Mapbox/__init__.py0000664000175000017500000000000012620627605024413 0ustar mattmatt00000000000000mapbox-vector-tile-0.5.0/MANIFEST.in0000664000175000017500000000002212670262441016727 0ustar mattmatt00000000000000include README.md mapbox-vector-tile-0.5.0/setup.cfg0000664000175000017500000000013012766777174017036 0ustar mattmatt00000000000000[bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 mapbox-vector-tile-0.5.0/README.md0000664000175000017500000002701012766776523016477 0ustar mattmatt00000000000000Mapbox Vector Tile ================== [![Build Status](https://travis-ci.org/tilezen/mapbox-vector-tile.svg?branch=master)](https://travis-ci.org/tilezen/mapbox-vector-tile) [![Coverage Status](https://coveralls.io/repos/github/tilezen/mapbox-vector-tile/badge.svg?branch=master)](https://coveralls.io/github/tilezen/mapbox-vector-tile?branch=master) Installation ------------ mapbox-vector-tile is compatible with Python 2.6, 2.7 and 3.5. It is listed on PyPi as `mapbox-vector-tile`. The recommended way to install is via `pip`: ```shell pip install mapbox-vector-tile ``` Note that `mapbox-vector-tile` depends on [Shapely](https://pypi.python.org/pypi/Shapely), a Python library for computational geometry which requires a library called [GEOS](https://trac.osgeo.org/geos/). Please see [Shapely's instructions](https://pypi.python.org/pypi/Shapely#installing-shapely) for information on how to install its prerequisites. Encoding -------- Encode method expects an array of layers or atleast a single valid layer. A valid layer is a dictionary with the following keys * `name`: layer name * `features`: an array of features. A feature is a dictionary with the following keys: * `geometry`: representation of the feature geometry in WKT, WKB, or a shapely geometry. Coordinates are relative to the tile, scaled in the range `[0, 4096)`. See below for example code to perform the necessary transformation. *Note* that `GeometryCollection` types are not supported, and will trigger a `ValueError`. * `properties`: a dictionary with a few keys and their corresponding values. ```python >>> import mapbox_vector_tile # Using WKT >>> mapbox_vector_tile.encode([ { "name": "water", "features": [ { "geometry":"POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))", "properties":{ "uid":123, "foo":"bar", "cat":"flew" } } ] }, { "name": "air", "features": [ { "geometry":"LINESTRING(159 3877, -1570 3877)", "properties":{ "uid":1234, "foo":"bar", "cat":"flew" } } ] } ]) '\x1aH\n\x05water\x12\x18\x12\x06\x00\x00\x01\x01\x02\x02\x18\x03"\x0c\t\x00\x80@\x1a\x00\x01\x02\x00\x00\x02\x0f\x1a\x03foo\x1a\x03uid\x1a\x03cat"\x05\n\x03bar"\x02 {"\x06\n\x04flew(\x80 x\x02\x1aD\n\x03air\x12\x15\x12\x06\x00\x00\x01\x01\x02\x02\x18\x02"\t\t\xbe\x02\xb6\x03\n\x81\x1b\x00\x1a\x03foo\x1a\x03uid\x1a\x03cat"\x05\n\x03bar"\x03 \xd2\t"\x06\n\x04flew(\x80 x\x02' # Using WKB >>> mapbox_vector_tile.encode([ { "name": "water", "features": [ { "geometry":"\001\003\000\000\000\001\000\000\000\005\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\360?\000\000\000\000\000\000\360?\000\000\000\000\000\000\360?\000\000\000\000\000\000\360?\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", "properties":{ "uid":123, "foo":"bar", "cat":"flew" } } ] }, { "name": "air", "features": [ { "geometry":"\001\003\000\000\000\001\000\000\000\005\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\360?\000\000\000\000\000\000\360?\000\000\000\000\000\000\360?\000\000\000\000\000\000\360?\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", "properties":{ "uid":1234, "foo":"bar", "cat":"flew" } } ] } ]) '\x1aJ\n\x05water\x12\x1a\x08\x01\x12\x06\x00\x00\x01\x01\x02\x02\x18\x03"\x0c\t\x00\x80@\x1a\x00\x01\x02\x00\x00\x02\x0f\x1a\x03foo\x1a\x03uid\x1a\x03cat"\x05\n\x03bar"\x02 {"\x06\n\x04flew(\x80 x\x02\x1aY\n\x03air\x12\x1c\x08\x01\x12\x08\x00\x00\x01\x01\x02\x02\x03\x03\x18\x03"\x0c\t\x00\x80@\x1a\x00\x01\x02\x00\x00\x02\x0f\x1a\x03foo\x1a\x03uid\x1a\x05balls\x1a\x03cat"\x05\n\x03bar"\x03 \xd2\t"\x05\n\x03foo"\x06\n\x04flew(\x80 x\x02' ``` ### Coordinate transformations for encoding The encoder expects geometries either: 1. In tile-relative coordinates, where the lower left corner is origin and values grow up and to the right, and the tile is 4096 pixels square. For example, `POINT(0 0)` is the lower left corner of the tile and `POINT(4096, 4096)` is the upper right corner of the tile. In this case, the library does no projection, and coordinates are encoded as-is. 2. In another coordinate system, with the tile bounds given by the `quantize_bounds` parameter. In this case, the library will scale coordinates so that the `quantize_bounds` fit within the range (0, 4096) in both `x` and `y` directions. Aside than the affine transformation, the library does no other projection. It is possible to control whether the tile is in a "y down" coordinate system by setting the parameter `y_coord_down=True` on the call to `encode()`. The default is "y up". It is possible to control the tile extents (by default, 4096 as used in the examples above), by setting the `extents` parameter on the call to `encode()`. The default is 4096. If you have geometries in longitude and latitude (EPSG:4326), you can convert to tile-based coordinates by first projecting to Spherical Mercator (EPSG:3857) and then computing the pixel location within the tile. This example code uses Django's included GEOS library to do the transformation for `LineString` objects: ```python SRID_SPHERICAL_MERCATOR = 3857 def linestring_in_tile(tile_bounds, line): # `mapbox-vector-tile` has a hardcoded tile extent of 4096 units. MVT_EXTENT = 4096 from django.contrib.gis.geos import LineString # We need tile bounds in spherical mercator assert tile_bounds.srid == SRID_SPHERICAL_MERCATOR # And we need the line to be in a known projection so we can re-project assert line.srid is not None line.transform(SRID_SPHERICAL_MERCATOR) (x0, y0, x_max, y_max) = tile_bounds.extent x_span = x_max - x0 y_span = y_max - y0 def xy_pairs(): for x_merc, y_merc in line: yield ( int((x_merc - x0) * MVT_EXTENT / x_span), int((y_merc - y0) * MVT_EXTENT / y_span), ``` The tile bounds can be found with `mercantile`, so a complete usage example might look like this: ```python from django.contrib.gis.geos import LineString, Polygon import mercantile import mapbox_vector_tile SRID_LNGLAT = 4326 SRID_SPHERICAL_MERCATOR = 3857 tile_xyz = (2452, 3422, 18) tile_bounds = Polygon.from_bbox(mercantile.bounds(*tile_xyz)) tile_bounds.srid = SRID_LNGLAT tile_bounds.transform(SRID_SPHERICAL_MERCATOR) lnglat_line = LineString(((-122.1, 45.1), (-122.2, 45.2)), srid=SRID_LNGLAT) tile_line = linestring_in_tile(tile_bounds, lnglat_line) tile_pbf = mapbox_vector_tile.encode({ "name": "my-layer", "features": [ { "geometry": tile_line.wkt, "properties": { "stuff": "things" }, } ] }) ``` Note that this example may not have anything visible within the tile when rendered. It's up to you to make sure you put the right data in the tile! Also note that the spec allows the extents to be modified, even though they are often set to 4096 by convention. `mapbox-vector-tile` assumes an extent of 4096. ### Quantization The encoder also has options to quantize the data for you via the `quantize_bounds` option. When encoding, pass in the bounds in the form (minx, miny, maxx, maxy) and the coordinates will be scaled appropriately during encoding. ```python mapbox_vector_tile.encode([ { "name": "water", "features": [ { "geometry":"POINT(15 15)", "properties":{ "foo":"bar", } } ] } ], quantize_bounds=(10.0, 10.0, 20.0, 20.0)) ``` In this example, the coordinate that would get encoded would be (2048, 2048) Additionally, if the data is already in a cooridnate system with y values going down, the encoder supports an option, `y_coord_down`, that can be set to True. This will suppress flipping the y coordinate values during encoding. ### Custom extents The encoder also supports passing in custom extents. These will be passed through to the layer in the pbf, and honored during any quantization or y coordinate flipping. ```python mapbox_vector_tile.encode([ { "name": "water", "features": [ { "geometry":"POINT(15 15)", "properties":{ "foo":"bar", } } ] } ], quantize_bounds=(0.0, 0.0, 10.0, 10.0), extents=50) ``` ### Custom rounding functions In order to maintain consistency between Python 2 and 3, the `decimal` module is used to explictly define `ROUND_HALF_EVEN` as the rounding method. This can be slower than the built-in `round()` function. Encode takes an optional `round_fn` where you can specify the round function to be used. ```python mapbox_vector_tile.encode([ { "name": "water", "features": [ { "geometry":"POINT(15 15)", "properties":{ "foo":"bar", } } ] } ], quantize_bounds=(0.0, 0.0, 10.0, 10.0), round_fn=round) ``` Decoding -------- Decode method takes in a valid google.protobuf.message Tile and returns decoded string in the following format: ```python { layername: { 'extent': 'integer layer extent' 'version': 'integer' 'features': [{ 'geometry': 'list of points', 'properties': 'dictionary of key/value pairs', 'id': 'unique id for the given feature within the layer ' }, ... ] }, layername2: { # ... } } ``` ```python >>> import mapbox_vector_tile >>> mapbox_vector_tile.decode('\x1aJ\n\x05water\x12\x1a\x08\x01\x12\x06\x00\x00\x01\x01\x02\x02\x18\x03"\x0c\t\x00\x80@\x1a\x00\x01\x02\x00\x00\x02\x0f\x1a\x03foo\x1a\x03uid\x1a\x03cat"\x05\n\x03bar"\x02 {"\x06\n\x04flew(\x80 x\x02\x1aY\n\x03air\x12\x1c\x08\x01\x12\x08\x00\x00\x01\x01\x02\x02\x03\x03\x18\x03"\x0c\t\x00\x80@\x1a\x00\x01\x02\x00\x00\x02\x0f\x1a\x03foo\x1a\x03uid\x1a\x05balls\x1a\x03cat"\x05\n\x03bar"\x03 \xd2\t"\x05\n\x03foo"\x06\n\x04flew(\x80 x\x02') { 'water': { 'extent': 4096, 'version': 2, 'features': [{ 'geometry': [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]], 'properties': { 'foo': 'bar', 'uid': 123, 'cat': 'flew' }, 'type': 3, 'id': 1 } ] }, 'air': { 'extent': 4096, 'version': 2, 'features': [{ 'geometry': [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]], 'properties': { 'foo': 'bar', 'uid': 1234, 'balls': 'foo', 'cat': 'flew' }, 'type': 3, 'id': 1 } ] } } ``` Here's how you might decode a tile from a file. ```python >>> import mapbox_vector_tile >>> with open('tile.mvt', 'rb') as f: >>> data = f.read() >>> decoded_data = mapbox_vector_tile.decode(data) >>> with open('out.txt', 'w') as f: >>> f.write(repr(decoded_data)) ``` Changelog --------- Click [here](https://github.com/tilezen/mapbox-vector-tile/blob/master/CHANGELOG.md) to see what changed over time in various versions. mapbox-vector-tile-0.5.0/setup.py0000664000175000017500000000152312766776552016735 0ustar mattmatt00000000000000import io from setuptools import setup, find_packages with io.open('README.md') as readme_file: long_description = readme_file.read() def test_suite(): try: import unittest2 as unittest except: import unittest suite = unittest.TestLoader().discover("tests") return suite setup(name='mapbox-vector-tile', version='0.5.0', description=u"Mapbox Vector Tile", long_description=long_description, classifiers=[], keywords='', author=u"Harish Krishna", author_email='harish.krsn@gmail.com', url='https://github.com/tilezen/mapbox-vector-tile', license='MIT', packages=find_packages(), include_package_data=True, zip_safe=False, test_suite="setup.test_suite", install_requires=["setuptools", "protobuf", "shapely", "future"] ) mapbox-vector-tile-0.5.0/tests/0000775000175000017500000000000012766777174016365 5ustar mattmatt00000000000000mapbox-vector-tile-0.5.0/tests/test_decoder.py0000664000175000017500000000650412722020760021356 0ustar mattmatt00000000000000# -*- coding: utf-8 -*- """ Tests for vector_tile/decoder.py """ import unittest import mapbox_vector_tile from mapbox_vector_tile.compat import PY3 class BaseTestCase(unittest.TestCase): def test_decoder(self): if PY3: vector_tile = b'\x1aI\n\x05water\x12\x1a\x08\x01\x12\x06\x00\x00\x01\x01\x02\x02\x18\x03"\x0c\t\x00\x80@\x1a\x00\x01\x02\x00\x00\x02\x0f\x1a\x03foo\x1a\x03baz\x1a\x03uid"\x05\n\x03bar"\x05\n\x03foo"\x02 {(\x80 x\x02' # noqa else: vector_tile = '\x1aI\n\x05water\x12\x1a\x08\x01\x12\x06\x00\x00\x01\x01\x02\x02\x18\x03"\x0c\t\x00\x80@\x1a\x00\x01\x02\x00\x00\x02\x0f\x1a\x03foo\x1a\x03baz\x1a\x03uid"\x05\n\x03bar"\x05\n\x03foo"\x02 {(\x80 x\x02' # noqa self.assertEqual(mapbox_vector_tile.decode(vector_tile), { 'water': { 'version': 2, 'extent': 4096, 'features': [{ 'geometry': [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]], 'properties': { 'foo': 'bar', 'baz': 'foo', 'uid': 123 }, 'id': 1, 'type': 3 }], }, }) def test_decode_polygon_no_cmd_seg_end(self): # the binary here was generated without including the # CMD_SEG_END after the polygon parts # this tests that the decoder can detect that a new # CMD_MOVE_TO implicitly closes the previous polygon if PY3: vector_tile = b'\x1a+\n\x05water\x12\x1d\x18\x03"\x19\t\x00\x80@"\x08\x00\x00\x07\x07\x00\x00\x08\t\x02\x01"\x00\x03\x04\x00\x00\x04\x03\x00(\x80 x\x02' # noqa else: vector_tile = '\x1a+\n\x05water\x12\x1d\x18\x03"\x19\t\x00\x80@"\x08\x00\x00\x07\x07\x00\x00\x08\t\x02\x01"\x00\x03\x04\x00\x00\x04\x03\x00(\x80 x\x02' # noqa self.assertEqual(mapbox_vector_tile.decode(vector_tile), { 'water': { 'version': 2, 'extent': 4096, 'features': [{ 'geometry': [ [[0, 0], [4, 0], [4, 4], [0, 4], [0, 0]], [[1, 1], [1, 3], [3, 3], [3, 1], [1, 1]], ], 'properties': {}, 'id': 0, 'type': 3 }], }, }) def test_nondefault_extent(self): if PY3: vector_tile = b'\x1aK\n\x05water\x12\x1c\x08\x01\x12\x06\x00\x00\x01\x01\x02\x02\x18\x02"\x0e\t\x80}\xd0\x12\x12\xbf>\xd86\xbf>\xd86\x1a\x03foo\x1a\x03baz\x1a\x03uid"\x05\n\x03bar"\x05\n\x03foo"\x02 {(\x80@x\x02' # noqa else: vector_tile = '\x1aK\n\x05water\x12\x1c\x08\x01\x12\x06\x00\x00\x01\x01\x02\x02\x18\x02"\x0e\t\x80}\xd0\x12\x12\xbf>\xd86\xbf>\xd86\x1a\x03foo\x1a\x03baz\x1a\x03uid"\x05\n\x03bar"\x05\n\x03foo"\x02 {(\x80@x\x02' # noqa self.assertEqual(mapbox_vector_tile.decode(vector_tile), { 'water': { 'version': 2, 'extent': 8192, 'features': [{ 'geometry': [[8000, 7000], [4000, 3500], [0, 0]], 'id': 1, 'properties': {'baz': 'foo', 'foo': 'bar', 'uid': 123}, 'type': 2 }], } }) mapbox-vector-tile-0.5.0/tests/test_encoder.py0000664000175000017500000006473312766776523021427 0ustar mattmatt00000000000000# -*- coding: utf-8 -*- """ Tests for vector_tile/encoder.py """ import unittest import mapbox_vector_tile from mapbox_vector_tile import encode, decode from mapbox_vector_tile.compat import PY3 from past.builtins import long, unicode from shapely import wkt class BaseTestCase(unittest.TestCase): def setUp(self): self.layer_name = "water" self.feature_properties = { "uid": 123, "foo": "bar", "baz": "foo" } self.feature_geometry = 'POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))' def assertRoundTrip(self, input_geometry, expected_geometry, name=None, properties=None, id=None, expected_len=1, expected_properties=None): if input_geometry is None: input_geometry = self.feature_geometry if name is None: name = self.layer_name if properties is None: properties = self.feature_properties if expected_properties is None: expected_properties = properties source = [{ "name": name, "features": [{ "geometry": input_geometry, "properties": properties }] }] if id: source[0]['features'][0]['id'] = id encoded = encode(source) decoded = decode(encoded) self.assertIn(name, decoded) layer = decoded[name] features = layer['features'] self.assertEqual(expected_len, len(features)) self.assertEqual(features[0]['properties'], expected_properties) self.assertEqual(features[0]['geometry'], expected_geometry) if id: self.assertEqual(features[0]['id'], id) class TestDifferentGeomFormats(BaseTestCase): def test_encoder(self): self.assertRoundTrip( input_geometry='POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))', expected_geometry=[[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]) def test_encoder_quantize_before_orient(self): self.assertRoundTrip( input_geometry='POLYGON ((0 0, 4 0, 4 4, 0 4, 0 0), (1 1, 3 2, 2 2, 1 1))', # noqa expected_geometry=[[[0, 0], [0, 4], [4, 4], [4, 0], [0, 0]], [[1, 1], [3, 2], [2, 2], [1, 1]]]) def test_encoder_winding_order_polygon(self): # example from the spec # https://github.com/mapbox/vector-tile-spec/tree/master/2.1#4355-example-polygon # the order given in the example is clockwise in a y-up coordinate # system, but the coordinate system given for the example is y-down! # therefore the y coordinate in this example is flipped negative. self.assertRoundTrip( input_geometry='POLYGON ((3 -6, 8 -12, 20 -34, 3 -6))', expected_geometry=[[[3, -6], [8, -12], [20, -34], [3, -6]]]) def test_encoder_winding_order_polygon_reverse(self): # tests that encode _corrects_ the winding order # example is the same as above - note the flipped coordinate system. self.assertRoundTrip( input_geometry='POLYGON ((3 -6, 20 -34, 8 -12, 3 -6))', expected_geometry=[[[3, -6], [8, -12], [20, -34], [3, -6]]]) def test_encoder_winding_order_multipolygon(self): # example from the spec # https://github.com/mapbox/vector-tile-spec/tree/master/2.1#4356-example-multi-polygon # the order given in the example is clockwise in a y-up coordinate # system, but the coordinate system given for the example is y-down! self.assertRoundTrip( input_geometry=('MULTIPOLYGON (' + '((0 0, 10 0, 10 -10, 0 -10, 0 0)),' + '((11 -11, 20 -11, 20 -20, 11 -20, 11 -11),' + ' (13 -13, 13 -17, 17 -17, 17 -13, 13 -13)))'), expected_geometry=[ [[[0, 0], [10, 0], [10, -10], [0, -10], [0, 0]]], [[[11, -11], [20, -11], [20, -20], [11, -20], [11, -11]], [[13, -13], [13, -17], [17, -17], [17, -13], [13, -13]]]]) def test_encoder_ensure_winding_after_quantization(self): self.assertRoundTrip( input_geometry='POLYGON ((0 0, 4 0, 4 4, 0 4, 0 0), (1 1, 3 2.4, 2 1.6, 1 1))', # noqa # should be single polygon with hole expected_geometry=[[[0, 0], [0, 4], [4, 4], [4, 0], [0, 0]], [[1, 1], [3, 2], [2, 2], [1, 1]]]) # but becomes multi-polygon # expected_geometry=[[[[0, 0], [0, 4], [4, 4], [4, 0], [0, 0]]], # [[[1, 1], [2, 2], [3, 2], [1, 1]]]]) def test_with_wkt(self): self.assertRoundTrip( input_geometry="LINESTRING(-71.160281 42.258729,-71.160837 42.259113,-71.161144 42.25932)", # noqa expected_geometry=[[-71, 42], [-71, 42], [-71, 42]]) def test_with_wkb(self): self.assertRoundTrip( input_geometry=b"\001\003\000\000\000\001\000\000\000\005\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\360?\000\000\000\000\000\000\360?\000\000\000\000\000\000\360?\000\000\000\000\000\000\360?\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", # noqa expected_geometry=[[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]) def test_with_shapely(self): geometry = "LINESTRING(-71.160281 42.258729,-71.160837 42.259113,-71.161144 42.25932)" # noqa geometry = wkt.loads(geometry) self.assertRoundTrip( input_geometry=geometry, expected_geometry=[[-71, 42], [-71, 42], [-71, 42]]) def test_with_invalid_geometry(self): expected_result = ('Can\'t do geometries that are not wkt, wkb, or ' 'shapely geometries') with self.assertRaises(NotImplementedError) as ex: mapbox_vector_tile.encode([{ "name": self.layer_name, "features": [{ "geometry": "xyz", "properties": self.feature_properties }] }]) self.assertEqual(str(ex.exception), expected_result) def test_encode_unicode_property(self): if PY3: func = str else: func = unicode geometry = "LINESTRING(-71.160281 42.258729,-71.160837 42.259113,-71.161144 42.25932)" # noqa properties = { "foo": func(self.feature_properties["foo"]), "baz": func(self.feature_properties["baz"]), } self.assertRoundTrip( input_geometry=geometry, expected_geometry=[[-71, 42], [-71, 42], [-71, 42]], properties=properties) def test_encode_unicode_property_key(self): geometry = "LINESTRING(-71.160281 42.258729,-71.160837 42.259113,-71.161144 42.25932)" # noqa properties = { u'☺': u'☺' } self.assertRoundTrip( input_geometry=geometry, expected_geometry=[[-71, 42], [-71, 42], [-71, 42]], properties=properties) def test_encode_float_little_endian(self): geometry = "LINESTRING(-71.160281 42.258729,-71.160837 42.259113,-71.161144 42.25932)" # noqa properties = { 'floatval': 3.14159 } self.assertRoundTrip( input_geometry=geometry, expected_geometry=[[-71, 42], [-71, 42], [-71, 42]], properties=properties) def test_encode_feature_with_id(self): geometry = 'POINT(1 1)' self.assertRoundTrip(input_geometry=geometry, expected_geometry=[[1, 1]], id=42) def test_encode_polygon_reverse_winding_order(self): geometry = 'POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))' self.assertRoundTrip( input_geometry=geometry, expected_geometry=[[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]) def test_encode_multilinestring(self): geometry = 'MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10))' # noqa self.assertRoundTrip(input_geometry=geometry, expected_geometry=[ [[10, 10], [20, 20], [10, 40]], [[40, 40], [30, 30], [40, 20], [30, 10]], ]) def test_encode_multipolygon_normal_winding_order(self): geometry = 'MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)), ((20 35, 10 30, 10 10, 30 5, 45 20, 20 35), (30 20, 20 15, 20 25, 30 20)))' # noqa self.assertRoundTrip( input_geometry=geometry, expected_geometry=[ [[[40, 40], [45, 30], [20, 45], [40, 40]]], [[[20, 35], [45, 20], [30, 5], [10, 10], [10, 30], [20, 35]], [[30, 20], [20, 25], [20, 15], [30, 20]]], ], expected_len=1) def test_encode_multipolygon_normal_winding_order_zero_area(self): geometry = 'MULTIPOLYGON (((40 40, 40 20, 40 45, 40 40)), ((20 35, 10 30, 10 10, 30 5, 45 20, 20 35), (30 20, 20 15, 20 25, 30 20)))' # noqa self.assertRoundTrip( input_geometry=geometry, expected_geometry=[ [[20, 35], [45, 20], [30, 5], [10, 10], [10, 30], [20, 35]], [[30, 20], [20, 25], [20, 15], [30, 20]], ], expected_len=1) def test_encode_multipolygon_reverse_winding_order(self): geometry = 'MULTIPOLYGON (((10 10, 10 0, 0 0, 0 10, 10 10), (8 8, 2 8, 2 0, 8 0, 8 8)))' # noqa self.assertRoundTrip( input_geometry=geometry, expected_geometry=[ [[10, 10], [10, 0], [0, 0], [0, 10], [10, 10]], [[8, 8], [2, 8], [2, 0], [8, 0], [8, 8]], ], expected_len=1) def test_encode_property_bool(self): geometry = 'POINT(0 0)' properties = { 'test_bool_true': True, 'test_bool_false': False } self.assertRoundTrip( input_geometry=geometry, expected_geometry=[[0, 0]], properties=properties) def test_encode_property_long(self): geometry = 'POINT(0 0)' properties = { 'test_int': int(1), 'test_long': long(1) } self.assertRoundTrip( input_geometry=geometry, expected_geometry=[[0, 0]], properties=properties) def test_encode_property_null(self): geometry = 'POINT(0 0)' properties = { 'test_none': None, 'test_empty': "" } self.assertRoundTrip( input_geometry=geometry, expected_geometry=[[0, 0]], properties=properties, expected_properties={'test_empty': ''}) def test_encode_property_list(self): geometry = 'POINT(0 0)' properties = { 'test_list': [1, 2, 3], 'test_empty': "" } self.assertRoundTrip( input_geometry=geometry, expected_geometry=[[0, 0]], properties=properties, expected_properties={'test_empty': ''}) def test_encode_multiple_values_test(self): geometry = 'POINT(0 0)' properties1 = dict(foo='bar', baz='bar') properties2 = dict(quux='morx', baz='bar') name = 'foo' feature1 = dict(geometry=geometry, properties=properties1) feature2 = dict(geometry=geometry, properties=properties2) source = [{ "name": name, "features": [feature1, feature2] }] encoded = encode(source) decoded = decode(encoded) self.assertIn(name, decoded) layer = decoded[name] features = layer['features'] self.assertEqual(2, len(features)) self.assertEqual(features[0]['properties'], properties1) self.assertEqual(features[1]['properties'], properties2) def test_encode_rounding_floats(self): geometry = 'LINESTRING(1.1 1.1, 41.5 41.8)' exp_geoemtry = [[1, 1], [42, 42]] self.assertRoundTrip( input_geometry=geometry, expected_geometry=exp_geoemtry, ) class QuantizeTest(unittest.TestCase): def test_quantize(self): from mapbox_vector_tile import decode from mapbox_vector_tile import encode props = dict(foo='bar') shape = 'POINT(15 15)' feature = dict(geometry=shape, properties=props) features = [feature] source = dict(name='layername', features=features) bounds = 10.0, 10.0, 20.0, 20.0 pbf = encode(source, quantize_bounds=bounds) result = decode(pbf) act_feature = result['layername']['features'][0] act_geom = act_feature['geometry'] exp_geom = [[2048, 2048]] self.assertEqual(exp_geom, act_geom) def test_y_coord_down(self): from mapbox_vector_tile import decode from mapbox_vector_tile import encode props = dict(foo='bar') shape = 'POINT(10 10)' feature = dict(geometry=shape, properties=props) features = [feature] source = dict(name='layername', features=features) pbf = encode(source, y_coord_down=True) result = decode(pbf, y_coord_down=True) act_feature = result['layername']['features'][0] act_geom = act_feature['geometry'] exp_geom = [[10, 10]] self.assertEqual(exp_geom, act_geom) def test_quantize_and_y_coord_down(self): from mapbox_vector_tile import decode from mapbox_vector_tile import encode props = dict(foo='bar') shape = 'POINT(30 30)' feature = dict(geometry=shape, properties=props) features = [feature] source = dict(name='layername', features=features) bounds = 0.0, 0.0, 50.0, 50.0 pbf = encode(source, quantize_bounds=bounds, y_coord_down=True) result_decode_no_flip = decode(pbf, y_coord_down=True) act_feature = result_decode_no_flip['layername']['features'][0] act_geom = act_feature['geometry'] exp_geom = [[2458, 2458]] self.assertEqual(exp_geom, act_geom) result_decode_flip = decode(pbf) act_feature = result_decode_flip['layername']['features'][0] act_geom = act_feature['geometry'] exp_geom = [[2458, 1638]] self.assertEqual(exp_geom, act_geom) class ExtentTest(unittest.TestCase): def test_custom_extent(self): from mapbox_vector_tile import decode from mapbox_vector_tile import encode props = dict(foo='bar') shape = 'POINT(10 10)' feature = dict(geometry=shape, properties=props) features = [feature] source = dict(name='layername', features=features) bounds = 0.0, 0.0, 10.0, 10.0 pbf = encode(source, quantize_bounds=bounds, extents=50) result = decode(pbf) act_feature = result['layername']['features'][0] act_geom = act_feature['geometry'] exp_geom = [[50, 50]] self.assertEqual(exp_geom, act_geom) class RoundTest(unittest.TestCase): def test_custom_rounding_function(self): from mapbox_vector_tile import decode from mapbox_vector_tile import encode props = dict(foo='bar') shape = 'POINT(10 10)' feature = dict(geometry=shape, properties=props) features = [feature] source = dict(name='layername', features=features) bounds = 0.0, 0.0, 10.0, 10.0 # A really bad, custom "rounding" function pbf = encode(source, quantize_bounds=bounds, round_fn=lambda x: 5) result = decode(pbf) act_feature = result['layername']['features'][0] act_geom = act_feature['geometry'] exp_geom = [[5, 5]] self.assertEqual(exp_geom, act_geom) class InvalidGeometryTest(unittest.TestCase): def test_invalid_geometry_ignore(self): from mapbox_vector_tile import encode from mapbox_vector_tile.encoder import on_invalid_geometry_ignore import shapely.wkt geometry = 'POLYGON ((10 10, 20 10, 20 20, 15 15, 15 5, 10 10))' shape = shapely.wkt.loads(geometry) self.assertFalse(shape.is_valid) feature = dict(geometry=shape, properties={}) source = dict(name='layername', features=[feature]) pbf = encode(source, on_invalid_geometry=on_invalid_geometry_ignore) result = decode(pbf) self.assertEqual(0, len(result['layername']['features'])) def test_invalid_geometry_raise(self): from mapbox_vector_tile import encode from mapbox_vector_tile.encoder import on_invalid_geometry_raise import shapely.wkt geometry = 'POLYGON ((10 10, 20 10, 20 20, 15 15, 15 5, 10 10))' shape = shapely.wkt.loads(geometry) self.assertFalse(shape.is_valid) feature = dict(geometry=shape, properties={}) source = dict(name='layername', features=[feature]) with self.assertRaises(Exception): encode(source, on_invalid_geometry=on_invalid_geometry_raise) def test_invalid_geometry_make_valid(self): from mapbox_vector_tile import encode from mapbox_vector_tile.encoder import on_invalid_geometry_make_valid import shapely.geometry import shapely.wkt geometry = 'POLYGON ((10 10, 20 10, 20 20, 15 15, 15 5, 10 10))' shape = shapely.wkt.loads(geometry) self.assertFalse(shape.is_valid) feature = dict(geometry=shape, properties={}) source = dict(name='layername', features=[feature]) pbf = encode(source, on_invalid_geometry=on_invalid_geometry_make_valid) result = decode(pbf) self.assertEqual(1, len(result['layername']['features'])) valid_geometry = result['layername']['features'][0]['geometry'] shape = shapely.geometry.Polygon(valid_geometry[0]) self.assertTrue(shape.is_valid) def test_bowtie_self_touching(self): from mapbox_vector_tile import encode from mapbox_vector_tile.encoder import on_invalid_geometry_make_valid import shapely.geometry import shapely.wkt bowtie = ('POLYGON ((0 0, 0 2, 1 1, 2 2, 2 0, 1 1, 0 0))') shape = shapely.wkt.loads(bowtie) self.assertFalse(shape.is_valid) feature = dict(geometry=shape, properties={}) source = dict(name='layername', features=[feature]) pbf = encode(source, on_invalid_geometry=on_invalid_geometry_make_valid) result = decode(pbf) self.assertEqual(1, len(result['layername']['features'])) valid_geometries = result['layername']['features'][0]['geometry'] self.assertEqual(2, len(valid_geometries)) shape1, shape2 = [shapely.geometry.Polygon(x[0]) for x in valid_geometries] self.assertTrue(shape1.is_valid) self.assertTrue(shape2.is_valid) self.assertGreater(shape1.area, 0) self.assertGreater(shape2.area, 0) def test_bowtie_self_crossing(self): from mapbox_vector_tile import encode from mapbox_vector_tile.encoder import on_invalid_geometry_make_valid import shapely.geometry import shapely.wkt bowtie = ('POLYGON ((0 0, 2 2, 2 0, 0 2, 0 0))') shape = shapely.wkt.loads(bowtie) self.assertFalse(shape.is_valid) feature = dict(geometry=shape, properties={}) source = dict(name='layername', features=[feature]) pbf = encode(source, on_invalid_geometry=on_invalid_geometry_make_valid) result = decode(pbf) self.assertEqual(1, len(result['layername']['features'])) valid_geometries = result['layername']['features'][0]['geometry'] total_area = 0 for g in valid_geometries: self.assertEquals(1, len(g)) p = shapely.geometry.Polygon(g[0]) self.assertTrue(p.is_valid) self.assertGreater(p.area, 0) total_area += p.area self.assertEquals(2, total_area) def test_validate_generates_rounding_error(self): from mapbox_vector_tile import encode from mapbox_vector_tile.encoder import on_invalid_geometry_make_valid import shapely.geometry import shapely.wkt bowtie = ('POLYGON((0 0, 1 1, 0 1, 1 0, 0 0))') shape = shapely.wkt.loads(bowtie) self.assertFalse(shape.is_valid) feature = dict(geometry=shape, properties={}) source = dict(name='layername', features=[feature]) pbf = encode(source, on_invalid_geometry=on_invalid_geometry_make_valid) result = decode(pbf) features = result['layername']['features'] self.assertEqual(1, len(features)) shape = shapely.geometry.Polygon(features[0]['geometry'][0]) self.assertTrue(shape.is_valid) self.assertGreater(shape.area, 0) def test_geometry_collection_raises(self): from mapbox_vector_tile import encode import shapely.wkt collection = shapely.wkt.loads('GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (POINT (4095 3664), LINESTRING (2889 0, 2889 0)), POINT (4095 3664), LINESTRING (2889 0, 2912 158, 3757 1700, 3732 1999, 4095 3277))') # noqa with self.assertRaises(ValueError): encode({'name': 'streets', 'features': [{'geometry': collection}]}) def test_quantize_makes_mutlipolygon_invalid(self): from mapbox_vector_tile import encode from mapbox_vector_tile.encoder import on_invalid_geometry_make_valid import shapely.wkt shape = shapely.wkt.loads('MULTIPOLYGON (((656510.8206577231 5674684.979891453, 656511.16 5674685.9, 656514.1758819892 5674684.979891453, 656510.8206577231 5674684.979891453)), ((657115.9120547654 5674684.979891453, 657118.85 5674690, 657118.0689111941 5674684.979891453, 657115.9120547654 5674684.979891453)))') # noqa quantize_bounds = (645740.0149532147, 5674684.979891453, 665307.8941942193, 5694252.8591324575) # noqa features = [dict(geometry=shape, properties={})] pbf = encode({'name': 'foo', 'features': features}, quantize_bounds=quantize_bounds, on_invalid_geometry=on_invalid_geometry_make_valid) result = decode(pbf) features = result['foo']['features'] self.assertEqual(1, len(features)) class LowLevelEncodingTestCase(unittest.TestCase): def test_example_multi_polygon(self): from mapbox_vector_tile.encoder import VectorTile # example from spec: # https://github.com/mapbox/vector-tile-spec/tree/master/2.1#4356-example-multi-polygon # note that examples are in **tile local coordinates** which are # y-down. input_geometry = 'MULTIPOLYGON (' + \ '((0 0, 10 0, 10 10, 0 10, 0 0)),' + \ '((11 11, 20 11, 20 20, 11 20, 11 11),' + \ ' (13 13, 13 17, 17 17, 17 13, 13 13)))' expected_commands = [ 9, # 1 x move to 0, 0, # ........... +0,+0 26, # 3 x line to 20, 0, # ........... +10,+0 0, 20, # ........... +0,+10 19, 0, # ........... -10,+0 15, # 1 x close path 9, # 1 x move to 22, 2, # ........... +11,+1 26, # 3 x line to 18, 0, # ........... +9,+0 0, 18, # ........... +0,+9 17, 0, # ........... -9,+0 15, # 1 x close path 9, # 1 x move to 4, 13, # ........... +2,-7 26, # 3 x line to 0, 8, # ........... +0,+4 8, 0, # ........... +4,+0 0, 7, # ........... +0,-4 15 # 1 x close path ] tile = VectorTile(4096) tile.addFeatures([dict(geometry=input_geometry)], 'example_layer', quantize_bounds=None, y_coord_down=True) self.assertEqual(1, len(tile.layer.features)) f = tile.layer.features[0] self.assertEqual(expected_commands, list(f.geometry)) def test_example_multi_polygon_y_up(self): from mapbox_vector_tile.encoder import VectorTile # example from spec: # https://github.com/mapbox/vector-tile-spec/tree/master/2.1#4356-example-multi-polygon # in this example, we transform the coordinates to their equivalents # in a y-up coordinate system. input_geometry = 'MULTIPOLYGON (' + \ '((0 20, 10 20, 10 10, 0 10, 0 20)),' + \ '((11 9, 20 9, 20 0, 11 0, 11 9),' + \ ' (13 7, 13 3, 17 3, 17 7, 13 7)))' expected_commands = [ 9, # 1 x move to 0, 0, # ........... +0,+0 26, # 3 x line to 20, 0, # ........... +10,+0 0, 20, # ........... +0,+10 19, 0, # ........... -10,+0 15, # 1 x close path 9, # 1 x move to 22, 2, # ........... +11,+1 26, # 3 x line to 18, 0, # ........... +9,+0 0, 18, # ........... +0,+9 17, 0, # ........... -9,+0 15, # 1 x close path 9, # 1 x move to 4, 13, # ........... +2,-7 26, # 3 x line to 0, 8, # ........... +0,+4 8, 0, # ........... +4,+0 0, 7, # ........... +0,-4 15 # 1 x close path ] tile = VectorTile(20) tile.addFeatures([dict(geometry=input_geometry)], 'example_layer', quantize_bounds=None, y_coord_down=False) self.assertEqual(1, len(tile.layer.features)) f = tile.layer.features[0] self.assertEqual(expected_commands, list(f.geometry)) def test_issue_57(self): from mapbox_vector_tile.encoder import VectorTile # example from issue: # https://github.com/tilezen/mapbox-vector-tile/issues/57 input_geometry = 'POLYGON ((2 2, 5 4, 2 6, 2 2))' expected_commands = [ 9, # 1 x move to 4, 4, # ........... +2,+2 18, # 2 x line to 6, 4, # ........... +3,+2 5, 4, # ........... -3,+2 15 # 1 x close path ] tile = VectorTile(4096) tile.addFeatures([dict(geometry=input_geometry)], 'example_layer', quantize_bounds=None, y_coord_down=True) self.assertEqual(1, len(tile.layer.features)) f = tile.layer.features[0] self.assertEqual(expected_commands, list(f.geometry)) mapbox-vector-tile-0.5.0/tests/__init__.py0000664000175000017500000000061212620627605020453 0ustar mattmatt00000000000000import doctest import glob import os optionflags = (doctest.REPORT_ONLY_FIRST_FAILURE | doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS) _basedir = os.path.dirname(__file__) paths = glob.glob("%s/*.txt" % _basedir) test_suite = doctest.DocFileSuite(*paths, **dict(module_relative=False, optionflags=optionflags)) mapbox-vector-tile-0.5.0/PKG-INFO0000664000175000017500000003424312766777174016326 0ustar mattmatt00000000000000Metadata-Version: 1.0 Name: mapbox-vector-tile Version: 0.5.0 Summary: Mapbox Vector Tile Home-page: https://github.com/tilezen/mapbox-vector-tile Author: Harish Krishna Author-email: harish.krsn@gmail.com License: MIT Description: Mapbox Vector Tile ================== [![Build Status](https://travis-ci.org/tilezen/mapbox-vector-tile.svg?branch=master)](https://travis-ci.org/tilezen/mapbox-vector-tile) [![Coverage Status](https://coveralls.io/repos/github/tilezen/mapbox-vector-tile/badge.svg?branch=master)](https://coveralls.io/github/tilezen/mapbox-vector-tile?branch=master) Installation ------------ mapbox-vector-tile is compatible with Python 2.6, 2.7 and 3.5. It is listed on PyPi as `mapbox-vector-tile`. The recommended way to install is via `pip`: ```shell pip install mapbox-vector-tile ``` Note that `mapbox-vector-tile` depends on [Shapely](https://pypi.python.org/pypi/Shapely), a Python library for computational geometry which requires a library called [GEOS](https://trac.osgeo.org/geos/). Please see [Shapely's instructions](https://pypi.python.org/pypi/Shapely#installing-shapely) for information on how to install its prerequisites. Encoding -------- Encode method expects an array of layers or atleast a single valid layer. A valid layer is a dictionary with the following keys * `name`: layer name * `features`: an array of features. A feature is a dictionary with the following keys: * `geometry`: representation of the feature geometry in WKT, WKB, or a shapely geometry. Coordinates are relative to the tile, scaled in the range `[0, 4096)`. See below for example code to perform the necessary transformation. *Note* that `GeometryCollection` types are not supported, and will trigger a `ValueError`. * `properties`: a dictionary with a few keys and their corresponding values. ```python >>> import mapbox_vector_tile # Using WKT >>> mapbox_vector_tile.encode([ { "name": "water", "features": [ { "geometry":"POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))", "properties":{ "uid":123, "foo":"bar", "cat":"flew" } } ] }, { "name": "air", "features": [ { "geometry":"LINESTRING(159 3877, -1570 3877)", "properties":{ "uid":1234, "foo":"bar", "cat":"flew" } } ] } ]) '\x1aH\n\x05water\x12\x18\x12\x06\x00\x00\x01\x01\x02\x02\x18\x03"\x0c\t\x00\x80@\x1a\x00\x01\x02\x00\x00\x02\x0f\x1a\x03foo\x1a\x03uid\x1a\x03cat"\x05\n\x03bar"\x02 {"\x06\n\x04flew(\x80 x\x02\x1aD\n\x03air\x12\x15\x12\x06\x00\x00\x01\x01\x02\x02\x18\x02"\t\t\xbe\x02\xb6\x03\n\x81\x1b\x00\x1a\x03foo\x1a\x03uid\x1a\x03cat"\x05\n\x03bar"\x03 \xd2\t"\x06\n\x04flew(\x80 x\x02' # Using WKB >>> mapbox_vector_tile.encode([ { "name": "water", "features": [ { "geometry":"\001\003\000\000\000\001\000\000\000\005\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\360?\000\000\000\000\000\000\360?\000\000\000\000\000\000\360?\000\000\000\000\000\000\360?\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", "properties":{ "uid":123, "foo":"bar", "cat":"flew" } } ] }, { "name": "air", "features": [ { "geometry":"\001\003\000\000\000\001\000\000\000\005\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\360?\000\000\000\000\000\000\360?\000\000\000\000\000\000\360?\000\000\000\000\000\000\360?\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", "properties":{ "uid":1234, "foo":"bar", "cat":"flew" } } ] } ]) '\x1aJ\n\x05water\x12\x1a\x08\x01\x12\x06\x00\x00\x01\x01\x02\x02\x18\x03"\x0c\t\x00\x80@\x1a\x00\x01\x02\x00\x00\x02\x0f\x1a\x03foo\x1a\x03uid\x1a\x03cat"\x05\n\x03bar"\x02 {"\x06\n\x04flew(\x80 x\x02\x1aY\n\x03air\x12\x1c\x08\x01\x12\x08\x00\x00\x01\x01\x02\x02\x03\x03\x18\x03"\x0c\t\x00\x80@\x1a\x00\x01\x02\x00\x00\x02\x0f\x1a\x03foo\x1a\x03uid\x1a\x05balls\x1a\x03cat"\x05\n\x03bar"\x03 \xd2\t"\x05\n\x03foo"\x06\n\x04flew(\x80 x\x02' ``` ### Coordinate transformations for encoding The encoder expects geometries either: 1. In tile-relative coordinates, where the lower left corner is origin and values grow up and to the right, and the tile is 4096 pixels square. For example, `POINT(0 0)` is the lower left corner of the tile and `POINT(4096, 4096)` is the upper right corner of the tile. In this case, the library does no projection, and coordinates are encoded as-is. 2. In another coordinate system, with the tile bounds given by the `quantize_bounds` parameter. In this case, the library will scale coordinates so that the `quantize_bounds` fit within the range (0, 4096) in both `x` and `y` directions. Aside than the affine transformation, the library does no other projection. It is possible to control whether the tile is in a "y down" coordinate system by setting the parameter `y_coord_down=True` on the call to `encode()`. The default is "y up". It is possible to control the tile extents (by default, 4096 as used in the examples above), by setting the `extents` parameter on the call to `encode()`. The default is 4096. If you have geometries in longitude and latitude (EPSG:4326), you can convert to tile-based coordinates by first projecting to Spherical Mercator (EPSG:3857) and then computing the pixel location within the tile. This example code uses Django's included GEOS library to do the transformation for `LineString` objects: ```python SRID_SPHERICAL_MERCATOR = 3857 def linestring_in_tile(tile_bounds, line): # `mapbox-vector-tile` has a hardcoded tile extent of 4096 units. MVT_EXTENT = 4096 from django.contrib.gis.geos import LineString # We need tile bounds in spherical mercator assert tile_bounds.srid == SRID_SPHERICAL_MERCATOR # And we need the line to be in a known projection so we can re-project assert line.srid is not None line.transform(SRID_SPHERICAL_MERCATOR) (x0, y0, x_max, y_max) = tile_bounds.extent x_span = x_max - x0 y_span = y_max - y0 def xy_pairs(): for x_merc, y_merc in line: yield ( int((x_merc - x0) * MVT_EXTENT / x_span), int((y_merc - y0) * MVT_EXTENT / y_span), ``` The tile bounds can be found with `mercantile`, so a complete usage example might look like this: ```python from django.contrib.gis.geos import LineString, Polygon import mercantile import mapbox_vector_tile SRID_LNGLAT = 4326 SRID_SPHERICAL_MERCATOR = 3857 tile_xyz = (2452, 3422, 18) tile_bounds = Polygon.from_bbox(mercantile.bounds(*tile_xyz)) tile_bounds.srid = SRID_LNGLAT tile_bounds.transform(SRID_SPHERICAL_MERCATOR) lnglat_line = LineString(((-122.1, 45.1), (-122.2, 45.2)), srid=SRID_LNGLAT) tile_line = linestring_in_tile(tile_bounds, lnglat_line) tile_pbf = mapbox_vector_tile.encode({ "name": "my-layer", "features": [ { "geometry": tile_line.wkt, "properties": { "stuff": "things" }, } ] }) ``` Note that this example may not have anything visible within the tile when rendered. It's up to you to make sure you put the right data in the tile! Also note that the spec allows the extents to be modified, even though they are often set to 4096 by convention. `mapbox-vector-tile` assumes an extent of 4096. ### Quantization The encoder also has options to quantize the data for you via the `quantize_bounds` option. When encoding, pass in the bounds in the form (minx, miny, maxx, maxy) and the coordinates will be scaled appropriately during encoding. ```python mapbox_vector_tile.encode([ { "name": "water", "features": [ { "geometry":"POINT(15 15)", "properties":{ "foo":"bar", } } ] } ], quantize_bounds=(10.0, 10.0, 20.0, 20.0)) ``` In this example, the coordinate that would get encoded would be (2048, 2048) Additionally, if the data is already in a cooridnate system with y values going down, the encoder supports an option, `y_coord_down`, that can be set to True. This will suppress flipping the y coordinate values during encoding. ### Custom extents The encoder also supports passing in custom extents. These will be passed through to the layer in the pbf, and honored during any quantization or y coordinate flipping. ```python mapbox_vector_tile.encode([ { "name": "water", "features": [ { "geometry":"POINT(15 15)", "properties":{ "foo":"bar", } } ] } ], quantize_bounds=(0.0, 0.0, 10.0, 10.0), extents=50) ``` ### Custom rounding functions In order to maintain consistency between Python 2 and 3, the `decimal` module is used to explictly define `ROUND_HALF_EVEN` as the rounding method. This can be slower than the built-in `round()` function. Encode takes an optional `round_fn` where you can specify the round function to be used. ```python mapbox_vector_tile.encode([ { "name": "water", "features": [ { "geometry":"POINT(15 15)", "properties":{ "foo":"bar", } } ] } ], quantize_bounds=(0.0, 0.0, 10.0, 10.0), round_fn=round) ``` Decoding -------- Decode method takes in a valid google.protobuf.message Tile and returns decoded string in the following format: ```python { layername: { 'extent': 'integer layer extent' 'version': 'integer' 'features': [{ 'geometry': 'list of points', 'properties': 'dictionary of key/value pairs', 'id': 'unique id for the given feature within the layer ' }, ... ] }, layername2: { # ... } } ``` ```python >>> import mapbox_vector_tile >>> mapbox_vector_tile.decode('\x1aJ\n\x05water\x12\x1a\x08\x01\x12\x06\x00\x00\x01\x01\x02\x02\x18\x03"\x0c\t\x00\x80@\x1a\x00\x01\x02\x00\x00\x02\x0f\x1a\x03foo\x1a\x03uid\x1a\x03cat"\x05\n\x03bar"\x02 {"\x06\n\x04flew(\x80 x\x02\x1aY\n\x03air\x12\x1c\x08\x01\x12\x08\x00\x00\x01\x01\x02\x02\x03\x03\x18\x03"\x0c\t\x00\x80@\x1a\x00\x01\x02\x00\x00\x02\x0f\x1a\x03foo\x1a\x03uid\x1a\x05balls\x1a\x03cat"\x05\n\x03bar"\x03 \xd2\t"\x05\n\x03foo"\x06\n\x04flew(\x80 x\x02') { 'water': { 'extent': 4096, 'version': 2, 'features': [{ 'geometry': [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]], 'properties': { 'foo': 'bar', 'uid': 123, 'cat': 'flew' }, 'type': 3, 'id': 1 } ] }, 'air': { 'extent': 4096, 'version': 2, 'features': [{ 'geometry': [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]], 'properties': { 'foo': 'bar', 'uid': 1234, 'balls': 'foo', 'cat': 'flew' }, 'type': 3, 'id': 1 } ] } } ``` Here's how you might decode a tile from a file. ```python >>> import mapbox_vector_tile >>> with open('tile.mvt', 'rb') as f: >>> data = f.read() >>> decoded_data = mapbox_vector_tile.decode(data) >>> with open('out.txt', 'w') as f: >>> f.write(repr(decoded_data)) ``` Changelog --------- Click [here](https://github.com/tilezen/mapbox-vector-tile/blob/master/CHANGELOG.md) to see what changed over time in various versions. Platform: UNKNOWN