From 65a16bccfd6ec03743a3e1d1b584d5b98cebbaf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Vinot?= Date: Fri, 3 May 2024 19:20:47 +0200 Subject: [PATCH] Update developments tools --- .flake8 | 8 -- .github/workflows/ci.yml | 14 +- .gitignore | 1 + .pre-commit-config.yaml | 41 +++--- CHANGELOG.md | 225 +++++++++++++++------------------ README.md | 28 ++-- bench/bench_encode.py | 4 +- mapbox_vector_tile/__init__.py | 6 +- mapbox_vector_tile/decoder.py | 31 +++-- mapbox_vector_tile/encoder.py | 21 +-- mapbox_vector_tile/optimise.py | 6 +- mapbox_vector_tile/polygon.py | 10 +- pyproject.toml | 28 ++-- tests/__init__.py | 8 +- tests/test_decoder.py | 23 +++- tests/test_encoder.py | 130 ++++++++++--------- tests/test_polygon.py | 7 +- 17 files changed, 273 insertions(+), 318 deletions(-) delete mode 100644 .flake8 diff --git a/.flake8 b/.flake8 deleted file mode 100644 index a97176c..0000000 --- a/.flake8 +++ /dev/null @@ -1,8 +0,0 @@ -[flake8] -max-line-length = 120 -show_source = true -max_complexity = 15 -exclude = mapbox_vector_tiles/Mapbox -extend-ignore = - # See https://github.com/PyCQA/pycodestyle/issues/373 - E203, diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c24faf2..25256ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,10 +12,10 @@ jobs: strategy: matrix: python-version: - - '3.8' - - '3.9' - - '3.10' - - '3.11' + - "3.8" + - "3.9" + - "3.10" + - "3.11" steps: - uses: actions/checkout@v3 @@ -27,7 +27,7 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - cache: 'poetry' + cache: "poetry" - name: Install dependencies run: | @@ -45,7 +45,7 @@ jobs: github-token: ${{ secrets.github_token }} flag-name: run-${{ matrix.python-version }} parallel: true - path-to-lcov: './coverage.lcov' + path-to-lcov: "./coverage.lcov" finish: needs: tests @@ -56,4 +56,4 @@ jobs: with: github-token: ${{ secrets.github_token }} parallel-finished: true - path-to-lcov: './coverage.lcov' + path-to-lcov: "./coverage.lcov" diff --git a/.gitignore b/.gitignore index 06e3be6..dd74ab2 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ mapbox_vector_tile.egg-info/ .tox/ .coverage bench/fgeoms.wkt.zip +node_modules/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 546074b..6513293 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,38 +1,29 @@ exclude: ^mapbox_vector_tile/Mapbox/vector_tile_pb2.py repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.6.0 hooks: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - id: check-toml - id: detect-private-key - - id: fix-encoding-pragma - args: [ --remove ] - id: check-merge-conflict - - repo: https://github.com/MarcoGorelli/absolufy-imports - rev: v0.3.1 + - repo: https://github.com/python-poetry/poetry + rev: 1.8.0 hooks: - - id: absolufy-imports - - repo: https://github.com/asottile/pyupgrade - rev: v3.3.1 + - id: poetry-check + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.4.2 hooks: - - id: pyupgrade - args: [ --py38-plus ] - - repo: https://github.com/PyCQA/isort - rev: 5.11.4 + - id: ruff + args: [--fix] + types_or: [python, pyi, jupyter] + - id: ruff-format + types_or: [python, pyi, jupyter] + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v4.0.0-alpha.8 hooks: - - id: isort - - repo: https://github.com/psf/black - rev: 22.12.0 - hooks: - - id: black - - repo: https://github.com/asottile/yesqa - rev: v1.4.0 - hooks: - - id: yesqa - - repo: https://github.com/pycqa/flake8 - rev: 6.0.0 - hooks: - - id: flake8 + - id: prettier + args: ["--print-width", "120"] + require_serial: true diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b7f0e7..d186341 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,172 +1,151 @@ -Version 2.0.2 -------------- +## Version 2.0.2 -*In development* +_In development_ -Version 2.0.1 -------------- +- Replace Isort, Flake8 and Black by Ruff in the `.pre-commit-config.yaml` file +- Use `prettier` pre-commit tool to prettify Markdown and Yaml files +- Use `poetry-check` pre-commit tool to avoid incompatibilities between `poetry.lock` and `pyproject.toml` -* Support previous pre 2.0 encode/decode method signatures with deprecation warning. +## Version 2.0.1 + +- Support previous pre 2.0 encode/decode method signatures with deprecation warning. [#129](https://github.com/tilezen/mapbox-vector-tile/pull/129) -Version 2.0.0 -------------- +## Version 2.0.0 -* Drop Python 2 support -* Usage of `tox` for tests -* Add GitHub Actions -* Add pre-commit tool -* Regenerate the vector tile protobuf Python code to solve +- Drop Python 2 support +- Usage of `tox` for tests +- Add GitHub Actions +- Add pre-commit tool +- Regenerate the vector tile protobuf Python code to solve [#113](https://github.com/tilezen/mapbox-vector-tile/issues/113) -* Support for Python 3.11 -* Delete the `round_fn` argument as Python 2 has been dropped -* Use `pyproject.toml` and Poetry to replace the `setup.py` file -* Use `geom_type` property instead of deprecated `type` -* Add the possibility to give a coordinates transformer -* Add a `geojson` option. See [#107](https://github.com/tilezen/mapbox-vector-tile/issues/107) -* Refactor the options using the `per_layer_options` and `default_options` dictionaries. -* Add the option `max_geometry_validate_tries`. - +- Support for Python 3.11 +- Delete the `round_fn` argument as Python 2 has been dropped +- Use `pyproject.toml` and Poetry to replace the `setup.py` file +- Use `geom_type` property instead of deprecated `type` +- Add the possibility to give a coordinates transformer +- Add a `geojson` option. See [#107](https://github.com/tilezen/mapbox-vector-tile/issues/107) +- Refactor the options using the `per_layer_options` and `default_options` dictionaries. +- Add the option `max_geometry_validate_tries`. -Version 1.2.1 -------------- +## Version 1.2.1 -* Add the trove classifiers to the setup.py +- Add the trove classifiers to the setup.py -Version 1.2.0 -------------- +## Version 1.2.0 -* Performance focused release, including: -* Enable Shapely speedups, when available -* Skip inners which cause exceptions -* Union inners in blocks when making valid -* Make benchmark script python3 compatible -* Fix test to support different versions of GEOS +- Performance focused release, including: +- Enable Shapely speedups, when available +- Skip inners which cause exceptions +- Union inners in blocks when making valid +- Make benchmark script python3 compatible +- Fix test to support different versions of GEOS -Version 1.1.0 -------------- +## Version 1.1.0 -* Include LICENSE & CHANGELOG.md in sdist tarballs -* Refactor geometry encoding logic, including skipping tiny geometries -* Decoded geometry is now geojson-ish dict -* Winding order is now optional -* Add benchmarking around round function and document how to improve performance -* Document performance tip for protobuf encoding with C bindings for Debian +- Include LICENSE & CHANGELOG.md in sdist tarballs +- Refactor geometry encoding logic, including skipping tiny geometries +- Decoded geometry is now geojson-ish dict +- Winding order is now optional +- Add benchmarking around round function and document how to improve performance +- Document performance tip for protobuf encoding with C bindings for Debian -Version 1.0.0 -------------- +## Version 1.0.0 -* Generate more valid polygons and multipolygons using [pyclipper](https://pypi.python.org/pypi/pyclipper) library for v2 MVT compliance (but we're still not fully v2 compliant for [other](https://github.com/tilezen/mapbox-vector-tile/issues/42) reasons). -* Handle edge cases where polygon buffer makes a multi-polygon, ensuring inner rings are dropped when subtracting them from the polygon would make it invalid, and not adding multipolygons as array elements for multipolygon constructor. -* Calculate area more properly by using PolyTree result from Clipper. -* Factor out polygon validity code into its own file. +- Generate more valid polygons and multipolygons using [pyclipper](https://pypi.python.org/pypi/pyclipper) library for v2 MVT compliance (but we're still not fully v2 compliant for [other](https://github.com/tilezen/mapbox-vector-tile/issues/42) reasons). +- Handle edge cases where polygon buffer makes a multi-polygon, ensuring inner rings are dropped when subtracting them from the polygon would make it invalid, and not adding multipolygons as array elements for multipolygon constructor. +- Calculate area more properly by using PolyTree result from Clipper. +- Factor out polygon validity code into its own file. -Version 0.5.0 -------------- +## Version 0.5.0 -* Improved results from `on_invalid_geometry_make_valid` when the geometry is self-crossing. It was possible for large parts of the geometry to be discarded, and it is now less likely. See [PR 66](https://github.com/tilezen/mapbox-vector-tile/pull/66) for more information. +- Improved results from `on_invalid_geometry_make_valid` when the geometry is self-crossing. It was possible for large parts of the geometry to be discarded, and it is now less likely. See [PR 66](https://github.com/tilezen/mapbox-vector-tile/pull/66) for more information. -Version 0.4.0 -------------- +## Version 0.4.0 -* Custom rounding functions: a `round_fn` parameter was added to the `encode` function, which allows control over how floating point coordinates are transformed to integer ones. See [PR 55](https://github.com/tilezen/mapbox-vector-tile/pull/55). -* Custom validity functions: an `on_invalid_geometry` parameter was added to the `encode` function, which is called when invalid geometry is found, or created through coordinate rounding. See [PR 46](https://github.com/tilezen/mapbox-vector-tile/pull/46). -* Winding order bug fix: See [issue 57](https://github.com/tilezen/mapbox-vector-tile/issues/57) and [PR 59](https://github.com/tilezen/mapbox-vector-tile/pull/59). -* Performance improvements: including a 2x speedup from using `tuple`s instead of `dict`s for coordinates, see [PR 56](https://github.com/tilezen/mapbox-vector-tile/pull/56). -* Improvements to PY3 compatibility: See [PR 52](https://github.com/tilezen/mapbox-vector-tile/pull/52). +- Custom rounding functions: a `round_fn` parameter was added to the `encode` function, which allows control over how floating point coordinates are transformed to integer ones. See [PR 55](https://github.com/tilezen/mapbox-vector-tile/pull/55). +- Custom validity functions: an `on_invalid_geometry` parameter was added to the `encode` function, which is called when invalid geometry is found, or created through coordinate rounding. See [PR 46](https://github.com/tilezen/mapbox-vector-tile/pull/46). +- Winding order bug fix: See [issue 57](https://github.com/tilezen/mapbox-vector-tile/issues/57) and [PR 59](https://github.com/tilezen/mapbox-vector-tile/pull/59). +- Performance improvements: including a 2x speedup from using `tuple`s instead of `dict`s for coordinates, see [PR 56](https://github.com/tilezen/mapbox-vector-tile/pull/56). +- Improvements to PY3 compatibility: See [PR 52](https://github.com/tilezen/mapbox-vector-tile/pull/52). -Version 0.3.0 -------------- +## Version 0.3.0 -* python3 compatability improvements -* travis integration -* documentation updates -* insert CMD_SEG_END for MultiPolygons -* decode multipolygons correctly -* encode tiles using version 1 +- python3 compatability improvements +- travis integration +- documentation updates +- insert CMD_SEG_END for MultiPolygons +- decode multipolygons correctly +- encode tiles using version 1 -Version 0.2.1 -------------- +## Version 0.2.1 -* include README.md in distribution to fix install +- include README.md in distribution to fix install -Version 0.2.0 -------------- +## Version 0.2.0 -* python3 updates -* enforce winding order on multipolygons -* update key/val handling -* round floating point values instead of truncating -* add option to quantize bounds -* add option to flip y coord system -* add ability to pass custom extents +- python3 updates +- enforce winding order on multipolygons +- update key/val handling +- round floating point values instead of truncating +- add option to quantize bounds +- add option to flip y coord system +- add ability to pass custom extents -Version 0.1.0 -------------- +## Version 0.1.0 -* Add compatibility with python 3 -* Handle multipolygons as single features -* Use winding order from mapbox vector tile 2.0 spec -* Support custom extents when decoding +- Add compatibility with python 3 +- Handle multipolygons as single features +- Use winding order from mapbox vector tile 2.0 spec +- Support custom extents when decoding -Version 0.0.11 --------------- +## Version 0.0.11 -* Decode string keys to utf-8 +- Decode string keys to utf-8 -Version 0.0.10 --------------- +## Version 0.0.10 -* Allow encoder to accept shapely objects directly +- Allow encoder to accept shapely objects directly -Version 0.0.9 -------------- +## Version 0.0.9 -* Handle tiles from java-vector-tile (zero pad binary integers) -* Update README +- Handle tiles from java-vector-tile (zero pad binary integers) +- Update README -Version 0.0.8 -------------- +## Version 0.0.8 -* Handle unicode properties +- Handle unicode properties -Version 0.0.7 -------------- +## Version 0.0.7 -* Update id handling behavior +- Update id handling behavior -Version 0.0.6 -------------- +## Version 0.0.6 -* Explode multipolygons into several features -* https://github.com/tilezen/mapbox-vector-tile/issues/4 -* Resolve issue when id is passed in -* More tests +- Explode multipolygons into several features +- https://github.com/tilezen/mapbox-vector-tile/issues/4 +- Resolve issue when id is passed in +- More tests -Version 0.0.5 -------------- +## Version 0.0.5 -* Removing the option of encoding floats in big endian -* Updated tests +- Removing the option of encoding floats in big endian +- Updated tests -Version 0.0.4 -------------- +## Version 0.0.4 -* Bug fix - does not try to load wkt geom if wkb succeeds +- Bug fix - does not try to load wkt geom if wkb succeeds -Version 0.0.3 -------------- +## Version 0.0.3 -* Option to encode floats in little endian +- Option to encode floats in little endian -Version 0.0.2 -------------- +## Version 0.0.2 -* WKT Support -* Better Documentation -* More tests +- WKT Support +- Better Documentation +- More tests -Version 0.0.1 -------------- +## Version 0.0.1 -* Initial release +- Initial release diff --git a/README.md b/README.md index a4d3505..fe8efb2 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,10 @@ -Mapbox Vector Tile -================== +# Mapbox Vector Tile [![CI](https://github.com/tilezen/mapbox-vector-tile/actions/workflows/ci.yml/badge.svg)](https://github.com/tilezen/mapbox-vector-tile/actions/workflows/ci.yml) [![pre-commit](https://github.com/tilezen/mapbox-vector-tile/actions/workflows/pre-commit.yml/badge.svg)](https://github.com/tilezen/mapbox-vector-tile/actions/workflows/pre-commit.yml) [![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 ------------- +## Installation mapbox-vector-tile is compatible with Python 3.8 or newer. It is listed on PyPi as `mapbox-vector-tile`. The recommended way to install is via `pip`: @@ -22,24 +20,22 @@ when changing the Coordinate Reference System when encoding or decoding tiles. pip install mapbox-vector-tile[proj] ``` -Encoding --------- +## Encoding Encode method expects an array of layers or at least 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: +- `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. + - `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. The encoding operation accepts options which can be defined per layer using the `per_layer_options` argument. If there is missing layer or missing options values in the `per_layer_options`, the options of `default_options` are taken into account. Finally, global default values are used. See the docstring of the `encode` method for more details about the available options and their global default values. - ```python >>> import mapbox_vector_tile @@ -275,8 +271,7 @@ mapbox_vector_tile.encode([ ], default_options={"quantize_bounds": (0.0, 0.0, 10.0, 10.0), "extents":50}) ``` -Decoding --------- +## Decoding Decode method takes in a valid google.protobuf.message Tile and returns decoded string in the following format: @@ -368,9 +363,7 @@ Here's how you might decode a tile from a file. The `decode` function has a `geojson` option which enforces a GeoJson RFC7946 compatible result. Its default value is `True`. To enforce the behaviour of versions <2.0.0, please use `geojson=False`. - -Use native protobuf library for performance ------------------------------------------- +## Use native protobuf library for performance The c++ implementation of the underlying protobuf library is more performant than the pure python one. Depending on your operating system, you might need to [compile the C++ library](https://github.com/google/protobuf/tree/master/python#c-implementation) or install it. @@ -392,7 +385,6 @@ and then: $ protoc -I=mapbox_vector_tile/Mapbox/ --python_out=mapbox_vector_tile/Mapbox/ mapbox_vector_tile/Mapbox/vector_tile.proto -Changelog ---------- +## Changelog Click [here](https://github.com/tilezen/mapbox-vector-tile/blob/master/CHANGELOG.md) to see what changed over time in various versions. diff --git a/bench/bench_encode.py b/bench/bench_encode.py index 4761003..7e71266 100755 --- a/bench/bench_encode.py +++ b/bench/bench_encode.py @@ -36,16 +36,14 @@ def make_layers(shapes, geom_dicts=False): def run_test(layers): print("Running perf test") - i = 0 profiler = cProfile.Profile() - for layer in layers: + for i, layer in enumerate(layers): layer_description = {"features": layer, "name": "bar"} profiler.enable() encode(layer_description, default_options={"on_invalid_geometry": on_invalid_geometry_ignore}) profiler.disable() if i % 100 == 0: print(f"{i} tiles produced") - i += 1 print("Perf result :") profiler.print_stats() diff --git a/mapbox_vector_tile/__init__.py b/mapbox_vector_tile/__init__.py index a7da990..3008086 100644 --- a/mapbox_vector_tile/__init__.py +++ b/mapbox_vector_tile/__init__.py @@ -33,7 +33,7 @@ def decode(tile, per_layer_options=None, default_options=None, **kwargs): to `False`, the retrieved dictionary is a valid geojson file. Default to `True`. """ if kwargs: - warnings.warn("`decode` signature has changed, use `default_options` instead", DeprecationWarning) + warnings.warn("`decode` signature has changed, use `default_options` instead", DeprecationWarning, stacklevel=2) default_options = {**kwargs, **(default_options or {})} vector_tile = decoder.TileData(pbf_data=tile, per_layer_options=per_layer_options, default_options=default_options) message = vector_tile.get_message() @@ -80,11 +80,11 @@ def encode(layers, per_layer_options=None, default_options=None, **kwargs): to 5. """ if kwargs: - warnings.warn("`encode` signature has changed, use `default_options` instead", DeprecationWarning) + warnings.warn("`encode` signature has changed, use `default_options` instead", DeprecationWarning, stacklevel=2) default_options = {**kwargs, **(default_options or {})} vector_tile = encoder.VectorTile(default_options=default_options) if per_layer_options is None: - per_layer_options = dict() + per_layer_options = {} if isinstance(layers, list): for layer in layers: layer_name = layer["name"] diff --git a/mapbox_vector_tile/decoder.py b/mapbox_vector_tile/decoder.py index 3c66198..2f45508 100644 --- a/mapbox_vector_tile/decoder.py +++ b/mapbox_vector_tile/decoder.py @@ -4,10 +4,10 @@ CMD_LINE_TO, CMD_MOVE_TO, CMD_SEG_END, - get_decode_options, LINESTRING, POINT, POLYGON, + get_decode_options, zig_zag_decode, ) @@ -17,7 +17,7 @@ def __init__(self, pbf_data, per_layer_options=None, default_options=None): self.tile = vector_tile.tile() self.tile.ParseFromString(pbf_data) self.default_options = default_options - self.per_layer_options = per_layer_options if per_layer_options is not None else dict() + self.per_layer_options = per_layer_options if per_layer_options is not None else {} def get_message(self): tile = {} @@ -81,7 +81,7 @@ def parse_value(val): @staticmethod 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)) + a = sum(ring[i][0] * ring[i + 1][1] - ring[i + 1][0] * ring[i][1] for i in range(len(ring) - 1)) return -1 if a < 0 else 1 if a > 0 else 0 @staticmethod @@ -112,19 +112,18 @@ def parse_geometry(self, geom, ftype, extent, y_coord_down, transformer): # noq coords = [] elif cmd in (CMD_MOVE_TO, 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: - self._ensure_polygon_closed(coords) - parts.append(coords) - coords = [] - - for point in range(0, cmd_len): + if coords and cmd == CMD_MOVE_TO and 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: + self._ensure_polygon_closed(coords) + parts.append(coords) + coords = [] + + for _ in range(cmd_len): x = geom[i] i = i + 1 diff --git a/mapbox_vector_tile/encoder.py b/mapbox_vector_tile/encoder.py index 07458a9..4d1f6aa 100644 --- a/mapbox_vector_tile/encoder.py +++ b/mapbox_vector_tile/encoder.py @@ -3,13 +3,13 @@ from shapely.geometry import shape as shapely_shape from shapely.geometry.base import BaseGeometry from shapely.geometry.multipolygon import MultiPolygon -from shapely.geometry.polygon import orient, Polygon +from shapely.geometry.polygon import Polygon, orient from shapely.ops import transform from shapely.wkb import loads as load_wkb from shapely.wkt import loads as load_wkt -from mapbox_vector_tile.Mapbox import vector_tile_pb2 as vector_tile from mapbox_vector_tile.geom_encoder import GeometryEncoder +from mapbox_vector_tile.Mapbox import vector_tile_pb2 as vector_tile from mapbox_vector_tile.polygon import make_it_valid from mapbox_vector_tile.utils import get_encode_options @@ -143,11 +143,7 @@ def enforce_multipolygon_winding_order(self, shape, n_try): if not parts: return None - if len(parts) == 1: - oriented_shape = parts[0] - else: - oriented_shape = MultiPolygon(parts) - + oriented_shape = parts[0] if len(parts) == 1 else MultiPolygon(parts) oriented_shape = self.handle_shape_validity(oriented_shape, n_try) return oriented_shape @@ -203,9 +199,8 @@ def add_feature(self, feature, shape): f = self.layer.features.add() fid = feature.get("id") - if fid is not None: - if isinstance(fid, Number) and fid >= 0: - f.id = fid + if fid is not None and isinstance(fid, Number) and fid >= 0: + f.id = fid # properties properties = feature.get("properties") @@ -253,11 +248,7 @@ def _handle_attr(self, layer, feature, props): feature.tags.append(self.seen_keys_idx[k]) - if isinstance(v, bool): - values_idx = self.seen_values_bool_idx - else: - values_idx = self.seen_values_idx - + values_idx = self.seen_values_bool_idx if isinstance(v, bool) else self.seen_values_idx if v not in values_idx: values_idx[v] = self.val_idx self.val_idx += 1 diff --git a/mapbox_vector_tile/optimise.py b/mapbox_vector_tile/optimise.py index 57d23a5..a0bea0b 100644 --- a/mapbox_vector_tile/optimise.py +++ b/mapbox_vector_tile/optimise.py @@ -28,7 +28,7 @@ def _update_table(counts, table): # Sort string table by usage, so most commonly-used values are # assigned the smallest indices. Since indices are encoded as # varints, this should make best use of the space. - sort = list(sorted(((c, k) for k, c in counts.items()), reverse=True)) + sort = sorted(((c, k) for k, c in counts.items()), reverse=True) # construct the re-ordered string table new_table = [] @@ -42,7 +42,7 @@ def _update_table(counts, table): # construct a lookup table from the old to the new indices. new_indexes = {} - for i, (c, k) in enumerate(sort): + for i, (_, k) in enumerate(sort): new_indexes[k] = i return new_indexes @@ -112,7 +112,7 @@ def _decode_lines(geom): current_line.extend(geom[i:next_i]) # but we still need to decode it to figure out where each move-to command is in absolute space. - for j in range(0, run_length): + for j in range(run_length): dx = zig_zag_decode(geom[i + 1 + 2 * j]) dy = zig_zag_decode(geom[i + 2 + 2 * j]) x += dx diff --git a/mapbox_vector_tile/polygon.py b/mapbox_vector_tile/polygon.py index 2ff2ee2..610eb9a 100644 --- a/mapbox_vector_tile/polygon.py +++ b/mapbox_vector_tile/polygon.py @@ -93,10 +93,7 @@ def _polytree_node_to_shapely(node): # Check expectations: a node should be a hole, _or_ return children. This is because children of holes must # be outers, and should be on the polygons list. assert len(children) == 0 - if node.Contour: - children = [node.Contour] - else: - children = [] + children = [node.Contour] if node.Contour else [] elif node.Contour: poly = _contour_to_poly(node.Contour) @@ -106,10 +103,7 @@ def _polytree_node_to_shapely(node): # 1,000s of seconds. Instead, we can group inners together, which reduces the number of times we call the # expensive 'difference' method. block_size = 200 - if len(children) > block_size: - inners = _union_in_blocks(children, block_size) - else: - inners = _generate_polys(children) + inners = _union_in_blocks(children, block_size) if len(children) > block_size else _generate_polys(children) for inner in inners: # The difference of two valid polygons may fail, and in this situation we'd like to be able to display diff --git a/pyproject.toml b/pyproject.toml index eebf293..9b1b60b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,23 +45,19 @@ proj = ["pyproj"] tox = "^4.0.16" coverage = { version = "^7.0.0", extras = ["toml"] } -# Black -[tool.black] -line-length = 120 -fast = true -extend-exclude = ''' -( - .*_pb2.py -) -''' -target-version = ["py38", "py39", "py310", "py311"] -# Isort -[tool.isort] -profile = "black" -line_length = 120 -force_alphabetical_sort_within_sections = true -case_sensitive = true +[tool.ruff] +line-length = 120 +target-version = "py38" +show-fixes = true +extend-include = ["*.ipynb"] +extend-exclude = ["mapbox_vector_tile/Mapbox/vector_tile_pb2.py"] +lint.select = ["E", "F", "C90", "W", "B", "UP", "I", "RUF100", "TID", "SIM", "PIE", "N", "C4", "G", "PTH"] +lint.unfixable = ["B"] +lint.extend-ignore = ["B024", "G004", "UP038"] +lint.flake8-tidy-imports.ban-relative-imports = "all" +lint.mccabe.max-complexity = 15 +lint.pep8-naming.extend-ignore-names = ["assertRoundTrip"] # Coverage [tool.coverage.run] diff --git a/tests/__init__.py b/tests/__init__.py index 1c6c979..d5880f6 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,9 +1,7 @@ import doctest -import glob -import os +from pathlib import Path 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)) +paths = Path(__file__).parent.glob("*.txt") +test_suite = doctest.DocFileSuite(*paths, module_relative=False, optionflags=optionflags) diff --git a/tests/test_decoder.py b/tests/test_decoder.py index 7e83b6b..8edc5c2 100644 --- a/tests/test_decoder.py +++ b/tests/test_decoder.py @@ -10,7 +10,11 @@ class BaseTestCase(unittest.TestCase): def test_decoder(self): - 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 + 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' + b'\x00\x01\x02\x00\x00\x02\x0f\x1a\x03foo\x1a\x03baz\x1a\x03uid"\x05\n\x03bar"\x05\n\x03foo"\x02 {(\x80 ' + b"x\x02" + ) self.assertEqual( mapbox_vector_tile.decode(vector_tile), { @@ -31,7 +35,11 @@ def test_decoder(self): ) def test_decoder_geojson(self): - 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 + 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' + b'\x00\x01\x02\x00\x00\x02\x0f\x1a\x03foo\x1a\x03baz\x1a\x03uid"\x05\n\x03bar"\x05\n\x03foo"\x02' + b" {(\x80 x\x02" + ) self.assertEqual( mapbox_vector_tile.decode(vector_tile, default_options={"geojson": False}), { @@ -55,7 +63,10 @@ def test_decode_polygon_no_cmd_seg_end(self): # 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 - 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 + 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"' + b"\x00\x03\x04\x00\x00\x04\x03\x00(\x80 x\x02" + ) self.assertEqual( mapbox_vector_tile.decode(vector_tile), { @@ -82,7 +93,11 @@ def test_decode_polygon_no_cmd_seg_end(self): ) def test_nondefault_extent(self): - 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 + 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' + b'\x12\xbf>\xd86\xbf>\xd86\x1a\x03foo\x1a\x03baz\x1a\x03uid"\x05\n\x03bar"\x05\n\x03foo"\x02 ' + b"{(\x80@x\x02" + ) self.assertEqual( mapbox_vector_tile.decode(vector_tile), { diff --git a/tests/test_encoder.py b/tests/test_encoder.py index b93111a..5d5f6c0 100644 --- a/tests/test_encoder.py +++ b/tests/test_encoder.py @@ -1,6 +1,7 @@ """ Tests for vector_tile/encoder.py """ + import unittest from shapely import wkt @@ -200,7 +201,11 @@ def test_with_wkt(self): 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 + 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" + b"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\360?" + b"\000\000\000\000\000\000\360?\000\000\000\000\000\000\360?\000\000\000\000\000\000\360?" + b"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" + b"\000\000\000", expected_geometry={"type": "Polygon", "coordinates": [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]]}, ) @@ -289,7 +294,10 @@ def test_encode_multilinestring(self): ) 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 + 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)))" + ) self.assertRoundTrip( input_geometry=geometry, expected_geometry={ @@ -306,7 +314,10 @@ def test_encode_multipolygon_normal_winding_order(self): ) 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 + 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)))" + ) # NB there is only one resultant polygon here self.assertRoundTrip( input_geometry=geometry, @@ -345,7 +356,7 @@ def test_encode_property_bool(self): def test_encode_property_int(self): geometry = "POINT(0 0)" properties = { - "test_int": int(1), + "test_int": 1, } self.assertRoundTrip( input_geometry=geometry, expected_geometry={"type": "Point", "coordinates": [0, 0]}, properties=properties @@ -373,11 +384,11 @@ def test_encode_property_list(self): def test_encode_multiple_values_test(self): geometry = "POINT(0 0)" - properties1 = dict(foo="bar", baz="bar") - properties2 = dict(quux="morx", baz="bar") + properties1 = {"foo": "bar", "baz": "bar"} + properties2 = {"quux": "morx", "baz": "bar"} name = "foo" - feature1 = dict(geometry=geometry, properties=properties1) - feature2 = dict(geometry=geometry, properties=properties2) + feature1 = {"geometry": geometry, "properties": properties1} + feature2 = {"geometry": geometry, "properties": properties2} source = [{"name": name, "features": [feature1, feature2]}] encoded = encode(source) decoded = decode(encoded) @@ -403,7 +414,7 @@ def test_too_small_linestring(self): from mapbox_vector_tile.encoder import on_invalid_geometry_make_valid shape = shapely.wkt.loads("LINESTRING(-71.160281 42.258729,-71.160837 42.259113,-71.161144 42.25932)") - features = [dict(geometry=shape, properties={})] + features = [{"geometry": shape, "properties": {}}] pbf = encode( {"name": "foo", "features": features}, default_options={"on_invalid_geometry": on_invalid_geometry_make_valid}, @@ -412,7 +423,7 @@ def test_too_small_linestring(self): features = result["foo"]["features"] self.assertEqual(0, len(features)) - def test_encode_1_True_values(self): + def test_encode_1_true_values(self): geometry = "POINT(0 0)" properties = { "foo": True, @@ -497,11 +508,11 @@ class QuantizeTest(unittest.TestCase): def test_quantize(self): from mapbox_vector_tile import decode, encode - props = dict(foo="bar") + props = {"foo": "bar"} shape = "POINT(15 15)" - feature = dict(geometry=shape, properties=props) + feature = {"geometry": shape, "properties": props} features = [feature] - source = dict(name="layername", features=features) + source = {"name": "layername", "features": features} bounds = 10.0, 10.0, 20.0, 20.0 pbf = encode(source, default_options={"quantize_bounds": bounds}) result = decode(pbf) @@ -513,11 +524,11 @@ def test_quantize(self): def test_y_coord_down(self): from mapbox_vector_tile import decode, encode - props = dict(foo="bar") + props = {"foo": "bar"} shape = "POINT(10 10)" - feature = dict(geometry=shape, properties=props) + feature = {"geometry": shape, "properties": props} features = [feature] - source = dict(name="layername", features=features) + source = {"name": "layername", "features": features} pbf = encode(source, default_options={"y_coord_down": True}) result = decode(pbf, default_options={"y_coord_down": True}) act_feature = result["layername"]["features"][0] @@ -528,11 +539,11 @@ def test_y_coord_down(self): def test_quantize_and_y_coord_down(self): from mapbox_vector_tile import decode, encode - props = dict(foo="bar") + props = {"foo": "bar"} shape = "POINT(30 30)" - feature = dict(geometry=shape, properties=props) + feature = {"geometry": shape, "properties": props} features = [feature] - source = dict(name="layername", features=features) + source = {"name": "layername", "features": features} bounds = 0.0, 0.0, 50.0, 50.0 pbf = encode(source, default_options={"quantize_bounds": bounds, "y_coord_down": True}) @@ -553,11 +564,11 @@ class ExtentTest(unittest.TestCase): def test_custom_extent(self): from mapbox_vector_tile import decode, encode - props = dict(foo="bar") + props = {"foo": "bar"} shape = "POINT(10 10)" - feature = dict(geometry=shape, properties=props) + feature = {"geometry": shape, "properties": props} features = [feature] - source = dict(name="layername", features=features) + source = {"name": "layername", "features": features} bounds = 0.0, 0.0, 10.0, 10.0 pbf = encode(source, default_options={"quantize_bounds": bounds, "extents": 50}) result = decode(pbf) @@ -577,8 +588,8 @@ def test_invalid_geometry_ignore(self): 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]) + feature = {"geometry": shape, "properties": {}} + source = {"name": "layername", "features": [feature]} pbf = encode(source, default_options={"on_invalid_geometry": on_invalid_geometry_ignore}) result = decode(pbf) self.assertEqual(0, len(result["layername"]["features"])) @@ -592,9 +603,9 @@ def test_invalid_geometry_raise(self): 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): + feature = {"geometry": shape, "properties": {}} + source = {"name": "layername", "features": [feature]} + with self.assertRaises(ValueError): encode(source, default_options={"on_invalid_geometry": on_invalid_geometry_raise}) def test_invalid_geometry_make_valid(self): @@ -606,8 +617,8 @@ def test_invalid_geometry_make_valid(self): 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]) + feature = {"geometry": shape, "properties": {}} + source = {"name": "layername", "features": [feature]} pbf = encode(source, default_options={"on_invalid_geometry": on_invalid_geometry_make_valid}) result = decode(pbf) self.assertEqual(1, len(result["layername"]["features"])) @@ -630,8 +641,8 @@ def test_bowtie_self_touching(self): 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]) + feature = {"geometry": shape, "properties": {}} + source = {"name": "layername", "features": [feature]} pbf = encode(source, default_options={"on_invalid_geometry": on_invalid_geometry_make_valid}) result = decode(pbf) self.assertEqual(1, len(result["layername"]["features"])) @@ -654,8 +665,8 @@ def test_bowtie_self_crossing(self): 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]) + feature = {"geometry": shape, "properties": {}} + source = {"name": "layername", "features": [feature]} pbf = encode(source, default_options={"on_invalid_geometry": on_invalid_geometry_make_valid}) result = decode(pbf) self.assertEqual(1, len(result["layername"]["features"])) @@ -682,8 +693,8 @@ def test_make_valid_self_crossing(self): 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]) + feature = {"geometry": shape, "properties": {}} + source = {"name": "layername", "features": [feature]} pbf = encode(source, default_options={"on_invalid_geometry": on_invalid_geometry_make_valid}) result = decode(pbf) self.assertEqual(1, len(result["layername"]["features"])) @@ -710,8 +721,8 @@ def test_validate_generates_rounding_error(self): 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]) + feature = {"geometry": shape, "properties": {}} + source = {"name": "layername", "features": [feature]} pbf = encode(source, default_options={"on_invalid_geometry": on_invalid_geometry_make_valid}) result = decode(pbf) features = result["layername"]["features"] @@ -747,7 +758,7 @@ def test_quantize_makes_mutlipolygon_invalid(self): """657115.9120547654 5674684.979891453)))""" ) quantize_bounds = (645740.0149532147, 5674684.979891453, 665307.8941942193, 5694252.8591324575) - features = [dict(geometry=shape, properties={})] + features = [{"geometry": shape, "properties": {}}] pbf = encode( {"name": "foo", "features": features}, default_options={ @@ -774,7 +785,7 @@ def test_flipped_geometry_produces_multipolygon(self): """3644 2326, 3605 2251, 3566 2230, 3547 2122, 3482 2014, 3479 1966, 3455 1944, 3458 1910, 3449 1902, """ """3449 1939))""" ) - features = [dict(geometry=shape, properties={})] + features = [{"geometry": shape, "properties": {}}] pbf = encode( {"name": "foo", "features": features}, default_options={"on_invalid_geometry": on_invalid_geometry_make_valid}, @@ -790,20 +801,20 @@ def test_flipped_geometry_produces_multipolygon(self): self.assertTrue(poly.is_valid) def test_make_valid_can_return_multipolygon(self): - import os.path + from pathlib import Path import shapely.wkt from mapbox_vector_tile import encode from mapbox_vector_tile.encoder import on_invalid_geometry_make_valid - test_dir = os.path.dirname(os.path.realpath(__file__)) + test_dir = Path(__file__).resolve().parent file_name = "error_nested_multipolygon.wkt" - with open(os.path.join(test_dir, file_name)) as fh: + with (test_dir / file_name).open() as fh: shape = wkt.loads(fh.read()) - features = [dict(geometry=shape, properties={})] + features = [{"geometry": shape, "properties": {}}] pbf = encode( {"name": "foo", "features": features}, default_options={ @@ -834,7 +845,7 @@ def test_too_small_geometry(self): shape = shapely.wkt.loads( "LINESTRING (3065.656210384849 3629.831662879646, 3066.458953567231 3629.725941289478)" ) - features = [dict(geometry=shape, properties={})] + features = [{"geometry": shape, "properties": {}}] pbf = encode( {"name": "foo", "features": features}, default_options={"on_invalid_geometry": on_invalid_geometry_make_valid}, @@ -895,10 +906,7 @@ def test_example_multi_polygon(self): ] tile = VectorTile(default_options={"extents": 4096, "quantize_bounds": None, "y_coord_down": True}) - tile.add_layer( - name="example_layer", - features=[dict(geometry=input_geometry)], - ) + tile.add_layer(name="example_layer", features=[{"geometry": input_geometry}]) self.assertEqual(1, len(tile.layer.features)) f = tile.layer.features[0] self.assertEqual(expected_commands, list(f.geometry)) @@ -953,7 +961,7 @@ def test_example_multi_polygon_y_up(self): ] tile = VectorTile(default_options={"extents": 20, "quantize_bounds": None, "y_coord_down": False}) - tile.add_layer(name="example_layer", features=[dict(geometry=input_geometry)]) + tile.add_layer(name="example_layer", features=[{"geometry": input_geometry}]) self.assertEqual(1, len(tile.layer.features)) f = tile.layer.features[0] self.assertEqual(expected_commands, list(f.geometry)) @@ -977,7 +985,7 @@ def test_issue_57(self): ] tile = VectorTile(default_options={"extents": 4096, "quantize_bounds": None, "y_coord_down": True}) - tile.add_layer(name="example_layer", features=[dict(geometry=input_geometry)]) + tile.add_layer(name="example_layer", features=[{"geometry": input_geometry}]) self.assertEqual(1, len(tile.layer.features)) f = tile.layer.features[0] self.assertEqual(expected_commands, list(f.geometry)) @@ -987,11 +995,11 @@ class InvalidVectorTileTest(unittest.TestCase): def test_duplicate_layer_name(self): from mapbox_vector_tile import encode - props = dict(foo="bar") + props = {"foo": "bar"} shape = "POINT(10 10)" - feature = dict(geometry=shape, properties=props) + feature = {"geometry": shape, "properties": props} features = [feature] - source = [dict(name="layername", features=features), dict(name="layername", features=features)] + source = [{"name": "layername", "features": features}, {"name": "layername", "features": features}] with self.assertRaises(ValueError) as ex: encode(source, default_options={"extents": 4096}) self.assertEqual(str(ex.exception), "The layer name 'layername' already exists in the vector tile.") @@ -999,16 +1007,16 @@ def test_duplicate_layer_name(self): def test_empty_layer_name(self): from mapbox_vector_tile import encode - props = dict(foo="bar") + props = {"foo": "bar"} shape = "POINT(10 10)" - feature = dict(geometry=shape, properties=props) + feature = {"geometry": shape, "properties": props} features = [feature] - source = [dict(name="", features=features)] + source = [{"name": "", "features": features}] with self.assertRaises(ValueError) as ex: encode(source, default_options={"extents": 4096}) self.assertEqual(str(ex.exception), "A layer name can not be empty. '' was provided.") - source = [dict(name=None, features=features)] + source = [{"name": None, "features": features}] with self.assertRaises(ValueError) as ex: encode(source, default_options={"extents": 4096}) self.assertEqual(str(ex.exception), "A layer name can not be empty. None was provided.") @@ -1021,17 +1029,17 @@ def test_empty_layer(self): self.assertEqual(res, b"") # Layer without feature - res = encode(dict(name="layer", features=[]), default_options={"extents": 4096}) + res = encode({"name": "layer", "features": []}, default_options={"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") + props = {"foo": "bar"} shape = "POINT(10 10)" - feature = dict(geometry=shape, properties=props) + feature = {"geometry": shape, "properties": props} features = [feature] - source = [dict(name="layername", features=features)] + source = [{"name": "layername", "features": features}] with self.assertRaises(ValueError) as ex: encode(source, default_options={"extents": 0}) self.assertEqual(str(ex.exception), "The extents must be positive. 0 provided.") diff --git a/tests/test_polygon.py b/tests/test_polygon.py index 5d24eda..1048b51 100644 --- a/tests/test_polygon.py +++ b/tests/test_polygon.py @@ -1,8 +1,9 @@ """ Tests for vector_tile/polygon.py """ -import os + import unittest +from pathlib import Path from shapely import wkt @@ -11,8 +12,8 @@ class TestPolygonMakeValid(unittest.TestCase): def test_dev_errors(self): - test_dir = os.path.dirname(os.path.realpath(__file__)) - with open(os.path.join(test_dir, "errors.wkt")) as fh: + test_dir = Path(__file__).resolve().parent + with (test_dir / "errors.wkt").open() as fh: for line in fh: geom = wkt.loads(line) fixed = make_it_valid(geom)