From 31a82d04a02884393645c1a110640b34768b455f Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 19 Apr 2020 04:56:02 +0200 Subject: [PATCH 01/24] 1.0.2.dev0 --- kivy_garden/mapview/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kivy_garden/mapview/_version.py b/kivy_garden/mapview/_version.py index a6221b3..90c32f7 100644 --- a/kivy_garden/mapview/_version.py +++ b/kivy_garden/mapview/_version.py @@ -1 +1 @@ -__version__ = '1.0.2' +__version__ = '1.0.2.dev0' From 8ad16787f21ddac461f0bd23d110b3af11f71eab Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 19 Apr 2020 05:09:05 +0200 Subject: [PATCH 02/24] Uses GitHub Action for PyPI deployments Travis is running a too outdated setuptools version for deploying. Also we can't seem to upgrade it. The deployment error was: ``` Traceback (most recent call last): File "setup.py", line 3, in from setuptools import setup, find_namespace_packages ImportError: cannot import name find_namespace_packages InvalidDistribution: Cannot find file (or expand pattern): 'dist/*' ``` --- .github/workflows/pypi-release.yml | 25 +++++++++++++++++++++++++ .github/workflows/pythonapp.yml | 2 +- .travis.yml | 11 ----------- README.md | 2 +- 4 files changed, 27 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/pypi-release.yml diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml new file mode 100644 index 0000000..2047400 --- /dev/null +++ b/.github/workflows/pypi-release.yml @@ -0,0 +1,25 @@ +name: PyPI release +on: [push] + +jobs: + pypi: + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@v1 + - name: Set up Python 3.x + uses: actions/setup-python@v1 + with: + python-version: 3.x + - name: Install dependencies + run: | + python -m pip install --user --upgrade setuptools wheel + - name: Build + run: | + python setup.py sdist bdist_wheel + python setup_meta.py sdist bdist_wheel + - name: Publish package + if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') + uses: pypa/gh-action-pypi-publish@v1.1.0 + with: + user: __token__ + password: ${{ secrets.pypi_password }} diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index d4f4ce6..e0d2e88 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -1,4 +1,4 @@ -name: Garden flower +name: Tests on: [push, pull_request] jobs: diff --git a/.travis.yml b/.travis.yml index 69b0f5e..d2b04cd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,14 +11,3 @@ install: script: - docker run -e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix mapview-linux /bin/sh -c 'tox' - -# Deploy to PyPI using token set in `PYPI_PASSWORD` environment variable -# https://pypi.org/manage/account/token/ -# https://travis-ci.com/github/kivy-garden/mapview/settings -deploy: - provider: pypi - distributions: sdist bdist_wheel - user: "__token__" - on: - tags: true - repo: kivy-garden/mapview diff --git a/README.md b/README.md index 6141114..a19e402 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Mapview -[![Github Build Status](https://github.com/kivy-garden/mapview/workflows/Garden%20flower/badge.svg)](https://github.com/kivy-garden/mapview/actions) +[![Github Build Status](https://github.com/kivy-garden/mapview/workflows/Tests/badge.svg)](https://github.com/kivy-garden/mapview/actions?query=workflow%3ATests) [![Build Status](https://travis-ci.com/kivy-garden/mapview.svg?branch=develop)](https://travis-ci.com/kivy-garden/mapview) [![Coverage Status](https://coveralls.io/repos/github/kivy-garden/mapview/badge.svg?branch=develop)](https://coveralls.io/github/kivy-garden/mapview?branch=develop) From 889fa367d25046d00eb1b11a72baf6f6628f652c Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 19 Apr 2020 14:23:33 +0200 Subject: [PATCH 03/24] Simplifies the Dockerfile --- Dockerfile | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/Dockerfile b/Dockerfile index dda88e3..c34bf81 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,35 +5,21 @@ # docker run mapview-linux /bin/sh -c 'tox' # Or for interactive shell: # docker run -it --rm mapview-linux +# For using the UI from Docker you may need to: +# xhost +local: FROM ubuntu:18.04 -# configure locale -RUN apt -y update -qq > /dev/null && apt install --yes --no-install-recommends \ - locales \ - && locale-gen en_US.UTF-8 \ - && apt -y autoremove \ - && apt -y clean \ - && rm -rf /var/lib/apt/lists/* -ENV LANG="en_US.UTF-8" \ - LANGUAGE="en_US.UTF-8" \ - LC_ALL="en_US.UTF-8" - # install system dependencies RUN apt -y update -qq > /dev/null && apt install --yes --no-install-recommends \ build-essential \ - git \ - lsb-release \ libsdl2-dev \ libsdl2-image-dev \ libsdl2-mixer-dev \ libsdl2-ttf-dev \ - libssl-dev \ - make \ pkg-config \ python3-pip \ python3-setuptools \ tox \ - virtualenv \ && python3 -m pip install --upgrade --no-cache setuptools \ && apt -y autoremove \ && apt -y clean \ From 13598c67f168c5c5ee4e7eef92760456a2ed1142 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 19 Apr 2020 14:46:02 +0200 Subject: [PATCH 04/24] Pytest is more concise and already a dependency --- tests/test_mapview.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/tests/test_mapview.py b/tests/test_mapview.py index 4023003..4020a12 100644 --- a/tests/test_mapview.py +++ b/tests/test_mapview.py @@ -1,18 +1,9 @@ -import unittest from kivy_garden.mapview import MapView -class TextInputTest(unittest.TestCase): - +class TestMapView: def test_init_simple_map(self): - """ - Makes sure we can initialize a simple MapView object. - """ + """Makes sure we can initialize a simple MapView object.""" kwargs = {} mapview = MapView(**kwargs) - self.assertEqual(len(mapview.children), 2) - - -if __name__ == '__main__': - import unittest - unittest.main() + assert len(mapview.children) == 2 From 53f5151b3a6947dcda23afeabcc1c6b90f950934 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 19 Apr 2020 14:55:51 +0200 Subject: [PATCH 05/24] Centralises pytest config --- .github/workflows/pythonapp.yml | 4 ++-- setup.cfg | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index e0d2e88..4b3ec81 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -55,7 +55,7 @@ jobs: draft: true - name: Test with pytest run: | - python3 -m pytest --cov=kivy_garden.mapview --cov-report term --cov-branch tests/ + python3 -m pytest tests/ - name: Coveralls # use this custom action until Coveralls fixes uptream issues # https://github.com/coverallsapp/github-action/issues/30 @@ -92,7 +92,7 @@ jobs: run: python -m pip install -e .[dev,ci] --extra-index-url https://kivy-garden.github.io/simple/ - name: Test with pytest run: | - python -m pytest --cov=kivy_garden.mapview --cov-report term --cov-branch tests/ + python -m pytest tests/ docs: runs-on: ubuntu-18.04 diff --git a/setup.cfg b/setup.cfg index a343008..cfbd419 100644 --- a/setup.cfg +++ b/setup.cfg @@ -12,3 +12,6 @@ extend-ignore = [coverage:run] relative_files = True + +[tool:pytest] +addopts = --cov=kivy_garden.mapview --cov-report term --cov-branch From f5fc78b5999c22e4ad2e8764bb34653d49e27ec4 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 19 Apr 2020 14:59:32 +0200 Subject: [PATCH 06/24] Adds basic GeoJsonMapLayer test Increases coverage from 14% to 17%. --- tests/test_geojson.py | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 tests/test_geojson.py diff --git a/tests/test_geojson.py b/tests/test_geojson.py new file mode 100644 index 0000000..832f0a2 --- /dev/null +++ b/tests/test_geojson.py @@ -0,0 +1,11 @@ +from kivy_garden.mapview.geojson import GeoJsonMapLayer + + +class TestGeoJsonMapLayer: + def test_init_simple(self): + """Makes sure we can initialize a simple GeoJsonMapLayer object.""" + kwargs = {} + maplayer = GeoJsonMapLayer(**kwargs) + assert maplayer.source == "" + assert maplayer.geojson is None + assert maplayer.cache_dir == "cache" From 2a55bf6e978186b2ef504fbef7f8cde09b49de3a Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 19 Apr 2020 15:36:19 +0200 Subject: [PATCH 07/24] test_http_source() Unit test GeoJsonMapLayer source download. Increases coverage from 17% to 20%. --- kivy_garden/mapview/geojson.py | 2 +- tests/test_geojson.py | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/kivy_garden/mapview/geojson.py b/kivy_garden/mapview/geojson.py index 7c0fccc..f296060 100644 --- a/kivy_garden/mapview/geojson.py +++ b/kivy_garden/mapview/geojson.py @@ -291,7 +291,7 @@ def on_geojson(self, instance, geojson, update=False): self._geojson_part(geojson, geotype="LineString") def on_source(self, instance, value): - if value.startswith("http://") or value.startswith("https://"): + if value.startswith(("http://", "https://")): Downloader.instance( cache_dir=self.cache_dir ).download(value, self._load_geojson_url) diff --git a/tests/test_geojson.py b/tests/test_geojson.py index 832f0a2..5955aa9 100644 --- a/tests/test_geojson.py +++ b/tests/test_geojson.py @@ -1,6 +1,16 @@ +from unittest import mock + +from kivy.clock import Clock + from kivy_garden.mapview.geojson import GeoJsonMapLayer +def patch_requests_get(response_json=None): + response = mock.Mock() + response.return_value.json.return_value = response_json + return mock.patch("requests.get", response) + + class TestGeoJsonMapLayer: def test_init_simple(self): """Makes sure we can initialize a simple GeoJsonMapLayer object.""" @@ -9,3 +19,18 @@ def test_init_simple(self): assert maplayer.source == "" assert maplayer.geojson is None assert maplayer.cache_dir == "cache" + + def test_http_source(self): + """Pulls the source from http(s).""" + source = "https://storage.googleapis.com/maps-devrel/google.json" + kwargs = {"source": source} + response_json = { + "type": "FeatureCollection", + } + with patch_requests_get(response_json) as m_get: + maplayer = GeoJsonMapLayer(**kwargs) + assert maplayer.source == source + assert maplayer.geojson is None + Clock.tick() + assert maplayer.geojson == response_json + assert m_get.call_args_list == [mock.call(source)] From 7a5c30f9c7fcae30ecd227789ed7724ff28ec718 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 19 Apr 2020 18:04:09 +0200 Subject: [PATCH 08/24] test_init_geojson() Providing the geojson directly, polygons should get added. This test would have caught the issue fixed by d62fd92. Increases coverage from 20% to 35%. --- tests/maps-devrel-google.json | 146 ++++++++++++++++++++++++++++++++++ tests/test_geojson.py | 44 +++++++++- 2 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 tests/maps-devrel-google.json diff --git a/tests/maps-devrel-google.json b/tests/maps-devrel-google.json new file mode 100644 index 0000000..be2f35f --- /dev/null +++ b/tests/maps-devrel-google.json @@ -0,0 +1,146 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "letter": "G", + "color": "blue", + "rank": "7", + "ascii": "71" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [123.61, -22.14], [122.38, -21.73], [121.06, -21.69], [119.66, -22.22], [119.00, -23.40], + [118.65, -24.76], [118.43, -26.07], [118.78, -27.56], [119.22, -28.57], [120.23, -29.49], + [121.77, -29.87], [123.57, -29.64], [124.45, -29.03], [124.71, -27.95], [124.80, -26.70], + [124.80, -25.60], [123.61, -25.64], [122.56, -25.64], [121.72, -25.72], [121.81, -26.62], + [121.86, -26.98], [122.60, -26.90], [123.57, -27.05], [123.57, -27.68], [123.35, -28.18], + [122.51, -28.38], [121.77, -28.26], [121.02, -27.91], [120.49, -27.21], [120.14, -26.50], + [120.10, -25.64], [120.27, -24.52], [120.67, -23.68], [121.72, -23.32], [122.43, -23.48], + [123.04, -24.04], [124.54, -24.28], [124.58, -23.20], [123.61, -22.14] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "letter": "o", + "color": "red", + "rank": "15", + "ascii": "111" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [128.84, -25.76], [128.18, -25.60], [127.96, -25.52], [127.88, -25.52], [127.70, -25.60], + [127.26, -25.79], [126.60, -26.11], [126.16, -26.78], [126.12, -27.68], [126.21, -28.42], + [126.69, -29.49], [127.74, -29.80], [128.80, -29.72], [129.41, -29.03], [129.72, -27.95], + [129.68, -27.21], [129.33, -26.23], [128.84, -25.76] + ], + [ + [128.45, -27.44], [128.32, -26.94], [127.70, -26.82], [127.35, -27.05], [127.17, -27.80], + [127.57, -28.22], [128.10, -28.42], [128.49, -27.80], [128.45, -27.44] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "letter": "o", + "color": "yellow", + "rank": "15", + "ascii": "111" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [131.87, -25.76], [131.35, -26.07], [130.95, -26.78], [130.82, -27.64], [130.86, -28.53], + [131.26, -29.22], [131.92, -29.76], [132.45, -29.87], [133.06, -29.76], [133.72, -29.34], + [134.07, -28.80], [134.20, -27.91], [134.07, -27.21], [133.81, -26.31], [133.37, -25.83], + [132.71, -25.64], [131.87, -25.76] + ], + [ + [133.15, -27.17], [132.71, -26.86], [132.09, -26.90], [131.74, -27.56], [131.79, -28.26], + [132.36, -28.45], [132.93, -28.34], [133.15, -27.76], [133.15, -27.17] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "letter": "g", + "color": "blue", + "rank": "7", + "ascii": "103" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [138.12, -25.04], [136.84, -25.16], [135.96, -25.36], [135.26, -25.99], [135, -26.90], + [135.04, -27.91], [135.26, -28.88], [136.05, -29.45], [137.02, -29.49], [137.81, -29.49], + [137.94, -29.99], [137.90, -31.20], [137.85, -32.24], [136.88, -32.69], [136.45, -32.36], + [136.27, -31.80], [134.95, -31.84], [135.17, -32.99], [135.52, -33.43], [136.14, -33.76], + [137.06, -33.83], [138.12, -33.65], [138.86, -33.21], [139.30, -32.28], [139.30, -31.24], + [139.30, -30.14], [139.21, -28.96], [139.17, -28.22], [139.08, -27.41], [139.08, -26.47], + [138.99, -25.40], [138.73, -25.00 ], [138.12, -25.04] + ], + [ + [137.50, -26.54], [136.97, -26.47], [136.49, -26.58], [136.31, -27.13], [136.31, -27.72], + [136.58, -27.99], [137.50, -28.03], [137.68, -27.68], [137.59, -26.78], [137.50, -26.54] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "letter": "l", + "color": "green", + "rank": "12", + "ascii": "108" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [140.14,-21.04], [140.31,-29.42], [141.67,-29.49], [141.59,-20.92], [140.14,-21.04] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "letter": "e", + "color": "red", + "rank": "5", + "ascii": "101" + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [144.14, -27.41], [145.67, -27.52], [146.86, -27.09], [146.82, -25.64], [146.25, -25.04], + [145.45, -24.68], [144.66, -24.60], [144.09, -24.76], [143.43, -25.08], [142.99, -25.40], + [142.64, -26.03], [142.64, -27.05], [142.64, -28.26], [143.30, -29.11], [144.18, -29.57], + [145.41, -29.64], [146.46, -29.19], [146.64, -28.72], [146.82, -28.14], [144.84, -28.42], + [144.31, -28.26], [144.14, -27.41] + ], + [ + [144.18, -26.39], [144.53, -26.58], [145.19, -26.62], [145.72, -26.35], [145.81, -25.91], + [145.41, -25.68], [144.97, -25.68], [144.49, -25.64], [144, -25.99], [144.18, -26.39] + ] + ] + } + } + ] +} \ No newline at end of file diff --git a/tests/test_geojson.py b/tests/test_geojson.py index 5955aa9..5b71b5d 100644 --- a/tests/test_geojson.py +++ b/tests/test_geojson.py @@ -1,7 +1,10 @@ +import json +import os from unittest import mock from kivy.clock import Clock +from kivy_garden.mapview import MapView from kivy_garden.mapview.geojson import GeoJsonMapLayer @@ -11,6 +14,14 @@ def patch_requests_get(response_json=None): return mock.patch("requests.get", response) +def load_json(filename): + test_dir = os.path.dirname(__file__) + json_file_path = os.path.join(test_dir, filename) + with open(json_file_path) as f: + json_file_content = f.read() + return json.loads(json_file_content) + + class TestGeoJsonMapLayer: def test_init_simple(self): """Makes sure we can initialize a simple GeoJsonMapLayer object.""" @@ -20,8 +31,11 @@ def test_init_simple(self): assert maplayer.geojson is None assert maplayer.cache_dir == "cache" - def test_http_source(self): - """Pulls the source from http(s).""" + def test_init_source(self): + """ + Providing the source from http(s). + The json object should get downloaded using the requests library. + """ source = "https://storage.googleapis.com/maps-devrel/google.json" kwargs = {"source": source} response_json = { @@ -34,3 +48,29 @@ def test_http_source(self): Clock.tick() assert maplayer.geojson == response_json assert m_get.call_args_list == [mock.call(source)] + + def test_init_geojson(self): + """Providing the geojson directly, polygons should get added.""" + options = {} + mapview = MapView(**options) + geojson = {} + kwargs = {"geojson": geojson} + maplayer = GeoJsonMapLayer(**kwargs) + mapview.add_layer(maplayer) + Clock.tick() + assert maplayer.source == "" + assert maplayer.geojson == geojson + assert len(maplayer.canvas_line.children) == 0 + assert len(maplayer.canvas_polygon.children) == 3 + assert len(maplayer.g_canvas_polygon.children) == 0 + geojson = load_json("maps-devrel-google.json") + kwargs = {"geojson": geojson} + maplayer = GeoJsonMapLayer(**kwargs) + mapview = MapView(**options) + mapview.add_layer(maplayer) + Clock.tick() + assert maplayer.source == "" + assert maplayer.geojson == geojson + assert len(maplayer.canvas_line.children) == 0 + assert len(maplayer.canvas_polygon.children) == 3 + assert len(maplayer.g_canvas_polygon.children) == 132 From 544c16a664b151a87753a94a719200e0a8932bf1 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 19 Apr 2020 18:26:45 +0200 Subject: [PATCH 09/24] Use Python 3 syntax - super() - implicit `object` inheritance --- kivy_garden/mapview/clustered_marker_layer.py | 18 +++++----- kivy_garden/mapview/downloader.py | 4 +-- kivy_garden/mapview/geojson.py | 2 +- kivy_garden/mapview/mbtsource.py | 10 +++--- kivy_garden/mapview/source.py | 4 +-- kivy_garden/mapview/view.py | 34 +++++++++---------- 6 files changed, 36 insertions(+), 36 deletions(-) diff --git a/kivy_garden/mapview/clustered_marker_layer.py b/kivy_garden/mapview/clustered_marker_layer.py index b9c7c2e..1271836 100644 --- a/kivy_garden/mapview/clustered_marker_layer.py +++ b/kivy_garden/mapview/clustered_marker_layer.py @@ -53,11 +53,11 @@ def yLat(y): return 360 * atan(exp(y2)) / pi - 90 -class KDBush(object): +class KDBush: # kdbush implementation from https://github.com/mourner/kdbush/blob/master/src/kdbush.js # def __init__(self, points, node_size=64): - super(KDBush, self).__init__() + super().__init__() self.points = points self.node_size = node_size @@ -222,9 +222,9 @@ def _sq_dist(self, ax, ay, bx, by): return dx * dx + dy * dy -class Cluster(object): +class Cluster: def __init__(self, x, y, num_points, id, props): - super(Cluster, self).__init__() + super().__init__() self.x = x self.y = y self.num_points = num_points @@ -239,9 +239,9 @@ def __init__(self, x, y, num_points, id, props): self.lat = yLat(y) -class Marker(object): +class Marker: def __init__(self, lon, lat, cls=MapMarker, options=None): - super(Marker, self).__init__() + super().__init__() self.lon = lon self.lat = lat self.cls = cls @@ -262,7 +262,7 @@ def __repr__(self): self.source) -class SuperCluster(object): +class SuperCluster: """Port of supercluster from mapbox in pure python """ @@ -272,7 +272,7 @@ def __init__(self, radius=40, extent=512, node_size=64): - super(SuperCluster, self).__init__() + super().__init__() self.min_zoom = min_zoom self.max_zoom = max_zoom self.radius = radius @@ -393,7 +393,7 @@ class ClusteredMarkerLayer(MapLayer): def __init__(self, **kwargs): self.cluster = None self.cluster_markers = [] - super(ClusteredMarkerLayer, self).__init__(**kwargs) + super().__init__(**kwargs) def add_marker(self, lon, lat, cls=MapMarker, options=None): if options is None: diff --git a/kivy_garden/mapview/downloader.py b/kivy_garden/mapview/downloader.py index e5f887e..a5de5ea 100644 --- a/kivy_garden/mapview/downloader.py +++ b/kivy_garden/mapview/downloader.py @@ -19,7 +19,7 @@ USER_AGENT = 'Kivy-garden.mapview' -class Downloader(object): +class Downloader: _instance = None MAX_WORKERS = 5 CAP_TIME = 0.064 # 15 FPS @@ -38,7 +38,7 @@ def __init__(self, max_workers=None, cap_time=None, **kwargs): max_workers = Downloader.MAX_WORKERS if cap_time is None: cap_time = Downloader.CAP_TIME - super(Downloader, self).__init__() + super().__init__() self.is_paused = False self.cap_time = cap_time self.executor = ThreadPoolExecutor(max_workers=max_workers) diff --git a/kivy_garden/mapview/geojson.py b/kivy_garden/mapview/geojson.py index f296060..8740201 100644 --- a/kivy_garden/mapview/geojson.py +++ b/kivy_garden/mapview/geojson.py @@ -195,7 +195,7 @@ class GeoJsonMapLayer(MapLayer): def __init__(self, **kwargs): self.first_time = True self.initial_zoom = None - super(GeoJsonMapLayer, self).__init__(**kwargs) + super().__init__(**kwargs) with self.canvas: self.canvas_polygon = Canvas() self.canvas_line = Canvas() diff --git a/kivy_garden/mapview/mbtsource.py b/kivy_garden/mapview/mbtsource.py index 4d8eabb..ffad825 100644 --- a/kivy_garden/mapview/mbtsource.py +++ b/kivy_garden/mapview/mbtsource.py @@ -20,7 +20,7 @@ class MBTilesMapSource(MapSource): def __init__(self, filename, **kwargs): - super(MBTilesMapSource, self).__init__(**kwargs) + super().__init__(**kwargs) self.filename = filename self.db = sqlite3.connect(filename) @@ -96,19 +96,19 @@ def _load_tile_done(self, tile, im): def get_x(self, zoom, lon): if self.is_xy: return lon - return super(MBTilesMapSource, self).get_x(zoom, lon) + return super().get_x(zoom, lon) def get_y(self, zoom, lat): if self.is_xy: return lat - return super(MBTilesMapSource, self).get_y(zoom, lat) + return super().get_y(zoom, lat) def get_lon(self, zoom, x): if self.is_xy: return x - return super(MBTilesMapSource, self).get_lon(zoom, x) + return super().get_lon(zoom, x) def get_lat(self, zoom, y): if self.is_xy: return y - return super(MBTilesMapSource, self).get_lat(zoom, y) + return super().get_lat(zoom, y) diff --git a/kivy_garden/mapview/source.py b/kivy_garden/mapview/source.py index 7e6c346..ba97bda 100644 --- a/kivy_garden/mapview/source.py +++ b/kivy_garden/mapview/source.py @@ -11,7 +11,7 @@ import hashlib -class MapSource(object): +class MapSource: """Base class for implementing a map source / provider """ @@ -48,7 +48,7 @@ def __init__(self, image_ext="png", attribution="© OpenStreetMap contributors", subdomains="abc", **kwargs): - super(MapSource, self).__init__() + super().__init__() if cache_key is None: # possible cache hit, but very unlikely cache_key = hashlib.sha224(url.encode("utf8")).hexdigest()[:10] diff --git a/kivy_garden/mapview/view.py b/kivy_garden/mapview/view.py index b5b3d5c..b4ff849 100644 --- a/kivy_garden/mapview/view.py +++ b/kivy_garden/mapview/view.py @@ -91,7 +91,7 @@ def on_ref_press(self, *args): class Tile(Rectangle): def __init__(self, *args, **kwargs): - super(Tile, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.cache_dir = kwargs.get('cache_dir', CACHE_DIR) @property @@ -152,7 +152,7 @@ def add_widget(self, widget): if not self.placeholder: self.placeholder = widget if self.is_open: - super(MapMarkerPopup, self).add_widget(self.placeholder) + super().add_widget(self.placeholder) else: self.placeholder.add_widget(widget) @@ -160,7 +160,7 @@ def remove_widget(self, widget): if widget is not self.placeholder: self.placeholder.remove_widget(widget) else: - super(MapMarkerPopup, self).remove_widget(widget) + super().remove_widget(widget) def on_is_open(self, *args): self.refresh_open_status() @@ -170,9 +170,9 @@ def on_release(self, *args): def refresh_open_status(self): if not self.is_open and self.placeholder.parent: - super(MapMarkerPopup, self).remove_widget(self.placeholder) + super().remove_widget(self.placeholder) elif self.is_open and not self.placeholder.parent: - super(MapMarkerPopup, self).add_widget(self.placeholder) + super().add_widget(self.placeholder) class MapLayer(Widget): @@ -201,7 +201,7 @@ class MarkerMapLayer(MapLayer): def __init__(self, **kwargs): self.markers = [] - super(MarkerMapLayer, self).__init__(**kwargs) + super().__init__(**kwargs) def insert_marker(self, marker, **kwargs): if self.order_marker_by_latitude: @@ -212,7 +212,7 @@ def insert_marker(self, marker, **kwargs): if before: kwargs['index'] = before[-1][0] + 1 - super(MarkerMapLayer, self).add_widget(marker, **kwargs) + super().add_widget(marker, **kwargs) def add_widget(self, marker): marker._layer = self @@ -223,7 +223,7 @@ def remove_widget(self, marker): marker._layer = None if marker in self.markers: self.markers.remove(marker) - super(MarkerMapLayer, self).remove_widget(marker) + super().remove_widget(marker) def reposition(self): if not self.markers: @@ -241,7 +241,7 @@ def reposition(self): if not marker.parent: self.insert_marker(marker) else: - super(MarkerMapLayer, self).remove_widget(marker) + super().remove_widget(marker) def set_marker_position(self, mapview, marker): x, y = mapview.get_window_xy_from(marker.lat, marker.lon, mapview.zoom) @@ -256,7 +256,7 @@ def unload(self): class MapViewScatter(Scatter): # internal def on_transform(self, *args): - super(MapViewScatter, self).on_transform(*args) + super().on_transform(*args) self.parent.on_transform(self.transform) def collide_point(self, x, y): @@ -488,7 +488,7 @@ def add_layer(self, layer, mode="window"): else: self.canvas = self.canvas_layers_out layer.canvas_parent = self.canvas - super(MapView, self).add_widget(layer) + super().add_widget(layer) self.canvas = c def remove_layer(self, layer): @@ -497,7 +497,7 @@ def remove_layer(self, layer): c = self.canvas self._layers.remove(layer) self.canvas = layer.canvas_parent - super(MapView, self).remove_widget(layer) + super().remove_widget(layer) self.canvas = c def sync_to(self, other): @@ -536,7 +536,7 @@ def __init__(self, **kwargs): Clock.schedule_interval(self._animate_color, 1 / 60.) self.lat = kwargs.get("lat", self.lat) self.lon = kwargs.get("lon", self.lon) - super(MapView, self).__init__(**kwargs) + super().__init__(**kwargs) def _animate_color(self, dt): # fast path @@ -571,7 +571,7 @@ def add_widget(self, widget): elif isinstance(widget, MapLayer): self.add_layer(widget) else: - super(MapView, self).add_widget(widget) + super().add_widget(widget) def remove_widget(self, widget): if isinstance(widget, MapMarker): @@ -579,7 +579,7 @@ def remove_widget(self, widget): elif isinstance(widget, MapLayer): self.remove_layer(widget) else: - super(MapView, self).remove_widget(widget) + super().remove_widget(widget) def on_map_relocated(self, zoom, coord): pass @@ -639,7 +639,7 @@ def on_touch_down(self, touch): self._touch_count += 1 if self._touch_count == 1: self._touch_zoom = (self.zoom, self._scale) - return super(MapView, self).on_touch_down(touch) + return super().on_touch_down(touch) def on_touch_up(self, touch): if touch.grab_current == self: @@ -656,7 +656,7 @@ def on_touch_up(self, touch): self.animated_diff_scale_at(2. - cur_scale, *touch.pos) self._pause = False return True - return super(MapView, self).on_touch_up(touch) + return super().on_touch_up(touch) def on_transform(self, *args): self._invalid_scale = True From 6bee3dec6061fe052bde488e150f9a5da93493a6 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 19 Apr 2020 18:54:03 +0200 Subject: [PATCH 10/24] Start testing Downloader via test_downloader.py Also made the cache_dir default optional. And removes a couple of flake8 exceptions. --- kivy_garden/mapview/downloader.py | 2 +- setup.cfg | 4 +--- tests/test_downloader.py | 15 +++++++++++++++ tox.ini | 2 +- 4 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 tests/test_downloader.py diff --git a/kivy_garden/mapview/downloader.py b/kivy_garden/mapview/downloader.py index a5de5ea..0f26452 100644 --- a/kivy_garden/mapview/downloader.py +++ b/kivy_garden/mapview/downloader.py @@ -25,7 +25,7 @@ class Downloader: CAP_TIME = 0.064 # 15 FPS @staticmethod - def instance(cache_dir): + def instance(cache_dir=None): if Downloader._instance is None: if not cache_dir: cache_dir = CACHE_DIR diff --git a/setup.cfg b/setup.cfg index cfbd419..99df1e6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,12 +3,10 @@ max-line-length = 88 extend-ignore = E122, E128, - E203, E265, F401, E402, - E501, - W504 + E501 [coverage:run] relative_files = True diff --git a/tests/test_downloader.py b/tests/test_downloader.py new file mode 100644 index 0000000..e17b8e8 --- /dev/null +++ b/tests/test_downloader.py @@ -0,0 +1,15 @@ +from kivy_garden.mapview.downloader import CACHE_DIR, Downloader + + +class TestDownloader: + def test_instance(self): + """Makes sure instance is a singleton.""" + assert Downloader._instance is None + downloader = Downloader.instance() + assert downloader == Downloader._instance + assert type(downloader) == Downloader + assert downloader.cache_dir == CACHE_DIR + Downloader._instance = None + new_cache_dir = "new_cache_dir" + downloader = Downloader.instance(new_cache_dir) + assert downloader.cache_dir == new_cache_dir diff --git a/tox.ini b/tox.ini index 6a0775d..a846486 100644 --- a/tox.ini +++ b/tox.ini @@ -10,4 +10,4 @@ commands = [testenv:pep8] deps = flake8 -commands = flake8 kivy_garden/ examples/ +commands = flake8 tests/ kivy_garden/ examples/ From f49de869a51511e922294e8da476f98c682d06d3 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 19 Apr 2020 19:07:03 +0200 Subject: [PATCH 11/24] Reformats with Black Applies black formatting once without enforcing it in the CI. --- examples/clustered_geojson.py | 5 +- examples/map_browser.py | 7 +- examples/map_with_marker_popup.py | 7 +- examples/simple_geojson.py | 1 + examples/simple_map.py | 1 + examples/simple_mbtiles.py | 14 +- examples/test_kdbush.py | 5 +- kivy_garden/mapview/__init__.py | 30 ++- kivy_garden/mapview/clustered_marker_layer.py | 65 +++--- kivy_garden/mapview/downloader.py | 23 +- kivy_garden/mapview/geojson.py | 33 +-- kivy_garden/mapview/mbtsource.py | 27 ++- kivy_garden/mapview/source.py | 134 ++++++++--- kivy_garden/mapview/utils.py | 10 +- kivy_garden/mapview/view.py | 217 +++++++++++------- pyproject.toml | 3 + 16 files changed, 370 insertions(+), 212 deletions(-) create mode 100644 pyproject.toml diff --git a/examples/clustered_geojson.py b/examples/clustered_geojson.py index 06ecbf1..ab965b2 100644 --- a/examples/clustered_geojson.py +++ b/examples/clustered_geojson.py @@ -3,6 +3,7 @@ if __name__ == '__main__' and __package__ is None: from os import path + sys.path.append(path.dirname(path.dirname(path.abspath(__file__)))) from kivy_garden.mapview import MapView, MapMarker @@ -27,9 +28,7 @@ view = MapView(**options) view.add_layer(layer) -marker_layer = ClusteredMarkerLayer( - cluster_radius=200 -) +marker_layer = ClusteredMarkerLayer(cluster_radius=200) view.add_layer(marker_layer) # create marker if they exists diff --git a/examples/map_browser.py b/examples/map_browser.py index f8e5117..6c4e475 100644 --- a/examples/map_browser.py +++ b/examples/map_browser.py @@ -3,9 +3,11 @@ if __name__ == '__main__' and __package__ is None: from os import sys, path + sys.path.append(path.dirname(path.dirname(path.abspath(__file__)))) -root = Builder.load_string(""" +root = Builder.load_string( + """ #:import MapSource kivy_garden.mapview.MapSource : @@ -69,6 +71,7 @@ text: "Longitude: {}".format(mapview.lon) Label: text: "Latitude: {}".format(mapview.lat) - """) + """ +) runTouchApp(root) diff --git a/examples/map_with_marker_popup.py b/examples/map_with_marker_popup.py index 642c87a..1788fac 100644 --- a/examples/map_with_marker_popup.py +++ b/examples/map_with_marker_popup.py @@ -4,10 +4,12 @@ if __name__ == '__main__' and __package__ is None: from os import path + sys.path.append(path.dirname(path.dirname(path.abspath(__file__)))) -root = Builder.load_string(""" +root = Builder.load_string( + """ #:import sys sys #:import MapSource kivy_garden.mapview.MapSource MapView: @@ -33,6 +35,7 @@ markup: True halign: "center" -""") +""" +) runTouchApp(root) diff --git a/examples/simple_geojson.py b/examples/simple_geojson.py index 29b8a9f..2475230 100644 --- a/examples/simple_geojson.py +++ b/examples/simple_geojson.py @@ -3,6 +3,7 @@ if __name__ == '__main__' and __package__ is None: from os import path + sys.path.append(path.dirname(path.dirname(path.abspath(__file__)))) from kivy_garden.mapview import MapView, MapMarker diff --git a/examples/simple_map.py b/examples/simple_map.py index 86256ea..40838ea 100644 --- a/examples/simple_map.py +++ b/examples/simple_map.py @@ -3,6 +3,7 @@ if __name__ == '__main__' and __package__ is None: from os import path + sys.path.append(path.dirname(path.dirname(path.abspath(__file__)))) from kivy_garden.mapview import MapView, MapSource diff --git a/examples/simple_mbtiles.py b/examples/simple_mbtiles.py index be3e364..0791edc 100644 --- a/examples/simple_mbtiles.py +++ b/examples/simple_mbtiles.py @@ -13,14 +13,18 @@ if __name__ == '__main__' and __package__ is None: from os import path + sys.path.append(path.dirname(path.dirname(path.abspath(__file__)))) from kivy_garden.mapview import MapView from kivy_garden.mapview.mbtsource import MBTilesMapSource source = MBTilesMapSource(sys.argv[1]) -runTouchApp(MapView( - map_source=source, - lat=source.default_lat, - lon=source.default_lon, - zoom=source.default_zoom)) +runTouchApp( + MapView( + map_source=source, + lat=source.default_lat, + lon=source.default_lon, + zoom=source.default_zoom, + ) +) diff --git a/examples/test_kdbush.py b/examples/test_kdbush.py index 89b3cc0..661bf1a 100644 --- a/examples/test_kdbush.py +++ b/examples/test_kdbush.py @@ -36,8 +36,7 @@ def build(self): with self.canvas_points: Color(1, 0, 0) for marker in points: - Rectangle( - pos=(marker.x * 600, marker.y * 600), size=(2, 2)) + Rectangle(pos=(marker.x * 600, marker.y * 600), size=(2, 2)) self.canvas.before.clear() with self.canvas.before: @@ -66,7 +65,7 @@ def on_touch_move(self, touch): def select(self, x, y): self.selection_center = (x, y) - self.selection = kdbush.within(x / 600., y / 600., self.radius) + self.selection = kdbush.within(x / 600.0, y / 600.0, self.radius) self.build() diff --git a/kivy_garden/mapview/__init__.py b/kivy_garden/mapview/__init__.py index 19a6280..0306a27 100644 --- a/kivy_garden/mapview/__init__.py +++ b/kivy_garden/mapview/__init__.py @@ -8,19 +8,28 @@ MapView is a Kivy widget that display maps. """ -__all__ = ["Coordinate", "Bbox", "MapView", "MapSource", "MapMarker", - "MapLayer", "MarkerMapLayer", "MapMarkerPopup"] +__all__ = [ + "Coordinate", + "Bbox", + "MapView", + "MapSource", + "MapMarker", + "MapLayer", + "MarkerMapLayer", + "MapMarkerPopup", +] __version__ = "0.2" -MIN_LATITUDE = -90. -MAX_LATITUDE = 90. -MIN_LONGITUDE = -180. -MAX_LONGITUDE = 180. +MIN_LATITUDE = -90.0 +MAX_LATITUDE = 90.0 +MIN_LONGITUDE = -180.0 +MAX_LONGITUDE = 180.0 CACHE_DIR = "cache" try: # fix if used within garden import sys + sys.modules['mapview'] = sys.modules['kivy.garden.mapview.mapview'] del sys except KeyError: @@ -28,5 +37,10 @@ from kivy_garden.mapview.types import Coordinate, Bbox from kivy_garden.mapview.source import MapSource -from kivy_garden.mapview.view import MapView, MapMarker, MapLayer, MarkerMapLayer, \ - MapMarkerPopup +from kivy_garden.mapview.view import ( + MapView, + MapMarker, + MapLayer, + MarkerMapLayer, + MapMarkerPopup, +) diff --git a/kivy_garden/mapview/clustered_marker_layer.py b/kivy_garden/mapview/clustered_marker_layer.py index 1271836..8d6a8c9 100644 --- a/kivy_garden/mapview/clustered_marker_layer.py +++ b/kivy_garden/mapview/clustered_marker_layer.py @@ -9,10 +9,16 @@ from kivy_garden.mapview.view import MapLayer, MapMarker from kivy.lang import Builder from kivy.metrics import dp -from kivy.properties import (ObjectProperty, NumericProperty, StringProperty, ListProperty) +from kivy.properties import ( + ObjectProperty, + NumericProperty, + StringProperty, + ListProperty, +) -Builder.load_string(""" +Builder.load_string( + """ : size_hint: None, None source: root.source @@ -25,12 +31,13 @@ size: root.size text: "{}".format(root.num_points) font_size: dp(18) -""") +""" +) # longitude/latitude to spherical mercator in [0..1] range def lngX(lng): - return lng / 360. + 0.5 + return lng / 360.0 + 0.5 def latY(lat): @@ -38,8 +45,8 @@ def latY(lat): return 0 if lat == -90: return 1 - s = sin(lat * pi / 180.) - y = (0.5 - 0.25 * log((1 + s) / (1 - s)) / pi) + s = sin(lat * pi / 180.0) + y = 0.5 - 0.25 * log((1 + s) / (1 - s)) / pi return min(1, max(0, y)) @@ -71,8 +78,9 @@ def __init__(self, points, node_size=64): self._sort(ids, coords, node_size, 0, len(ids) - 1, 0) def range(self, min_x, min_y, max_x, max_y): - return self._range(self.ids, self.coords, min_x, min_y, max_x, max_y, - self.node_size) + return self._range( + self.ids, self.coords, min_x, min_y, max_x, max_y, self.node_size + ) def within(self, x, y, r): return self._within(self.ids, self.coords, x, y, r, self.node_size) @@ -80,7 +88,7 @@ def within(self, x, y, r): def _sort(self, ids, coords, node_size, left, right, depth): if right - left <= node_size: return - m = int(floor((left + right) / 2.)) + m = int(floor((left + right) / 2.0)) self._select(ids, coords, m, left, right, depth % 2) self._sort(ids, coords, node_size, left, m - 1, depth + 1) self._sort(ids, coords, node_size, m + 1, right, depth + 1) @@ -92,9 +100,8 @@ def _select(self, ids, coords, k, left, right, inc): n = float(right - left + 1) m = k - left + 1 z = log(n) - s = 0.5 + exp(2 * z / 3.) - sd = 0.5 * sqrt(z * s * (n - s) / n) * (-1 - if (m - n / 2.) < 0 else 1) + s = 0.5 + exp(2 * z / 3.0) + sd = 0.5 * sqrt(z * s * (n - s) / n) * (-1 if (m - n / 2.0) < 0 else 1) new_left = max(left, int(floor(k - m * s / n + sd))) new_right = min(right, int(floor(k + (n - m) * s / n + sd))) self._select(ids, coords, k, new_left, new_right, inc) @@ -152,26 +159,25 @@ def _range(self, ids, coords, min_x, min_y, max_x, max_y, node_size): for i in range(left, right + 1): x = coords[2 * i] y = coords[2 * i + 1] - if (x >= min_x and x <= max_x and y >= min_y and - y <= max_y): + if x >= min_x and x <= max_x and y >= min_y and y <= max_y: result.append(ids[i]) continue - m = int(floor((left + right) / 2.)) + m = int(floor((left + right) / 2.0)) x = coords[2 * m] y = coords[2 * m + 1] - if (x >= min_x and x <= max_x and y >= min_y and y <= max_y): + if x >= min_x and x <= max_x and y >= min_y and y <= max_y: result.append(ids[m]) nextAxis = (axis + 1) % 2 - if (min_x <= x if axis == 0 else min_y <= y): + if min_x <= x if axis == 0 else min_y <= y: stack.append(left) stack.append(m - 1) stack.append(nextAxis) - if (max_x >= x if axis == 0 else max_y >= y): + if max_x >= x if axis == 0 else max_y >= y: stack.append(m + 1) stack.append(right) stack.append(nextAxis) @@ -195,7 +201,7 @@ def _within(self, ids, coords, qx, qy, r, node_size): result.append(ids[i]) continue - m = int(floor((left + right) / 2.)) + m = int(floor((left + right) / 2.0)) x = coords[2 * m] y = coords[2 * m + 1] @@ -258,20 +264,16 @@ def __init__(self, lon, lat, cls=MapMarker, options=None): self.widget = None def __repr__(self): - return "".format(self.lon, self.lat, - self.source) + return "".format( + self.lon, self.lat, self.source + ) class SuperCluster: """Port of supercluster from mapbox in pure python """ - def __init__(self, - min_zoom=0, - max_zoom=16, - radius=40, - extent=512, - node_size=64): + def __init__(self, min_zoom=0, max_zoom=16, radius=40, extent=512, node_size=64): super().__init__() self.min_zoom = min_zoom self.max_zoom = max_zoom @@ -284,6 +286,7 @@ def load(self, points): Once loaded, the index is immutable. """ from time import time + self.trees = {} self.points = points @@ -365,7 +368,9 @@ def _cluster(self, points, zoom): c_append(p) else: p.parent_id = i - c_append(Cluster(wx / num_points, wy / num_points, num_points, i, props)) + c_append( + Cluster(wx / num_points, wy / num_points, num_points, i, props) + ) return clusters @@ -373,7 +378,7 @@ class ClusterMapMarker(MapMarker): source = StringProperty(join(dirname(__file__), "icons", "cluster.png")) cluster = ObjectProperty() num_points = NumericProperty() - text_color = ListProperty([.1, .1, .1, 1]) + text_color = ListProperty([0.1, 0.1, 0.1, 1]) def on_cluster(self, instance, cluster): self.num_points = cluster.num_points @@ -427,7 +432,7 @@ def build_cluster(self): max_zoom=self.cluster_max_zoom, radius=self.cluster_radius, extent=self.cluster_extent, - node_size=self.cluster_node_size + node_size=self.cluster_node_size, ) self.cluster.load(self.cluster_markers) diff --git a/kivy_garden/mapview/downloader.py b/kivy_garden/mapview/downloader.py index 0f26452..37c7bf5 100644 --- a/kivy_garden/mapview/downloader.py +++ b/kivy_garden/mapview/downloader.py @@ -43,7 +43,7 @@ def __init__(self, max_workers=None, cap_time=None, **kwargs): self.cap_time = cap_time self.executor = ThreadPoolExecutor(max_workers=max_workers) self._futures = [] - Clock.schedule_interval(self._check_executor, 1 / 60.) + Clock.schedule_interval(self._check_executor, 1 / 60.0) if not exists(self.cache_dir): makedirs(self.cache_dir) @@ -53,23 +53,25 @@ def submit(self, f, *args, **kwargs): def download_tile(self, tile): if DEBUG: - print("Downloader: queue(tile) zoom={} x={} y={}".format( - tile.zoom, tile.tile_x, tile.tile_y)) + print( + "Downloader: queue(tile) zoom={} x={} y={}".format( + tile.zoom, tile.tile_x, tile.tile_y + ) + ) future = self.executor.submit(self._load_tile, tile) self._futures.append(future) def download(self, url, callback, **kwargs): if DEBUG: print("Downloader: queue(url) {}".format(url)) - future = self.executor.submit( - self._download_url, url, callback, kwargs) + future = self.executor.submit(self._download_url, url, callback, kwargs) self._futures.append(future) def _download_url(self, url, callback, kwargs): if DEBUG: print("Downloader: download(url) {}".format(url)) r = requests.get(url, **kwargs) - return callback, (url, r, ) + return callback, (url, r,) def _load_tile(self, tile): if tile.state == "done": @@ -78,10 +80,11 @@ def _load_tile(self, tile): if exists(cache_fn): if DEBUG: print("Downloader: use cache {}".format(cache_fn)) - return tile.set_source, (cache_fn, ) + return tile.set_source, (cache_fn,) tile_y = tile.map_source.get_row_count(tile.zoom) - tile.tile_y - 1 - uri = tile.map_source.url.format(z=tile.zoom, x=tile.tile_x, y=tile_y, - s=choice(tile.map_source.subdomains)) + uri = tile.map_source.url.format( + z=tile.zoom, x=tile.tile_x, y=tile_y, s=choice(tile.map_source.subdomains) + ) if DEBUG: print("Downloader: download(tile) {}".format(uri)) req = requests.get(uri, headers={'User-agent': USER_AGENT}, timeout=5) @@ -92,7 +95,7 @@ def _load_tile(self, tile): fd.write(data) if DEBUG: print("Downloaded {} bytes: {}".format(len(data), uri)) - return tile.set_source, (cache_fn, ) + return tile.set_source, (cache_fn,) except Exception as e: print("Downloader error: {!r}".format(e)) diff --git a/kivy_garden/mapview/geojson.py b/kivy_garden/mapview/geojson.py index 8740201..aacea62 100644 --- a/kivy_garden/mapview/geojson.py +++ b/kivy_garden/mapview/geojson.py @@ -21,8 +21,14 @@ import json from kivy.properties import StringProperty, ObjectProperty -from kivy.graphics import (Canvas, PushMatrix, PopMatrix, MatrixInstruction, - Translate, Scale) +from kivy.graphics import ( + Canvas, + PushMatrix, + PopMatrix, + MatrixInstruction, + Translate, + Scale, +) from kivy.graphics import Mesh, Line, Color from kivy.graphics.tesselator import Tesselator, WINDING_ODD, TYPE_POLYGONS from kivy.utils import get_color_from_hex @@ -178,7 +184,7 @@ 'white': '#ffffff', 'whitesmoke': '#f5f5f5', 'yellow': '#ffff00', - 'yellowgreen': '#9acd32' + 'yellowgreen': '#9acd32', } @@ -216,12 +222,12 @@ def reposition(self): if zoom is None: self.initial_zoom = zoom = pzoom if zoom != pzoom: - diff = 2**(pzoom - zoom) + diff = 2 ** (pzoom - zoom) vx /= diff vy /= diff self.g_scale.x = self.g_scale.y = diff else: - self.g_scale.x = self.g_scale.y = 1. + self.g_scale.x = self.g_scale.y = 1.0 self.g_translate.xy = vx, vy self.g_matrix.matrix = self.parent._scatter.transform @@ -269,14 +275,15 @@ def _get_bounds(feature): for polygon in geometry["coordinates"]: for coordinate in polygon[0]: _submit_coordinate(coordinate) + self.traverse_feature(_get_bounds) return bounds @property def center(self): min_lon, max_lon, min_lat, max_lat = self.bounds - cx = (max_lon - min_lon) / 2. - cy = (max_lat - min_lat) / 2. + cx = (max_lon - min_lon) / 2.0 + cy = (max_lat - min_lat) / 2.0 return min_lon + cx, min_lat + cy def on_geojson(self, instance, geojson, update=False): @@ -292,9 +299,9 @@ def on_geojson(self, instance, geojson, update=False): def on_source(self, instance, value): if value.startswith(("http://", "https://")): - Downloader.instance( - cache_dir=self.cache_dir - ).download(value, self._load_geojson_url) + Downloader.instance(cache_dir=self.cache_dir).download( + value, self._load_geojson_url + ) else: with open(value, "rb") as fd: geojson = json.load(fd) @@ -344,10 +351,8 @@ def _geojson_part_geometry(self, geometry, properties): graphics.append(Color(*color)) for vertices, indices in tess.meshes: graphics.append( - Mesh( - vertices=vertices, - indices=indices, - mode="triangle_fan")) + Mesh(vertices=vertices, indices=indices, mode="triangle_fan") + ) elif tp == "LineString": stroke = get_color_from_hex(properties.get("stroke", "#ffffff")) diff --git a/kivy_garden/mapview/mbtsource.py b/kivy_garden/mapview/mbtsource.py index ffad825..20ba21c 100644 --- a/kivy_garden/mapview/mbtsource.py +++ b/kivy_garden/mapview/mbtsource.py @@ -33,21 +33,21 @@ def __init__(self, filename, **kwargs): self.max_zoom = int(metadata["maxzoom"]) self.attribution = metadata.get("attribution", "") self.bounds = bounds = None - cx = cy = 0. + cx = cy = 0.0 cz = 5 if "bounds" in metadata: self.bounds = bounds = map(float, metadata["bounds"].split(",")) if "center" in metadata: cx, cy, cz = map(float, metadata["center"].split(",")) elif self.bounds: - cx = (bounds[2] + bounds[0]) / 2. - cy = (bounds[3] + bounds[1]) / 2. + cx = (bounds[2] + bounds[0]) / 2.0 + cy = (bounds[3] + bounds[1]) / 2.0 cz = self.min_zoom self.default_lon = cx self.default_lat = cy self.default_zoom = int(cz) self.projection = metadata.get("projection", "") - self.is_xy = (self.projection == "xy") + self.is_xy = self.projection == "xy" def fill_tile(self, tile): if tile.state == "done": @@ -63,9 +63,12 @@ def _load_tile(self, tile): # get the right tile c = ctx.db.cursor() c.execute( - ("SELECT tile_data FROM tiles WHERE " - "zoom_level=? AND tile_column=? AND tile_row=?"), - (tile.zoom, tile.tile_x, tile.tile_y)) + ( + "SELECT tile_data FROM tiles WHERE " + "zoom_level=? AND tile_column=? AND tile_row=?" + ), + (tile.zoom, tile.tile_x, tile.tile_y), + ) # print "fetch", tile.zoom, tile.tile_x, tile.tile_y row = c.fetchone() if not row: @@ -79,15 +82,17 @@ def _load_tile(self, tile): # android issue, "buffer" does not have the buffer interface # ie row[0] buffer is not compatible with BytesIO on Android?? data = io.BytesIO(bytes(row[0])) - im = CoreImage(data, ext='png', - filename="{}.{}.{}.png".format(tile.zoom, tile.tile_x, - tile.tile_y)) + im = CoreImage( + data, + ext='png', + filename="{}.{}.{}.png".format(tile.zoom, tile.tile_x, tile.tile_y), + ) if im is None: tile.state = "done" return - return self._load_tile_done, (tile, im, ) + return self._load_tile_done, (tile, im,) def _load_tile_done(self, tile, im): tile.texture = im.texture diff --git a/kivy_garden/mapview/source.py b/kivy_garden/mapview/source.py index ba97bda..37b22d3 100644 --- a/kivy_garden/mapview/source.py +++ b/kivy_garden/mapview/source.py @@ -4,8 +4,13 @@ from kivy.metrics import dp from math import cos, ceil, log, tan, pi, atan, exp -from kivy_garden.mapview import MIN_LONGITUDE, MAX_LONGITUDE, MIN_LATITUDE, MAX_LATITUDE, \ - CACHE_DIR +from kivy_garden.mapview import ( + MIN_LONGITUDE, + MAX_LONGITUDE, + MIN_LATITUDE, + MAX_LATITUDE, + CACHE_DIR, +) from kivy_garden.mapview.downloader import Downloader from kivy_garden.mapview.utils import clamp import hashlib @@ -21,33 +26,91 @@ class MapSource: # list of available providers # cache_key: (is_overlay, minzoom, maxzoom, url, attribution) providers = { - "osm": (0, 0, 19, "http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", attribution_osm), - "osm-hot": (0, 0, 19, "http://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png", ""), - "osm-de": (0, 0, 18, "http://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png", "Tiles @ OSM DE"), - "osm-fr": (0, 0, 20, "http://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png", "Tiles @ OSM France"), - "cyclemap": (0, 0, 17, "http://{s}.tile.opencyclemap.org/cycle/{z}/{x}/{y}.png", "Tiles @ Andy Allan"), - "thunderforest-cycle": (0, 0, 19, "http://{s}.tile.thunderforest.com/cycle/{z}/{x}/{y}.png", attribution_thunderforest), - "thunderforest-transport": (0, 0, 19, "http://{s}.tile.thunderforest.com/transport/{z}/{x}/{y}.png", attribution_thunderforest), - "thunderforest-landscape": (0, 0, 19, "http://{s}.tile.thunderforest.com/landscape/{z}/{x}/{y}.png", attribution_thunderforest), - "thunderforest-outdoors": (0, 0, 19, "http://{s}.tile.thunderforest.com/outdoors/{z}/{x}/{y}.png", attribution_thunderforest), - + "osm": ( + 0, + 0, + 19, + "http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", + attribution_osm, + ), + "osm-hot": ( + 0, + 0, + 19, + "http://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png", + "", + ), + "osm-de": ( + 0, + 0, + 18, + "http://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png", + "Tiles @ OSM DE", + ), + "osm-fr": ( + 0, + 0, + 20, + "http://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png", + "Tiles @ OSM France", + ), + "cyclemap": ( + 0, + 0, + 17, + "http://{s}.tile.opencyclemap.org/cycle/{z}/{x}/{y}.png", + "Tiles @ Andy Allan", + ), + "thunderforest-cycle": ( + 0, + 0, + 19, + "http://{s}.tile.thunderforest.com/cycle/{z}/{x}/{y}.png", + attribution_thunderforest, + ), + "thunderforest-transport": ( + 0, + 0, + 19, + "http://{s}.tile.thunderforest.com/transport/{z}/{x}/{y}.png", + attribution_thunderforest, + ), + "thunderforest-landscape": ( + 0, + 0, + 19, + "http://{s}.tile.thunderforest.com/landscape/{z}/{x}/{y}.png", + attribution_thunderforest, + ), + "thunderforest-outdoors": ( + 0, + 0, + 19, + "http://{s}.tile.thunderforest.com/outdoors/{z}/{x}/{y}.png", + attribution_thunderforest, + ), # no longer available - #"mapquest-osm": (0, 0, 19, "http://otile{s}.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.jpeg", "Tiles Courtesy of Mapquest", {"subdomains": "1234", "image_ext": "jpeg"}), - #"mapquest-aerial": (0, 0, 19, "http://oatile{s}.mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpeg", "Tiles Courtesy of Mapquest", {"subdomains": "1234", "image_ext": "jpeg"}), - + # "mapquest-osm": (0, 0, 19, "http://otile{s}.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.jpeg", "Tiles Courtesy of Mapquest", {"subdomains": "1234", "image_ext": "jpeg"}), + # "mapquest-aerial": (0, 0, 19, "http://oatile{s}.mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpeg", "Tiles Courtesy of Mapquest", {"subdomains": "1234", "image_ext": "jpeg"}), # more to add with # https://github.com/leaflet-extras/leaflet-providers/blob/master/leaflet-providers.js # not working ? - #"openseamap": (0, 0, 19, "http://tiles.openseamap.org/seamark/{z}/{x}/{y}.png", + # "openseamap": (0, 0, 19, "http://tiles.openseamap.org/seamark/{z}/{x}/{y}.png", # "Map data @ OpenSeaMap contributors"), } - def __init__(self, - url="http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", - cache_key=None, min_zoom=0, max_zoom=19, tile_size=256, - image_ext="png", - attribution="© OpenStreetMap contributors", - subdomains="abc", **kwargs): + def __init__( + self, + url="http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", + cache_key=None, + min_zoom=0, + max_zoom=19, + tile_size=256, + image_ext="png", + attribution="© OpenStreetMap contributors", + subdomains="abc", + **kwargs + ): super().__init__() if cache_key is None: # possible cache hit, but very unlikely @@ -74,39 +137,46 @@ def from_provider(key, **kwargs): is_overlay, min_zoom, max_zoom, url, attribution = provider[:5] if len(provider) > 5: options = provider[5] - return MapSource(cache_key=key, min_zoom=min_zoom, - max_zoom=max_zoom, url=url, cache_dir=cache_dir, - attribution=attribution, **options) + return MapSource( + cache_key=key, + min_zoom=min_zoom, + max_zoom=max_zoom, + url=url, + cache_dir=cache_dir, + attribution=attribution, + **options + ) def get_x(self, zoom, lon): """Get the x position on the map using this map source's projection (0, 0) is located at the top left. """ lon = clamp(lon, MIN_LONGITUDE, MAX_LONGITUDE) - return ((lon + 180.) / 360. * pow(2., zoom)) * self.dp_tile_size + return ((lon + 180.0) / 360.0 * pow(2.0, zoom)) * self.dp_tile_size def get_y(self, zoom, lat): """Get the y position on the map using this map source's projection (0, 0) is located at the top left. """ lat = clamp(-lat, MIN_LATITUDE, MAX_LATITUDE) - lat = lat * pi / 180. - return ((1.0 - log(tan(lat) + 1.0 / cos(lat)) / pi) / - 2. * pow(2., zoom)) * self.dp_tile_size + lat = lat * pi / 180.0 + return ( + (1.0 - log(tan(lat) + 1.0 / cos(lat)) / pi) / 2.0 * pow(2.0, zoom) + ) * self.dp_tile_size def get_lon(self, zoom, x): """Get the longitude to the x position in the map source's projection """ dx = x / float(self.dp_tile_size) - lon = dx / pow(2., zoom) * 360. - 180. + lon = dx / pow(2.0, zoom) * 360.0 - 180.0 return clamp(lon, MIN_LONGITUDE, MAX_LONGITUDE) def get_lat(self, zoom, y): """Get the latitude to the y position in the map source's projection """ dy = y / float(self.dp_tile_size) - n = pi - 2 * pi * dy / pow(2., zoom) - lat = -180. / pi * atan(.5 * (exp(n) - exp(-n))) + n = pi - 2 * pi * dy / pow(2.0, zoom) + lat = -180.0 / pi * atan(0.5 * (exp(n) - exp(-n))) return clamp(lat, MIN_LATITUDE, MAX_LATITUDE) def get_row_count(self, zoom): diff --git a/kivy_garden/mapview/utils.py b/kivy_garden/mapview/utils.py index 775ed23..bb49bba 100644 --- a/kivy_garden/mapview/utils.py +++ b/kivy_garden/mapview/utils.py @@ -24,21 +24,21 @@ def haversine(lon1, lat1, lon2, lat2): # haversine formula dlon = lon2 - lon1 dlat = lat2 - lat1 - a = sin(dlat / 2)**2 + cos(lat1) * cos(lat2) * sin(dlon / 2)**2 + a = sin(dlat / 2) ** 2 + cos(lat1) * cos(lat2) * sin(dlon / 2) ** 2 c = 2 * asin(sqrt(a)) km = 6367 * c return km -def get_zoom_for_radius(radius_km, lat=None, tile_size=256.): +def get_zoom_for_radius(radius_km, lat=None, tile_size=256.0): """See: https://wiki.openstreetmap.org/wiki/Zoom_levels""" - radius = radius_km * 1000. + radius = radius_km * 1000.0 if lat is None: - lat = 0. # Do not compensate for the latitude + lat = 0.0 # Do not compensate for the latitude # Calculate the equatorial circumference based on the WGS-84 radius - earth_circumference = 2. * pi * 6378137. * cos(lat * pi / 180.) + earth_circumference = 2.0 * pi * 6378137.0 * cos(lat * pi / 180.0) # Check how many tiles that are currently in view nr_tiles_shown = min(Window.size) / dp(tile_size) diff --git a/kivy_garden/mapview/view.py b/kivy_garden/mapview/view.py index b4ff849..73b0c47 100644 --- a/kivy_garden/mapview/view.py +++ b/kivy_garden/mapview/view.py @@ -1,7 +1,6 @@ # coding=utf-8 -__all__ = ["MapView", "MapMarker", "MapMarkerPopup", "MapLayer", - "MarkerMapLayer"] +__all__ = ["MapView", "MapMarker", "MapMarkerPopup", "MapLayer", "MarkerMapLayer"] from os.path import join, dirname from kivy.clock import Clock @@ -11,22 +10,36 @@ from kivy.uix.image import Image from kivy.uix.scatter import Scatter from kivy.uix.behaviors import ButtonBehavior -from kivy.properties import NumericProperty, ObjectProperty, ListProperty, \ - AliasProperty, BooleanProperty, StringProperty +from kivy.properties import ( + NumericProperty, + ObjectProperty, + ListProperty, + AliasProperty, + BooleanProperty, + StringProperty, +) from kivy.graphics import Canvas, Color, Rectangle from kivy.graphics.transformation import Matrix from kivy.lang import Builder from kivy.compat import string_types from math import ceil -from kivy_garden.mapview import MIN_LONGITUDE, MAX_LONGITUDE, MIN_LATITUDE, MAX_LATITUDE, \ - CACHE_DIR, Coordinate, Bbox +from kivy_garden.mapview import ( + MIN_LONGITUDE, + MAX_LONGITUDE, + MIN_LATITUDE, + MAX_LATITUDE, + CACHE_DIR, + Coordinate, + Bbox, +) from kivy_garden.mapview.source import MapSource from kivy_garden.mapview.utils import clamp from itertools import takewhile import webbrowser -Builder.load_string(""" +Builder.load_string( + """ : size_hint: None, None source: root.source @@ -81,7 +94,8 @@ center_x: root.center_x size: root.popup_size -""") +""" +) class ClickableLabel(Label): @@ -100,7 +114,8 @@ def cache_fn(self): fn = map_source.cache_fmt.format( image_ext=map_source.image_ext, cache_key=map_source.cache_key, - **self.__dict__) + **self.__dict__ + ) return join(self.cache_dir, fn) def set_source(self, cache_fn): @@ -179,6 +194,7 @@ class MapLayer(Widget): """A map layer, that is repositionned everytime the :class:`MapView` is moved. """ + viewport_x = NumericProperty(0) viewport_y = NumericProperty(0) @@ -197,6 +213,7 @@ def unload(self): class MarkerMapLayer(MapLayer): """A map layer for :class:`MapMarker` """ + order_marker_by_latitude = BooleanProperty(True) def __init__(self, **kwargs): @@ -205,10 +222,9 @@ def __init__(self, **kwargs): def insert_marker(self, marker, **kwargs): if self.order_marker_by_latitude: - before = list(takewhile( - lambda i_m: i_m[1].lat < marker.lat, - enumerate(self.children) - )) + before = list( + takewhile(lambda i_m: i_m[1].lat < marker.lat, enumerate(self.children)) + ) if before: kwargs['index'] = before[-1][0] + 1 @@ -309,11 +325,11 @@ class MapView(Widget): delta_x = NumericProperty(0) delta_y = NumericProperty(0) - background_color = ListProperty([181 / 255., 208 / 255., 208 / 255., 1]) + background_color = ListProperty([181 / 255.0, 208 / 255.0, 208 / 255.0, 1]) cache_dir = StringProperty(CACHE_DIR) _zoom = NumericProperty(0) _pause = BooleanProperty(False) - _scale = 1. + _scale = 1.0 _disabled_count = 0 __events__ = ["on_map_relocated"] @@ -337,8 +353,7 @@ def get_bbox(self, margin=0): top/right (lat2, lon2). """ x1, y1 = self.to_local(0 - margin, 0 - margin) - x2, y2 = self.to_local((self.width + margin), - (self.height + margin)) + x2, y2 = self.to_local((self.width + margin), (self.height + margin)) c1 = self.get_latlon_at(x1, y1) c2 = self.get_latlon_at(x2, y2) return Bbox((c1.lat, c1.lon, c2.lat, c2.lon)) @@ -395,23 +410,25 @@ def set_zoom_at(self, zoom, x, y, scale=None): """Sets the zoom level, leaving the (x, y) at the exact same point in the view. """ - zoom = clamp(zoom, - self.map_source.get_min_zoom(), - self.map_source.get_max_zoom()) + zoom = clamp( + zoom, self.map_source.get_min_zoom(), self.map_source.get_max_zoom() + ) if int(zoom) == int(self._zoom): if scale is None: return elif scale == self.scale: return - scale = scale or 1. + scale = scale or 1.0 # first, rescale the scatter scatter = self._scatter scale = clamp(scale, scatter.scale_min, scatter.scale_max) rescale = scale * 1.0 / scatter.scale - scatter.apply_transform(Matrix().scale(rescale, rescale, rescale), - post_multiply=True, - anchor=scatter.to_local(x, y)) + scatter.apply_transform( + Matrix().scale(rescale, rescale, rescale), + post_multiply=True, + anchor=scatter.to_local(x, y), + ) # adjust position if the zoom changed c1 = self.map_source.get_col_count(self._zoom) @@ -421,9 +438,9 @@ def set_zoom_at(self, zoom, x, y, scale=None): self.delta_x = scatter.x + self.delta_x * f self.delta_y = scatter.y + self.delta_y * f # back to 0 every time - scatter.apply_transform(Matrix().translate( - -scatter.x, -scatter.y, 0 - ), post_multiply=True) + scatter.apply_transform( + Matrix().translate(-scatter.x, -scatter.y, 0), post_multiply=True + ) # avoid triggering zoom changes. self._zoom = zoom @@ -447,7 +464,8 @@ def get_latlon_at(self, x, y, zoom=None): scale = self._scale return Coordinate( lat=self.map_source.get_lat(zoom, y / scale + vy), - lon=self.map_source.get_lon(zoom, x / scale + vx)) + lon=self.map_source.get_lon(zoom, x / scale + vx), + ) def add_marker(self, marker, layer=None): """Add a marker into the layer. If layer is None, it will be added in @@ -477,9 +495,8 @@ def add_layer(self, layer, mode="window"): widget yourself: think as Z-sprite / billboard. Defaults to "window". """ - assert (mode in ("scatter", "window")) - if self._default_marker_layer is None and \ - isinstance(layer, MarkerMapLayer): + assert mode in ("scatter", "window") + if self._default_marker_layer is None and isinstance(layer, MarkerMapLayer): self._default_marker_layer = layer self._layers.append(layer) c = self.canvas @@ -511,6 +528,7 @@ def sync_to(self, other): def __init__(self, **kwargs): from kivy.base import EventLoop + EventLoop.ensure_window() self._invalid_scale = True self._tiles = [] @@ -530,10 +548,10 @@ def __init__(self, **kwargs): with self.canvas: self.canvas_layers_out = Canvas() self._scale_target_anim = False - self._scale_target = 1. + self._scale_target = 1.0 self._touch_count = 0 self.map_source.cache_dir = self.cache_dir - Clock.schedule_interval(self._animate_color, 1 / 60.) + Clock.schedule_interval(self._animate_color, 1 / 60.0) self.lat = kwargs.get("lat", self.lat) self.lon = kwargs.get("lon", self.lon) super().__init__(**kwargs) @@ -544,14 +562,14 @@ def _animate_color(self, dt): if d == 0: for tile in self._tiles: if tile.state == "need-animation": - tile.g_color.a = 1. + tile.g_color.a = 1.0 tile.state = "animated" for tile in self._tiles_bg: if tile.state == "need-animation": - tile.g_color.a = 1. + tile.g_color.a = 1.0 tile.state = "animated" else: - d = d / 1000. + d = d / 1000.0 for tile in self._tiles: if tile.state != "need-animation": continue @@ -585,7 +603,7 @@ def on_map_relocated(self, zoom, coord): pass def animated_diff_scale_at(self, d, x, y): - self._scale_target_time = 1. + self._scale_target_time = 1.0 self._scale_target_pos = x, y if self._scale_target_anim is False: self._scale_target_anim = True @@ -593,10 +611,10 @@ def animated_diff_scale_at(self, d, x, y): else: self._scale_target += d Clock.unschedule(self._animate_scale) - Clock.schedule_interval(self._animate_scale, 1 / 60.) + Clock.schedule_interval(self._animate_scale, 1 / 60.0) def _animate_scale(self, dt): - diff = self._scale_target / 3. + diff = self._scale_target / 3.0 if abs(diff) < 0.01: diff = self._scale_target self._scale_target = 0 @@ -618,17 +636,18 @@ def scale_at(self, scale, x, y): scatter = self._scatter scale = clamp(scale, scatter.scale_min, scatter.scale_max) rescale = scale * 1.0 / scatter.scale - scatter.apply_transform(Matrix().scale(rescale, rescale, rescale), - post_multiply=True, - anchor=scatter.to_local(x, y)) + scatter.apply_transform( + Matrix().scale(rescale, rescale, rescale), + post_multiply=True, + anchor=scatter.to_local(x, y), + ) def on_touch_down(self, touch): if not self.collide_point(*touch.pos): return if self.pause_on_action: self._pause = True - if "button" in touch.profile and touch.button in ( - "scrolldown", "scrollup"): + if "button" in touch.profile and touch.button in ("scrolldown", "scrollup"): d = 1 if touch.button == "scrollup" else -1 self.animated_diff_scale_at(d, *touch.pos) return True @@ -651,9 +670,9 @@ def on_touch_up(self, touch): cur_zoom = self.zoom cur_scale = self._scale if cur_zoom < zoom or cur_scale < scale: - self.animated_diff_scale_at(1. - cur_scale, *touch.pos) + self.animated_diff_scale_at(1.0 - cur_scale, *touch.pos) elif cur_zoom > zoom or cur_scale > scale: - self.animated_diff_scale_at(2. - cur_scale, *touch.pos) + self.animated_diff_scale_at(2.0 - cur_scale, *touch.pos) self._pause = False return True return super().on_touch_up(touch) @@ -668,19 +687,19 @@ def on_transform(self, *args): zoom = self._zoom scatter = self._scatter scale = scatter.scale - if scale >= 2.: + if scale >= 2.0: zoom += 1 - scale /= 2. + scale /= 2.0 elif scale < 1: zoom -= 1 - scale *= 2. + scale *= 2.0 zoom = clamp(zoom, map_source.min_zoom, map_source.max_zoom) if zoom != self._zoom: self.set_zoom_at(zoom, scatter.x, scatter.y, scale=scale) self.trigger_update(True) else: - if zoom == map_source.min_zoom and scatter.scale < 1.: - scatter.scale = 1. + if zoom == map_source.min_zoom and scatter.scale < 1.0: + scatter.scale = 1.0 self.trigger_update(True) else: self.trigger_update(False) @@ -705,16 +724,16 @@ def _apply_bounds(self): oxmin, oymin = self._scatter.to_local(self.x, self.y) oxmax, oymax = self._scatter.to_local(self.right, self.top) s = self._scale - cxmin = (oxmin - dx) + cxmin = oxmin - dx if cxmin < xmin: self._scatter.x += (cxmin - xmin) * s - cymin = (oymin - dy) + cymin = oymin - dy if cymin < ymin: self._scatter.y += (cymin - ymin) * s - cxmax = (oxmax - dx) + cxmax = oxmax - dx if cxmax > xmax: self._scatter.x -= (xmax - cxmax) * s - cymax = (oymax - dy) + cymax = oymax - dy if cymax > ymax: self._scatter.y -= (ymax - cymax) * s @@ -730,12 +749,12 @@ def trigger_update(self, full): def do_update(self, dt): zoom = self._zoom scale = self._scale - self.lon = self.map_source.get_lon(zoom, - ( - self.center_x - self._scatter.x) / scale - self.delta_x) - self.lat = self.map_source.get_lat(zoom, - ( - self.center_y - self._scatter.y) / scale - self.delta_y) + self.lon = self.map_source.get_lon( + zoom, (self.center_x - self._scatter.x) / scale - self.delta_x + ) + self.lat = self.map_source.get_lat( + zoom, (self.center_y - self._scatter.y) / scale - self.delta_y + ) self.dispatch("on_map_relocated", zoom, Coordinate(self.lon, self.lat)) for layer in self._layers: layer.reposition() @@ -768,8 +787,7 @@ def bbox_for_zoom(self, vx, vy, w, h, zoom): x_count = tile_x_last - tile_x_first y_count = tile_y_last - tile_y_first - return (tile_x_first, tile_y_first, tile_x_last, tile_y_last, - x_count, y_count) + return (tile_x_first, tile_y_first, tile_x_last, tile_y_last, x_count, y_count) def load_visible_tiles(self): map_source = self.map_source @@ -779,8 +797,14 @@ def load_visible_tiles(self): bbox_for_zoom = self.bbox_for_zoom size = map_source.dp_tile_size - tile_x_first, tile_y_first, tile_x_last, tile_y_last, \ - x_count, y_count = bbox_for_zoom(vx, vy, self.width, self.height, zoom) + ( + tile_x_first, + tile_y_first, + tile_x_last, + tile_y_last, + x_count, + y_count, + ) = bbox_for_zoom(vx, vy, self.width, self.height, zoom) # print "Range {},{} to {},{}".format( # tile_x_first, tile_y_first, @@ -794,11 +818,21 @@ def load_visible_tiles(self): f = 2 ** (zoom - tile.zoom) w = self.width / f h = self.height / f - btile_x_first, btile_y_first, btile_x_last, btile_y_last, \ - _, _ = bbox_for_zoom(vx / f, vy / f, w, h, tile.zoom) - - if tile_x < btile_x_first or tile_x >= btile_x_last or \ - tile_y < btile_y_first or tile_y >= btile_y_last: + ( + btile_x_first, + btile_y_first, + btile_x_last, + btile_y_last, + _, + _, + ) = bbox_for_zoom(vx / f, vy / f, w, h, tile.zoom) + + if ( + tile_x < btile_x_first + or tile_x >= btile_x_last + or tile_y < btile_y_first + or tile_y >= btile_y_last + ): tile.state = "done" self._tiles_bg.remove(tile) self.canvas_map.before.remove(tile.g_color) @@ -807,17 +841,19 @@ def load_visible_tiles(self): tsize = size * f tile.size = tsize, tsize - tile.pos = ( - tile_x * tsize + self.delta_x, - tile_y * tsize + self.delta_y) + tile.pos = (tile_x * tsize + self.delta_x, tile_y * tsize + self.delta_y) # Get rid of old tiles first for tile in self._tiles[:]: tile_x = tile.tile_x tile_y = tile.tile_y - if tile_x < tile_x_first or tile_x >= tile_x_last or \ - tile_y < tile_y_first or tile_y >= tile_y_last: + if ( + tile_x < tile_x_first + or tile_x >= tile_x_last + or tile_y < tile_y_first + or tile_y >= tile_y_last + ): tile.state = "done" self.tile_map_set(tile_x, tile_y, False) self._tiles.remove(tile) @@ -825,8 +861,7 @@ def load_visible_tiles(self): self.canvas_map.remove(tile.g_color) else: tile.size = (size, size) - tile.pos = ( - tile_x * size + self.delta_x, tile_y * size + self.delta_y) + tile.pos = (tile_x * size + self.delta_x, tile_y * size + self.delta_y) # Load new tiles if needed x = tile_x_first + x_count // 2 - 1 @@ -836,9 +871,13 @@ def load_visible_tiles(self): turn = 0 while arm_size < arm_max: for i in range(arm_size): - if not self.tile_in_tile_map(x, y) and \ - y >= tile_y_first and y < tile_y_last and \ - x >= tile_x_first and x < tile_x_last: + if ( + not self.tile_in_tile_map(x, y) + and y >= tile_y_first + and y < tile_y_last + and x >= tile_x_first + and x < tile_x_last + ): self.load_tile(x, y, size, zoom) x += dirs[turn % 4 + 1] @@ -852,7 +891,7 @@ def load_visible_tiles(self): def load_tile(self, x, y, size, zoom): if self.tile_in_tile_map(x, y) or zoom != self._zoom: return - self.load_tile_for_source(self.map_source, 1., size, x, y, zoom) + self.load_tile_for_source(self.map_source, 1.0, size, x, y, zoom) # XXX do overlay support self.tile_map_set(x, y, True) @@ -947,15 +986,19 @@ def on_map_source(self, instance, source): self.map_source = MapSource.from_provider(source) elif isinstance(source, (tuple, list)): cache_key, min_zoom, max_zoom, url, attribution, options = source - self.map_source = MapSource(url=url, cache_key=cache_key, - min_zoom=min_zoom, max_zoom=max_zoom, - attribution=attribution, - cache_dir=self.cache_dir, **options) + self.map_source = MapSource( + url=url, + cache_key=cache_key, + min_zoom=min_zoom, + max_zoom=max_zoom, + attribution=attribution, + cache_dir=self.cache_dir, + **options + ) elif isinstance(source, MapSource): self.map_source = source else: raise Exception("Invalid map source provider") - self.zoom = clamp(self.zoom, - self.map_source.min_zoom, self.map_source.max_zoom) + self.zoom = clamp(self.zoom, self.map_source.min_zoom, self.map_source.max_zoom) self.remove_all_tiles() self.trigger_update(True) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9acad64 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[tool.black] +line-length = 88 +skip-string-normalization = true From 4d31b424507a812ab32592e7eb971d17fdd5a75d Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 19 Apr 2020 19:14:51 +0200 Subject: [PATCH 12/24] Removes PEP8 addressed exceptions --- setup.cfg | 3 --- 1 file changed, 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index 99df1e6..ec0e12b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,9 +1,6 @@ [flake8] max-line-length = 88 extend-ignore = - E122, - E128, - E265, F401, E402, E501 From 1d1b9cc052c0e6a96807ddfde67af020d0f7790e Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 19 Apr 2020 19:24:20 +0200 Subject: [PATCH 13/24] PEP8/Imports re-org Fixes PEP8 error: ``` E402 module level import not at top of file ``` On Linux examples can still be ran without installing the project by forcing the `PYTHONPATH`. ``` PYTHONPATH=. python examples/simple_geojson.py ``` --- examples/clustered_geojson.py | 5 ----- examples/simple_geojson.py | 4 ---- examples/simple_map.py | 5 ----- examples/simple_mbtiles.py | 5 ----- kivy_garden/mapview/__init__.py | 35 ++++++++----------------------- kivy_garden/mapview/constants.py | 5 +++++ kivy_garden/mapview/downloader.py | 2 +- kivy_garden/mapview/geojson.py | 2 +- kivy_garden/mapview/source.py | 2 +- kivy_garden/mapview/view.py | 5 ++--- setup.cfg | 1 - tests/test_downloader.py | 3 ++- 12 files changed, 21 insertions(+), 53 deletions(-) create mode 100644 kivy_garden/mapview/constants.py diff --git a/examples/clustered_geojson.py b/examples/clustered_geojson.py index ab965b2..859fd69 100644 --- a/examples/clustered_geojson.py +++ b/examples/clustered_geojson.py @@ -1,11 +1,6 @@ from kivy.base import runTouchApp import sys -if __name__ == '__main__' and __package__ is None: - from os import path - - sys.path.append(path.dirname(path.dirname(path.abspath(__file__)))) - from kivy_garden.mapview import MapView, MapMarker from kivy_garden.mapview.geojson import GeoJsonMapLayer from kivy_garden.mapview.clustered_marker_layer import ClusteredMarkerLayer diff --git a/examples/simple_geojson.py b/examples/simple_geojson.py index 2475230..be8060d 100644 --- a/examples/simple_geojson.py +++ b/examples/simple_geojson.py @@ -1,10 +1,6 @@ from kivy.base import runTouchApp import sys -if __name__ == '__main__' and __package__ is None: - from os import path - - sys.path.append(path.dirname(path.dirname(path.abspath(__file__)))) from kivy_garden.mapview import MapView, MapMarker from kivy_garden.mapview.geojson import GeoJsonMapLayer diff --git a/examples/simple_map.py b/examples/simple_map.py index 40838ea..de81b19 100644 --- a/examples/simple_map.py +++ b/examples/simple_map.py @@ -1,11 +1,6 @@ import sys from kivy.base import runTouchApp -if __name__ == '__main__' and __package__ is None: - from os import path - - sys.path.append(path.dirname(path.dirname(path.abspath(__file__)))) - from kivy_garden.mapview import MapView, MapSource kwargs = {} diff --git a/examples/simple_mbtiles.py b/examples/simple_mbtiles.py index 0791edc..9433ae0 100644 --- a/examples/simple_mbtiles.py +++ b/examples/simple_mbtiles.py @@ -10,11 +10,6 @@ import sys from kivy.base import runTouchApp - -if __name__ == '__main__' and __package__ is None: - from os import path - - sys.path.append(path.dirname(path.dirname(path.abspath(__file__)))) from kivy_garden.mapview import MapView from kivy_garden.mapview.mbtsource import MBTilesMapSource diff --git a/kivy_garden/mapview/__init__.py b/kivy_garden/mapview/__init__.py index 0306a27..373538f 100644 --- a/kivy_garden/mapview/__init__.py +++ b/kivy_garden/mapview/__init__.py @@ -7,6 +7,15 @@ MapView is a Kivy widget that display maps. """ +from kivy_garden.mapview.types import Coordinate, Bbox +from kivy_garden.mapview.source import MapSource +from kivy_garden.mapview.view import ( + MapView, + MapMarker, + MapLayer, + MarkerMapLayer, + MapMarkerPopup, +) __all__ = [ "Coordinate", @@ -18,29 +27,3 @@ "MarkerMapLayer", "MapMarkerPopup", ] -__version__ = "0.2" - -MIN_LATITUDE = -90.0 -MAX_LATITUDE = 90.0 -MIN_LONGITUDE = -180.0 -MAX_LONGITUDE = 180.0 -CACHE_DIR = "cache" - -try: - # fix if used within garden - import sys - - sys.modules['mapview'] = sys.modules['kivy.garden.mapview.mapview'] - del sys -except KeyError: - pass - -from kivy_garden.mapview.types import Coordinate, Bbox -from kivy_garden.mapview.source import MapSource -from kivy_garden.mapview.view import ( - MapView, - MapMarker, - MapLayer, - MarkerMapLayer, - MapMarkerPopup, -) diff --git a/kivy_garden/mapview/constants.py b/kivy_garden/mapview/constants.py new file mode 100644 index 0000000..b6998f8 --- /dev/null +++ b/kivy_garden/mapview/constants.py @@ -0,0 +1,5 @@ +MIN_LATITUDE = -90.0 +MAX_LATITUDE = 90.0 +MIN_LONGITUDE = -180.0 +MAX_LONGITUDE = 180.0 +CACHE_DIR = "cache" diff --git a/kivy_garden/mapview/downloader.py b/kivy_garden/mapview/downloader.py index 37c7bf5..f1ed128 100644 --- a/kivy_garden/mapview/downloader.py +++ b/kivy_garden/mapview/downloader.py @@ -10,7 +10,7 @@ import requests import traceback from time import time -from kivy_garden.mapview import CACHE_DIR +from kivy_garden.mapview.constants import CACHE_DIR DEBUG = "MAPVIEW_DEBUG_DOWNLOADER" in environ diff --git a/kivy_garden/mapview/geojson.py b/kivy_garden/mapview/geojson.py index aacea62..b21beb0 100644 --- a/kivy_garden/mapview/geojson.py +++ b/kivy_garden/mapview/geojson.py @@ -33,7 +33,7 @@ from kivy.graphics.tesselator import Tesselator, WINDING_ODD, TYPE_POLYGONS from kivy.utils import get_color_from_hex from kivy.metrics import dp -from kivy_garden.mapview import CACHE_DIR +from kivy_garden.mapview.constants import CACHE_DIR from kivy_garden.mapview.view import MapLayer from kivy_garden.mapview.downloader import Downloader diff --git a/kivy_garden/mapview/source.py b/kivy_garden/mapview/source.py index 37b22d3..ad659c0 100644 --- a/kivy_garden/mapview/source.py +++ b/kivy_garden/mapview/source.py @@ -4,7 +4,7 @@ from kivy.metrics import dp from math import cos, ceil, log, tan, pi, atan, exp -from kivy_garden.mapview import ( +from kivy_garden.mapview.constants import ( MIN_LONGITUDE, MAX_LONGITUDE, MIN_LATITUDE, diff --git a/kivy_garden/mapview/view.py b/kivy_garden/mapview/view.py index 73b0c47..ff57e99 100644 --- a/kivy_garden/mapview/view.py +++ b/kivy_garden/mapview/view.py @@ -23,15 +23,14 @@ from kivy.lang import Builder from kivy.compat import string_types from math import ceil -from kivy_garden.mapview import ( +from kivy_garden.mapview.constants import ( MIN_LONGITUDE, MAX_LONGITUDE, MIN_LATITUDE, MAX_LATITUDE, CACHE_DIR, - Coordinate, - Bbox, ) +from kivy_garden.mapview import Coordinate, Bbox from kivy_garden.mapview.source import MapSource from kivy_garden.mapview.utils import clamp from itertools import takewhile diff --git a/setup.cfg b/setup.cfg index ec0e12b..0783483 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,7 +2,6 @@ max-line-length = 88 extend-ignore = F401, - E402, E501 [coverage:run] diff --git a/tests/test_downloader.py b/tests/test_downloader.py index e17b8e8..8e23e80 100644 --- a/tests/test_downloader.py +++ b/tests/test_downloader.py @@ -1,4 +1,5 @@ -from kivy_garden.mapview.downloader import CACHE_DIR, Downloader +from kivy_garden.mapview.downloader import Downloader +from kivy_garden.mapview.constants import CACHE_DIR class TestDownloader: From 16ae3f81b973a92e6dfea38f786e947262a62463 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 19 Apr 2020 19:43:56 +0200 Subject: [PATCH 14/24] isort imports Applies isort once without enfocing it in the CI. Note the configuration was taken from Black. https://github.com/psf/black#how-black-wraps-lines --- examples/clustered_geojson.py | 9 ++-- examples/map_with_marker_popup.py | 1 + examples/simple_geojson.py | 6 +-- examples/simple_map.py | 3 +- examples/simple_mbtiles.py | 3 +- examples/test_kdbush.py | 6 ++- kivy_garden/mapview/__init__.py | 8 ++-- kivy_garden/mapview/clustered_marker_layer.py | 9 ++-- kivy_garden/mapview/downloader.py | 13 +++--- kivy_garden/mapview/geojson.py | 20 +++++---- kivy_garden/mapview/mbtsource.py | 13 +++--- kivy_garden/mapview/source.py | 12 +++--- kivy_garden/mapview/utils.py | 2 +- kivy_garden/mapview/view.py | 43 ++++++++++--------- setup.cfg | 7 +++ tests/test_downloader.py | 2 +- 16 files changed, 91 insertions(+), 66 deletions(-) diff --git a/examples/clustered_geojson.py b/examples/clustered_geojson.py index 859fd69..8503a4a 100644 --- a/examples/clustered_geojson.py +++ b/examples/clustered_geojson.py @@ -1,10 +1,11 @@ -from kivy.base import runTouchApp import sys -from kivy_garden.mapview import MapView, MapMarker -from kivy_garden.mapview.geojson import GeoJsonMapLayer +from kivy.base import runTouchApp + +from kivy_garden.mapview import MapMarker, MapView from kivy_garden.mapview.clustered_marker_layer import ClusteredMarkerLayer -from kivy_garden.mapview.utils import haversine, get_zoom_for_radius +from kivy_garden.mapview.geojson import GeoJsonMapLayer +from kivy_garden.mapview.utils import get_zoom_for_radius, haversine source = sys.argv[1] diff --git a/examples/map_with_marker_popup.py b/examples/map_with_marker_popup.py index 1788fac..e26c906 100644 --- a/examples/map_with_marker_popup.py +++ b/examples/map_with_marker_popup.py @@ -1,4 +1,5 @@ import sys + from kivy.base import runTouchApp from kivy.lang import Builder diff --git a/examples/simple_geojson.py b/examples/simple_geojson.py index be8060d..b8e3931 100644 --- a/examples/simple_geojson.py +++ b/examples/simple_geojson.py @@ -1,10 +1,10 @@ -from kivy.base import runTouchApp import sys +from kivy.base import runTouchApp -from kivy_garden.mapview import MapView, MapMarker +from kivy_garden.mapview import MapMarker, MapView from kivy_garden.mapview.geojson import GeoJsonMapLayer -from kivy_garden.mapview.utils import haversine, get_zoom_for_radius +from kivy_garden.mapview.utils import get_zoom_for_radius, haversine if len(sys.argv) > 1: source = sys.argv[1] diff --git a/examples/simple_map.py b/examples/simple_map.py index de81b19..3e279be 100644 --- a/examples/simple_map.py +++ b/examples/simple_map.py @@ -1,7 +1,8 @@ import sys + from kivy.base import runTouchApp -from kivy_garden.mapview import MapView, MapSource +from kivy_garden.mapview import MapSource, MapView kwargs = {} if len(sys.argv) > 1: diff --git a/examples/simple_mbtiles.py b/examples/simple_mbtiles.py index 9433ae0..d94c8f8 100644 --- a/examples/simple_mbtiles.py +++ b/examples/simple_mbtiles.py @@ -9,11 +9,12 @@ """ import sys + from kivy.base import runTouchApp + from kivy_garden.mapview import MapView from kivy_garden.mapview.mbtsource import MBTilesMapSource - source = MBTilesMapSource(sys.argv[1]) runTouchApp( MapView( diff --git a/examples/test_kdbush.py b/examples/test_kdbush.py index 661bf1a..ecb66c5 100644 --- a/examples/test_kdbush.py +++ b/examples/test_kdbush.py @@ -5,11 +5,13 @@ the selected red dot. """ +from random import random + from kivy.app import App +from kivy.graphics import Canvas, Color, Ellipse, Rectangle from kivy.uix.widget import Widget -from kivy.graphics import Color, Rectangle, Ellipse, Canvas + from kivy_garden.mapview.clustered_marker_layer import KDBush, Marker -from random import random # creating markers points = [] diff --git a/kivy_garden/mapview/__init__.py b/kivy_garden/mapview/__init__.py index 373538f..fd2301b 100644 --- a/kivy_garden/mapview/__init__.py +++ b/kivy_garden/mapview/__init__.py @@ -7,14 +7,14 @@ MapView is a Kivy widget that display maps. """ -from kivy_garden.mapview.types import Coordinate, Bbox from kivy_garden.mapview.source import MapSource +from kivy_garden.mapview.types import Bbox, Coordinate from kivy_garden.mapview.view import ( - MapView, - MapMarker, MapLayer, - MarkerMapLayer, + MapMarker, MapMarkerPopup, + MapView, + MarkerMapLayer, ) __all__ = [ diff --git a/kivy_garden/mapview/clustered_marker_layer.py b/kivy_garden/mapview/clustered_marker_layer.py index 8d6a8c9..91f7bc8 100644 --- a/kivy_garden/mapview/clustered_marker_layer.py +++ b/kivy_garden/mapview/clustered_marker_layer.py @@ -4,18 +4,19 @@ =================================== """ +from math import atan, exp, floor, log, pi, sin, sqrt from os.path import dirname, join -from math import sin, log, pi, atan, exp, floor, sqrt -from kivy_garden.mapview.view import MapLayer, MapMarker + from kivy.lang import Builder from kivy.metrics import dp from kivy.properties import ( - ObjectProperty, + ListProperty, NumericProperty, + ObjectProperty, StringProperty, - ListProperty, ) +from kivy_garden.mapview.view import MapLayer, MapMarker Builder.load_string( """ diff --git a/kivy_garden/mapview/downloader.py b/kivy_garden/mapview/downloader.py index f1ed128..c3f6d76 100644 --- a/kivy_garden/mapview/downloader.py +++ b/kivy_garden/mapview/downloader.py @@ -2,16 +2,17 @@ __all__ = ["Downloader"] -from kivy.clock import Clock -from os.path import join, exists -from os import makedirs, environ +import traceback from concurrent.futures import ThreadPoolExecutor, TimeoutError, as_completed +from os import environ, makedirs +from os.path import exists, join from random import choice -import requests -import traceback from time import time -from kivy_garden.mapview.constants import CACHE_DIR +import requests +from kivy.clock import Clock + +from kivy_garden.mapview.constants import CACHE_DIR DEBUG = "MAPVIEW_DEBUG_DOWNLOADER" in environ # user agent is needed because since may 2019 OSM gives me a 429 or 403 server error diff --git a/kivy_garden/mapview/geojson.py b/kivy_garden/mapview/geojson.py index b21beb0..2e35ced 100644 --- a/kivy_garden/mapview/geojson.py +++ b/kivy_garden/mapview/geojson.py @@ -20,22 +20,26 @@ __all__ = ["GeoJsonMapLayer"] import json -from kivy.properties import StringProperty, ObjectProperty + from kivy.graphics import ( Canvas, - PushMatrix, - PopMatrix, + Color, + Line, MatrixInstruction, - Translate, + Mesh, + PopMatrix, + PushMatrix, Scale, + Translate, ) -from kivy.graphics import Mesh, Line, Color -from kivy.graphics.tesselator import Tesselator, WINDING_ODD, TYPE_POLYGONS -from kivy.utils import get_color_from_hex +from kivy.graphics.tesselator import TYPE_POLYGONS, WINDING_ODD, Tesselator from kivy.metrics import dp +from kivy.properties import ObjectProperty, StringProperty +from kivy.utils import get_color_from_hex + from kivy_garden.mapview.constants import CACHE_DIR -from kivy_garden.mapview.view import MapLayer from kivy_garden.mapview.downloader import Downloader +from kivy_garden.mapview.view import MapLayer COLORS = { 'aliceblue': '#f0f8ff', diff --git a/kivy_garden/mapview/mbtsource.py b/kivy_garden/mapview/mbtsource.py index 20ba21c..dc35ebd 100644 --- a/kivy_garden/mapview/mbtsource.py +++ b/kivy_garden/mapview/mbtsource.py @@ -10,12 +10,15 @@ __all__ = ["MBTilesMapSource"] -from kivy_garden.mapview.source import MapSource -from kivy_garden.mapview.downloader import Downloader -from kivy.core.image import Image as CoreImage, ImageLoader -import threading -import sqlite3 import io +import sqlite3 +import threading + +from kivy.core.image import Image as CoreImage +from kivy.core.image import ImageLoader + +from kivy_garden.mapview.downloader import Downloader +from kivy_garden.mapview.source import MapSource class MBTilesMapSource(MapSource): diff --git a/kivy_garden/mapview/source.py b/kivy_garden/mapview/source.py index ad659c0..41adda7 100644 --- a/kivy_garden/mapview/source.py +++ b/kivy_garden/mapview/source.py @@ -2,18 +2,20 @@ __all__ = ["MapSource"] +import hashlib +from math import atan, ceil, cos, exp, log, pi, tan + from kivy.metrics import dp -from math import cos, ceil, log, tan, pi, atan, exp + from kivy_garden.mapview.constants import ( - MIN_LONGITUDE, + CACHE_DIR, + MAX_LATITUDE, MAX_LONGITUDE, MIN_LATITUDE, - MAX_LATITUDE, - CACHE_DIR, + MIN_LONGITUDE, ) from kivy_garden.mapview.downloader import Downloader from kivy_garden.mapview.utils import clamp -import hashlib class MapSource: diff --git a/kivy_garden/mapview/utils.py b/kivy_garden/mapview/utils.py index bb49bba..1ecc84f 100644 --- a/kivy_garden/mapview/utils.py +++ b/kivy_garden/mapview/utils.py @@ -2,7 +2,7 @@ __all__ = ["clamp", "haversine", "get_zoom_for_radius"] -from math import radians, cos, sin, asin, sqrt, pi +from math import asin, cos, pi, radians, sin, sqrt from kivy.core.window import Window from kivy.metrics import dp diff --git a/kivy_garden/mapview/view.py b/kivy_garden/mapview/view.py index ff57e99..7db4695 100644 --- a/kivy_garden/mapview/view.py +++ b/kivy_garden/mapview/view.py @@ -2,40 +2,41 @@ __all__ = ["MapView", "MapMarker", "MapMarkerPopup", "MapLayer", "MarkerMapLayer"] -from os.path import join, dirname +import webbrowser +from itertools import takewhile +from math import ceil +from os.path import dirname, join + from kivy.clock import Clock +from kivy.compat import string_types +from kivy.graphics import Canvas, Color, Rectangle +from kivy.graphics.transformation import Matrix +from kivy.lang import Builder from kivy.metrics import dp -from kivy.uix.widget import Widget -from kivy.uix.label import Label -from kivy.uix.image import Image -from kivy.uix.scatter import Scatter -from kivy.uix.behaviors import ButtonBehavior from kivy.properties import ( - NumericProperty, - ObjectProperty, - ListProperty, AliasProperty, BooleanProperty, + ListProperty, + NumericProperty, + ObjectProperty, StringProperty, ) -from kivy.graphics import Canvas, Color, Rectangle -from kivy.graphics.transformation import Matrix -from kivy.lang import Builder -from kivy.compat import string_types -from math import ceil +from kivy.uix.behaviors import ButtonBehavior +from kivy.uix.image import Image +from kivy.uix.label import Label +from kivy.uix.scatter import Scatter +from kivy.uix.widget import Widget + +from kivy_garden.mapview import Bbox, Coordinate from kivy_garden.mapview.constants import ( - MIN_LONGITUDE, + CACHE_DIR, + MAX_LATITUDE, MAX_LONGITUDE, MIN_LATITUDE, - MAX_LATITUDE, - CACHE_DIR, + MIN_LONGITUDE, ) -from kivy_garden.mapview import Coordinate, Bbox from kivy_garden.mapview.source import MapSource from kivy_garden.mapview.utils import clamp -from itertools import takewhile - -import webbrowser Builder.load_string( """ diff --git a/setup.cfg b/setup.cfg index 0783483..1bfde65 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,6 +4,13 @@ extend-ignore = F401, E501 +[isort] +multi_line_output=3 +include_trailing_comma=True +force_grid_wrap=0 +use_parentheses=True +line_length=88 + [coverage:run] relative_files = True diff --git a/tests/test_downloader.py b/tests/test_downloader.py index 8e23e80..2b80cee 100644 --- a/tests/test_downloader.py +++ b/tests/test_downloader.py @@ -1,5 +1,5 @@ -from kivy_garden.mapview.downloader import Downloader from kivy_garden.mapview.constants import CACHE_DIR +from kivy_garden.mapview.downloader import Downloader class TestDownloader: From bccecc22bb20ba8d3c138fe99ea4ec99810fd543 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 19 Apr 2020 20:08:09 +0200 Subject: [PATCH 15/24] TestDownloader.test_download() Also fixes a couple of variable names. --- kivy_garden/mapview/downloader.py | 10 +++++----- kivy_garden/mapview/geojson.py | 4 ++-- tests/test_downloader.py | 20 ++++++++++++++++++++ tests/test_geojson.py | 10 +++------- tests/utils.py | 7 +++++++ 5 files changed, 37 insertions(+), 14 deletions(-) create mode 100644 tests/utils.py diff --git a/kivy_garden/mapview/downloader.py b/kivy_garden/mapview/downloader.py index c3f6d76..8257252 100644 --- a/kivy_garden/mapview/downloader.py +++ b/kivy_garden/mapview/downloader.py @@ -71,8 +71,8 @@ def download(self, url, callback, **kwargs): def _download_url(self, url, callback, kwargs): if DEBUG: print("Downloader: download(url) {}".format(url)) - r = requests.get(url, **kwargs) - return callback, (url, r,) + response = requests.get(url, **kwargs) + return callback, (url, response) def _load_tile(self, tile): if tile.state == "done": @@ -88,10 +88,10 @@ def _load_tile(self, tile): ) if DEBUG: print("Downloader: download(tile) {}".format(uri)) - req = requests.get(uri, headers={'User-agent': USER_AGENT}, timeout=5) + response = requests.get(uri, headers={'User-agent': USER_AGENT}, timeout=5) try: - req.raise_for_status() - data = req.content + response.raise_for_status() + data = response.content with open(cache_fn, "wb") as fd: fd.write(data) if DEBUG: diff --git a/kivy_garden/mapview/geojson.py b/kivy_garden/mapview/geojson.py index 2e35ced..8402380 100644 --- a/kivy_garden/mapview/geojson.py +++ b/kivy_garden/mapview/geojson.py @@ -311,8 +311,8 @@ def on_source(self, instance, value): geojson = json.load(fd) self.geojson = geojson - def _load_geojson_url(self, url, r): - self.geojson = r.json() + def _load_geojson_url(self, url, response): + self.geojson = response.json() def _geojson_part(self, part, geotype=None): tp = part["type"] diff --git a/tests/test_downloader.py b/tests/test_downloader.py index 2b80cee..1186d84 100644 --- a/tests/test_downloader.py +++ b/tests/test_downloader.py @@ -1,8 +1,16 @@ +from unittest import mock + +from kivy.clock import Clock + from kivy_garden.mapview.constants import CACHE_DIR from kivy_garden.mapview.downloader import Downloader +from tests.utils import patch_requests_get class TestDownloader: + def teardown_method(self): + Downloader._instance = None + def test_instance(self): """Makes sure instance is a singleton.""" assert Downloader._instance is None @@ -14,3 +22,15 @@ def test_instance(self): new_cache_dir = "new_cache_dir" downloader = Downloader.instance(new_cache_dir) assert downloader.cache_dir == new_cache_dir + + def test_download(self): + """Checks download() callback.""" + callback = mock.Mock() + url = "https://ifconfig.me/" + downloader = Downloader.instance() + with patch_requests_get() as m_get: + downloader.download(url, callback) + assert m_get.call_args_list == [mock.call(url)] + assert callback.call_args_list == [] + Clock.tick() + assert callback.call_args_list == [mock.call(url, mock.ANY)] diff --git a/tests/test_geojson.py b/tests/test_geojson.py index 5b71b5d..f4eabf7 100644 --- a/tests/test_geojson.py +++ b/tests/test_geojson.py @@ -6,12 +6,7 @@ from kivy_garden.mapview import MapView from kivy_garden.mapview.geojson import GeoJsonMapLayer - - -def patch_requests_get(response_json=None): - response = mock.Mock() - response.return_value.json.return_value = response_json - return mock.patch("requests.get", response) +from tests.utils import patch_requests_get def load_json(filename): @@ -45,7 +40,8 @@ def test_init_source(self): maplayer = GeoJsonMapLayer(**kwargs) assert maplayer.source == source assert maplayer.geojson is None - Clock.tick() + while maplayer.geojson is None: + Clock.tick() assert maplayer.geojson == response_json assert m_get.call_args_list == [mock.call(source)] diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000..a4a55ac --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,7 @@ +from unittest import mock + + +def patch_requests_get(response_json=None): + response = mock.Mock() + response.return_value.json.return_value = response_json + return mock.patch("requests.get", response) From 937e5a90252e8075f63ed1bb0aad919014455368 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 19 Apr 2020 21:14:03 +0200 Subject: [PATCH 16/24] test_download_status_error() Follow up PR will actually check for status code, refs #6 Also note the requests.get() is not yet mocked, but should. --- tests/test_downloader.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_downloader.py b/tests/test_downloader.py index 1186d84..1fcc82b 100644 --- a/tests/test_downloader.py +++ b/tests/test_downloader.py @@ -28,9 +28,25 @@ def test_download(self): callback = mock.Mock() url = "https://ifconfig.me/" downloader = Downloader.instance() + assert len(downloader._futures) == 0 with patch_requests_get() as m_get: downloader.download(url, callback) assert m_get.call_args_list == [mock.call(url)] assert callback.call_args_list == [] + assert len(downloader._futures) == 1 Clock.tick() assert callback.call_args_list == [mock.call(url, mock.ANY)] + assert len(downloader._futures) == 0 + + def test_download_status_error(self): + """Error status code should be checked, but is currently not.""" + callback = mock.Mock() + url = "https://httpstat.us/404" + downloader = Downloader.instance() + assert len(downloader._futures) == 0 + downloader.download(url, callback) + assert len(downloader._futures) == 1 + assert callback.call_args_list == [] + while len(downloader._futures) > 0: + Clock.tick() + assert len(downloader._futures) == 0 From 8aa5e57e5af0ae5e59de02e908fde3cbbbc954c7 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 19 Apr 2020 21:50:43 +0200 Subject: [PATCH 17/24] Checks HTTP response status code, fixes #6 On error in the HTTP status code, the callback function will no longer be called. --- kivy_garden/mapview/downloader.py | 1 + tests/test_downloader.py | 11 +++++++++-- tests/utils.py | 12 ++++++++---- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/kivy_garden/mapview/downloader.py b/kivy_garden/mapview/downloader.py index 8257252..b419588 100644 --- a/kivy_garden/mapview/downloader.py +++ b/kivy_garden/mapview/downloader.py @@ -72,6 +72,7 @@ def _download_url(self, url, callback, kwargs): if DEBUG: print("Downloader: download(url) {}".format(url)) response = requests.get(url, **kwargs) + response.raise_for_status() return callback, (url, response) def _load_tile(self, tile): diff --git a/tests/test_downloader.py b/tests/test_downloader.py index 1fcc82b..9cb44a9 100644 --- a/tests/test_downloader.py +++ b/tests/test_downloader.py @@ -39,14 +39,21 @@ def test_download(self): assert len(downloader._futures) == 0 def test_download_status_error(self): - """Error status code should be checked, but is currently not.""" + """ + Error status code should be checked. + Callback function will not be invoked on error. + """ callback = mock.Mock() url = "https://httpstat.us/404" + status_code = 404 downloader = Downloader.instance() assert len(downloader._futures) == 0 - downloader.download(url, callback) + with patch_requests_get(status_code=status_code) as m_get: + downloader.download(url, callback) + assert m_get.call_args_list == [mock.call(url)] assert len(downloader._futures) == 1 assert callback.call_args_list == [] while len(downloader._futures) > 0: Clock.tick() + assert callback.call_args_list == [] assert len(downloader._futures) == 0 diff --git a/tests/utils.py b/tests/utils.py index a4a55ac..8e43c44 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,7 +1,11 @@ from unittest import mock +from requests.models import Response -def patch_requests_get(response_json=None): - response = mock.Mock() - response.return_value.json.return_value = response_json - return mock.patch("requests.get", response) + +def patch_requests_get(response_json=None, status_code=200): + response = Response() + response.json = lambda: response_json + response.status_code = status_code + m_get = mock.Mock(return_value=response) + return mock.patch("requests.get", m_get) From 2b7cc83b66fd0af6d6586173e535d8ceae4cebf9 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 19 Apr 2020 22:00:38 +0200 Subject: [PATCH 18/24] Updates CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30159cc..fcf56df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## [Unreleased] + + - Fix `Downloader` now checks for HTTP status code, refs #6 + ## [1.0.2] - Fix `AttributeError` on `GeoJsonMapLayer.canvas_line` From ba2ff59302f5e0008a6eb47da5225b52c0020df4 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 19 Apr 2020 22:02:42 +0200 Subject: [PATCH 19/24] Removes commented out prints --- kivy_garden/mapview/geojson.py | 2 -- kivy_garden/mapview/mbtsource.py | 1 - kivy_garden/mapview/view.py | 5 ----- 3 files changed, 8 deletions(-) diff --git a/kivy_garden/mapview/geojson.py b/kivy_garden/mapview/geojson.py index 8402380..5fafe62 100644 --- a/kivy_garden/mapview/geojson.py +++ b/kivy_garden/mapview/geojson.py @@ -294,10 +294,8 @@ def on_geojson(self, instance, geojson, update=False): if self.parent is None: return if not update: - # print "Reload geojson (polygon)" self.g_canvas_polygon.clear() self._geojson_part(geojson, geotype="Polygon") - # print "Reload geojson (LineString)" self.canvas_line.clear() self._geojson_part(geojson, geotype="LineString") diff --git a/kivy_garden/mapview/mbtsource.py b/kivy_garden/mapview/mbtsource.py index dc35ebd..e90f87a 100644 --- a/kivy_garden/mapview/mbtsource.py +++ b/kivy_garden/mapview/mbtsource.py @@ -72,7 +72,6 @@ def _load_tile(self, tile): ), (tile.zoom, tile.tile_x, tile.tile_y), ) - # print "fetch", tile.zoom, tile.tile_x, tile.tile_y row = c.fetchone() if not row: tile.state = "done" diff --git a/kivy_garden/mapview/view.py b/kivy_garden/mapview/view.py index 7db4695..48b5457 100644 --- a/kivy_garden/mapview/view.py +++ b/kivy_garden/mapview/view.py @@ -276,7 +276,6 @@ def on_transform(self, *args): self.parent.on_transform(self.transform) def collide_point(self, x, y): - # print "collide_point", x, y return True @@ -806,10 +805,6 @@ def load_visible_tiles(self): y_count, ) = bbox_for_zoom(vx, vy, self.width, self.height, zoom) - # print "Range {},{} to {},{}".format( - # tile_x_first, tile_y_first, - # tile_x_last, tile_y_last) - # Adjust tiles behind us for tile in self._tiles_bg[:]: tile_x = tile.tile_x From 20fd5a51b22084b72e3f85532aab93dab0bba0ee Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 19 Apr 2020 22:32:41 +0200 Subject: [PATCH 20/24] Uses the Kivy logger for debugging --- kivy_garden/mapview/downloader.py | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/kivy_garden/mapview/downloader.py b/kivy_garden/mapview/downloader.py index b419588..93203e3 100644 --- a/kivy_garden/mapview/downloader.py +++ b/kivy_garden/mapview/downloader.py @@ -2,6 +2,7 @@ __all__ = ["Downloader"] +import logging import traceback from concurrent.futures import ThreadPoolExecutor, TimeoutError, as_completed from os import environ, makedirs @@ -11,10 +12,13 @@ import requests from kivy.clock import Clock +from kivy.logger import LOG_LEVELS, Logger from kivy_garden.mapview.constants import CACHE_DIR -DEBUG = "MAPVIEW_DEBUG_DOWNLOADER" in environ +if "MAPVIEW_DEBUG_DOWNLOADER" in environ: + Logger.setLevel(LOG_LEVELS['debug']) + # user agent is needed because since may 2019 OSM gives me a 429 or 403 server error # I tried it with a simpler one (just Mozilla/5.0) this also gets rejected USER_AGENT = 'Kivy-garden.mapview' @@ -53,24 +57,21 @@ def submit(self, f, *args, **kwargs): self._futures.append(future) def download_tile(self, tile): - if DEBUG: - print( - "Downloader: queue(tile) zoom={} x={} y={}".format( - tile.zoom, tile.tile_x, tile.tile_y - ) + Logger.debug( + "Downloader: queue(tile) zoom={} x={} y={}".format( + tile.zoom, tile.tile_x, tile.tile_y ) + ) future = self.executor.submit(self._load_tile, tile) self._futures.append(future) def download(self, url, callback, **kwargs): - if DEBUG: - print("Downloader: queue(url) {}".format(url)) + Logger.debug("Downloader: queue(url) {}".format(url)) future = self.executor.submit(self._download_url, url, callback, kwargs) self._futures.append(future) def _download_url(self, url, callback, kwargs): - if DEBUG: - print("Downloader: download(url) {}".format(url)) + Logger.debug("Downloader: download(url) {}".format(url)) response = requests.get(url, **kwargs) response.raise_for_status() return callback, (url, response) @@ -80,23 +81,20 @@ def _load_tile(self, tile): return cache_fn = tile.cache_fn if exists(cache_fn): - if DEBUG: - print("Downloader: use cache {}".format(cache_fn)) + Logger.debug("Downloader: use cache {}".format(cache_fn)) return tile.set_source, (cache_fn,) tile_y = tile.map_source.get_row_count(tile.zoom) - tile.tile_y - 1 uri = tile.map_source.url.format( z=tile.zoom, x=tile.tile_x, y=tile_y, s=choice(tile.map_source.subdomains) ) - if DEBUG: - print("Downloader: download(tile) {}".format(uri)) + Logger.debug("Downloader: download(tile) {}".format(uri)) response = requests.get(uri, headers={'User-agent': USER_AGENT}, timeout=5) try: response.raise_for_status() data = response.content with open(cache_fn, "wb") as fd: fd.write(data) - if DEBUG: - print("Downloaded {} bytes: {}".format(len(data), uri)) + Logger.debug("Downloaded {} bytes: {}".format(len(data), uri)) return tile.set_source, (cache_fn,) except Exception as e: print("Downloader error: {!r}".format(e)) From c89661ca0fb49acf5e8ac417b085ed9f25afe11f Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 19 Apr 2020 23:19:05 +0200 Subject: [PATCH 21/24] PyPI badge and CHANGELOG.md update --- CHANGELOG.md | 2 +- README.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcf56df..d543f75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ ## [1.0.2] - - Fix `AttributeError` on `GeoJsonMapLayer.canvas_line` + - Fix `AttributeError` on `GeoJsonMapLayer.canvas_line`, refs #12 ## [1.0.1] diff --git a/README.md b/README.md index a19e402..5439318 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![Github Build Status](https://github.com/kivy-garden/mapview/workflows/Tests/badge.svg)](https://github.com/kivy-garden/mapview/actions?query=workflow%3ATests) [![Build Status](https://travis-ci.com/kivy-garden/mapview.svg?branch=develop)](https://travis-ci.com/kivy-garden/mapview) [![Coverage Status](https://coveralls.io/repos/github/kivy-garden/mapview/badge.svg?branch=develop)](https://coveralls.io/github/kivy-garden/mapview?branch=develop) +[![PyPI version](https://badge.fury.io/py/mapview.svg)](https://badge.fury.io/py/mapview) Mapview is a Kivy widget for displaying interactive maps. It has been designed with lot of inspirations of From 79401ab76098c3ea1918cec28a8fc8ef586f253e Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 19 Apr 2020 23:55:06 +0200 Subject: [PATCH 22/24] Drops super() calls for classes without parent --- kivy_garden/mapview/clustered_marker_layer.py | 11 +++++------ kivy_garden/mapview/downloader.py | 1 - kivy_garden/mapview/source.py | 1 - 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/kivy_garden/mapview/clustered_marker_layer.py b/kivy_garden/mapview/clustered_marker_layer.py index 91f7bc8..c2eaac0 100644 --- a/kivy_garden/mapview/clustered_marker_layer.py +++ b/kivy_garden/mapview/clustered_marker_layer.py @@ -62,10 +62,12 @@ def yLat(y): class KDBush: - # kdbush implementation from https://github.com/mourner/kdbush/blob/master/src/kdbush.js - # + """ + kdbush implementation from: + https://github.com/mourner/kdbush/blob/master/src/kdbush.js + """ + def __init__(self, points, node_size=64): - super().__init__() self.points = points self.node_size = node_size @@ -231,7 +233,6 @@ def _sq_dist(self, ax, ay, bx, by): class Cluster: def __init__(self, x, y, num_points, id, props): - super().__init__() self.x = x self.y = y self.num_points = num_points @@ -248,7 +249,6 @@ def __init__(self, x, y, num_points, id, props): class Marker: def __init__(self, lon, lat, cls=MapMarker, options=None): - super().__init__() self.lon = lon self.lat = lat self.cls = cls @@ -275,7 +275,6 @@ class SuperCluster: """ def __init__(self, min_zoom=0, max_zoom=16, radius=40, extent=512, node_size=64): - super().__init__() self.min_zoom = min_zoom self.max_zoom = max_zoom self.radius = radius diff --git a/kivy_garden/mapview/downloader.py b/kivy_garden/mapview/downloader.py index 93203e3..2706ad8 100644 --- a/kivy_garden/mapview/downloader.py +++ b/kivy_garden/mapview/downloader.py @@ -43,7 +43,6 @@ def __init__(self, max_workers=None, cap_time=None, **kwargs): max_workers = Downloader.MAX_WORKERS if cap_time is None: cap_time = Downloader.CAP_TIME - super().__init__() self.is_paused = False self.cap_time = cap_time self.executor = ThreadPoolExecutor(max_workers=max_workers) diff --git a/kivy_garden/mapview/source.py b/kivy_garden/mapview/source.py index 41adda7..61722fd 100644 --- a/kivy_garden/mapview/source.py +++ b/kivy_garden/mapview/source.py @@ -113,7 +113,6 @@ def __init__( subdomains="abc", **kwargs ): - super().__init__() if cache_key is None: # possible cache hit, but very unlikely cache_key = hashlib.sha224(url.encode("utf8")).hexdigest()[:10] From 5b9cfa01203d9d08dd28b487d1009921f24ce177 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Mon, 20 Apr 2020 00:13:19 +0200 Subject: [PATCH 23/24] Update README.md --- README.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 5439318..7655eaf 100644 --- a/README.md +++ b/README.md @@ -32,13 +32,7 @@ the latests state-of-the-art Kivy's methods. # Requirements -It requires the `concurrent.futures` and `requests`. If you are on python 2.7, -you can use `futures`: - -``` -pip install futures requests -``` - +It requires the `concurrent.futures` and `requests`. If you use it on Android / iOS, don't forget to add `openssl` as a requirements, otherwise you'll have an issue when importing `urllib3` from `requests`. @@ -65,7 +59,9 @@ class MapViewApp(App): MapViewApp().run() ``` -More extensive documentation will come soon. +Find out more: +- [examples/](https://github.com/kivy-garden/mapview/tree/master/examples) +- Contributing From f800b7f465d3b82c6550347e694ede3aabbf9955 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Mon, 20 Apr 2020 09:06:04 +0200 Subject: [PATCH 24/24] 1.0.3 --- kivy_garden/mapview/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kivy_garden/mapview/_version.py b/kivy_garden/mapview/_version.py index 90c32f7..976498a 100644 --- a/kivy_garden/mapview/_version.py +++ b/kivy_garden/mapview/_version.py @@ -1 +1 @@ -__version__ = '1.0.2.dev0' +__version__ = "1.0.3"