diff --git a/mapbox_vector_tile/encoder.py b/mapbox_vector_tile/encoder.py index 955f189..157105d 100644 --- a/mapbox_vector_tile/encoder.py +++ b/mapbox_vector_tile/encoder.py @@ -27,6 +27,9 @@ def on_invalid_geometry_make_valid(shape): class VectorTile: def __init__(self, extents, on_invalid_geometry=None, max_geometry_validate_tries=5, check_winding_order=True): + if extents <= 0: + raise ValueError(f"The extents must be positive. {extents} provided.") + self.tile = vector_tile.tile() self.extents = extents self.on_invalid_geometry = on_invalid_geometry @@ -39,11 +42,17 @@ def __init__(self, extents, on_invalid_geometry=None, max_geometry_validate_trie self.seen_keys_idx = {} self.seen_values_idx = {} self.seen_values_bool_idx = {} - - def add_features(self, features, layer_name="", quantize_bounds=None, y_coord_down=False): + self.seen_layer_names = set() + + def add_features(self, features, layer_name, quantize_bounds=None, y_coord_down=False): + if not layer_name: + raise ValueError(f"A layer name can not be empty. {layer_name!r} was provided.") + if layer_name in self.seen_layer_names: + raise ValueError(f"The layer name {layer_name!r} already exists in the vector tile.") + self.seen_layer_names.add(layer_name) self.layer = self.tile.layers.add() self.layer.name = layer_name - self.layer.version = 1 + self.layer.version = 2 self.layer.extent = self.extents self.key_idx = 0 diff --git a/tests/test_encoder.py b/tests/test_encoder.py index cc4d2e3..6b1158e 100644 --- a/tests/test_encoder.py +++ b/tests/test_encoder.py @@ -966,3 +966,61 @@ def test_issue_57(self): self.assertEqual(1, len(tile.layer.features)) f = tile.layer.features[0] self.assertEqual(expected_commands, list(f.geometry)) + + +class InvalidVectorTileTest(unittest.TestCase): + def test_duplicate_layer_name(self): + 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), dict(name="layername", features=features)] + with self.assertRaises(ValueError) as ex: + encode(source, extents=4096) + self.assertEqual(str(ex.exception), "The layer name 'layername' already exists in the vector tile.") + + def test_empty_layer_name(self): + 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="", features=features)] + with self.assertRaises(ValueError) as ex: + encode(source, extents=4096) + self.assertEqual(str(ex.exception), "A layer name can not be empty. '' was provided.") + + source = [dict(name=None, features=features)] + with self.assertRaises(ValueError) as ex: + encode(source, extents=4096) + self.assertEqual(str(ex.exception), "A layer name can not be empty. None was provided.") + + def test_empty_layer(self): + from mapbox_vector_tile import encode + + # No content + res = encode([], extents=4096) + self.assertEqual(res, b"") + + # Layer without feature + res = encode(dict(name="layer", features=[]), extents=4096) + self.assertEqual(res, b"\x1a\x0c\n\x05layer(\x80 x\x02") + + def test_invalid_extent(self): + 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)] + with self.assertRaises(ValueError) as ex: + encode(source, extents=0) + self.assertEqual(str(ex.exception), "The extents must be positive. 0 provided.") + + with self.assertRaises(ValueError) as ex: + encode(source, extents=-2.3) + self.assertEqual(str(ex.exception), "The extents must be positive. -2.3 provided.")