From f6c43c0cd016bfc4436260dd19a27837bcac1cda Mon Sep 17 00:00:00 2001 From: lowtower Date: Mon, 1 Nov 2021 11:38:10 +0100 Subject: [PATCH 001/147] open with encoding --- examples/draw_gpx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/draw_gpx.py b/examples/draw_gpx.py index 584227d..fd9e6ae 100644 --- a/examples/draw_gpx.py +++ b/examples/draw_gpx.py @@ -11,7 +11,7 @@ context = staticmaps.Context() context.set_tile_provider(staticmaps.tile_provider_ArcGISWorldImagery) -with open(sys.argv[1], "r") as file: +with open(sys.argv[1], "r", encoding='utf-8') as file: gpx = gpxpy.parse(file) for track in gpx.tracks: From 64b6952a474a05c9f98002048a7983512ece8998 Mon Sep 17 00:00:00 2001 From: lowtower Date: Mon, 1 Nov 2021 11:39:46 +0100 Subject: [PATCH 002/147] Update draw_gpx.py --- examples/draw_gpx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/draw_gpx.py b/examples/draw_gpx.py index fd9e6ae..f7e71e6 100644 --- a/examples/draw_gpx.py +++ b/examples/draw_gpx.py @@ -11,7 +11,7 @@ context = staticmaps.Context() context.set_tile_provider(staticmaps.tile_provider_ArcGISWorldImagery) -with open(sys.argv[1], "r", encoding='utf-8') as file: +with open(sys.argv[1], "r", encoding="utf-8") as file: gpx = gpxpy.parse(file) for track in gpx.tracks: From 6e26d43cdcd4f537f69d1aab4e229758addfa54f Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 9 Dec 2021 14:10:15 +0100 Subject: [PATCH 003/147] - add carto providers with labels --- staticmaps/tile_provider.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/staticmaps/tile_provider.py b/staticmaps/tile_provider.py index a5d724d..e2e26a4 100644 --- a/staticmaps/tile_provider.py +++ b/staticmaps/tile_provider.py @@ -127,6 +127,14 @@ def url(self, zoom: int, x: int, y: int) -> typing.Optional[str]: max_zoom=24, ) +tile_provider_Carto = TileProvider( + "carto", + url_pattern="http://$s.basemaps.cartocdn.com/rastertiles/light_all/$z/$x/$y.png", + shards=["a", "b", "c", "d"], + attribution="Maps (C) CARTO (C) OpenStreetMap.org contributors", + max_zoom=20, +) + tile_provider_CartoNoLabels = TileProvider( "carto-nolabels", url_pattern="http://$s.basemaps.cartocdn.com/rastertiles/light_nolabels/$z/$x/$y.png", @@ -135,6 +143,14 @@ def url(self, zoom: int, x: int, y: int) -> typing.Optional[str]: max_zoom=20, ) +tile_provider_CartoDark = TileProvider( + "carto-dark", + url_pattern="http://$s.basemaps.cartocdn.com/rastertiles/dark_all/$z/$x/$y.png", + shards=["a", "b", "c", "d"], + attribution="Maps (C) CARTO (C) OpenStreetMap.org contributors", + max_zoom=20, +) + tile_provider_CartoDarkNoLabels = TileProvider( "carto-darknolabels", url_pattern="http://$s.basemaps.cartocdn.com/rastertiles/dark_nolabels/$z/$x/$y.png", @@ -156,7 +172,9 @@ def url(self, zoom: int, x: int, y: int) -> typing.Optional[str]: tile_provider_StamenToner.name(): tile_provider_StamenToner, tile_provider_StamenTonerLite.name(): tile_provider_StamenTonerLite, tile_provider_ArcGISWorldImagery.name(): tile_provider_ArcGISWorldImagery, + tile_provider_Carto.name(): tile_provider_Carto, tile_provider_CartoNoLabels.name(): tile_provider_CartoNoLabels, + tile_provider_CartoDark.name(): tile_provider_CartoDark, tile_provider_CartoDarkNoLabels.name(): tile_provider_CartoDarkNoLabels, tile_provider_None.name(): tile_provider_None, } From ce512f8f06ac24c0a8d1276ff5cd6e93eb4b5643 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 9 Dec 2021 17:13:30 +0100 Subject: [PATCH 004/147] support for api keys in cli args new tile_provider "stadia maps" (requires key) --- staticmaps/cli.py | 9 ++++++++- staticmaps/context.py | 6 +++++- staticmaps/tile_provider.py | 20 ++++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/staticmaps/cli.py b/staticmaps/cli.py index f0fa295..62b37eb 100755 --- a/staticmaps/cli.py +++ b/staticmaps/cli.py @@ -86,6 +86,13 @@ def main() -> None: choices=staticmaps.default_tile_providers.keys(), default=staticmaps.tile_provider_OSM.name(), ) + args_parser.add_argument( + "--tiles-api-key", + dest="tiles_api_key", + metavar="API_KEY", + type=str, + default=None, + ) args_parser.add_argument( "--file-format", metavar="FORMAT", @@ -104,7 +111,7 @@ def main() -> None: context = staticmaps.Context() - context.set_tile_provider(staticmaps.default_tile_providers[args.tiles]) + context.set_tile_provider(staticmaps.default_tile_providers[args.tiles], args.tiles_api_key) if args.center is not None: context.set_center(staticmaps.parse_latlng(args.center)) diff --git a/staticmaps/context.py b/staticmaps/context.py index d058566..4780916 100644 --- a/staticmaps/context.py +++ b/staticmaps/context.py @@ -77,13 +77,17 @@ def set_tile_downloader(self, downloader: TileDownloader) -> None: """ self._tile_downloader = downloader - def set_tile_provider(self, provider: TileProvider) -> None: + def set_tile_provider(self, provider: TileProvider, api_key: typing.Optional[str] = None) -> None: """Set tile provider :param provider: tile provider :type provider: TileProvider + :param api_key: api key (if needed) + :type api_key: str """ self._tile_provider = provider + if api_key: + self._tile_provider.set_api_key(api_key) def add_object(self, obj: Object) -> None: """Add object for the static map (e.g. line, area, marker) diff --git a/staticmaps/tile_provider.py b/staticmaps/tile_provider.py index e2e26a4..1e7c5e9 100644 --- a/staticmaps/tile_provider.py +++ b/staticmaps/tile_provider.py @@ -159,6 +159,24 @@ def url(self, zoom: int, x: int, y: int) -> typing.Optional[str]: max_zoom=20, ) +tile_provider_StadiaAlidadeSmooth = TileProvider( + "stadia-alidade-smooth", + url_pattern="https://tiles.stadiamaps.com/tiles/alidade_smooth/$z/$x/$y.png?api_key=$k", + shards=["a", "b", "c", "d"], + attribution="Maps (C) Stadia Maps (C) OpenMapTiles (C) OpenStreetMap.org contributors", + max_zoom=20, + api_key="", +) + +tile_provider_StadiaAlidadeSmoothDark = TileProvider( + "stadia-alidade-smooth", + url_pattern="https://tiles.stadiamaps.com/tiles/alidade_smooth_dark/$z/$x/$y.png?api_key=$k", + shards=["a", "b", "c", "d"], + attribution="Maps (C) Stadia Maps (C) OpenMapTiles (C) OpenStreetMap.org contributors", + max_zoom=20, + api_key="", +) + tile_provider_None = TileProvider( "none", url_pattern="", @@ -176,5 +194,7 @@ def url(self, zoom: int, x: int, y: int) -> typing.Optional[str]: tile_provider_CartoNoLabels.name(): tile_provider_CartoNoLabels, tile_provider_CartoDark.name(): tile_provider_CartoDark, tile_provider_CartoDarkNoLabels.name(): tile_provider_CartoDarkNoLabels, + tile_provider_StadiaAlidadeSmooth.name(): tile_provider_StadiaAlidadeSmooth, + tile_provider_StadiaAlidadeSmoothDark.name(): tile_provider_StadiaAlidadeSmoothDark, tile_provider_None.name(): tile_provider_None, } From 8ad6dfafb1002e61c24e7a824043718e084cd9b6 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 9 Dec 2021 17:48:41 +0100 Subject: [PATCH 005/147] new tile_provider "jawg maps" (requires key) --- staticmaps/tile_provider.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/staticmaps/tile_provider.py b/staticmaps/tile_provider.py index 1e7c5e9..f18af38 100644 --- a/staticmaps/tile_provider.py +++ b/staticmaps/tile_provider.py @@ -174,7 +174,22 @@ def url(self, zoom: int, x: int, y: int) -> typing.Optional[str]: shards=["a", "b", "c", "d"], attribution="Maps (C) Stadia Maps (C) OpenMapTiles (C) OpenStreetMap.org contributors", max_zoom=20, - api_key="", +) + +tile_provider_JawgLight = TileProvider( + "jawg-light", + url_pattern="https://$s.tile.jawg.io/jawg-light/$z/$x/$y.png?access-token=$k", + shards=["a", "b", "c", "d"], + attribution="Maps (C) Jawg Maps (C) OpenStreetMap.org contributors", + max_zoom=22, +) + +tile_provider_JawgDark = TileProvider( + "jawg-dark", + url_pattern="https://$s.tile.jawg.io/jawg-dark/$z/$x/$y.png?access-token=$k", + shards=["a", "b", "c", "d"], + attribution="Maps (C) Jawg Maps (C) OpenStreetMap.org contributors", + max_zoom=22, ) tile_provider_None = TileProvider( @@ -196,5 +211,7 @@ def url(self, zoom: int, x: int, y: int) -> typing.Optional[str]: tile_provider_CartoDarkNoLabels.name(): tile_provider_CartoDarkNoLabels, tile_provider_StadiaAlidadeSmooth.name(): tile_provider_StadiaAlidadeSmooth, tile_provider_StadiaAlidadeSmoothDark.name(): tile_provider_StadiaAlidadeSmoothDark, + tile_provider_JawgLight.name(): tile_provider_JawgLight, + tile_provider_JawgDark.name(): tile_provider_JawgDark, tile_provider_None.name(): tile_provider_None, } From e5a5d67990ba2980d4e409ea3516e54afcf7b710 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 17 Jan 2022 13:20:34 +0100 Subject: [PATCH 006/147] extend gitignore --- .gitignore | 70 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index e2c050f..b749d69 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,62 @@ -.cache -.env -.mypy_cache -.pytest_cache -.vscode -*.egg-info -*.pyc -*.png -*.svg +# Python __pycache__ -build +*.pyc +# sqlite +*.sql +*.sqlite3 +*.sqlite +# Sphinx documentation build +_build/ +# Testing +.cache/ +.eggs/ +junit*.xml +# virtualenv +.env*/ +env*/ +.venv*/ +venv*/ +# setuptools +*.egg-info +dist/ +# coverage.py +.coverage +htmlcov/ +coverage*.xml +# Temporary data directory +tmp/* +.pytest_cache/* +# OSX +.DS_Store +# PyCharm +.idea +# VS Code +.vscode +pylint_usage_code.txt +pylint_usage_tests.txt +rackguru-api.iml +# Jupyter +.ipynb_checkpoints/ +*.ipynb +# Static files +# +# Build dist +sdist +.tox +# mypy +.mypy_cache + +# Project specific +gpx_dir/* +output/* +data +*.svg +*.gif +*.mov +*.avi +*.mpg +*.mpeg +*.mp4 +*.mkv +*.wmv' From 3bc17c0e807f8aa917431d46f532d49b51c5e281 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 17 Jan 2022 13:22:59 +0100 Subject: [PATCH 007/147] add python 3.9 to Classifiers --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 143b45a..b780f35 100644 --- a/setup.py +++ b/setup.py @@ -58,6 +58,7 @@ def _read_reqs(rel_path: str) -> typing.List[str]: "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", ], keywords="map staticmap osm markers", packages=[PACKAGE], From 8f309637a7d3c81acf576bb79ddf6f59f94e3a11 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 17 Jan 2022 13:33:35 +0100 Subject: [PATCH 008/147] add windows and macOS to workflow matrix --- .github/workflows/main.yml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 01627ee..e2e1539 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,20 +4,26 @@ on: [push, pull_request] jobs: lint_and_test: - runs-on: ubuntu-latest + runs-on: ${{matrix.os}} strategy: max-parallel: 4 matrix: + os: [ubuntu-latest, macos-latest, windows-latest] python-version: [3.6, 3.7, 3.8, 3.9] steps: + - name: Git config + run: git config --global core.autocrlf input - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} + - name: Set up Python v${{matrix.python-version}} - ${{runner.os}} uses: actions/setup-python@v2 with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies + python-version: ${{matrix.python-version}} + cache: pip + - name: Display Python version + run: python --version + - name: Install Python dependencies run: | - python -m pip install --upgrade pip + python -m pip install --upgrade pip setuptools wheel pip install -r requirements.txt pip install -r requirements-dev.txt pip install -r requirements-examples.txt From b88123c1bff30fdf3ff5b957af7b20e20addeb77 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 17 Jan 2022 15:06:36 +0100 Subject: [PATCH 009/147] add automatic build of examples to workflow --- .github/workflows/main.yml | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e2e1539..e5883b3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,7 +26,6 @@ jobs: python -m pip install --upgrade pip setuptools wheel pip install -r requirements.txt pip install -r requirements-dev.txt - pip install -r requirements-examples.txt - name: Check formatting (black) run: black --line-length 120 --check --diff staticmaps examples tests - name: Lint (pylint) @@ -37,3 +36,36 @@ jobs: run: mypy staticmaps examples tests - name: Run tests (pytest) run: python -m pytest tests + build: + runs-on: ${{matrix.os}} + strategy: + matrix: + os: [ubuntu-latest] + python-version: [3.7] + steps: + - name: Git config + run: git config --global core.autocrlf input + - uses: actions/checkout@v2 + - name: Set up Python v${{matrix.python-version}} - ${{runner.os}} + uses: actions/setup-python@v2 + with: + python-version: ${{matrix.python-version}} + cache: pip + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip setuptools wheel + pip install -r requirements.txt + pip install -r requirements-examples.txt + - name: Build examples + uses: actions/upload-artifact@v2 + run: + (cd examples && PYTHONPATH=.. ../.env/bin/python custom_objects.py && custom_objects*png build/.) + (cd examples && PYTHONPATH=.. ../.env/bin/python draw_gpx.py running.gpx && draw_gpx*png build/.) + (cd examples && PYTHONPATH=.. ../.env/bin/python frankfurt_newyork.py && frankfurt_newyork*png build/.) + (cd examples && PYTHONPATH=.. ../.env/bin/python freiburg_area.py && freiburg_area*png build/.) + (cd examples && PYTHONPATH=.. ../.env/bin/python geodesic_circles.py && geodesic_circles*png build/.) + (cd examples && PYTHONPATH=.. ../.env/bin/python tile_providers.py && tile_providers*png build/.) + (cd examples && PYTHONPATH=.. ../.env/bin/python us_capitals.py && us_capitals*png build/.) + with: + name: build_examples + path: examples/build From 3bea0befa77f2e0e670e591f4f90b821334e7d81 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 17 Jan 2022 15:17:20 +0100 Subject: [PATCH 010/147] move generated examples to build subfolder --- .github/workflows/main.yml | 18 +++++++++++------- Makefile | 5 ++++- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e5883b3..26302fc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -59,13 +59,17 @@ jobs: - name: Build examples uses: actions/upload-artifact@v2 run: - (cd examples && PYTHONPATH=.. ../.env/bin/python custom_objects.py && custom_objects*png build/.) - (cd examples && PYTHONPATH=.. ../.env/bin/python draw_gpx.py running.gpx && draw_gpx*png build/.) - (cd examples && PYTHONPATH=.. ../.env/bin/python frankfurt_newyork.py && frankfurt_newyork*png build/.) - (cd examples && PYTHONPATH=.. ../.env/bin/python freiburg_area.py && freiburg_area*png build/.) - (cd examples && PYTHONPATH=.. ../.env/bin/python geodesic_circles.py && geodesic_circles*png build/.) - (cd examples && PYTHONPATH=.. ../.env/bin/python tile_providers.py && tile_providers*png build/.) - (cd examples && PYTHONPATH=.. ../.env/bin/python us_capitals.py && us_capitals*png build/.) + (cd examples && PYTHONPATH=.. ../.env/bin/python custom_objects.py) + (cd examples && PYTHONPATH=.. ../.env/bin/python draw_gpx.py running.gpx) + (cd examples && PYTHONPATH=.. ../.env/bin/python frankfurt_newyork.py) + (cd examples && PYTHONPATH=.. ../.env/bin/python freiburg_area.py) + (cd examples && PYTHONPATH=.. ../.env/bin/python geodesic_circles.py) + (cd examples && PYTHONPATH=.. ../.env/bin/python tile_providers.py) + (cd examples && PYTHONPATH=.. ../.env/bin/python us_capitals.py) + (cd examples && mv *.svg build/.) + (cd examples && mv *pillow*.png build/.) + (cd examples && mv *cairo*.png build/.) + (cd -) with: name: build_examples path: examples/build diff --git a/Makefile b/Makefile index 8fd87be..0928763 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,10 @@ run-examples: (cd examples && PYTHONPATH=.. ../.env/bin/python geodesic_circles.py) (cd examples && PYTHONPATH=.. ../.env/bin/python tile_providers.py) (cd examples && PYTHONPATH=.. ../.env/bin/python us_capitals.py) - + (cd examples && mv *.svg build/.) + (cd examples && mv *pillow*png build/.) + (cd examples && mv *cairo*png build/.) + (cd -) .PHONY: test test: PYTHONPATH=. .env/bin/python -m pytest tests From e2ca730337f09c7c57d050c1fdc51f258438c3fe Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 17 Jan 2022 15:34:06 +0100 Subject: [PATCH 011/147] correct upload of examples --- .github/workflows/main.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 26302fc..f9c6cb5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -57,7 +57,6 @@ jobs: pip install -r requirements.txt pip install -r requirements-examples.txt - name: Build examples - uses: actions/upload-artifact@v2 run: (cd examples && PYTHONPATH=.. ../.env/bin/python custom_objects.py) (cd examples && PYTHONPATH=.. ../.env/bin/python draw_gpx.py running.gpx) @@ -70,6 +69,8 @@ jobs: (cd examples && mv *pillow*.png build/.) (cd examples && mv *cairo*.png build/.) (cd -) + - name: Archive examples + uses: actions/upload-artifact@v2 with: name: build_examples path: examples/build From 8f3a58b3175a9639df3df6d615ed65889ae36c00 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 17 Jan 2022 15:36:40 +0100 Subject: [PATCH 012/147] correct syntax --- .github/workflows/main.yml | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f9c6cb5..b84aa8e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -58,17 +58,18 @@ jobs: pip install -r requirements-examples.txt - name: Build examples run: - (cd examples && PYTHONPATH=.. ../.env/bin/python custom_objects.py) - (cd examples && PYTHONPATH=.. ../.env/bin/python draw_gpx.py running.gpx) - (cd examples && PYTHONPATH=.. ../.env/bin/python frankfurt_newyork.py) - (cd examples && PYTHONPATH=.. ../.env/bin/python freiburg_area.py) - (cd examples && PYTHONPATH=.. ../.env/bin/python geodesic_circles.py) - (cd examples && PYTHONPATH=.. ../.env/bin/python tile_providers.py) - (cd examples && PYTHONPATH=.. ../.env/bin/python us_capitals.py) - (cd examples && mv *.svg build/.) - (cd examples && mv *pillow*.png build/.) - (cd examples && mv *cairo*.png build/.) - (cd -) + cd examples + PYTHONPATH=.. ../.env/bin/python custom_objects.py + PYTHONPATH=.. ../.env/bin/python draw_gpx.py running.gpx + PYTHONPATH=.. ../.env/bin/python frankfurt_newyork.py + PYTHONPATH=.. ../.env/bin/python freiburg_area.py + PYTHONPATH=.. ../.env/bin/python geodesic_circles.py + PYTHONPATH=.. ../.env/bin/python tile_providers.py + PYTHONPATH=.. ../.env/bin/python us_capitals.py + mv *.svg build/. + mv *pillow*.png build/. + mv *cairo*.png build/. + cd - - name: Archive examples uses: actions/upload-artifact@v2 with: From ee0902a25eddf920bcc4d6a505fd2f892e61c600 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 17 Jan 2022 15:41:29 +0100 Subject: [PATCH 013/147] correct syntax --- .github/workflows/main.yml | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b84aa8e..a379577 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -42,6 +42,7 @@ jobs: matrix: os: [ubuntu-latest] python-version: [3.7] + needs: "lint_and_test" steps: - name: Git config run: git config --global core.autocrlf input @@ -58,18 +59,18 @@ jobs: pip install -r requirements-examples.txt - name: Build examples run: - cd examples - PYTHONPATH=.. ../.env/bin/python custom_objects.py - PYTHONPATH=.. ../.env/bin/python draw_gpx.py running.gpx - PYTHONPATH=.. ../.env/bin/python frankfurt_newyork.py - PYTHONPATH=.. ../.env/bin/python freiburg_area.py - PYTHONPATH=.. ../.env/bin/python geodesic_circles.py - PYTHONPATH=.. ../.env/bin/python tile_providers.py - PYTHONPATH=.. ../.env/bin/python us_capitals.py - mv *.svg build/. - mv *pillow*.png build/. - mv *cairo*.png build/. - cd - + cd examples ; + PYTHONPATH=.. ../.env/bin/python custom_objects.py ; + PYTHONPATH=.. ../.env/bin/python draw_gpx.py running.gpx ; + PYTHONPATH=.. ../.env/bin/python frankfurt_newyork.py ; + PYTHONPATH=.. ../.env/bin/python freiburg_area.py ; + PYTHONPATH=.. ../.env/bin/python geodesic_circles.py ; + PYTHONPATH=.. ../.env/bin/python tile_providers.py ; + PYTHONPATH=.. ../.env/bin/python us_capitals.py ; + mv *.svg build/. ; + mv *pillow*.png build/. ; + mv *cairo*.png build/. ; + cd - ; - name: Archive examples uses: actions/upload-artifact@v2 with: From 6c066c47b6e7522d0b5e2826096c4ba2a6d8cab9 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 17 Jan 2022 15:42:22 +0100 Subject: [PATCH 014/147] correct syntax --- .github/workflows/main.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a379577..1df704e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -58,19 +58,19 @@ jobs: pip install -r requirements.txt pip install -r requirements-examples.txt - name: Build examples - run: - cd examples ; - PYTHONPATH=.. ../.env/bin/python custom_objects.py ; - PYTHONPATH=.. ../.env/bin/python draw_gpx.py running.gpx ; - PYTHONPATH=.. ../.env/bin/python frankfurt_newyork.py ; - PYTHONPATH=.. ../.env/bin/python freiburg_area.py ; - PYTHONPATH=.. ../.env/bin/python geodesic_circles.py ; - PYTHONPATH=.. ../.env/bin/python tile_providers.py ; - PYTHONPATH=.. ../.env/bin/python us_capitals.py ; - mv *.svg build/. ; - mv *pillow*.png build/. ; - mv *cairo*.png build/. ; - cd - ; + run: | + cd examples + PYTHONPATH=.. ../.env/bin/python custom_objects.py + PYTHONPATH=.. ../.env/bin/python draw_gpx.py running.gpx + PYTHONPATH=.. ../.env/bin/python frankfurt_newyork.py + PYTHONPATH=.. ../.env/bin/python freiburg_area.py + PYTHONPATH=.. ../.env/bin/python geodesic_circles.py + PYTHONPATH=.. ../.env/bin/python tile_providers.py + PYTHONPATH=.. ../.env/bin/python us_capitals.py + mv *.svg build/. + mv *pillow*.png build/. + mv *cairo*.png build/. + cd - - name: Archive examples uses: actions/upload-artifact@v2 with: From 71792cad3554112d30d9ad4fe650946e3355588a Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 17 Jan 2022 15:43:59 +0100 Subject: [PATCH 015/147] add examples requirements to linting again --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1df704e..58bed49 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,6 +26,7 @@ jobs: python -m pip install --upgrade pip setuptools wheel pip install -r requirements.txt pip install -r requirements-dev.txt + pip install -r requirements-examples.txt - name: Check formatting (black) run: black --line-length 120 --check --diff staticmaps examples tests - name: Lint (pylint) From b3466897b38ee03daaad8cf700f4f37b386df50a Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 17 Jan 2022 15:54:09 +0100 Subject: [PATCH 016/147] create virtual environment first --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 58bed49..ccab3e4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -55,6 +55,7 @@ jobs: cache: pip - name: Install Python dependencies run: | + python -m venv .env python -m pip install --upgrade pip setuptools wheel pip install -r requirements.txt pip install -r requirements-examples.txt From 1cd8813a36c278ac1ba0ada5901919307e184b73 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 17 Jan 2022 15:55:09 +0100 Subject: [PATCH 017/147] create virtual environment first --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ccab3e4..5465c47 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -55,7 +55,7 @@ jobs: cache: pip - name: Install Python dependencies run: | - python -m venv .env + python -m venv .env python -m pip install --upgrade pip setuptools wheel pip install -r requirements.txt pip install -r requirements-examples.txt From f4db1331aeb7741588da8fba8944917dd319ff1c Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 17 Jan 2022 16:02:21 +0100 Subject: [PATCH 018/147] discard venv --- .github/workflows/main.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5465c47..3980351 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -55,20 +55,19 @@ jobs: cache: pip - name: Install Python dependencies run: | - python -m venv .env python -m pip install --upgrade pip setuptools wheel pip install -r requirements.txt pip install -r requirements-examples.txt - name: Build examples run: | cd examples - PYTHONPATH=.. ../.env/bin/python custom_objects.py - PYTHONPATH=.. ../.env/bin/python draw_gpx.py running.gpx - PYTHONPATH=.. ../.env/bin/python frankfurt_newyork.py - PYTHONPATH=.. ../.env/bin/python freiburg_area.py - PYTHONPATH=.. ../.env/bin/python geodesic_circles.py - PYTHONPATH=.. ../.env/bin/python tile_providers.py - PYTHONPATH=.. ../.env/bin/python us_capitals.py + PYTHONPATH=.. python custom_objects.py + PYTHONPATH=.. python draw_gpx.py running.gpx + PYTHONPATH=.. python frankfurt_newyork.py + PYTHONPATH=.. python freiburg_area.py + PYTHONPATH=.. python geodesic_circles.py + PYTHONPATH=.. python tile_providers.py + PYTHONPATH=.. python us_capitals.py mv *.svg build/. mv *pillow*.png build/. mv *cairo*.png build/. From 5e97181f9b0c7ba7a49b2c73f8bbbbe323763c74 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 17 Jan 2022 16:10:42 +0100 Subject: [PATCH 019/147] mkdir build directory --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3980351..2b53419 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -61,6 +61,7 @@ jobs: - name: Build examples run: | cd examples + mkdir build PYTHONPATH=.. python custom_objects.py PYTHONPATH=.. python draw_gpx.py running.gpx PYTHONPATH=.. python frankfurt_newyork.py From 0706247e1c826816e7ec6e208e21ce418b2f2a6a Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 17 Jan 2022 16:27:31 +0100 Subject: [PATCH 020/147] if statement for image files --- .github/workflows/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2b53419..e33884c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -69,9 +69,9 @@ jobs: PYTHONPATH=.. python geodesic_circles.py PYTHONPATH=.. python tile_providers.py PYTHONPATH=.. python us_capitals.py - mv *.svg build/. - mv *pillow*.png build/. - mv *cairo*.png build/. + (ls *cairo*.png && mv *.svg build/.) || echo "no svg files found!" + (ls *cairo*.png && mv *pillow*.png build/.) || echo "no pllow png files found!" + (ls *cairo*.png && mv *cairo*.png build/.) || echo "no cairo png files found!" cd - - name: Archive examples uses: actions/upload-artifact@v2 From 3e81014539b367249574b8c9d6b73e6800dff290 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 17 Jan 2022 16:36:36 +0100 Subject: [PATCH 021/147] correct ls statements --- .github/workflows/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e33884c..dac565d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -69,9 +69,9 @@ jobs: PYTHONPATH=.. python geodesic_circles.py PYTHONPATH=.. python tile_providers.py PYTHONPATH=.. python us_capitals.py - (ls *cairo*.png && mv *.svg build/.) || echo "no svg files found!" - (ls *cairo*.png && mv *pillow*.png build/.) || echo "no pllow png files found!" - (ls *cairo*.png && mv *cairo*.png build/.) || echo "no cairo png files found!" + (ls *.svg && mv *.svg build/.) || echo "no svg files found!" + (ls *pillow*.png && mv *pillow*.png build/.) || echo "no pillow png files found!" + (ls *cairo*.png && mv *cairo*.png build/.) || echo "no cairo png files found!" cd - - name: Archive examples uses: actions/upload-artifact@v2 From 2b1dc88595cbb6c411fdaf83e2cf69b595f48cca Mon Sep 17 00:00:00 2001 From: Lowtower Date: Fri, 21 Jan 2022 11:13:26 +0100 Subject: [PATCH 022/147] implement tightening to absolute boundaries (of all objects) for svg --- staticmaps/cairo_renderer.py | 12 +++++++++-- staticmaps/context.py | 5 +++-- staticmaps/pillow_renderer.py | 11 ++++++++-- staticmaps/renderer.py | 15 ++++++++++---- staticmaps/svg_renderer.py | 39 ++++++++++++++++++++++++++++++++--- 5 files changed, 69 insertions(+), 13 deletions(-) diff --git a/staticmaps/cairo_renderer.py b/staticmaps/cairo_renderer.py index d27d151..0cfd7af 100644 --- a/staticmaps/cairo_renderer.py +++ b/staticmaps/cairo_renderer.py @@ -6,6 +6,8 @@ import sys import typing +import s2sphere # type: ignore + try: import cairo # type: ignore except ImportError: @@ -83,11 +85,13 @@ def create_image(image_data: bytes) -> cairo_ImageSurface: png_bytes.seek(0) return cairo.ImageSurface.create_from_png(png_bytes) - def render_objects(self, objects: typing.List["Object"]) -> None: + def render_objects(self, objects: typing.List["Object"], bbox: s2sphere.LatLngRect = None) -> None: """Render all objects of static map :param objects: objects of static map :type objects: typing.List["Object"] + :param bbox: boundary box of all objects + :type bbox: s2sphere.LatLngRect """ x_count = math.ceil(self._trans.image_width() / (2 * self._trans.world_width())) for obj in objects: @@ -109,11 +113,15 @@ def render_background(self, color: typing.Optional[Color]) -> None: self._context.rectangle(0, 0, *self._trans.image_size()) self._context.fill() - def render_tiles(self, download: typing.Callable[[int, int, int], typing.Optional[bytes]]) -> None: + def render_tiles( + self, download: typing.Callable[[int, int, int], typing.Optional[bytes]], bbox: s2sphere.LatLngRect = None + ) -> None: """Render background of static map :param download: url of tiles provider :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] + :param bbox: boundary box of all objects + :type bbox: s2sphere.LatLngRect """ for yy in range(0, self._trans.tiles_y()): y = self._trans.first_tile_y() + yy diff --git a/staticmaps/context.py b/staticmaps/context.py index d058566..cf7e90c 100644 --- a/staticmaps/context.py +++ b/staticmaps/context.py @@ -187,11 +187,12 @@ def render_svg(self, width: int, height: int) -> svgwrite.Drawing: raise RuntimeError("Cannot render map without center/zoom.") trans = Transformer(width, height, zoom, center, self._tile_provider.tile_size()) + bbox = self.object_bounds() renderer = SvgRenderer(trans) renderer.render_background(self._background_color) - renderer.render_tiles(self._fetch_tile) - renderer.render_objects(self._objects) + renderer.render_tiles(self._fetch_tile, bbox) + renderer.render_objects(self._objects, bbox) renderer.render_attribution(self._tile_provider.attribution()) return renderer.drawing() diff --git a/staticmaps/pillow_renderer.py b/staticmaps/pillow_renderer.py index 38827bf..6bd7ac0 100644 --- a/staticmaps/pillow_renderer.py +++ b/staticmaps/pillow_renderer.py @@ -5,6 +5,7 @@ import math import typing +import s2sphere # type: ignore from PIL import Image as PIL_Image # type: ignore from PIL import ImageDraw as PIL_ImageDraw # type: ignore @@ -40,11 +41,13 @@ def alpha_compose(self, image: PIL_Image.Image) -> None: self._image = PIL_Image.alpha_composite(self._image, image) self._draw = PIL_ImageDraw.Draw(self._image) - def render_objects(self, objects: typing.List["Object"]) -> None: + def render_objects(self, objects: typing.List["Object"], bbox: s2sphere.LatLngRect = None) -> None: """Render all objects of static map :param objects: objects of static map :type objects: typing.List["Object"] + :param bbox: boundary box of all objects + :type bbox: s2sphere.LatLngRect """ x_count = math.ceil(self._trans.image_width() / (2 * self._trans.world_width())) for obj in objects: @@ -62,11 +65,15 @@ def render_background(self, color: typing.Optional[Color]) -> None: return self.draw().rectangle([(0, 0), self.image().size], fill=color.int_rgba()) - def render_tiles(self, download: typing.Callable[[int, int, int], typing.Optional[bytes]]) -> None: + def render_tiles( + self, download: typing.Callable[[int, int, int], typing.Optional[bytes]], bbox: s2sphere.LatLngRect = None + ) -> None: """Render background of static map :param download: url of tiles provider :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] + :param bbox: boundary box of all objects + :type bbox: s2sphere.LatLngRect """ for yy in range(0, self._trans.tiles_y()): y = self._trans.first_tile_y() + yy diff --git a/staticmaps/renderer.py b/staticmaps/renderer.py index a94177d..c03842f 100644 --- a/staticmaps/renderer.py +++ b/staticmaps/renderer.py @@ -1,13 +1,14 @@ # py-staticmaps # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information -from abc import ABC, abstractmethod import typing +from abc import ABC, abstractmethod + +import s2sphere # type: ignore from .color import Color from .transformer import Transformer - if typing.TYPE_CHECKING: # avoid circlic import from .area import Area # pylint: disable=cyclic-import @@ -32,11 +33,13 @@ def transformer(self) -> Transformer: return self._trans @abstractmethod - def render_objects(self, objects: typing.List["Object"]) -> None: + def render_objects(self, objects: typing.List["Object"], bbox: s2sphere.LatLngRect) -> None: """Render all objects of static map :param objects: objects of static map :type objects: typing.List["Object"] + :param bbox: boundary box of all objects + :type bbox: s2sphere.LatLngRect """ @abstractmethod @@ -48,11 +51,15 @@ def render_background(self, color: typing.Optional[Color]) -> None: """ @abstractmethod - def render_tiles(self, download: typing.Callable[[int, int, int], typing.Optional[bytes]]) -> None: + def render_tiles( + self, download: typing.Callable[[int, int, int], typing.Optional[bytes]], bbox: s2sphere.LatLngRect + ) -> None: """Render background of static map :param download: url of tiles provider :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] + :param bbox: boundary box of all objects + :type bbox: s2sphere.LatLngRect """ def render_marker_object(self, marker: "Marker") -> None: diff --git a/staticmaps/svg_renderer.py b/staticmaps/svg_renderer.py index 3a4a69f..3b5d729 100644 --- a/staticmaps/svg_renderer.py +++ b/staticmaps/svg_renderer.py @@ -5,6 +5,7 @@ import math import typing +import s2sphere # type: ignore import svgwrite # type: ignore from .color import Color, BLACK, WHITE @@ -46,11 +47,13 @@ def group(self) -> svgwrite.container.Group: assert self._group is not None return self._group - def render_objects(self, objects: typing.List["Object"]) -> None: + def render_objects(self, objects: typing.List["Object"], bbox: s2sphere.LatLngRect = None) -> None: """Render all objects of static map :param objects: objects of static map :type objects: typing.List["Object"] + :param bbox: boundary box of all objects + :type bbox: s2sphere.LatLngRect """ x_count = math.ceil(self._trans.image_width() / (2 * self._trans.world_width())) for obj in objects: @@ -59,6 +62,8 @@ def render_objects(self, objects: typing.List["Object"]) -> None: clip_path="url(#page)", transform=f"translate({p * self._trans.world_width()}, 0)" ) obj.render_svg(self) + if bbox: + self._tighten_to_boundary(bbox) self._draw.add(self._group) self._group = None @@ -74,11 +79,15 @@ def render_background(self, color: typing.Optional[Color]) -> None: group.add(self._draw.rect(insert=(0, 0), size=self._trans.image_size(), rx=None, ry=None, fill=color.hex_rgb())) self._draw.add(group) - def render_tiles(self, download: typing.Callable[[int, int, int], typing.Optional[bytes]]) -> None: - """Render background of static map + def render_tiles( + self, download: typing.Callable[[int, int, int], typing.Optional[bytes]], bbox: s2sphere.LatLngRect = None + ) -> None: + """Render tiles of static map :param download: url of tiles provider :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] + :param bbox: boundary box of all objects + :type bbox: s2sphere.LatLngRect """ group = self._draw.g(clip_path="url(#page)") for yy in range(0, self._trans.tiles_y()): @@ -103,8 +112,32 @@ def render_tiles(self, download: typing.Callable[[int, int, int], typing.Optiona ) except RuntimeError: pass + if bbox: + self._tighten_to_boundary(bbox) self._draw.add(group) + def _tighten_to_boundary(self, bbox: s2sphere.LatLngRect) -> None: + """Calculate scale and offset for tight rendering on the boundary""" + # boundary points + lo_le_x, lo_le_y = self._trans.ll2pixel(s2sphere.LatLng.from_angles(bbox.lat_lo(), bbox.lng_lo())) + up_ri_x, up_ri_y = self._trans.ll2pixel(s2sphere.LatLng.from_angles(bbox.lat_hi(), bbox.lng_hi())) + # boundary size + size_x = up_ri_x - lo_le_x + size_y = lo_le_y - up_ri_y + # scale to boundaries + width = self._trans.image_width() + height = self._trans.image_height() + scale_x = size_x / width + scale_y = size_y / height + scale = 1 / max(scale_x, scale_y) + # translate new center to old center + off_x = -0.5 * width * (scale - 1) + off_y = -0.5 * height * (scale - 1) + # finally, translate and scale + if isinstance(self._group, svgwrite.container.Group): + self._group.translate(off_x, off_y) + self._group.scale(scale) + def render_attribution(self, attribution: typing.Optional[str]) -> None: """Render attribution from given tiles provider From 1f7bfe13376f1a1e5f3af7495ba6dbf88de0906c Mon Sep 17 00:00:00 2001 From: Lowtower Date: Fri, 21 Jan 2022 15:30:54 +0100 Subject: [PATCH 023/147] remove small bug with max_b --- staticmaps/context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/staticmaps/context.py b/staticmaps/context.py index cf7e90c..ec1bec4 100644 --- a/staticmaps/context.py +++ b/staticmaps/context.py @@ -234,7 +234,7 @@ def extra_pixel_bounds(self) -> PixelBoundsT: max_l, max_t, max_r, max_b = self._extra_pixel_bounds attribution = self._tile_provider.attribution() if (attribution is None) or (attribution == ""): - max_b = 12 + max_b = max(max_b, 12) for obj in self._objects: (l, t, r, b) = obj.extra_pixel_bounds() max_l = max(max_l, l) From bff7cb60f954590ae1226c405fa260e1d4de4124 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Fri, 21 Jan 2022 15:46:13 +0100 Subject: [PATCH 024/147] remove small bug self._bounds (union) --- staticmaps/context.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/staticmaps/context.py b/staticmaps/context.py index ec1bec4..1a5ea62 100644 --- a/staticmaps/context.py +++ b/staticmaps/context.py @@ -105,7 +105,10 @@ def add_bounds( :param extra_pixel_bounds: extra pixel bounds to be respected :type extra_pixel_bounds: int, tuple """ - self._bounds = latlngrect + if self._bounds: + self._bounds = self._bounds.union(latlngrect) + else: + self._bounds = latlngrect if extra_pixel_bounds: if isinstance(extra_pixel_bounds, tuple): self._extra_pixel_bounds = extra_pixel_bounds From 00866fdaed33e573c98fef7b55a351c7f89d1792 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Fri, 21 Jan 2022 15:55:00 +0100 Subject: [PATCH 025/147] remove small bug self._bounds (union) - correct test --- tests/test_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_context.py b/tests/test_context.py index 68751a1..94a8b66 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -39,7 +39,7 @@ def test_bounds() -> None: context.add_bounds(s2sphere.LatLngRect(staticmaps.create_latlng(47.5, 7.5), staticmaps.create_latlng(48, 8))) assert context.object_bounds() is not None assert context.object_bounds() == s2sphere.LatLngRect( - staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8) + staticmaps.create_latlng(46, 6), staticmaps.create_latlng(49, 9) ) From 2dd075e45c2669ae350c79519df30400b7df8fe5 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Fri, 21 Jan 2022 17:17:22 +0100 Subject: [PATCH 026/147] take extra pixel bounds into account --- staticmaps/cairo_renderer.py | 16 ++++++++++++++-- staticmaps/context.py | 5 +++-- staticmaps/pillow_renderer.py | 16 ++++++++++++++-- staticmaps/renderer.py | 15 ++++++++++++--- staticmaps/svg_renderer.py | 35 ++++++++++++++++++++++++++--------- 5 files changed, 69 insertions(+), 18 deletions(-) diff --git a/staticmaps/cairo_renderer.py b/staticmaps/cairo_renderer.py index 0cfd7af..bc63d71 100644 --- a/staticmaps/cairo_renderer.py +++ b/staticmaps/cairo_renderer.py @@ -85,13 +85,20 @@ def create_image(image_data: bytes) -> cairo_ImageSurface: png_bytes.seek(0) return cairo.ImageSurface.create_from_png(png_bytes) - def render_objects(self, objects: typing.List["Object"], bbox: s2sphere.LatLngRect = None) -> None: + def render_objects( + self, + objects: typing.List["Object"], + bbox: s2sphere.LatLngRect = None, + epb: typing.Tuple[int, int, int, int] = None, + ) -> None: """Render all objects of static map :param objects: objects of static map :type objects: typing.List["Object"] :param bbox: boundary box of all objects :type bbox: s2sphere.LatLngRect + :param epb: extra pixel bounds + :type epb: typing.Tuple[int, int, int, int] """ x_count = math.ceil(self._trans.image_width() / (2 * self._trans.world_width())) for obj in objects: @@ -114,7 +121,10 @@ def render_background(self, color: typing.Optional[Color]) -> None: self._context.fill() def render_tiles( - self, download: typing.Callable[[int, int, int], typing.Optional[bytes]], bbox: s2sphere.LatLngRect = None + self, + download: typing.Callable[[int, int, int], typing.Optional[bytes]], + bbox: s2sphere.LatLngRect = None, + epb: typing.Tuple[int, int, int, int] = None, ) -> None: """Render background of static map @@ -122,6 +132,8 @@ def render_tiles( :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] :param bbox: boundary box of all objects :type bbox: s2sphere.LatLngRect + :param epb: extra pixel bounds + :type epb: typing.Tuple[int, int, int, int] """ for yy in range(0, self._trans.tiles_y()): y = self._trans.first_tile_y() + yy diff --git a/staticmaps/context.py b/staticmaps/context.py index 1a5ea62..6276a55 100644 --- a/staticmaps/context.py +++ b/staticmaps/context.py @@ -191,11 +191,12 @@ def render_svg(self, width: int, height: int) -> svgwrite.Drawing: trans = Transformer(width, height, zoom, center, self._tile_provider.tile_size()) bbox = self.object_bounds() + epb = self.extra_pixel_bounds() renderer = SvgRenderer(trans) renderer.render_background(self._background_color) - renderer.render_tiles(self._fetch_tile, bbox) - renderer.render_objects(self._objects, bbox) + renderer.render_tiles(self._fetch_tile, bbox, epb) + renderer.render_objects(self._objects, bbox, epb) renderer.render_attribution(self._tile_provider.attribution()) return renderer.drawing() diff --git a/staticmaps/pillow_renderer.py b/staticmaps/pillow_renderer.py index 6bd7ac0..70f7c2b 100644 --- a/staticmaps/pillow_renderer.py +++ b/staticmaps/pillow_renderer.py @@ -41,13 +41,20 @@ def alpha_compose(self, image: PIL_Image.Image) -> None: self._image = PIL_Image.alpha_composite(self._image, image) self._draw = PIL_ImageDraw.Draw(self._image) - def render_objects(self, objects: typing.List["Object"], bbox: s2sphere.LatLngRect = None) -> None: + def render_objects( + self, + objects: typing.List["Object"], + bbox: s2sphere.LatLngRect = None, + epb: typing.Tuple[int, int, int, int] = None, + ) -> None: """Render all objects of static map :param objects: objects of static map :type objects: typing.List["Object"] :param bbox: boundary box of all objects :type bbox: s2sphere.LatLngRect + :param epb: extra pixel bounds + :type epb: typing.Tuple[int, int, int, int] """ x_count = math.ceil(self._trans.image_width() / (2 * self._trans.world_width())) for obj in objects: @@ -66,7 +73,10 @@ def render_background(self, color: typing.Optional[Color]) -> None: self.draw().rectangle([(0, 0), self.image().size], fill=color.int_rgba()) def render_tiles( - self, download: typing.Callable[[int, int, int], typing.Optional[bytes]], bbox: s2sphere.LatLngRect = None + self, + download: typing.Callable[[int, int, int], typing.Optional[bytes]], + bbox: s2sphere.LatLngRect = None, + epb: typing.Tuple[int, int, int, int] = None, ) -> None: """Render background of static map @@ -74,6 +84,8 @@ def render_tiles( :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] :param bbox: boundary box of all objects :type bbox: s2sphere.LatLngRect + :param epb: extra pixel bounds + :type epb: typing.Tuple[int, int, int, int] """ for yy in range(0, self._trans.tiles_y()): y = self._trans.first_tile_y() + yy diff --git a/staticmaps/renderer.py b/staticmaps/renderer.py index c03842f..882a083 100644 --- a/staticmaps/renderer.py +++ b/staticmaps/renderer.py @@ -33,13 +33,17 @@ def transformer(self) -> Transformer: return self._trans @abstractmethod - def render_objects(self, objects: typing.List["Object"], bbox: s2sphere.LatLngRect) -> None: + def render_objects( + self, objects: typing.List["Object"], bbox: s2sphere.LatLngRect, epb: typing.Tuple[int, int, int, int] = None + ) -> None: """Render all objects of static map :param objects: objects of static map :type objects: typing.List["Object"] :param bbox: boundary box of all objects :type bbox: s2sphere.LatLngRect + :param epb: extra pixel bounds + :type epb: typing.Tuple[int, int, int, int] """ @abstractmethod @@ -52,14 +56,19 @@ def render_background(self, color: typing.Optional[Color]) -> None: @abstractmethod def render_tiles( - self, download: typing.Callable[[int, int, int], typing.Optional[bytes]], bbox: s2sphere.LatLngRect + self, + download: typing.Callable[[int, int, int], typing.Optional[bytes]], + bbox: s2sphere.LatLngRect, + epb: typing.Tuple[int, int, int, int] = None, ) -> None: - """Render background of static map + """Render tiles of static map :param download: url of tiles provider :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] :param bbox: boundary box of all objects :type bbox: s2sphere.LatLngRect + :param epb: extra pixel bounds + :type epb: typing.Tuple[int, int, int, int] """ def render_marker_object(self, marker: "Marker") -> None: diff --git a/staticmaps/svg_renderer.py b/staticmaps/svg_renderer.py index 3b5d729..8bbda7f 100644 --- a/staticmaps/svg_renderer.py +++ b/staticmaps/svg_renderer.py @@ -47,13 +47,20 @@ def group(self) -> svgwrite.container.Group: assert self._group is not None return self._group - def render_objects(self, objects: typing.List["Object"], bbox: s2sphere.LatLngRect = None) -> None: + def render_objects( + self, + objects: typing.List["Object"], + bbox: s2sphere.LatLngRect = None, + epb: typing.Tuple[int, int, int, int] = None, + ) -> None: """Render all objects of static map :param objects: objects of static map :type objects: typing.List["Object"] :param bbox: boundary box of all objects :type bbox: s2sphere.LatLngRect + :param epb: extra pixel bounds + :type epb: typing.Tuple[int, int, int, int] """ x_count = math.ceil(self._trans.image_width() / (2 * self._trans.world_width())) for obj in objects: @@ -63,7 +70,7 @@ def render_objects(self, objects: typing.List["Object"], bbox: s2sphere.LatLngRe ) obj.render_svg(self) if bbox: - self._tighten_to_boundary(bbox) + self._tighten_to_boundary(bbox, epb) self._draw.add(self._group) self._group = None @@ -80,7 +87,10 @@ def render_background(self, color: typing.Optional[Color]) -> None: self._draw.add(group) def render_tiles( - self, download: typing.Callable[[int, int, int], typing.Optional[bytes]], bbox: s2sphere.LatLngRect = None + self, + download: typing.Callable[[int, int, int], typing.Optional[bytes]], + bbox: s2sphere.LatLngRect = None, + epb: typing.Tuple[int, int, int, int] = None, ) -> None: """Render tiles of static map @@ -88,6 +98,8 @@ def render_tiles( :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] :param bbox: boundary box of all objects :type bbox: s2sphere.LatLngRect + :param epb: extra pixel bounds + :type epb: typing.Tuple[int, int, int, int] """ group = self._draw.g(clip_path="url(#page)") for yy in range(0, self._trans.tiles_y()): @@ -113,17 +125,22 @@ def render_tiles( except RuntimeError: pass if bbox: - self._tighten_to_boundary(bbox) + self._tighten_to_boundary(bbox, epb) self._draw.add(group) - def _tighten_to_boundary(self, bbox: s2sphere.LatLngRect) -> None: + def _tighten_to_boundary(self, bbox: s2sphere.LatLngRect, epb: tuple) -> None: """Calculate scale and offset for tight rendering on the boundary""" # boundary points - lo_le_x, lo_le_y = self._trans.ll2pixel(s2sphere.LatLng.from_angles(bbox.lat_lo(), bbox.lng_lo())) - up_ri_x, up_ri_y = self._trans.ll2pixel(s2sphere.LatLng.from_angles(bbox.lat_hi(), bbox.lng_hi())) + nw_x, nw_y = self._trans.ll2pixel(s2sphere.LatLng.from_angles(bbox.lat_lo(), bbox.lng_lo())) + se_x, se_y = self._trans.ll2pixel(s2sphere.LatLng.from_angles(bbox.lat_hi(), bbox.lng_hi())) + epb_l, epb_t, epb_r, epb_b = epb # boundary size - size_x = up_ri_x - lo_le_x - size_y = lo_le_y - up_ri_y + size_x = se_x - nw_x + size_y = nw_y - se_y + print(size_x, size_y) + size_x = se_x - nw_x + epb_r + epb_l + size_y = nw_y - se_y + epb_t + epb_b + print(size_x, size_y) # scale to boundaries width = self._trans.image_width() height = self._trans.image_height() From 0edcdb2451cac9d956a78ee6c4914ca89e4e62a2 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Fri, 21 Jan 2022 17:27:29 +0100 Subject: [PATCH 027/147] pylint --- staticmaps/svg_renderer.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/staticmaps/svg_renderer.py b/staticmaps/svg_renderer.py index 8bbda7f..e16a880 100644 --- a/staticmaps/svg_renderer.py +++ b/staticmaps/svg_renderer.py @@ -128,19 +128,20 @@ def render_tiles( self._tighten_to_boundary(bbox, epb) self._draw.add(group) - def _tighten_to_boundary(self, bbox: s2sphere.LatLngRect, epb: tuple) -> None: + def _tighten_to_boundary( + self, bbox: s2sphere.LatLngRect, epb: typing.Optional[typing.Tuple[int, int, int, int]] = None + ) -> None: """Calculate scale and offset for tight rendering on the boundary""" + # pylint: disable=too-many-locals # boundary points nw_x, nw_y = self._trans.ll2pixel(s2sphere.LatLng.from_angles(bbox.lat_lo(), bbox.lng_lo())) se_x, se_y = self._trans.ll2pixel(s2sphere.LatLng.from_angles(bbox.lat_hi(), bbox.lng_hi())) - epb_l, epb_t, epb_r, epb_b = epb + epb_l, epb_t, epb_r, epb_b = 0, 0, 0, 0 + if epb: + epb_l, epb_t, epb_r, epb_b = epb # boundary size - size_x = se_x - nw_x - size_y = nw_y - se_y - print(size_x, size_y) size_x = se_x - nw_x + epb_r + epb_l size_y = nw_y - se_y + epb_t + epb_b - print(size_x, size_y) # scale to boundaries width = self._trans.image_width() height = self._trans.image_height() From 0c14bc753ba064a20938487ca028e66119ec97ac Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 24 Jan 2022 11:27:59 +0100 Subject: [PATCH 028/147] add group for all objects, translate and scale afterwards --- staticmaps/svg_renderer.py | 45 ++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/staticmaps/svg_renderer.py b/staticmaps/svg_renderer.py index e16a880..91b9f9f 100644 --- a/staticmaps/svg_renderer.py +++ b/staticmaps/svg_renderer.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +"""py-staticmaps - SvgRenderer""" + # py-staticmaps # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information @@ -50,8 +53,8 @@ def group(self) -> svgwrite.container.Group: def render_objects( self, objects: typing.List["Object"], - bbox: s2sphere.LatLngRect = None, - epb: typing.Tuple[int, int, int, int] = None, + bbox: typing.Optional[s2sphere.LatLngRect] = None, + epb: typing.Optional[typing.Tuple[int, int, int, int]] = None, ) -> None: """Render all objects of static map @@ -62,17 +65,16 @@ def render_objects( :param epb: extra pixel bounds :type epb: typing.Tuple[int, int, int, int] """ + self._group = self._draw.g(clip_path="url(#page)") x_count = math.ceil(self._trans.image_width() / (2 * self._trans.world_width())) for obj in objects: for p in range(-x_count, x_count + 1): - self._group = self._draw.g( - clip_path="url(#page)", transform=f"translate({p * self._trans.world_width()}, 0)" - ) + group = self._draw.g(clip_path="url(#page)", transform=f"translate({p * self._trans.world_width()}, 0)") obj.render_svg(self) - if bbox: - self._tighten_to_boundary(bbox, epb) - self._draw.add(self._group) - self._group = None + self._group.add(group) + objects_group = self._tighten_to_boundary(self._group, bbox, epb) + self._draw.add(objects_group) + self._group = None def render_background(self, color: typing.Optional[Color]) -> None: """Render background of static map @@ -101,7 +103,7 @@ def render_tiles( :param epb: extra pixel bounds :type epb: typing.Tuple[int, int, int, int] """ - group = self._draw.g(clip_path="url(#page)") + self._group = self._draw.g(clip_path="url(#page)") for yy in range(0, self._trans.tiles_y()): y = self._trans.first_tile_y() + yy if y < 0 or y >= self._trans.number_of_tiles(): @@ -112,7 +114,7 @@ def render_tiles( tile_img = self.fetch_tile(download, x, y) if tile_img is None: continue - group.add( + self._group.add( self._draw.image( tile_img, insert=( @@ -124,15 +126,20 @@ def render_tiles( ) except RuntimeError: pass - if bbox: - self._tighten_to_boundary(bbox, epb) - self._draw.add(group) + tiles_group = self._tighten_to_boundary(self._group, bbox, epb) + self._draw.add(tiles_group) + self._group = None def _tighten_to_boundary( - self, bbox: s2sphere.LatLngRect, epb: typing.Optional[typing.Tuple[int, int, int, int]] = None - ) -> None: + self, + group: svgwrite.container.Group, + bbox: typing.Optional[s2sphere.LatLngRect] = None, + epb: typing.Optional[typing.Tuple[int, int, int, int]] = None, + ) -> svgwrite.container.Group: """Calculate scale and offset for tight rendering on the boundary""" # pylint: disable=too-many-locals + if not bbox or not epb: + return group # boundary points nw_x, nw_y = self._trans.ll2pixel(s2sphere.LatLng.from_angles(bbox.lat_lo(), bbox.lng_lo())) se_x, se_y = self._trans.ll2pixel(s2sphere.LatLng.from_angles(bbox.lat_hi(), bbox.lng_hi())) @@ -152,9 +159,9 @@ def _tighten_to_boundary( off_x = -0.5 * width * (scale - 1) off_y = -0.5 * height * (scale - 1) # finally, translate and scale - if isinstance(self._group, svgwrite.container.Group): - self._group.translate(off_x, off_y) - self._group.scale(scale) + group.translate(off_x, off_y) + group.scale(scale) + return group def render_attribution(self, attribution: typing.Optional[str]) -> None: """Render attribution from given tiles provider From 61b4ba17caa345d34cdcd58f93fba892b0117d9f Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 24 Jan 2022 11:52:34 +0100 Subject: [PATCH 029/147] command line argument for tightening to boundaries (default: old behaviour) --- staticmaps/cli.py | 8 ++++++++ staticmaps/context.py | 19 +++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/staticmaps/cli.py b/staticmaps/cli.py index f0fa295..c7056e5 100755 --- a/staticmaps/cli.py +++ b/staticmaps/cli.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +"""py-staticmaps - cli.py - entry point""" # py-staticmaps # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information @@ -79,6 +80,12 @@ def main() -> None: metavar="LAT,LNG LAT,LNG", type=str, ) + args_parser.add_argument( + "--tight-to-bounds", + action="store_true", + default=False, + help="Tighten static map to minimum boundaries of objects, custom boundaries, ...(default: False)", + ) args_parser.add_argument( "--tiles", metavar="TILEPROVIDER", @@ -123,6 +130,7 @@ def main() -> None: context.add_object(staticmaps.Marker(staticmaps.parse_latlng(coords))) if args.bounds is not None: context.add_bounds(staticmaps.parse_latlngs2rect(args.bounds)) + context.set_tighten_to_bounds(args.tighten_to_bounds) file_name = args.filename[0] if determine_file_format(args.file_format, file_name) == FileFormat.PNG: diff --git a/staticmaps/context.py b/staticmaps/context.py index 6276a55..70ef0bc 100644 --- a/staticmaps/context.py +++ b/staticmaps/context.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +"""py-staticmaps - Context""" + # py-staticmaps # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information @@ -33,6 +36,7 @@ def __init__(self) -> None: self._tile_provider = tile_provider_OSM self._tile_downloader = TileDownloader() self._cache_dir = os.path.join(appdirs.user_cache_dir(LIB_NAME), "tiles") + self._tighten_to_bounds: bool = False def set_zoom(self, zoom: int) -> None: """Set zoom for static map @@ -85,6 +89,14 @@ def set_tile_provider(self, provider: TileProvider) -> None: """ self._tile_provider = provider + def set_tighten_to_bounds(self, tighten: bool = False) -> None: + """Set tighten to bounds + + :param tighten: tighten or not + :type tighten: bool + """ + self._tighten_to_bounds = tighten + def add_object(self, obj: Object) -> None: """Add object for the static map (e.g. line, area, marker) @@ -190,8 +202,11 @@ def render_svg(self, width: int, height: int) -> svgwrite.Drawing: raise RuntimeError("Cannot render map without center/zoom.") trans = Transformer(width, height, zoom, center, self._tile_provider.tile_size()) - bbox = self.object_bounds() - epb = self.extra_pixel_bounds() + bbox = None + epb = None + if self._tighten_to_bounds: + bbox = self.object_bounds() + epb = self.extra_pixel_bounds() renderer = SvgRenderer(trans) renderer.render_background(self._background_color) From abb8f1a6a8e8ed36c89ac4b84dcedde38114bb11 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 24 Jan 2022 12:35:03 +0100 Subject: [PATCH 030/147] extend tests for context regarding boundaries --- tests/test_context.py | 129 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 127 insertions(+), 2 deletions(-) diff --git a/tests/test_context.py b/tests/test_context.py index 94a8b66..2a86dd5 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +"""py-staticmaps - Test Context""" + # py-staticmaps # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information @@ -9,6 +12,81 @@ from .mock_tile_downloader import MockTileDownloader +def test_add_marker_adds_bounds_is_point() -> None: + context = staticmaps.Context() + assert context.object_bounds() is None + + context.add_object(staticmaps.Marker(staticmaps.create_latlng(48, 8))) + assert context.object_bounds() is not None + assert context.object_bounds() == s2sphere.LatLngRect( + staticmaps.create_latlng(48, 8), staticmaps.create_latlng(48, 8) + ) + bounds = context.object_bounds() + assert bounds is not None + assert bounds.is_point() + + +def test_add_two_markers_adds_bounds_is_not_point() -> None: + context = staticmaps.Context() + assert context.object_bounds() is None + + context.add_object(staticmaps.Marker(staticmaps.create_latlng(47, 7))) + assert context.object_bounds() is not None + assert context.object_bounds() == s2sphere.LatLngRect( + staticmaps.create_latlng(47, 7), staticmaps.create_latlng(47, 7) + ) + context.add_object(staticmaps.Marker(staticmaps.create_latlng(48, 8))) + assert context.object_bounds() == s2sphere.LatLngRect( + staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8) + ) + bounds = context.object_bounds() + assert bounds is not None + assert not bounds.is_point() + + +def test_add_line_adds_bounds_is_rect() -> None: + context = staticmaps.Context() + assert context.object_bounds() is None + + context.add_object(staticmaps.Line([staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8)])) + assert context.object_bounds() is not None + assert context.object_bounds() == s2sphere.LatLngRect( + staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8) + ) + + +def test_add_greater_line_extends_bounds() -> None: + context = staticmaps.Context() + assert context.object_bounds() is None + + context.add_object(staticmaps.Line([staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8)])) + assert context.object_bounds() is not None + assert context.object_bounds() == s2sphere.LatLngRect( + staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8) + ) + context.add_object(staticmaps.Line([staticmaps.create_latlng(46, 6), staticmaps.create_latlng(49, 9)])) + assert context.object_bounds() is not None + assert context.object_bounds() == s2sphere.LatLngRect( + staticmaps.create_latlng(46, 6), staticmaps.create_latlng(49, 9) + ) + + +def test_add_smaller_line_keeps_bounds() -> None: + context = staticmaps.Context() + assert context.object_bounds() is None + + context.add_object(staticmaps.Line([staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8)])) + assert context.object_bounds() is not None + assert context.object_bounds() == s2sphere.LatLngRect( + staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8) + ) + context.add_object(staticmaps.Line([staticmaps.create_latlng(47.5, 7.5), staticmaps.create_latlng(48, 8)])) + assert context.object_bounds() is not None + assert context.object_bounds() == s2sphere.LatLngRect( + staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8) + ) + + def test_bounds() -> None: context = staticmaps.Context() assert context.object_bounds() is None @@ -36,14 +114,45 @@ def test_bounds() -> None: staticmaps.create_latlng(46, 6), staticmaps.create_latlng(49, 9) ) + +def test_add_greater_custom_bound_extends_bounds() -> None: + context = staticmaps.Context() + assert context.object_bounds() is None + + context.add_object(staticmaps.Marker(staticmaps.create_latlng(47, 7))) + context.add_object(staticmaps.Marker(staticmaps.create_latlng(48, 8))) + assert context.object_bounds() is not None + + context.add_bounds(s2sphere.LatLngRect(staticmaps.create_latlng(49, 7.5), staticmaps.create_latlng(49, 8))) + assert context.object_bounds() == s2sphere.LatLngRect( + staticmaps.create_latlng(47, 7), staticmaps.create_latlng(49, 8) + ) + + +def test_add_smaller_custom_bound_keeps_bounds() -> None: + context = staticmaps.Context() + assert context.object_bounds() is None + + context.add_object(staticmaps.Marker(staticmaps.create_latlng(47, 7))) + context.add_object(staticmaps.Marker(staticmaps.create_latlng(48, 8))) + assert context.object_bounds() is not None + context.add_bounds(s2sphere.LatLngRect(staticmaps.create_latlng(47.5, 7.5), staticmaps.create_latlng(48, 8))) assert context.object_bounds() is not None assert context.object_bounds() == s2sphere.LatLngRect( - staticmaps.create_latlng(46, 6), staticmaps.create_latlng(49, 9) + staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8) ) -def test_render_empty() -> None: +def test_set_wrong_zoom_raises_exception() -> None: + context = staticmaps.Context() + with pytest.raises(ValueError): + context.set_zoom(-1) + with pytest.raises(ValueError): + context.set_zoom(31) + + +def test_render_empty_raises_exception() -> None: context = staticmaps.Context() with pytest.raises(RuntimeError): context.render_svg(200, 100) @@ -55,3 +164,19 @@ def test_render_center_zoom() -> None: context.set_center(staticmaps.create_latlng(48, 8)) context.set_zoom(15) context.render_svg(200, 100) + + +def test_render_with_zoom_without_center_raises_exception() -> None: + context = staticmaps.Context() + context.set_tile_downloader(MockTileDownloader()) + context.set_zoom(15) + with pytest.raises(RuntimeError): + context.render_svg(200, 100) + + +def test_render_with_center_without_zoom_sets_zoom_15() -> None: + context = staticmaps.Context() + context.set_tile_downloader(MockTileDownloader()) + context.set_center(staticmaps.create_latlng(48, 8)) + context.render_svg(200, 100) + assert context.determine_center_zoom(200, 100) == (staticmaps.create_latlng(48, 8), 15) From e0576b1d123d6f7ef03d9eebd30fb95a6e3ed14c Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 24 Jan 2022 13:48:31 +0100 Subject: [PATCH 031/147] remove executable line --- staticmaps/context.py | 1 - staticmaps/svg_renderer.py | 1 - tests/test_context.py | 1 - 3 files changed, 3 deletions(-) diff --git a/staticmaps/context.py b/staticmaps/context.py index 70ef0bc..4077662 100644 --- a/staticmaps/context.py +++ b/staticmaps/context.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python """py-staticmaps - Context""" # py-staticmaps diff --git a/staticmaps/svg_renderer.py b/staticmaps/svg_renderer.py index 91b9f9f..22842d9 100644 --- a/staticmaps/svg_renderer.py +++ b/staticmaps/svg_renderer.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python """py-staticmaps - SvgRenderer""" # py-staticmaps diff --git a/tests/test_context.py b/tests/test_context.py index 2a86dd5..27ec16a 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python """py-staticmaps - Test Context""" # py-staticmaps From 78362c3dc397c4d331be30d65e67dfba31b3c012 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Tue, 25 Jan 2022 12:38:38 +0100 Subject: [PATCH 032/147] add examples for tight boundaries --- examples/frankfurt_newyork.py | 7 +++++++ examples/freiburg_area.py | 7 +++++++ examples/us_capitals.py | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/examples/frankfurt_newyork.py b/examples/frankfurt_newyork.py index de7bb86..5bb1998 100644 --- a/examples/frankfurt_newyork.py +++ b/examples/frankfurt_newyork.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +"""py-staticmaps - Example Frankfurt-New York""" # py-staticmaps # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information @@ -28,3 +29,9 @@ svg_image = context.render_svg(800, 500) with open("frankfurt_newyork.svg", "w", encoding="utf-8") as f: svg_image.write(f, pretty=True) + +# render svg - tight boundaries +context.set_tighten_to_bounds(True) +svg_image = context.render_svg(800, 500) +with open("frankfurt_newyork.tight.svg", "w", encoding="utf-8") as f: + svg_image.write(f, pretty=True) diff --git a/examples/freiburg_area.py b/examples/freiburg_area.py index 11558e2..82b23d6 100644 --- a/examples/freiburg_area.py +++ b/examples/freiburg_area.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +"""py-staticmaps - Example Freiburg Area""" # py-staticmaps # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information @@ -457,3 +458,9 @@ svg_image = context.render_svg(800, 500) with open("freiburg_area.svg", "w", encoding="utf-8") as f: svg_image.write(f, pretty=True) + +# render svg - tight boundaries +context.set_tighten_to_bounds(True) +svg_image = context.render_svg(800, 500) +with open("freiburg_area.tight.svg", "w", encoding="utf-8") as f: + svg_image.write(f, pretty=True) diff --git a/examples/us_capitals.py b/examples/us_capitals.py index 98c086b..a8cf0f8 100644 --- a/examples/us_capitals.py +++ b/examples/us_capitals.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +"""py-staticmaps - Example US capitals""" # py-staticmaps # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information @@ -32,3 +33,9 @@ svg_image = context.render_svg(800, 500) with open("us_capitals.svg", "w", encoding="utf-8") as f: svg_image.write(f, pretty=True) + +# render svg - tight boundaries +context.set_tighten_to_bounds(True) +svg_image = context.render_svg(800, 500) +with open("us_capitals.tight.svg", "w", encoding="utf-8") as f: + svg_image.write(f, pretty=True) From eeaa772af126396ad1584b9bf1d0b33f31df582d Mon Sep 17 00:00:00 2001 From: Lowtower Date: Tue, 25 Jan 2022 15:57:33 +0100 Subject: [PATCH 033/147] copyright year and doc string --- staticmaps/cairo_renderer.py | 4 +++- staticmaps/context.py | 2 +- staticmaps/pillow_renderer.py | 4 +++- staticmaps/svg_renderer.py | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/staticmaps/cairo_renderer.py b/staticmaps/cairo_renderer.py index bc63d71..2f782f9 100644 --- a/staticmaps/cairo_renderer.py +++ b/staticmaps/cairo_renderer.py @@ -1,5 +1,7 @@ +"""py-staticmaps - CaroRenderer""" + # py-staticmaps -# Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information +# Copyright (c) 2022 Florian Pigorsch; see /LICENSE for licensing information import io import math diff --git a/staticmaps/context.py b/staticmaps/context.py index 4077662..220feee 100644 --- a/staticmaps/context.py +++ b/staticmaps/context.py @@ -1,7 +1,7 @@ """py-staticmaps - Context""" # py-staticmaps -# Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information +# Copyright (c) 2022 Florian Pigorsch; see /LICENSE for licensing information import math import os diff --git a/staticmaps/pillow_renderer.py b/staticmaps/pillow_renderer.py index 70f7c2b..f50b0e1 100644 --- a/staticmaps/pillow_renderer.py +++ b/staticmaps/pillow_renderer.py @@ -1,5 +1,7 @@ +"""py-staticmaps - PillowRenderer""" + # py-staticmaps -# Copyright (c) 2021 Florian Pigorsch; see /LICENSE for licensing information +# Copyright (c) 2022 Florian Pigorsch; see /LICENSE for licensing information import io import math diff --git a/staticmaps/svg_renderer.py b/staticmaps/svg_renderer.py index 22842d9..c1f2d8e 100644 --- a/staticmaps/svg_renderer.py +++ b/staticmaps/svg_renderer.py @@ -1,7 +1,7 @@ """py-staticmaps - SvgRenderer""" # py-staticmaps -# Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information +# Copyright (c) 2022 Florian Pigorsch; see /LICENSE for licensing information import base64 import math From 5152f43671e03b7497069d97908d6156178b39f4 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Tue, 25 Jan 2022 16:02:39 +0100 Subject: [PATCH 034/147] doc strings --- staticmaps/pillow_renderer.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/staticmaps/pillow_renderer.py b/staticmaps/pillow_renderer.py index f50b0e1..d5165ab 100644 --- a/staticmaps/pillow_renderer.py +++ b/staticmaps/pillow_renderer.py @@ -30,15 +30,35 @@ def __init__(self, transformer: Transformer) -> None: self._offset_x = 0 def draw(self) -> PIL_ImageDraw.Draw: + """Return pillow image draw + + :return: pillow image draw + :rtype: PIL_ImageDraw.Draw + """ return self._draw def image(self) -> PIL_Image.Image: + """Return pillow image + + :return: pillow image + :rtype: PIL_Image.Image + """ return self._image def offset_x(self) -> int: + """Return x offset + + :return: x offset + :rtype: int + """ return self._offset_x def alpha_compose(self, image: PIL_Image.Image) -> None: + """Generate Pillow alpha composition + + :param image: pillow image + :type image: PIL_Image.Image + """ assert image.size == self._image.size self._image = PIL_Image.alpha_composite(self._image, image) self._draw = PIL_ImageDraw.Draw(self._image) @@ -72,7 +92,7 @@ def render_background(self, color: typing.Optional[Color]) -> None: """ if color is None: return - self.draw().rectangle([(0, 0), self.image().size], fill=color.int_rgba()) + self.draw().rectangle(((0, 0), self.image().size), fill=color.int_rgba()) def render_tiles( self, @@ -123,7 +143,7 @@ def render_attribution(self, attribution: typing.Optional[str]) -> None: h = self._trans.image_height() overlay = PIL_Image.new("RGBA", self._image.size, (255, 255, 255, 0)) draw = PIL_ImageDraw.Draw(overlay) - draw.rectangle([(0, h - th - 2 * margin), (w, h)], fill=(255, 255, 255, 204)) + draw.rectangle(((0, h - th - 2 * margin), (w, h)), fill=(255, 255, 255, 204)) self.alpha_compose(overlay) self.draw().text((margin, h - th - margin), attribution, fill=(0, 0, 0, 255)) From 02877dd0494ff18f4e6d481ab8e14c923d4288e5 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Tue, 25 Jan 2022 16:05:34 +0100 Subject: [PATCH 035/147] augmented assignment --- staticmaps/cairo_renderer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/staticmaps/cairo_renderer.py b/staticmaps/cairo_renderer.py index 2f782f9..26d7285 100644 --- a/staticmaps/cairo_renderer.py +++ b/staticmaps/cairo_renderer.py @@ -175,7 +175,7 @@ def render_attribution(self, attribution: typing.Optional[str]) -> None: t_width = self._context.text_extents(attribution)[3] if t_width < width - 4: break - font_size = font_size - 0.25 + font_size -= 0.25 self._context.set_source_rgba(*WHITE.float_rgb(), 0.8) self._context.rectangle(0, height - f_height - f_descent - 2, width, height) self._context.fill() From c56090283b38486441745d75aa530216332494fc Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 17 Jan 2022 15:54:09 +0100 Subject: [PATCH 036/147] create virtual environment first --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index dac565d..3043103 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -55,6 +55,7 @@ jobs: cache: pip - name: Install Python dependencies run: | + python -m venv .env python -m pip install --upgrade pip setuptools wheel pip install -r requirements.txt pip install -r requirements-examples.txt From 6119c5465f550eec43f633f1632703b07fc37afd Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 17 Jan 2022 15:55:09 +0100 Subject: [PATCH 037/147] create virtual environment first --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3043103..48f5215 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -55,7 +55,7 @@ jobs: cache: pip - name: Install Python dependencies run: | - python -m venv .env + python -m venv .env python -m pip install --upgrade pip setuptools wheel pip install -r requirements.txt pip install -r requirements-examples.txt From a28e8a2bba83455159f595afb0d6a26cd34612d5 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Fri, 21 Jan 2022 11:13:26 +0100 Subject: [PATCH 038/147] implement tightening to absolute boundaries (of all objects) for svg --- staticmaps/cairo_renderer.py | 12 +++++++++-- staticmaps/context.py | 5 +++-- staticmaps/pillow_renderer.py | 11 ++++++++-- staticmaps/renderer.py | 15 ++++++++++---- staticmaps/svg_renderer.py | 39 ++++++++++++++++++++++++++++++++--- 5 files changed, 69 insertions(+), 13 deletions(-) diff --git a/staticmaps/cairo_renderer.py b/staticmaps/cairo_renderer.py index d27d151..0cfd7af 100644 --- a/staticmaps/cairo_renderer.py +++ b/staticmaps/cairo_renderer.py @@ -6,6 +6,8 @@ import sys import typing +import s2sphere # type: ignore + try: import cairo # type: ignore except ImportError: @@ -83,11 +85,13 @@ def create_image(image_data: bytes) -> cairo_ImageSurface: png_bytes.seek(0) return cairo.ImageSurface.create_from_png(png_bytes) - def render_objects(self, objects: typing.List["Object"]) -> None: + def render_objects(self, objects: typing.List["Object"], bbox: s2sphere.LatLngRect = None) -> None: """Render all objects of static map :param objects: objects of static map :type objects: typing.List["Object"] + :param bbox: boundary box of all objects + :type bbox: s2sphere.LatLngRect """ x_count = math.ceil(self._trans.image_width() / (2 * self._trans.world_width())) for obj in objects: @@ -109,11 +113,15 @@ def render_background(self, color: typing.Optional[Color]) -> None: self._context.rectangle(0, 0, *self._trans.image_size()) self._context.fill() - def render_tiles(self, download: typing.Callable[[int, int, int], typing.Optional[bytes]]) -> None: + def render_tiles( + self, download: typing.Callable[[int, int, int], typing.Optional[bytes]], bbox: s2sphere.LatLngRect = None + ) -> None: """Render background of static map :param download: url of tiles provider :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] + :param bbox: boundary box of all objects + :type bbox: s2sphere.LatLngRect """ for yy in range(0, self._trans.tiles_y()): y = self._trans.first_tile_y() + yy diff --git a/staticmaps/context.py b/staticmaps/context.py index 694af08..38c225e 100644 --- a/staticmaps/context.py +++ b/staticmaps/context.py @@ -191,11 +191,12 @@ def render_svg(self, width: int, height: int) -> svgwrite.Drawing: raise RuntimeError("Cannot render map without center/zoom.") trans = Transformer(width, height, zoom, center, self._tile_provider.tile_size()) + bbox = self.object_bounds() renderer = SvgRenderer(trans) renderer.render_background(self._background_color) - renderer.render_tiles(self._fetch_tile) - renderer.render_objects(self._objects) + renderer.render_tiles(self._fetch_tile, bbox) + renderer.render_objects(self._objects, bbox) renderer.render_attribution(self._tile_provider.attribution()) return renderer.drawing() diff --git a/staticmaps/pillow_renderer.py b/staticmaps/pillow_renderer.py index 38827bf..6bd7ac0 100644 --- a/staticmaps/pillow_renderer.py +++ b/staticmaps/pillow_renderer.py @@ -5,6 +5,7 @@ import math import typing +import s2sphere # type: ignore from PIL import Image as PIL_Image # type: ignore from PIL import ImageDraw as PIL_ImageDraw # type: ignore @@ -40,11 +41,13 @@ def alpha_compose(self, image: PIL_Image.Image) -> None: self._image = PIL_Image.alpha_composite(self._image, image) self._draw = PIL_ImageDraw.Draw(self._image) - def render_objects(self, objects: typing.List["Object"]) -> None: + def render_objects(self, objects: typing.List["Object"], bbox: s2sphere.LatLngRect = None) -> None: """Render all objects of static map :param objects: objects of static map :type objects: typing.List["Object"] + :param bbox: boundary box of all objects + :type bbox: s2sphere.LatLngRect """ x_count = math.ceil(self._trans.image_width() / (2 * self._trans.world_width())) for obj in objects: @@ -62,11 +65,15 @@ def render_background(self, color: typing.Optional[Color]) -> None: return self.draw().rectangle([(0, 0), self.image().size], fill=color.int_rgba()) - def render_tiles(self, download: typing.Callable[[int, int, int], typing.Optional[bytes]]) -> None: + def render_tiles( + self, download: typing.Callable[[int, int, int], typing.Optional[bytes]], bbox: s2sphere.LatLngRect = None + ) -> None: """Render background of static map :param download: url of tiles provider :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] + :param bbox: boundary box of all objects + :type bbox: s2sphere.LatLngRect """ for yy in range(0, self._trans.tiles_y()): y = self._trans.first_tile_y() + yy diff --git a/staticmaps/renderer.py b/staticmaps/renderer.py index a94177d..c03842f 100644 --- a/staticmaps/renderer.py +++ b/staticmaps/renderer.py @@ -1,13 +1,14 @@ # py-staticmaps # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information -from abc import ABC, abstractmethod import typing +from abc import ABC, abstractmethod + +import s2sphere # type: ignore from .color import Color from .transformer import Transformer - if typing.TYPE_CHECKING: # avoid circlic import from .area import Area # pylint: disable=cyclic-import @@ -32,11 +33,13 @@ def transformer(self) -> Transformer: return self._trans @abstractmethod - def render_objects(self, objects: typing.List["Object"]) -> None: + def render_objects(self, objects: typing.List["Object"], bbox: s2sphere.LatLngRect) -> None: """Render all objects of static map :param objects: objects of static map :type objects: typing.List["Object"] + :param bbox: boundary box of all objects + :type bbox: s2sphere.LatLngRect """ @abstractmethod @@ -48,11 +51,15 @@ def render_background(self, color: typing.Optional[Color]) -> None: """ @abstractmethod - def render_tiles(self, download: typing.Callable[[int, int, int], typing.Optional[bytes]]) -> None: + def render_tiles( + self, download: typing.Callable[[int, int, int], typing.Optional[bytes]], bbox: s2sphere.LatLngRect + ) -> None: """Render background of static map :param download: url of tiles provider :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] + :param bbox: boundary box of all objects + :type bbox: s2sphere.LatLngRect """ def render_marker_object(self, marker: "Marker") -> None: diff --git a/staticmaps/svg_renderer.py b/staticmaps/svg_renderer.py index 3a4a69f..3b5d729 100644 --- a/staticmaps/svg_renderer.py +++ b/staticmaps/svg_renderer.py @@ -5,6 +5,7 @@ import math import typing +import s2sphere # type: ignore import svgwrite # type: ignore from .color import Color, BLACK, WHITE @@ -46,11 +47,13 @@ def group(self) -> svgwrite.container.Group: assert self._group is not None return self._group - def render_objects(self, objects: typing.List["Object"]) -> None: + def render_objects(self, objects: typing.List["Object"], bbox: s2sphere.LatLngRect = None) -> None: """Render all objects of static map :param objects: objects of static map :type objects: typing.List["Object"] + :param bbox: boundary box of all objects + :type bbox: s2sphere.LatLngRect """ x_count = math.ceil(self._trans.image_width() / (2 * self._trans.world_width())) for obj in objects: @@ -59,6 +62,8 @@ def render_objects(self, objects: typing.List["Object"]) -> None: clip_path="url(#page)", transform=f"translate({p * self._trans.world_width()}, 0)" ) obj.render_svg(self) + if bbox: + self._tighten_to_boundary(bbox) self._draw.add(self._group) self._group = None @@ -74,11 +79,15 @@ def render_background(self, color: typing.Optional[Color]) -> None: group.add(self._draw.rect(insert=(0, 0), size=self._trans.image_size(), rx=None, ry=None, fill=color.hex_rgb())) self._draw.add(group) - def render_tiles(self, download: typing.Callable[[int, int, int], typing.Optional[bytes]]) -> None: - """Render background of static map + def render_tiles( + self, download: typing.Callable[[int, int, int], typing.Optional[bytes]], bbox: s2sphere.LatLngRect = None + ) -> None: + """Render tiles of static map :param download: url of tiles provider :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] + :param bbox: boundary box of all objects + :type bbox: s2sphere.LatLngRect """ group = self._draw.g(clip_path="url(#page)") for yy in range(0, self._trans.tiles_y()): @@ -103,8 +112,32 @@ def render_tiles(self, download: typing.Callable[[int, int, int], typing.Optiona ) except RuntimeError: pass + if bbox: + self._tighten_to_boundary(bbox) self._draw.add(group) + def _tighten_to_boundary(self, bbox: s2sphere.LatLngRect) -> None: + """Calculate scale and offset for tight rendering on the boundary""" + # boundary points + lo_le_x, lo_le_y = self._trans.ll2pixel(s2sphere.LatLng.from_angles(bbox.lat_lo(), bbox.lng_lo())) + up_ri_x, up_ri_y = self._trans.ll2pixel(s2sphere.LatLng.from_angles(bbox.lat_hi(), bbox.lng_hi())) + # boundary size + size_x = up_ri_x - lo_le_x + size_y = lo_le_y - up_ri_y + # scale to boundaries + width = self._trans.image_width() + height = self._trans.image_height() + scale_x = size_x / width + scale_y = size_y / height + scale = 1 / max(scale_x, scale_y) + # translate new center to old center + off_x = -0.5 * width * (scale - 1) + off_y = -0.5 * height * (scale - 1) + # finally, translate and scale + if isinstance(self._group, svgwrite.container.Group): + self._group.translate(off_x, off_y) + self._group.scale(scale) + def render_attribution(self, attribution: typing.Optional[str]) -> None: """Render attribution from given tiles provider From ab3dc6fa960cb72255c2d6560ff76a4263cbb438 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Fri, 21 Jan 2022 15:30:54 +0100 Subject: [PATCH 039/147] remove small bug with max_b --- staticmaps/context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/staticmaps/context.py b/staticmaps/context.py index 38c225e..bc08777 100644 --- a/staticmaps/context.py +++ b/staticmaps/context.py @@ -238,7 +238,7 @@ def extra_pixel_bounds(self) -> PixelBoundsT: max_l, max_t, max_r, max_b = self._extra_pixel_bounds attribution = self._tile_provider.attribution() if (attribution is None) or (attribution == ""): - max_b = 12 + max_b = max(max_b, 12) for obj in self._objects: (l, t, r, b) = obj.extra_pixel_bounds() max_l = max(max_l, l) From 4426fd15f9ed5a162b926555f1c1932d280aede9 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Fri, 21 Jan 2022 15:46:13 +0100 Subject: [PATCH 040/147] remove small bug self._bounds (union) --- staticmaps/context.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/staticmaps/context.py b/staticmaps/context.py index bc08777..a31d162 100644 --- a/staticmaps/context.py +++ b/staticmaps/context.py @@ -109,7 +109,10 @@ def add_bounds( :param extra_pixel_bounds: extra pixel bounds to be respected :type extra_pixel_bounds: int, tuple """ - self._bounds = latlngrect + if self._bounds: + self._bounds = self._bounds.union(latlngrect) + else: + self._bounds = latlngrect if extra_pixel_bounds: if isinstance(extra_pixel_bounds, tuple): self._extra_pixel_bounds = extra_pixel_bounds From 8172dad41157689d13350f4e980fcf1b07df7bcc Mon Sep 17 00:00:00 2001 From: Lowtower Date: Fri, 21 Jan 2022 15:55:00 +0100 Subject: [PATCH 041/147] remove small bug self._bounds (union) - correct test --- tests/test_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_context.py b/tests/test_context.py index 68751a1..94a8b66 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -39,7 +39,7 @@ def test_bounds() -> None: context.add_bounds(s2sphere.LatLngRect(staticmaps.create_latlng(47.5, 7.5), staticmaps.create_latlng(48, 8))) assert context.object_bounds() is not None assert context.object_bounds() == s2sphere.LatLngRect( - staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8) + staticmaps.create_latlng(46, 6), staticmaps.create_latlng(49, 9) ) From e18cd1effb173047a5966f3c89a21e3f361fad00 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Fri, 21 Jan 2022 17:17:22 +0100 Subject: [PATCH 042/147] take extra pixel bounds into account --- staticmaps/cairo_renderer.py | 16 ++++++++++++++-- staticmaps/context.py | 5 +++-- staticmaps/pillow_renderer.py | 16 ++++++++++++++-- staticmaps/renderer.py | 15 ++++++++++++--- staticmaps/svg_renderer.py | 35 ++++++++++++++++++++++++++--------- 5 files changed, 69 insertions(+), 18 deletions(-) diff --git a/staticmaps/cairo_renderer.py b/staticmaps/cairo_renderer.py index 0cfd7af..bc63d71 100644 --- a/staticmaps/cairo_renderer.py +++ b/staticmaps/cairo_renderer.py @@ -85,13 +85,20 @@ def create_image(image_data: bytes) -> cairo_ImageSurface: png_bytes.seek(0) return cairo.ImageSurface.create_from_png(png_bytes) - def render_objects(self, objects: typing.List["Object"], bbox: s2sphere.LatLngRect = None) -> None: + def render_objects( + self, + objects: typing.List["Object"], + bbox: s2sphere.LatLngRect = None, + epb: typing.Tuple[int, int, int, int] = None, + ) -> None: """Render all objects of static map :param objects: objects of static map :type objects: typing.List["Object"] :param bbox: boundary box of all objects :type bbox: s2sphere.LatLngRect + :param epb: extra pixel bounds + :type epb: typing.Tuple[int, int, int, int] """ x_count = math.ceil(self._trans.image_width() / (2 * self._trans.world_width())) for obj in objects: @@ -114,7 +121,10 @@ def render_background(self, color: typing.Optional[Color]) -> None: self._context.fill() def render_tiles( - self, download: typing.Callable[[int, int, int], typing.Optional[bytes]], bbox: s2sphere.LatLngRect = None + self, + download: typing.Callable[[int, int, int], typing.Optional[bytes]], + bbox: s2sphere.LatLngRect = None, + epb: typing.Tuple[int, int, int, int] = None, ) -> None: """Render background of static map @@ -122,6 +132,8 @@ def render_tiles( :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] :param bbox: boundary box of all objects :type bbox: s2sphere.LatLngRect + :param epb: extra pixel bounds + :type epb: typing.Tuple[int, int, int, int] """ for yy in range(0, self._trans.tiles_y()): y = self._trans.first_tile_y() + yy diff --git a/staticmaps/context.py b/staticmaps/context.py index a31d162..d1bf82d 100644 --- a/staticmaps/context.py +++ b/staticmaps/context.py @@ -195,11 +195,12 @@ def render_svg(self, width: int, height: int) -> svgwrite.Drawing: trans = Transformer(width, height, zoom, center, self._tile_provider.tile_size()) bbox = self.object_bounds() + epb = self.extra_pixel_bounds() renderer = SvgRenderer(trans) renderer.render_background(self._background_color) - renderer.render_tiles(self._fetch_tile, bbox) - renderer.render_objects(self._objects, bbox) + renderer.render_tiles(self._fetch_tile, bbox, epb) + renderer.render_objects(self._objects, bbox, epb) renderer.render_attribution(self._tile_provider.attribution()) return renderer.drawing() diff --git a/staticmaps/pillow_renderer.py b/staticmaps/pillow_renderer.py index 6bd7ac0..70f7c2b 100644 --- a/staticmaps/pillow_renderer.py +++ b/staticmaps/pillow_renderer.py @@ -41,13 +41,20 @@ def alpha_compose(self, image: PIL_Image.Image) -> None: self._image = PIL_Image.alpha_composite(self._image, image) self._draw = PIL_ImageDraw.Draw(self._image) - def render_objects(self, objects: typing.List["Object"], bbox: s2sphere.LatLngRect = None) -> None: + def render_objects( + self, + objects: typing.List["Object"], + bbox: s2sphere.LatLngRect = None, + epb: typing.Tuple[int, int, int, int] = None, + ) -> None: """Render all objects of static map :param objects: objects of static map :type objects: typing.List["Object"] :param bbox: boundary box of all objects :type bbox: s2sphere.LatLngRect + :param epb: extra pixel bounds + :type epb: typing.Tuple[int, int, int, int] """ x_count = math.ceil(self._trans.image_width() / (2 * self._trans.world_width())) for obj in objects: @@ -66,7 +73,10 @@ def render_background(self, color: typing.Optional[Color]) -> None: self.draw().rectangle([(0, 0), self.image().size], fill=color.int_rgba()) def render_tiles( - self, download: typing.Callable[[int, int, int], typing.Optional[bytes]], bbox: s2sphere.LatLngRect = None + self, + download: typing.Callable[[int, int, int], typing.Optional[bytes]], + bbox: s2sphere.LatLngRect = None, + epb: typing.Tuple[int, int, int, int] = None, ) -> None: """Render background of static map @@ -74,6 +84,8 @@ def render_tiles( :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] :param bbox: boundary box of all objects :type bbox: s2sphere.LatLngRect + :param epb: extra pixel bounds + :type epb: typing.Tuple[int, int, int, int] """ for yy in range(0, self._trans.tiles_y()): y = self._trans.first_tile_y() + yy diff --git a/staticmaps/renderer.py b/staticmaps/renderer.py index c03842f..882a083 100644 --- a/staticmaps/renderer.py +++ b/staticmaps/renderer.py @@ -33,13 +33,17 @@ def transformer(self) -> Transformer: return self._trans @abstractmethod - def render_objects(self, objects: typing.List["Object"], bbox: s2sphere.LatLngRect) -> None: + def render_objects( + self, objects: typing.List["Object"], bbox: s2sphere.LatLngRect, epb: typing.Tuple[int, int, int, int] = None + ) -> None: """Render all objects of static map :param objects: objects of static map :type objects: typing.List["Object"] :param bbox: boundary box of all objects :type bbox: s2sphere.LatLngRect + :param epb: extra pixel bounds + :type epb: typing.Tuple[int, int, int, int] """ @abstractmethod @@ -52,14 +56,19 @@ def render_background(self, color: typing.Optional[Color]) -> None: @abstractmethod def render_tiles( - self, download: typing.Callable[[int, int, int], typing.Optional[bytes]], bbox: s2sphere.LatLngRect + self, + download: typing.Callable[[int, int, int], typing.Optional[bytes]], + bbox: s2sphere.LatLngRect, + epb: typing.Tuple[int, int, int, int] = None, ) -> None: - """Render background of static map + """Render tiles of static map :param download: url of tiles provider :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] :param bbox: boundary box of all objects :type bbox: s2sphere.LatLngRect + :param epb: extra pixel bounds + :type epb: typing.Tuple[int, int, int, int] """ def render_marker_object(self, marker: "Marker") -> None: diff --git a/staticmaps/svg_renderer.py b/staticmaps/svg_renderer.py index 3b5d729..8bbda7f 100644 --- a/staticmaps/svg_renderer.py +++ b/staticmaps/svg_renderer.py @@ -47,13 +47,20 @@ def group(self) -> svgwrite.container.Group: assert self._group is not None return self._group - def render_objects(self, objects: typing.List["Object"], bbox: s2sphere.LatLngRect = None) -> None: + def render_objects( + self, + objects: typing.List["Object"], + bbox: s2sphere.LatLngRect = None, + epb: typing.Tuple[int, int, int, int] = None, + ) -> None: """Render all objects of static map :param objects: objects of static map :type objects: typing.List["Object"] :param bbox: boundary box of all objects :type bbox: s2sphere.LatLngRect + :param epb: extra pixel bounds + :type epb: typing.Tuple[int, int, int, int] """ x_count = math.ceil(self._trans.image_width() / (2 * self._trans.world_width())) for obj in objects: @@ -63,7 +70,7 @@ def render_objects(self, objects: typing.List["Object"], bbox: s2sphere.LatLngRe ) obj.render_svg(self) if bbox: - self._tighten_to_boundary(bbox) + self._tighten_to_boundary(bbox, epb) self._draw.add(self._group) self._group = None @@ -80,7 +87,10 @@ def render_background(self, color: typing.Optional[Color]) -> None: self._draw.add(group) def render_tiles( - self, download: typing.Callable[[int, int, int], typing.Optional[bytes]], bbox: s2sphere.LatLngRect = None + self, + download: typing.Callable[[int, int, int], typing.Optional[bytes]], + bbox: s2sphere.LatLngRect = None, + epb: typing.Tuple[int, int, int, int] = None, ) -> None: """Render tiles of static map @@ -88,6 +98,8 @@ def render_tiles( :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] :param bbox: boundary box of all objects :type bbox: s2sphere.LatLngRect + :param epb: extra pixel bounds + :type epb: typing.Tuple[int, int, int, int] """ group = self._draw.g(clip_path="url(#page)") for yy in range(0, self._trans.tiles_y()): @@ -113,17 +125,22 @@ def render_tiles( except RuntimeError: pass if bbox: - self._tighten_to_boundary(bbox) + self._tighten_to_boundary(bbox, epb) self._draw.add(group) - def _tighten_to_boundary(self, bbox: s2sphere.LatLngRect) -> None: + def _tighten_to_boundary(self, bbox: s2sphere.LatLngRect, epb: tuple) -> None: """Calculate scale and offset for tight rendering on the boundary""" # boundary points - lo_le_x, lo_le_y = self._trans.ll2pixel(s2sphere.LatLng.from_angles(bbox.lat_lo(), bbox.lng_lo())) - up_ri_x, up_ri_y = self._trans.ll2pixel(s2sphere.LatLng.from_angles(bbox.lat_hi(), bbox.lng_hi())) + nw_x, nw_y = self._trans.ll2pixel(s2sphere.LatLng.from_angles(bbox.lat_lo(), bbox.lng_lo())) + se_x, se_y = self._trans.ll2pixel(s2sphere.LatLng.from_angles(bbox.lat_hi(), bbox.lng_hi())) + epb_l, epb_t, epb_r, epb_b = epb # boundary size - size_x = up_ri_x - lo_le_x - size_y = lo_le_y - up_ri_y + size_x = se_x - nw_x + size_y = nw_y - se_y + print(size_x, size_y) + size_x = se_x - nw_x + epb_r + epb_l + size_y = nw_y - se_y + epb_t + epb_b + print(size_x, size_y) # scale to boundaries width = self._trans.image_width() height = self._trans.image_height() From 26f79b8731863285ab05c8bd737ae1be22ec30de Mon Sep 17 00:00:00 2001 From: Lowtower Date: Fri, 21 Jan 2022 17:27:29 +0100 Subject: [PATCH 043/147] pylint --- staticmaps/svg_renderer.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/staticmaps/svg_renderer.py b/staticmaps/svg_renderer.py index 8bbda7f..e16a880 100644 --- a/staticmaps/svg_renderer.py +++ b/staticmaps/svg_renderer.py @@ -128,19 +128,20 @@ def render_tiles( self._tighten_to_boundary(bbox, epb) self._draw.add(group) - def _tighten_to_boundary(self, bbox: s2sphere.LatLngRect, epb: tuple) -> None: + def _tighten_to_boundary( + self, bbox: s2sphere.LatLngRect, epb: typing.Optional[typing.Tuple[int, int, int, int]] = None + ) -> None: """Calculate scale and offset for tight rendering on the boundary""" + # pylint: disable=too-many-locals # boundary points nw_x, nw_y = self._trans.ll2pixel(s2sphere.LatLng.from_angles(bbox.lat_lo(), bbox.lng_lo())) se_x, se_y = self._trans.ll2pixel(s2sphere.LatLng.from_angles(bbox.lat_hi(), bbox.lng_hi())) - epb_l, epb_t, epb_r, epb_b = epb + epb_l, epb_t, epb_r, epb_b = 0, 0, 0, 0 + if epb: + epb_l, epb_t, epb_r, epb_b = epb # boundary size - size_x = se_x - nw_x - size_y = nw_y - se_y - print(size_x, size_y) size_x = se_x - nw_x + epb_r + epb_l size_y = nw_y - se_y + epb_t + epb_b - print(size_x, size_y) # scale to boundaries width = self._trans.image_width() height = self._trans.image_height() From 5efe122967748d5a48767f19c7121f61b982a74a Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 24 Jan 2022 11:27:59 +0100 Subject: [PATCH 044/147] add group for all objects, translate and scale afterwards --- staticmaps/svg_renderer.py | 45 ++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/staticmaps/svg_renderer.py b/staticmaps/svg_renderer.py index e16a880..91b9f9f 100644 --- a/staticmaps/svg_renderer.py +++ b/staticmaps/svg_renderer.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +"""py-staticmaps - SvgRenderer""" + # py-staticmaps # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information @@ -50,8 +53,8 @@ def group(self) -> svgwrite.container.Group: def render_objects( self, objects: typing.List["Object"], - bbox: s2sphere.LatLngRect = None, - epb: typing.Tuple[int, int, int, int] = None, + bbox: typing.Optional[s2sphere.LatLngRect] = None, + epb: typing.Optional[typing.Tuple[int, int, int, int]] = None, ) -> None: """Render all objects of static map @@ -62,17 +65,16 @@ def render_objects( :param epb: extra pixel bounds :type epb: typing.Tuple[int, int, int, int] """ + self._group = self._draw.g(clip_path="url(#page)") x_count = math.ceil(self._trans.image_width() / (2 * self._trans.world_width())) for obj in objects: for p in range(-x_count, x_count + 1): - self._group = self._draw.g( - clip_path="url(#page)", transform=f"translate({p * self._trans.world_width()}, 0)" - ) + group = self._draw.g(clip_path="url(#page)", transform=f"translate({p * self._trans.world_width()}, 0)") obj.render_svg(self) - if bbox: - self._tighten_to_boundary(bbox, epb) - self._draw.add(self._group) - self._group = None + self._group.add(group) + objects_group = self._tighten_to_boundary(self._group, bbox, epb) + self._draw.add(objects_group) + self._group = None def render_background(self, color: typing.Optional[Color]) -> None: """Render background of static map @@ -101,7 +103,7 @@ def render_tiles( :param epb: extra pixel bounds :type epb: typing.Tuple[int, int, int, int] """ - group = self._draw.g(clip_path="url(#page)") + self._group = self._draw.g(clip_path="url(#page)") for yy in range(0, self._trans.tiles_y()): y = self._trans.first_tile_y() + yy if y < 0 or y >= self._trans.number_of_tiles(): @@ -112,7 +114,7 @@ def render_tiles( tile_img = self.fetch_tile(download, x, y) if tile_img is None: continue - group.add( + self._group.add( self._draw.image( tile_img, insert=( @@ -124,15 +126,20 @@ def render_tiles( ) except RuntimeError: pass - if bbox: - self._tighten_to_boundary(bbox, epb) - self._draw.add(group) + tiles_group = self._tighten_to_boundary(self._group, bbox, epb) + self._draw.add(tiles_group) + self._group = None def _tighten_to_boundary( - self, bbox: s2sphere.LatLngRect, epb: typing.Optional[typing.Tuple[int, int, int, int]] = None - ) -> None: + self, + group: svgwrite.container.Group, + bbox: typing.Optional[s2sphere.LatLngRect] = None, + epb: typing.Optional[typing.Tuple[int, int, int, int]] = None, + ) -> svgwrite.container.Group: """Calculate scale and offset for tight rendering on the boundary""" # pylint: disable=too-many-locals + if not bbox or not epb: + return group # boundary points nw_x, nw_y = self._trans.ll2pixel(s2sphere.LatLng.from_angles(bbox.lat_lo(), bbox.lng_lo())) se_x, se_y = self._trans.ll2pixel(s2sphere.LatLng.from_angles(bbox.lat_hi(), bbox.lng_hi())) @@ -152,9 +159,9 @@ def _tighten_to_boundary( off_x = -0.5 * width * (scale - 1) off_y = -0.5 * height * (scale - 1) # finally, translate and scale - if isinstance(self._group, svgwrite.container.Group): - self._group.translate(off_x, off_y) - self._group.scale(scale) + group.translate(off_x, off_y) + group.scale(scale) + return group def render_attribution(self, attribution: typing.Optional[str]) -> None: """Render attribution from given tiles provider From 5be5114c442c46af2f1e71407f27b2c7dcb1f3a0 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 24 Jan 2022 11:52:34 +0100 Subject: [PATCH 045/147] command line argument for tightening to boundaries (default: old behaviour) --- staticmaps/cli.py | 8 ++++++++ staticmaps/context.py | 19 +++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/staticmaps/cli.py b/staticmaps/cli.py index 62b37eb..685d3a0 100755 --- a/staticmaps/cli.py +++ b/staticmaps/cli.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +"""py-staticmaps - cli.py - entry point""" # py-staticmaps # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information @@ -79,6 +80,12 @@ def main() -> None: metavar="LAT,LNG LAT,LNG", type=str, ) + args_parser.add_argument( + "--tight-to-bounds", + action="store_true", + default=False, + help="Tighten static map to minimum boundaries of objects, custom boundaries, ...(default: False)", + ) args_parser.add_argument( "--tiles", metavar="TILEPROVIDER", @@ -130,6 +137,7 @@ def main() -> None: context.add_object(staticmaps.Marker(staticmaps.parse_latlng(coords))) if args.bounds is not None: context.add_bounds(staticmaps.parse_latlngs2rect(args.bounds)) + context.set_tighten_to_bounds(args.tighten_to_bounds) file_name = args.filename[0] if determine_file_format(args.file_format, file_name) == FileFormat.PNG: diff --git a/staticmaps/context.py b/staticmaps/context.py index d1bf82d..0bd7072 100644 --- a/staticmaps/context.py +++ b/staticmaps/context.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +"""py-staticmaps - Context""" + # py-staticmaps # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information @@ -33,6 +36,7 @@ def __init__(self) -> None: self._tile_provider = tile_provider_OSM self._tile_downloader = TileDownloader() self._cache_dir = os.path.join(appdirs.user_cache_dir(LIB_NAME), "tiles") + self._tighten_to_bounds: bool = False def set_zoom(self, zoom: int) -> None: """Set zoom for static map @@ -89,6 +93,14 @@ def set_tile_provider(self, provider: TileProvider, api_key: typing.Optional[str if api_key: self._tile_provider.set_api_key(api_key) + def set_tighten_to_bounds(self, tighten: bool = False) -> None: + """Set tighten to bounds + + :param tighten: tighten or not + :type tighten: bool + """ + self._tighten_to_bounds = tighten + def add_object(self, obj: Object) -> None: """Add object for the static map (e.g. line, area, marker) @@ -194,8 +206,11 @@ def render_svg(self, width: int, height: int) -> svgwrite.Drawing: raise RuntimeError("Cannot render map without center/zoom.") trans = Transformer(width, height, zoom, center, self._tile_provider.tile_size()) - bbox = self.object_bounds() - epb = self.extra_pixel_bounds() + bbox = None + epb = None + if self._tighten_to_bounds: + bbox = self.object_bounds() + epb = self.extra_pixel_bounds() renderer = SvgRenderer(trans) renderer.render_background(self._background_color) From e1fbff97f057fa98570997f18d952a9eba27617d Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 24 Jan 2022 12:35:03 +0100 Subject: [PATCH 046/147] extend tests for context regarding boundaries --- tests/test_context.py | 129 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 127 insertions(+), 2 deletions(-) diff --git a/tests/test_context.py b/tests/test_context.py index 94a8b66..2a86dd5 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +"""py-staticmaps - Test Context""" + # py-staticmaps # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information @@ -9,6 +12,81 @@ from .mock_tile_downloader import MockTileDownloader +def test_add_marker_adds_bounds_is_point() -> None: + context = staticmaps.Context() + assert context.object_bounds() is None + + context.add_object(staticmaps.Marker(staticmaps.create_latlng(48, 8))) + assert context.object_bounds() is not None + assert context.object_bounds() == s2sphere.LatLngRect( + staticmaps.create_latlng(48, 8), staticmaps.create_latlng(48, 8) + ) + bounds = context.object_bounds() + assert bounds is not None + assert bounds.is_point() + + +def test_add_two_markers_adds_bounds_is_not_point() -> None: + context = staticmaps.Context() + assert context.object_bounds() is None + + context.add_object(staticmaps.Marker(staticmaps.create_latlng(47, 7))) + assert context.object_bounds() is not None + assert context.object_bounds() == s2sphere.LatLngRect( + staticmaps.create_latlng(47, 7), staticmaps.create_latlng(47, 7) + ) + context.add_object(staticmaps.Marker(staticmaps.create_latlng(48, 8))) + assert context.object_bounds() == s2sphere.LatLngRect( + staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8) + ) + bounds = context.object_bounds() + assert bounds is not None + assert not bounds.is_point() + + +def test_add_line_adds_bounds_is_rect() -> None: + context = staticmaps.Context() + assert context.object_bounds() is None + + context.add_object(staticmaps.Line([staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8)])) + assert context.object_bounds() is not None + assert context.object_bounds() == s2sphere.LatLngRect( + staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8) + ) + + +def test_add_greater_line_extends_bounds() -> None: + context = staticmaps.Context() + assert context.object_bounds() is None + + context.add_object(staticmaps.Line([staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8)])) + assert context.object_bounds() is not None + assert context.object_bounds() == s2sphere.LatLngRect( + staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8) + ) + context.add_object(staticmaps.Line([staticmaps.create_latlng(46, 6), staticmaps.create_latlng(49, 9)])) + assert context.object_bounds() is not None + assert context.object_bounds() == s2sphere.LatLngRect( + staticmaps.create_latlng(46, 6), staticmaps.create_latlng(49, 9) + ) + + +def test_add_smaller_line_keeps_bounds() -> None: + context = staticmaps.Context() + assert context.object_bounds() is None + + context.add_object(staticmaps.Line([staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8)])) + assert context.object_bounds() is not None + assert context.object_bounds() == s2sphere.LatLngRect( + staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8) + ) + context.add_object(staticmaps.Line([staticmaps.create_latlng(47.5, 7.5), staticmaps.create_latlng(48, 8)])) + assert context.object_bounds() is not None + assert context.object_bounds() == s2sphere.LatLngRect( + staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8) + ) + + def test_bounds() -> None: context = staticmaps.Context() assert context.object_bounds() is None @@ -36,14 +114,45 @@ def test_bounds() -> None: staticmaps.create_latlng(46, 6), staticmaps.create_latlng(49, 9) ) + +def test_add_greater_custom_bound_extends_bounds() -> None: + context = staticmaps.Context() + assert context.object_bounds() is None + + context.add_object(staticmaps.Marker(staticmaps.create_latlng(47, 7))) + context.add_object(staticmaps.Marker(staticmaps.create_latlng(48, 8))) + assert context.object_bounds() is not None + + context.add_bounds(s2sphere.LatLngRect(staticmaps.create_latlng(49, 7.5), staticmaps.create_latlng(49, 8))) + assert context.object_bounds() == s2sphere.LatLngRect( + staticmaps.create_latlng(47, 7), staticmaps.create_latlng(49, 8) + ) + + +def test_add_smaller_custom_bound_keeps_bounds() -> None: + context = staticmaps.Context() + assert context.object_bounds() is None + + context.add_object(staticmaps.Marker(staticmaps.create_latlng(47, 7))) + context.add_object(staticmaps.Marker(staticmaps.create_latlng(48, 8))) + assert context.object_bounds() is not None + context.add_bounds(s2sphere.LatLngRect(staticmaps.create_latlng(47.5, 7.5), staticmaps.create_latlng(48, 8))) assert context.object_bounds() is not None assert context.object_bounds() == s2sphere.LatLngRect( - staticmaps.create_latlng(46, 6), staticmaps.create_latlng(49, 9) + staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8) ) -def test_render_empty() -> None: +def test_set_wrong_zoom_raises_exception() -> None: + context = staticmaps.Context() + with pytest.raises(ValueError): + context.set_zoom(-1) + with pytest.raises(ValueError): + context.set_zoom(31) + + +def test_render_empty_raises_exception() -> None: context = staticmaps.Context() with pytest.raises(RuntimeError): context.render_svg(200, 100) @@ -55,3 +164,19 @@ def test_render_center_zoom() -> None: context.set_center(staticmaps.create_latlng(48, 8)) context.set_zoom(15) context.render_svg(200, 100) + + +def test_render_with_zoom_without_center_raises_exception() -> None: + context = staticmaps.Context() + context.set_tile_downloader(MockTileDownloader()) + context.set_zoom(15) + with pytest.raises(RuntimeError): + context.render_svg(200, 100) + + +def test_render_with_center_without_zoom_sets_zoom_15() -> None: + context = staticmaps.Context() + context.set_tile_downloader(MockTileDownloader()) + context.set_center(staticmaps.create_latlng(48, 8)) + context.render_svg(200, 100) + assert context.determine_center_zoom(200, 100) == (staticmaps.create_latlng(48, 8), 15) From 24a97f4d00495ab36826d714a68552b1a8847fa5 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 24 Jan 2022 13:48:31 +0100 Subject: [PATCH 047/147] remove executable line --- staticmaps/context.py | 1 - staticmaps/svg_renderer.py | 1 - tests/test_context.py | 1 - 3 files changed, 3 deletions(-) diff --git a/staticmaps/context.py b/staticmaps/context.py index 0bd7072..074f414 100644 --- a/staticmaps/context.py +++ b/staticmaps/context.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python """py-staticmaps - Context""" # py-staticmaps diff --git a/staticmaps/svg_renderer.py b/staticmaps/svg_renderer.py index 91b9f9f..22842d9 100644 --- a/staticmaps/svg_renderer.py +++ b/staticmaps/svg_renderer.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python """py-staticmaps - SvgRenderer""" # py-staticmaps diff --git a/tests/test_context.py b/tests/test_context.py index 2a86dd5..27ec16a 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python """py-staticmaps - Test Context""" # py-staticmaps From 642b3ca9cb06f6fa8c4d18c17c4df545072b77b1 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Tue, 25 Jan 2022 12:38:38 +0100 Subject: [PATCH 048/147] add examples for tight boundaries --- examples/frankfurt_newyork.py | 7 +++++++ examples/freiburg_area.py | 7 +++++++ examples/us_capitals.py | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/examples/frankfurt_newyork.py b/examples/frankfurt_newyork.py index de7bb86..5bb1998 100644 --- a/examples/frankfurt_newyork.py +++ b/examples/frankfurt_newyork.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +"""py-staticmaps - Example Frankfurt-New York""" # py-staticmaps # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information @@ -28,3 +29,9 @@ svg_image = context.render_svg(800, 500) with open("frankfurt_newyork.svg", "w", encoding="utf-8") as f: svg_image.write(f, pretty=True) + +# render svg - tight boundaries +context.set_tighten_to_bounds(True) +svg_image = context.render_svg(800, 500) +with open("frankfurt_newyork.tight.svg", "w", encoding="utf-8") as f: + svg_image.write(f, pretty=True) diff --git a/examples/freiburg_area.py b/examples/freiburg_area.py index 11558e2..82b23d6 100644 --- a/examples/freiburg_area.py +++ b/examples/freiburg_area.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +"""py-staticmaps - Example Freiburg Area""" # py-staticmaps # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information @@ -457,3 +458,9 @@ svg_image = context.render_svg(800, 500) with open("freiburg_area.svg", "w", encoding="utf-8") as f: svg_image.write(f, pretty=True) + +# render svg - tight boundaries +context.set_tighten_to_bounds(True) +svg_image = context.render_svg(800, 500) +with open("freiburg_area.tight.svg", "w", encoding="utf-8") as f: + svg_image.write(f, pretty=True) diff --git a/examples/us_capitals.py b/examples/us_capitals.py index 98c086b..a8cf0f8 100644 --- a/examples/us_capitals.py +++ b/examples/us_capitals.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +"""py-staticmaps - Example US capitals""" # py-staticmaps # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information @@ -32,3 +33,9 @@ svg_image = context.render_svg(800, 500) with open("us_capitals.svg", "w", encoding="utf-8") as f: svg_image.write(f, pretty=True) + +# render svg - tight boundaries +context.set_tighten_to_bounds(True) +svg_image = context.render_svg(800, 500) +with open("us_capitals.tight.svg", "w", encoding="utf-8") as f: + svg_image.write(f, pretty=True) From 14c74383f669b70a472d7368a271e3ffbf0c43f7 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 7 Feb 2022 16:31:18 +0100 Subject: [PATCH 049/147] add codespell change linting order add generating coverage report --- Makefile | 18 ++++++++++++------ requirements-dev.txt | 3 +++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 0928763..3349aef 100644 --- a/Makefile +++ b/Makefile @@ -12,17 +12,19 @@ install: setup .PHONY: lint lint: - .env/bin/pylint \ - setup.py staticmaps examples tests - .env/bin/flake8 \ - setup.py staticmaps examples tests - .env/bin/mypy \ - setup.py staticmaps examples tests .env/bin/black \ --line-length 120 \ --check \ --diff \ setup.py staticmaps examples tests + .env/bin/flake8 \ + setup.py staticmaps examples tests + .env/bin/pylint \ + setup.py staticmaps examples tests + .env/bin/mypy \ + setup.py staticmaps examples tests + .env/bin/codespell \ + README.md staticmaps/*.py tests/*.py examples/*.py .PHONY: format format: @@ -47,6 +49,10 @@ run-examples: test: PYTHONPATH=. .env/bin/python -m pytest tests +.PHONY: coverage +coverage: + PYTHONPATH=. .env/bin/python -m pytest --cov=staticmaps --cov-branch --cov-report=term --cov-report=html tests + .PHONY: build-package build-package: rm -rf dist diff --git a/requirements-dev.txt b/requirements-dev.txt index 16db555..3797d10 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,9 @@ black +codespell +coverage flake8 mypy pylint pytest +pytest-cov twine From 3e79f3983bc9e492eef554608c3f3d6f661f4ec5 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 7 Feb 2022 17:54:43 +0100 Subject: [PATCH 050/147] add docstrings to color.py add tests for color.py --- staticmaps/color.py | 30 ++++++++- tests/test_color.py | 152 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 174 insertions(+), 8 deletions(-) diff --git a/staticmaps/color.py b/staticmaps/color.py index 0c127d8..998a0d7 100644 --- a/staticmaps/color.py +++ b/staticmaps/color.py @@ -1,4 +1,4 @@ -# py-staticmaps +"""py-staticmaps - Color""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import random @@ -57,12 +57,27 @@ def int_rgba(self) -> typing.Tuple[int, int, int, int]: return self._r, self._g, self._b, self._a def float_rgb(self) -> typing.Tuple[float, float, float]: + """Return color in rgb float values + + :return: color in rgb float values + :rtype:tuple + """ return self._r / 255.0, self._g / 255.0, self._b / 255.0 def float_rgba(self) -> typing.Tuple[float, float, float, float]: + """Return color in rgba float values with transparency + + :return: color in rgba float values + :rtype:tuple + """ return self._r / 255.0, self._g / 255.0, self._b / 255.0, self._a / 255.0 def float_a(self) -> float: + """Return transparency as float value + + :return: transparency as float value + :rtype:tuple + """ return self._a / 255.0 @@ -79,6 +94,14 @@ def float_a(self) -> float: def parse_color(s: str) -> Color: + """Parses a given string and return a color + + :param s: string defining a color + :type s: str + :return: a color depending on given string + :rtype: Color + :raises ValueError: raises a value error if string cannot be parsed to a color + """ re_rgb = re.compile(r"^(0x|#)([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})$") re_rgba = re.compile(r"^(0x|#)([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})$") @@ -120,6 +143,11 @@ def parse_color(s: str) -> Color: def random_color() -> Color: + """Return a random color + + :return: a random color from a list of colors + :rtype: Color + """ return random.choice( [ BLACK, diff --git a/tests/test_color.py b/tests/test_color.py index fc578bb..8900073 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -1,11 +1,135 @@ -# py-staticmaps +"""py-staticmaps - Test Color""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information +import math import pytest # type: ignore import staticmaps +def test_text_color() -> None: + white = [ + (0, 0, 0), + (255, 0, 0), + (0, 0, 255), + (0, 0, 0, 0), + (0, 0, 0, 255), + ] + for rgb in white: + color = staticmaps.Color(*rgb) + assert staticmaps.WHITE == color.text_color() + assert staticmaps.WHITE.int_rgb() == color.text_color().int_rgb() + + black = [ + (0, 255, 0), + (255, 255, 0), + (0, 255, 255), + (255, 255, 255), + ] + for rgb in black: + color = staticmaps.Color(*rgb) + assert staticmaps.BLACK == color.text_color() + assert staticmaps.BLACK.int_rgb() == color.text_color().int_rgb() + + +def test_hex_rgb() -> None: + colors = [ + ((0, 0, 0), "#000000"), + ((255, 0, 0), "#ff0000"), + ((0, 255, 0), "#00ff00"), + ((0, 0, 255), "#0000ff"), + ((255, 255, 0), "#ffff00"), + ((0, 255, 255), "#00ffff"), + ((255, 255, 255), "#ffffff"), + ] + for rgb, hex_color in colors: + color = staticmaps.Color(*rgb) + assert hex_color == color.hex_rgb() + + +def test_int_rgb() -> None: + colors = [ + (0, 0, 0), + (255, 0, 0), + (0, 0, 255), + (0, 255, 0), + (255, 255, 0), + (0, 255, 255), + (255, 255, 255), + ] + for rgb in colors: + color = staticmaps.Color(*rgb) + assert rgb == color.int_rgb() + + +def test_int_rgba() -> None: + colors = [ + (0, 0, 0, 0), + (255, 0, 0, 0), + (0, 0, 255, 255), + (0, 255, 0, 255), + (255, 255, 0, 0), + (0, 255, 255, 0), + (255, 255, 255, 255), + ] + for rgb in colors: + color = staticmaps.Color(*rgb) + assert rgb == color.int_rgba() + + +def test_float_rgb() -> None: + colors = [ + ((0, 0, 0), (0.0, 0.0, 0.0)), + ((255, 0, 0), (1.0, 0.0, 0.0)), + ((0, 255, 0), (0.0, 1.0, 0.0)), + ((0, 0, 255), (0.0, 0.0, 1.0)), + ((255, 255, 0), (1.0, 1.0, 0.0)), + ((0, 255, 255), (0.0, 1.0, 1.0)), + ((255, 255, 255), (1.0, 1.0, 1.0)), + ] + for rgb, float_color in colors: + color = staticmaps.Color(*rgb) + assert float_color == color.float_rgb() + + +def test_float_rgba() -> None: + colors = [ + ((0, 0, 0), (0.0, 0.0, 0.0, 1.0)), + ((0, 0, 0, 0), (0.0, 0.0, 0.0, 0.0)), + ((255, 0, 0, 0), (1.0, 0.0, 0.0, 0.0)), + ((0, 255, 0, 255), (0.0, 1.0, 0.0, 1.0)), + ((0, 0, 255, 255), (0.0, 0.0, 1.0, 1.0)), + ((255, 255, 0, 0), (1.0, 1.0, 0.0, 0.0)), + ((0, 255, 255, 0), (0.0, 1.0, 1.0, 0.0)), + ((255, 255, 255, 255), (1.0, 1.0, 1.0, 1.0)), + ((0, 0, 0), (0.0, 0.0, 0.0, 1.0)), + ((255, 255, 255), (1.0, 1.0, 1.0, 1.0)), + ] + for rgb, float_color in colors: + color = staticmaps.Color(*rgb) + assert float_color == color.float_rgba() + + +def test_float_a() -> None: + colors = [ + ((0, 0, 0, 0), 0.0), + ((255, 255, 255, 255), 1.0), + ((0, 0, 0), 1.0), + ((255, 255, 255), 1.0), + ((255, 255, 255, 100), 0.39215663), + ((255, 255, 255, 200), 0.78431373), + ] + for rgb, float_alpha in colors: + color = staticmaps.Color(*rgb) + assert math.isclose(float_alpha, color.float_a(), rel_tol=0.0001) + + def test_parse_color() -> None: + good = ["0x1a2b3c", "0x1A2B3C", "#1a2b3c", "0x1A2B3C", "0x1A2B3C4D", "black", "RED", "Green", "transparent"] + for s in good: + staticmaps.parse_color(s) + + +def test_parse_color_raises_value_error() -> None: bad = [ "", "aaa", @@ -18,12 +142,13 @@ def test_parse_color() -> None: with pytest.raises(ValueError): staticmaps.parse_color(s) - good = ["0x1a2b3c", "0x1A2B3C", "#1a2b3c", "0x1A2B3C", "0x1A2B3C4D", "black", "RED", "Green", "transparent"] - for s in good: - staticmaps.parse_color(s) - def test_create() -> None: + staticmaps.Color(1, 2, 3) + staticmaps.Color(1, 2, 3, 4) + + +def test_create_raises_value_error() -> None: bad = [ (-1, 0, 0), (256, 0, 0), @@ -38,5 +163,18 @@ def test_create() -> None: with pytest.raises(ValueError): staticmaps.Color(*rgb) - staticmaps.Color(1, 2, 3) - staticmaps.Color(1, 2, 3, 4) + +def test_random_color() -> None: + colors = [ + staticmaps.BLACK, + staticmaps.BLUE, + staticmaps.BROWN, + staticmaps.GREEN, + staticmaps.ORANGE, + staticmaps.PURPLE, + staticmaps.RED, + staticmaps.YELLOW, + staticmaps.WHITE, + ] + for _ in [0, 10]: + assert staticmaps.random_color() in colors From 2f8e929cb92f1f6a54dd1f39cd0c61ac4b186cfd Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 7 Feb 2022 18:00:54 +0100 Subject: [PATCH 051/147] add file docstrings to coordinates.py rearrange tests for coordinates.py --- staticmaps/coordinates.py | 2 +- tests/test_coordinates.py | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/staticmaps/coordinates.py b/staticmaps/coordinates.py index 124fd87..5c16f82 100644 --- a/staticmaps/coordinates.py +++ b/staticmaps/coordinates.py @@ -1,4 +1,4 @@ -# py-staticmaps +"""py-staticmaps - Coordinates""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import typing diff --git a/tests/test_coordinates.py b/tests/test_coordinates.py index 1793166..596f8fd 100644 --- a/tests/test_coordinates.py +++ b/tests/test_coordinates.py @@ -1,4 +1,4 @@ -# py-staticmaps +"""py-staticmaps - Test Coordinates""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import pytest # type: ignore @@ -7,6 +7,13 @@ def test_parse_latlng() -> None: + good = ["48,8", " 48 , 8 ", "-48,8", "+48,8", "48,-8", "48,+8", "48.123,8.456"] + for s in good: + c = staticmaps.parse_latlng(s) + assert c.is_valid() + + +def test_parse_latlng_raises_value_error() -> None: bad = [ "", "aaa", @@ -24,11 +31,6 @@ def test_parse_latlng() -> None: with pytest.raises(ValueError): staticmaps.parse_latlng(s) - good = ["48,8", " 48 , 8 ", "-48,8", "+48,8", "48,-8", "48,+8", "48.123,8.456"] - for s in good: - c = staticmaps.parse_latlng(s) - assert c.is_valid() - def test_parse_latlngs() -> None: good = [("", 0), ("48,8", 1), ("48,8 47,7", 2), (" 48,8 47,7 ", 2), ("48,7 48,8 47,7", 3)] @@ -36,6 +38,8 @@ def test_parse_latlngs() -> None: a = staticmaps.parse_latlngs(s) assert len(a) == expected_len + +def test_parse_latlngs_raises_value_error() -> None: bad = ["xyz", "48,8 xyz", "48,8 48,181"] for s in bad: with pytest.raises(ValueError): @@ -48,6 +52,8 @@ def test_parse_latlngs2rect() -> None: r = staticmaps.parse_latlngs2rect(s) assert r.is_valid() + +def test_parse_latlngs2rect_raises_value_error() -> None: bad = ["xyz", "48,8 xyz", "48,8 48,181", "48,7", "48,7 48,8 47,7"] for s in bad: with pytest.raises(ValueError): From a4826578bac85dea5293616dec2f1980345d90d3 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 10 Oct 2022 15:55:07 +0200 Subject: [PATCH 052/147] remove obsolete pylint config --- .pylintrc | 48 +----------------------------------------------- 1 file changed, 1 insertion(+), 47 deletions(-) diff --git a/.pylintrc b/.pylintrc index b07c3ce..104490f 100644 --- a/.pylintrc +++ b/.pylintrc @@ -16,7 +16,7 @@ persistent=yes # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. -load-plugins = pylint.extensions.check_docs +load-plugins = pylint.extensions.docparams # Use multiple processes to speed up Pylint. jobs=0 @@ -52,10 +52,8 @@ confidence= # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" disable= - bad-continuation, duplicate-code, missing-docstring, - no-init, no-member, no-value-for-parameter, too-few-public-methods, @@ -69,11 +67,6 @@ disable= # mypackage.mymodule.MyReporterClass. output-format=colorized -# Put messages in a separate file for each module / package specified on the -# command line instead of printing them on stdout. Reports (if any) will be -# written in a file name "pylint_global.[txt|html]". -files-output=no - # Tells whether to display a full report or only the messages reports=no @@ -147,12 +140,6 @@ ignore-long-lines=^\s*(# )??$ # else. single-line-if-stmt=no -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma - # Maximum number of lines in a module max-module-lines=500 @@ -169,9 +156,6 @@ expected-line-ending-format=LF [BASIC] -# List of builtins function names that should not be used, separated by a comma -bad-functions=map,filter,input - # Good variable names which should always be accepted, separated by a comma good-names=i,_ @@ -188,63 +172,33 @@ include-naming-hint=yes # Regular expression matching correct function names function-rgx=([a-z_][a-z0-9_]{1,40}|test_[A-Za-z0-9_]{3,70})$ -# Naming hint for function names -function-name-hint=[a-z_][a-z0-9_]{1,40}$ - # Regular expression matching correct variable names variable-rgx=[a-z_][a-z0-9_]{0,40}$ -# Naming hint for variable names -variable-name-hint=[a-z_][a-z0-9_]{0,40}$ - # Regular expression matching correct constant names const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__)|(urls|urlpatterns|register))$ -# Naming hint for constant names -const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - # Regular expression matching correct attribute names attr-rgx=[a-z_][a-z0-9_]{0,30}$ -# Naming hint for attribute names -attr-name-hint=[a-z_][a-z0-9_]{0,30}$ - # Regular expression matching correct argument names argument-rgx=[a-z_][a-z0-9_]{0,30}$ -# Naming hint for argument names -argument-name-hint=[a-z_][a-z0-9_]{0,30}$ - # Regular expression matching correct class attribute names class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{1,40}|(__.*__))$ -# Naming hint for class attribute names -class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{1,40}|(__.*__))$ - # Regular expression matching correct inline iteration names inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ -# Naming hint for inline iteration names -inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ - # Regular expression matching correct class names class-rgx=[A-Z_][a-zA-Z0-9]+$ -# Naming hint for class names -class-name-hint=[A-Z_][a-zA-Z0-9]+$ - # Regular expression matching correct module names module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ -# Naming hint for module names -module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - # Regular expression matching correct method names method-rgx=[a-z_][a-z0-9_]{1,30}$ -# Naming hint for method names -method-name-hint=[a-z_][a-z0-9_]{1,30}$ - # Regular expression which should only match function or class names that do # not require a docstring. no-docstring-rgx=^_ From 674fbab423ac179c588def2f750e19128e0c392a Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 10 Oct 2022 15:58:34 +0200 Subject: [PATCH 053/147] enable mkdocs --- Makefile | 10 ++++++++- docs/SUMMARY.md | 2 ++ docs/gen_ref_pages.py | 45 +++++++++++++++++++++++++++++++++++++ docs/index.md | 3 +++ mkdocs.yml | 52 +++++++++++++++++++++++++++++++++++++++++++ requirements-dev.txt | 18 +++++++++++++++ 6 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 docs/SUMMARY.md create mode 100644 docs/gen_ref_pages.py create mode 100644 docs/index.md create mode 100644 mkdocs.yml diff --git a/Makefile b/Makefile index 0928763..8cd8634 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,8 @@ +PYTHON=python3 + .PHONY: setup setup: - python3 -m venv .env + $(PYTHON) -m venv .env .env/bin/pip install --upgrade pip .env/bin/pip install --upgrade --requirement requirements.txt .env/bin/pip install --upgrade --requirement requirements-dev.txt @@ -60,3 +62,9 @@ upload-package-test: .PHONY: upload-package upload-package: PYTHONPATH=. .env/bin/twine upload --repository py-staticmaps dist/* + +.PHONY: documentation +documentation: + @if type mkdocs >/dev/null 2>&1 ; then .env/bin/python -m mkdocs build --clean --verbose ; \ + else echo "SKIPPED. Run '$(PIP) install mkdocs' first." >&2 ; fi + diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md new file mode 100644 index 0000000..37f489d --- /dev/null +++ b/docs/SUMMARY.md @@ -0,0 +1,2 @@ +* [Start](index.md) +* [Code Reference](reference/) diff --git a/docs/gen_ref_pages.py b/docs/gen_ref_pages.py new file mode 100644 index 0000000..e4c486f --- /dev/null +++ b/docs/gen_ref_pages.py @@ -0,0 +1,45 @@ +"""Generate the code reference pages and navigation.""" + +from pathlib import Path + +import mkdocs_gen_files +TOP_LEVEL_NAME = "staticmaps" +DIRECTORY = "reference" +SRC = "staticmaps" + + +def main() -> None: + """ + main entry point + """ + + nav = mkdocs_gen_files.Nav() + + for path in sorted(Path(SRC).rglob("*.py")): + module_path = path.relative_to(SRC).with_suffix("") + + doc_path = path.relative_to(SRC).with_suffix(".md") + full_doc_path = Path(DIRECTORY, doc_path) + + parts = list(module_path.parts) + # omit __init__, __main__, cli.py + if parts[-1] in ["__init__", "__main__", "cli"]: + continue + + if not parts: + continue + + nav[parts] = doc_path.as_posix() + + with mkdocs_gen_files.open(full_doc_path, "w") as file_handle: + ident = ".".join(parts) + file_handle.write(f"::: {ident}") + + mkdocs_gen_files.set_edit_path(full_doc_path, path) + # mkdocs_gen_files.set_edit_path(full_doc_path, Path("../") / path) + + with mkdocs_gen_files.open(f"{DIRECTORY}/SUMMARY.md", "w") as nav_file: + nav_file.writelines(nav.build_literate_nav()) + + +main() diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..536d380 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,3 @@ +# py-staticmaps + +A python module to create static map images (PNG, SVG) with markers, geodesic lines, etc. diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..d0d05f8 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,52 @@ +site_name: py-staticmaps +site_description: "A python module to create static map images (PNG, SVG) with markers, geodesic lines, etc." + +repo_url: https://github.com/flopp/py-staticmaps +repo_name: py-staticmaps + +use_directory_urls: false + +theme: + icon: + repo: fontawesome/brands/github + name: "material" + palette: + - media: "(prefers-color-scheme: light)" + primary: "blue" + accent: "grey" + scheme: default + toggle: + icon: material/toggle-switch-off-outline + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + primary: "blue" + accent: "white" + scheme: slate + toggle: + icon: material/toggle-switch + name: Switch to light mode + +plugins: + - search + - autorefs + - section-index + - gen-files: + scripts: + - docs/gen_ref_pages.py + - literate-nav: + nav_file: SUMMARY.md + - mkdocstrings: + default_handler: python + handlers: + python: + paths: [staticmaps] + options: + show_root_heading: true + show_source: true + watch: + - src/sta_run_all_loadcases + +markdown_extensions: + - pymdownx.highlight + - pymdownx.magiclink + - pymdownx.superfences diff --git a/requirements-dev.txt b/requirements-dev.txt index 16db555..b9efa9c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,24 @@ +# Runtime requirements +--requirement requirements.txt + +# Linting/Tooling black flake8 mypy pylint + +# Testing pytest + +# Building twine + +# Documentation +mkdocs +mkdocstrings +mkdocstrings-python +mkdocs-gen-files +mkdocs-literate-nav +mkdocs-material +mkdocs-section-index +Pygments From 9c795e8d99030ca7bb47fd371549e05a3370de78 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 10 Oct 2022 17:30:38 +0200 Subject: [PATCH 054/147] Add and change docstrings in ggole style --- .pylintrc | 4 +- docs/gen_ref_pages.py | 2 +- examples/us_capitals.py | 2 +- setup.py | 2 +- staticmaps/__init__.py | 2 +- staticmaps/area.py | 21 +++--- staticmaps/cairo_renderer.py | 57 ++++++++------- staticmaps/circle.py | 18 ++--- staticmaps/cli.py | 18 ++++- staticmaps/color.py | 51 +++++++++++--- staticmaps/context.py | 127 ++++++++++++++++++---------------- staticmaps/coordinates.py | 51 ++++++++------ staticmaps/image_marker.py | 50 +++++++------ staticmaps/line.py | 38 +++++----- staticmaps/marker.py | 40 ++++++----- staticmaps/meta.py | 2 +- staticmaps/object.py | 6 +- staticmaps/pillow_renderer.py | 64 +++++++++++------ staticmaps/renderer.py | 40 ++++++----- staticmaps/svg_renderer.py | 60 ++++++++-------- staticmaps/tile_downloader.py | 66 +++++++++--------- staticmaps/tile_provider.py | 52 +++++++++----- staticmaps/transformer.py | 117 ++++++++++++++++--------------- tests/test_tile_provider.py | 20 +++++- 24 files changed, 527 insertions(+), 383 deletions(-) diff --git a/.pylintrc b/.pylintrc index 104490f..6d46353 100644 --- a/.pylintrc +++ b/.pylintrc @@ -9,7 +9,7 @@ # Add files or directories to the blacklist. They should be base names, not # paths. -ignore=.tox,.env,.venv,.eggs,build,migrations,south_migrations +ignore=.tox,.env,.venv,.eggs,build,migrations,south_migrations,examples,tests # Pickle collected data for later comparisons. persistent=yes @@ -53,7 +53,7 @@ confidence= # --disable=W" disable= duplicate-code, - missing-docstring, +# missing-docstring, no-member, no-value-for-parameter, too-few-public-methods, diff --git a/docs/gen_ref_pages.py b/docs/gen_ref_pages.py index e4c486f..655e8c2 100644 --- a/docs/gen_ref_pages.py +++ b/docs/gen_ref_pages.py @@ -29,7 +29,7 @@ def main() -> None: if not parts: continue - nav[parts] = doc_path.as_posix() + nav[tuple(parts)] = doc_path.as_posix() with mkdocs_gen_files.open(full_doc_path, "w") as file_handle: ident = ".".join(parts) diff --git a/examples/us_capitals.py b/examples/us_capitals.py index 98c086b..ba7b959 100644 --- a/examples/us_capitals.py +++ b/examples/us_capitals.py @@ -14,7 +14,7 @@ "https://gist.githubusercontent.com/jpriebe/d62a45e29f24e843c974/" "raw/b1d3066d245e742018bce56e41788ac7afa60e29/us_state_capitals.json" ) -response = requests.get(URL) +response = requests.get(URL, timeout=3600) for _, data in json.loads(response.text).items(): capital = staticmaps.create_latlng(float(data["lat"]), float(data["long"])) context.add_object(staticmaps.Marker(capital, size=5)) diff --git a/setup.py b/setup.py index b780f35..a02118f 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -# py-staticmaps +"""py-staticmaps setup""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import os diff --git a/staticmaps/__init__.py b/staticmaps/__init__.py index 860b0aa..90d48bc 100644 --- a/staticmaps/__init__.py +++ b/staticmaps/__init__.py @@ -1,4 +1,4 @@ -# py-staticmaps +"""py-staticmaps __init__""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information # flake8: noqa diff --git a/staticmaps/area.py b/staticmaps/area.py index ec4e0e0..b694620 100644 --- a/staticmaps/area.py +++ b/staticmaps/area.py @@ -1,4 +1,4 @@ -# py-staticmaps +"""py-staticmaps - area""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import typing @@ -17,7 +17,8 @@ class Area(Line): """Render an area using different renderers - :param master: A line object + Parameters: + master: A line object """ def __init__( @@ -32,16 +33,16 @@ def __init__( def fill_color(self) -> Color: """Return fill color of the area - :return: color object - :rtype: Color + Returns: + Color: color object """ return self._fill_color def render_pillow(self, renderer: PillowRenderer) -> None: """Render area using PILLOW - :param renderer: pillow renderer - :type renderer: PillowRenderer + Parameters: + renderer (PillowRenderer): pillow renderer """ xys = [ (x + renderer.offset_x(), y) @@ -57,8 +58,8 @@ def render_pillow(self, renderer: PillowRenderer) -> None: def render_svg(self, renderer: SvgRenderer) -> None: """Render area using svgwrite - :param renderer: svg renderer - :type renderer: SvgRenderer + Parameters: + renderer (SvgRenderer): svg renderer """ xys = [renderer.transformer().ll2pixel(latlng) for latlng in self.interpolate()] @@ -82,8 +83,8 @@ def render_svg(self, renderer: SvgRenderer) -> None: def render_cairo(self, renderer: CairoRenderer) -> None: """Render area using cairo - :param renderer: cairo renderer - :type renderer: CairoRenderer + Parameters: + renderer (CairoRenderer): cairo renderer """ xys = [renderer.transformer().ll2pixel(latlng) for latlng in self.interpolate()] diff --git a/staticmaps/cairo_renderer.py b/staticmaps/cairo_renderer.py index d27d151..8df80e0 100644 --- a/staticmaps/cairo_renderer.py +++ b/staticmaps/cairo_renderer.py @@ -1,4 +1,4 @@ -# py-staticmaps +"""py-staticmaps - cairo_renderer""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import io @@ -25,8 +25,8 @@ def cairo_is_supported() -> bool: """Check whether cairo is supported - :return: Is cairo supported - :rtype: bool + Returns: + bool: Is cairo supported """ return "cairo" in sys.modules @@ -50,17 +50,15 @@ def __init__(self, transformer: Transformer) -> None: def image_surface(self) -> cairo_ImageSurface: """ - - :return: cairo image surface - :rtype: cairo.ImageSurface + Returns: + cairo.ImageSurface: cairo image surface """ return self._surface def context(self) -> cairo_Context: """ - - :return: cairo context - :rtype: cairo.Context + Returns: + cairo.Context: cairo context """ return self._context @@ -68,11 +66,11 @@ def context(self) -> cairo_Context: def create_image(image_data: bytes) -> cairo_ImageSurface: """Create a cairo image - :param image_data: Image data - :type image_data: bytes + Parameters: + image_data (bytes): Image data - :return: cairo image surface - :rtype: cairo.ImageSurface + Returns: + cairo.ImageSurface: cairo image surface """ image = PIL_Image.open(io.BytesIO(image_data)) if image.format == "PNG": @@ -86,8 +84,8 @@ def create_image(image_data: bytes) -> cairo_ImageSurface: def render_objects(self, objects: typing.List["Object"]) -> None: """Render all objects of static map - :param objects: objects of static map - :type objects: typing.List["Object"] + Parameters: + objects (typing.List["Object"]): objects of static map """ x_count = math.ceil(self._trans.image_width() / (2 * self._trans.world_width())) for obj in objects: @@ -100,8 +98,8 @@ def render_objects(self, objects: typing.List["Object"]) -> None: def render_background(self, color: typing.Optional[Color]) -> None: """Render background of static map - :param color: background color - :type color: typing.Optional[Color] + Parameters: + color (typing.Optional[Color]): background color """ if color is None: return @@ -112,8 +110,9 @@ def render_background(self, color: typing.Optional[Color]) -> None: def render_tiles(self, download: typing.Callable[[int, int, int], typing.Optional[bytes]]) -> None: """Render background of static map - :param download: url of tiles provider - :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] + Parameters: + download (typing.Callable[[int, int, int], typing.Optional[bytes]]): + url of tiles provider """ for yy in range(0, self._trans.tiles_y()): y = self._trans.first_tile_y() + yy @@ -139,8 +138,9 @@ def render_tiles(self, download: typing.Callable[[int, int, int], typing.Optiona def render_attribution(self, attribution: typing.Optional[str]) -> None: """Render attribution from given tiles provider - :param attribution: Attribution for the given tiles provider - :type attribution: typing.Optional[str]: + Parameters: + attribution (typing.Optional[str]:): Attribution for the + given tiles provider """ if (attribution is None) or (attribution == ""): return @@ -168,15 +168,14 @@ def fetch_tile( ) -> typing.Optional[cairo_ImageSurface]: """Fetch tiles from given tiles provider - :param download: callable - :param x: width - :param y: height - :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] - :type x: int - :type y: int + Parameters: + download (typing.Callable[[int, int, int], typing.Optional[bytes]]): + callable + x (int): width + y (int): height - :return: cairo image surface - :rtype: typing.Optional[cairo_ImageSurface] + Returns: + typing.Optional[cairo_ImageSurface]: cairo image surface """ image_data = download(self._trans.zoom(), x, y) if image_data is None: diff --git a/staticmaps/circle.py b/staticmaps/circle.py index d2ec92e..f1ec2dc 100644 --- a/staticmaps/circle.py +++ b/staticmaps/circle.py @@ -1,4 +1,4 @@ -# py-staticmaps +"""py-staticmaps - circle""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import typing @@ -14,7 +14,8 @@ class Circle(Area): """Render a circle using different renderers - :param master: an area object + Parameters: + master: an area object """ def __init__( @@ -31,13 +32,12 @@ def __init__( def compute_circle(center: s2sphere.LatLng, radius_km: float) -> typing.Iterator[s2sphere.LatLng]: """Compute a circle with given center and radius - :param center: Center of the circle - :param radius_km: Radius of the circle - :type center: s2sphere.LatLng - :type radius_km: float + Parameters: + center (s2sphere.LatLng): Center of the circle + radius_km (float): Radius of the circle - :return: circle - :rtype: typing.Iterator[s2sphere.LatLng] + Yields: + typing.Iterator[s2sphere.LatLng]: circle """ first = None delta_angle = 0.1 @@ -55,6 +55,6 @@ def compute_circle(center: s2sphere.LatLng, radius_km: float) -> typing.Iterator if first is None: first = latlng yield latlng - angle = angle + delta_angle + angle += delta_angle if first: yield first diff --git a/staticmaps/cli.py b/staticmaps/cli.py index 62b37eb..c348e31 100755 --- a/staticmaps/cli.py +++ b/staticmaps/cli.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# py-staticmaps +"""py-staticmaps cli""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import argparse @@ -11,12 +11,27 @@ class FileFormat(enum.Enum): + """FileFormat""" + GUESS = "guess" PNG = "png" SVG = "svg" def determine_file_format(file_format: FileFormat, file_name: str) -> FileFormat: + """ + determine_file_format Try to determine the file format + + Parameters: + file_format (FileFormat): The File Format + file_name (str): The file name + + Raises: + RuntimeError: If the file format cannot be determined + + Returns: + FileFormat: A FileFormat object + """ if file_format != FileFormat.GUESS: return file_format extension = os.path.splitext(file_name)[1] @@ -28,6 +43,7 @@ def determine_file_format(file_format: FileFormat, file_name: str) -> FileFormat def main() -> None: + """main Entry point""" args_parser = argparse.ArgumentParser(prog="createstaticmap") args_parser.add_argument( "--center", diff --git a/staticmaps/color.py b/staticmaps/color.py index 0c127d8..b3fba2e 100644 --- a/staticmaps/color.py +++ b/staticmaps/color.py @@ -1,4 +1,4 @@ -# py-staticmaps +"""py-staticmaps - color""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import random @@ -26,8 +26,8 @@ def __init__(self, r: int, g: int, b: int, a: int = 255): def text_color(self) -> "Color": """Return text color depending on luminance - :return: a color depending on luminance - :rtype: Color + Returns: + Color: a color depending on luminance """ luminance = 0.299 * self._r + 0.587 * self._g + 0.114 * self._b return BLACK if luminance >= 0x7F else WHITE @@ -35,34 +35,49 @@ def text_color(self) -> "Color": def hex_rgb(self) -> str: """Return color in rgb hex values - :return: color in rgb hex values - :rtype:str + Returns: + str: color in rgb hex values """ return f"#{self._r:02x}{self._g:02x}{self._b:02x}" def int_rgb(self) -> typing.Tuple[int, int, int]: """Return color in int values - :return: color in int values - :rtype:tuple + Returns: + tuple: color in int values """ return self._r, self._g, self._b def int_rgba(self) -> typing.Tuple[int, int, int, int]: """Return color in rgba int values with transparency - :return: color in rgba int values - :rtype:tuple + Returns: + tuple: color in rgba int values """ return self._r, self._g, self._b, self._a def float_rgb(self) -> typing.Tuple[float, float, float]: + """Return color in rgb float values + + Returns: + tuple: color in rgb float values + """ return self._r / 255.0, self._g / 255.0, self._b / 255.0 def float_rgba(self) -> typing.Tuple[float, float, float, float]: + """Return color in rgba float values with transparency + + Returns: + tuple: color in rgba float values + """ return self._r / 255.0, self._g / 255.0, self._b / 255.0, self._a / 255.0 def float_a(self) -> float: + """Return alpha channel as float value + + Returns: + float: alpha channel as float value + """ return self._a / 255.0 @@ -79,6 +94,18 @@ def float_a(self) -> float: def parse_color(s: str) -> Color: + """ + parse_color Parse a string to a color object + + Parameters: + s (str): A string representing a color + + Raises: + ValueError: If string is not recognised as a color representation + + Returns: + Color: A Color object + """ re_rgb = re.compile(r"^(0x|#)([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})$") re_rgba = re.compile(r"^(0x|#)([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})$") @@ -120,6 +147,12 @@ def parse_color(s: str) -> Color: def random_color() -> Color: + """ + random_color Return a color object of a random color + + Returns: + Color: A Color object + """ return random.choice( [ BLACK, diff --git a/staticmaps/context.py b/staticmaps/context.py index 694af08..4cb71b6 100644 --- a/staticmaps/context.py +++ b/staticmaps/context.py @@ -1,4 +1,4 @@ -# py-staticmaps +"""py-staticmaps - context""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import math @@ -22,6 +22,8 @@ class Context: + """Context""" + # pylint: disable=too-many-instance-attributes def __init__(self) -> None: self._background_color: typing.Optional[Color] = None @@ -37,9 +39,11 @@ def __init__(self) -> None: def set_zoom(self, zoom: int) -> None: """Set zoom for static map - :param zoom: zoom for static map - :type zoom: int - :raises ValueError: raises value error for invalid zoom factors + Parameters: + zoom (int): zoom for static map + + Raises: + ValueError: raises value error for invalid zoom factors """ if zoom < 0 or zoom > 30: raise ValueError(f"Bad zoom value: {zoom}") @@ -48,42 +52,41 @@ def set_zoom(self, zoom: int) -> None: def set_center(self, latlng: s2sphere.LatLng) -> None: """Set center for static map - :param latlng: zoom for static map - :type latlng: s2sphere.LatLng + Parameters: + latlng (s2sphere.LatLng): zoom for static map """ self._center = latlng def set_background_color(self, color: Color) -> None: """Set background color for static map - :param color: background color for static map - :type color: s2sphere.LatLng + Parameters: + color (s2sphere.LatLng): background color for static map """ self._background_color = color def set_cache_dir(self, directory: str) -> None: """Set cache dir - :param directory: cache directory - :type directory: str + Parameters: + directory (str): cache directory """ self._cache_dir = directory def set_tile_downloader(self, downloader: TileDownloader) -> None: """Set tile downloader - :param downloader: tile downloader - :type downloader: TileDownloader + Parameters: + downloader (TileDownloader): tile downloader """ self._tile_downloader = downloader def set_tile_provider(self, provider: TileProvider, api_key: typing.Optional[str] = None) -> None: """Set tile provider - :param provider: tile provider - :type provider: TileProvider - :param api_key: api key (if needed) - :type api_key: str + Parameters: + provider (TileProvider): tile provider + api_key (str): api key (if needed) """ self._tile_provider = provider if api_key: @@ -92,8 +95,8 @@ def set_tile_provider(self, provider: TileProvider, api_key: typing.Optional[str def add_object(self, obj: Object) -> None: """Add object for the static map (e.g. line, area, marker) - :param obj: map object - :type obj: Object + Parameters: + obj (Object): map object """ self._objects.append(obj) @@ -104,10 +107,10 @@ def add_bounds( ) -> None: """Add boundaries that shall be respected by the static map - :param latlngrect: boundaries to be respected - :type latlngrect: s2sphere.LatLngRect - :param extra_pixel_bounds: extra pixel bounds to be respected - :type extra_pixel_bounds: int, tuple + Parameters: + latlngrect (s2sphere.LatLngRect): boundaries to be respected + extra_pixel_bounds (int, tuple): extra pixel bounds to be + respected """ self._bounds = latlngrect if extra_pixel_bounds: @@ -124,14 +127,17 @@ def add_bounds( def render_cairo(self, width: int, height: int) -> typing.Any: """Render area using cairo - :param width: width of static map - :type width: int - :param height: height of static map - :type height: int - :return: cairo image - :rtype: cairo.ImageSurface - :raises RuntimeError: raises runtime error if cairo is not available - :raises RuntimeError: raises runtime error if map has no center and zoom + Parameters: + width (int): width of static map + height (int): height of static map + + Returns: + cairo.ImageSurface: cairo image + + Raises: + RuntimeError: raises runtime error if cairo is not available + RuntimeError: raises runtime error if map has no center and + zoom """ if not cairo_is_supported(): raise RuntimeError('You need to install the "cairo" module to enable "render_cairo".') @@ -153,13 +159,15 @@ def render_cairo(self, width: int, height: int) -> typing.Any: def render_pillow(self, width: int, height: int) -> PIL_Image: """Render context using PILLOW - :param width: width of static map - :type width: int - :param height: height of static map - :type height: int - :return: pillow image - :rtype: PIL_Image - :raises RuntimeError: raises runtime error if map has no center and zoom + Parameters: + width (int): width of static map + height (int): height of static map + + Returns: + PIL_Image: pillow image + + Raises: + RuntimeError: raises runtime error if map has no center and zoom """ center, zoom = self.determine_center_zoom(width, height) if center is None or zoom is None: @@ -178,13 +186,15 @@ def render_pillow(self, width: int, height: int) -> PIL_Image: def render_svg(self, width: int, height: int) -> svgwrite.Drawing: """Render context using svgwrite - :param width: width of static map - :type width: int - :param height: height of static map - :type height: int - :return: svg drawing - :rtype: svgwrite.Drawing - :raises RuntimeError: raises runtime error if map has no center and zoom + Parameters: + width (int): width of static map + height (int): height of static map + + Returns: + svgwrite.Drawing: svg drawing + + Raises: + RuntimeError: raises runtime error if map has no center and zoom """ center, zoom = self.determine_center_zoom(width, height) if center is None or zoom is None: @@ -203,8 +213,8 @@ def render_svg(self, width: int, height: int) -> svgwrite.Drawing: def object_bounds(self) -> typing.Optional[s2sphere.LatLngRect]: """return maximum bounds of all objects - :return: maximum of all object bounds - :rtype: s2sphere.LatLngRect + Returns: + s2sphere.LatLngRect: maximum of all object bounds """ bounds = None if len(self._objects) != 0: @@ -217,10 +227,11 @@ def object_bounds(self) -> typing.Optional[s2sphere.LatLngRect]: def _custom_bounds(self, bounds: typing.Optional[s2sphere.LatLngRect]) -> typing.Optional[s2sphere.LatLngRect]: """check for additional bounds and return the union with object bounds - :param bounds: boundaries from objects - :type bounds: s2sphere.LatLngRect - :return: maximum of additional and object bounds - :rtype: s2sphere.LatLngRect + Parameters: + bounds (s2sphere.LatLngRect): boundaries from objects + + Returns: + s2sphere.LatLngRect: maximum of additional and object bounds """ if not self._bounds: return bounds @@ -231,8 +242,8 @@ def _custom_bounds(self, bounds: typing.Optional[s2sphere.LatLngRect]) -> typing def extra_pixel_bounds(self) -> PixelBoundsT: """return extra pixel bounds from all objects - :return: extra pixel object bounds - :rtype: PixelBoundsT + Returns: + PixelBoundsT: extra pixel object bounds """ max_l, max_t, max_r, max_b = self._extra_pixel_bounds attribution = self._tile_provider.attribution() @@ -251,12 +262,12 @@ def determine_center_zoom( ) -> typing.Tuple[typing.Optional[s2sphere.LatLng], typing.Optional[int]]: """return center and zoom of static map - :param width: width of static map - :param height: height of static map - :type width: int - :type height: int - :return: center, zoom - :rtype: tuple + Parameters: + width (int): width of static map + height (int): height of static map + + Returns: + tuple: center, zoom """ if self._center is not None: if self._zoom is not None: diff --git a/staticmaps/coordinates.py b/staticmaps/coordinates.py index 124fd87..3327185 100644 --- a/staticmaps/coordinates.py +++ b/staticmaps/coordinates.py @@ -1,4 +1,4 @@ -# py-staticmaps +"""py-staticmaps - context""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import typing @@ -9,12 +9,12 @@ def create_latlng(lat: float, lng: float) -> s2sphere.LatLng: """Create a LatLng object from float values - :param lat: latitude - :type lat: float - :param lng: longitude - :type lng: float - :return: LatLng object - :rtype: s2sphere.LatLng + Parameters: + lat (float): latitude + lng (float): longitude + + Returns: + s2sphere.LatLng: LatLng object """ return s2sphere.LatLng.from_degrees(lat, lng) @@ -22,11 +22,14 @@ def create_latlng(lat: float, lng: float) -> s2sphere.LatLng: def parse_latlng(s: str) -> s2sphere.LatLng: """Parse a string with comma separated latitude,longitude values and create a LatLng object from float values - :param s: string with latitude,longitude values - :type s: str - :return: LatLng object - :rtype: s2sphere.LatLng - :raises ValueError: raises a value error if the format is wrong + Parameters: + s (str): string with latitude,longitude values + + Returns: + s2sphere.LatLng: LatLng object + + Raises: + ValueError: raises a value error if the format is wrong """ a = s.split(",") if len(a) != 2: @@ -47,10 +50,12 @@ def parse_latlng(s: str) -> s2sphere.LatLng: def parse_latlngs(s: str) -> typing.List[s2sphere.LatLng]: """Parse a string with multiple comma separated latitude,longitude values and create a list of LatLng objects - :param s: string with multiple latitude,longitude values separated with empty space - :type s: str - :return: list of LatLng objects - :rtype: typing.List[s2sphere.LatLng] + Parameters: + s (str): string with multiple latitude,longitude values + separated with empty space + + Returns: + typing.List[s2sphere.LatLng]: list of LatLng objects """ res = [] for c in s.split(): @@ -64,11 +69,15 @@ def parse_latlngs2rect(s: str) -> s2sphere.LatLngRect: """Parse a string with two comma separated latitude,longitude values and create a LatLngRect object - :param s: string with two latitude,longitude values separated with empty space - :type s: str - :return: LatLngRect from LatLng pair - :rtype: s2sphere.LatLngRect - :raises ValueError: exactly two lat/lng pairs must be given as argument + Parameters: + s (str): string with two latitude,longitude values separated + with empty space + + Returns: + s2sphere.LatLngRect: LatLngRect from LatLng pair + + Raises: + ValueError: exactly two lat/lng pairs must be given as argument """ latlngs = parse_latlngs(s) if len(latlngs) != 2: diff --git a/staticmaps/image_marker.py b/staticmaps/image_marker.py index ad6634f..292e4d2 100644 --- a/staticmaps/image_marker.py +++ b/staticmaps/image_marker.py @@ -1,4 +1,4 @@ -# py-staticmaps +"""py-staticmaps - image_marker""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import io @@ -14,6 +14,10 @@ class ImageMarker(Object): + """ + ImageMarker A marker for an image object + """ + def __init__(self, latlng: s2sphere.LatLng, png_file: str, origin_x: int, origin_y: int) -> None: Object.__init__(self) self._latlng = latlng @@ -27,24 +31,24 @@ def __init__(self, latlng: s2sphere.LatLng, png_file: str, origin_x: int, origin def origin_x(self) -> int: """Return x origin of the image marker - :return: x origin of the image marker - :rtype: int + Returns: + int: x origin of the image marker """ return self._origin_x def origin_y(self) -> int: """Return y origin of the image marker - :return: y origin of the image marker - :rtype: int + Returns: + int: y origin of the image marker """ return self._origin_y def width(self) -> int: """Return width of the image marker - :return: width of the image marker - :rtype: int + Returns: + int: width of the image marker """ if self._image_data is None: self.load_image_data() @@ -53,8 +57,8 @@ def width(self) -> int: def height(self) -> int: """Return height of the image marker - :return: height of the image marker - :rtype: int + Returns: + int: height of the image marker """ if self._image_data is None: self.load_image_data() @@ -63,8 +67,8 @@ def height(self) -> int: def image_data(self) -> bytes: """Return image data of the image marker - :return: image data of the image marker - :rtype: bytes + Returns: + bytes: image data of the image marker """ if self._image_data is None: self.load_image_data() @@ -74,24 +78,24 @@ def image_data(self) -> bytes: def latlng(self) -> s2sphere.LatLng: """Return LatLng of the image marker - :return: LatLng of the image marker - :rtype: s2sphere.LatLng + Returns: + s2sphere.LatLng: LatLng of the image marker """ return self._latlng def bounds(self) -> s2sphere.LatLngRect: """Return bounds of the image marker - :return: bounds of the image marker - :rtype: s2sphere.LatLngRect + Returns: + s2sphere.LatLngRect: bounds of the image marker """ return s2sphere.LatLngRect.from_point(self._latlng) def extra_pixel_bounds(self) -> PixelBoundsT: """Return extra pixel bounds of the image marker - :return: extra pixel bounds of the image marker - :rtype: PixelBoundsT + Returns: + PixelBoundsT: extra pixel bounds of the image marker """ return ( max(0, self._origin_x), @@ -103,8 +107,8 @@ def extra_pixel_bounds(self) -> PixelBoundsT: def render_pillow(self, renderer: PillowRenderer) -> None: """Render marker using PILLOW - :param renderer: pillow renderer - :type renderer: PillowRenderer + Parameters: + renderer (PillowRenderer): pillow renderer """ x, y = renderer.transformer().ll2pixel(self.latlng()) image = renderer.create_image(self.image_data()) @@ -122,8 +126,8 @@ def render_pillow(self, renderer: PillowRenderer) -> None: def render_svg(self, renderer: SvgRenderer) -> None: """Render marker using svgwrite - :param renderer: svg renderer - :type renderer: SvgRenderer + Parameters: + renderer (SvgRenderer): svg renderer """ x, y = renderer.transformer().ll2pixel(self.latlng()) image = renderer.create_inline_image(self.image_data()) @@ -139,8 +143,8 @@ def render_svg(self, renderer: SvgRenderer) -> None: def render_cairo(self, renderer: CairoRenderer) -> None: """Render marker using cairo - :param renderer: cairo renderer - :type renderer: CairoRenderer + Parameters: + renderer (CairoRenderer): cairo renderer """ x, y = renderer.transformer().ll2pixel(self.latlng()) image = renderer.create_image(self.image_data()) diff --git a/staticmaps/line.py b/staticmaps/line.py index 437c86f..a7cf510 100644 --- a/staticmaps/line.py +++ b/staticmaps/line.py @@ -1,4 +1,4 @@ -# py-staticmaps +"""py-staticmaps - line""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import math @@ -16,6 +16,10 @@ class Line(Object): + """ + Line A line object + """ + def __init__(self, latlngs: typing.List[s2sphere.LatLng], color: Color = RED, width: int = 2) -> None: Object.__init__(self) if latlngs is None or len(latlngs) < 2: @@ -31,24 +35,24 @@ def __init__(self, latlngs: typing.List[s2sphere.LatLng], color: Color = RED, wi def color(self) -> Color: """Return color of the line - :return: color object - :rtype: Color + Returns: + Color: color object """ return self._color def width(self) -> int: """Return width of line - :return: width - :rtype: int + Returns: + int: width """ return self._width def bounds(self) -> s2sphere.LatLngRect: """Return bounds of line - :return: bounds of line - :rtype: s2sphere.LatLngRect + Returns: + s2sphere.LatLngRect: bounds of line """ b = s2sphere.LatLngRect() for latlng in self.interpolate(): @@ -58,16 +62,16 @@ def bounds(self) -> s2sphere.LatLngRect: def extra_pixel_bounds(self) -> PixelBoundsT: """Return extra pixel bounds from line - :return: extra pixel bounds - :rtype: PixelBoundsT + Returns: + PixelBoundsT: extra pixel bounds """ return self._width, self._width, self._width, self._width def interpolate(self) -> typing.List[s2sphere.LatLng]: """Interpolate bounds - :return: list of LatLng - :rtype: typing.List[s2sphere.LatLng] + Returns: + typing.List[s2sphere.LatLng]: list of LatLng """ if self._interpolation_cache is not None: return self._interpolation_cache @@ -107,8 +111,8 @@ def interpolate(self) -> typing.List[s2sphere.LatLng]: def render_pillow(self, renderer: PillowRenderer) -> None: """Render line using PILLOW - :param renderer: pillow renderer - :type renderer: PillowRenderer + Parameters: + renderer (PillowRenderer): pillow renderer """ if self.width() == 0: return @@ -121,8 +125,8 @@ def render_pillow(self, renderer: PillowRenderer) -> None: def render_svg(self, renderer: SvgRenderer) -> None: """Render line using svgwrite - :param renderer: svg renderer - :type renderer: SvgRenderer + Parameters: + renderer (SvgRenderer): svg renderer """ if self.width() == 0: return @@ -139,8 +143,8 @@ def render_svg(self, renderer: SvgRenderer) -> None: def render_cairo(self, renderer: CairoRenderer) -> None: """Render line using cairo - :param renderer: cairo renderer - :type renderer: CairoRenderer + Parameters: + renderer (CairoRenderer): cairo renderer """ if self.width() == 0: return diff --git a/staticmaps/marker.py b/staticmaps/marker.py index 961893b..f69e5ae 100644 --- a/staticmaps/marker.py +++ b/staticmaps/marker.py @@ -1,4 +1,4 @@ -# py-staticmaps +"""py-staticmaps - marker""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import math @@ -13,6 +13,10 @@ class Marker(Object): + """ + Marker A marker object + """ + def __init__(self, latlng: s2sphere.LatLng, color: Color = RED, size: int = 10) -> None: Object.__init__(self) self._latlng = latlng @@ -22,51 +26,51 @@ def __init__(self, latlng: s2sphere.LatLng, color: Color = RED, size: int = 10) def latlng(self) -> s2sphere.LatLng: """Return LatLng of the marker - :return: LatLng of the marker - :rtype: s2sphere.LatLng + Returns: + s2sphere.LatLng: LatLng of the marker """ return self._latlng def color(self) -> Color: """Return color of the marker - :return: color object - :rtype: Color + Returns: + Color: color object """ return self._color def size(self) -> int: """Return size of the marker - :return: size of the marker - :rtype: int + Returns: + int: size of the marker """ return self._size def bounds(self) -> s2sphere.LatLngRect: """Return bounds of the marker - :return: bounds of the marker - :rtype: s2sphere.LatLngRect + Returns: + s2sphere.LatLngRect: bounds of the marker """ return s2sphere.LatLngRect.from_point(self._latlng) def extra_pixel_bounds(self) -> PixelBoundsT: """Return extra pixel bounds of the marker - :return: extra pixel bounds of the marker - :rtype: PixelBoundsT + Returns: + PixelBoundsT: extra pixel bounds of the marker """ return self._size, self._size, self._size, 0 def render_pillow(self, renderer: PillowRenderer) -> None: """Render marker using PILLOW - :param renderer: pillow renderer - :type renderer: PillowRenderer + Parameters: + renderer (PillowRenderer): pillow renderer """ x, y = renderer.transformer().ll2pixel(self.latlng()) - x = x + renderer.offset_x() + x += renderer.offset_x() r = self.size() dx = math.sin(math.pi / 3.0) @@ -89,8 +93,8 @@ def render_pillow(self, renderer: PillowRenderer) -> None: def render_svg(self, renderer: SvgRenderer) -> None: """Render marker using svgwrite - :param renderer: svg renderer - :type renderer: SvgRenderer + Parameters: + renderer (SvgRenderer): svg renderer """ x, y = renderer.transformer().ll2pixel(self.latlng()) r = self.size() @@ -111,8 +115,8 @@ def render_svg(self, renderer: SvgRenderer) -> None: def render_cairo(self, renderer: CairoRenderer) -> None: """Render marker using cairo - :param renderer: cairo renderer - :type renderer: CairoRenderer + Parameters: + renderer (CairoRenderer): cairo renderer """ x, y = renderer.transformer().ll2pixel(self.latlng()) r = self.size() diff --git a/staticmaps/meta.py b/staticmaps/meta.py index 325d0df..66288dd 100644 --- a/staticmaps/meta.py +++ b/staticmaps/meta.py @@ -1,4 +1,4 @@ -# py-staticmaps +"""py-staticmaps - meta""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information GITHUB_URL = "https://github.com/flopp/py-staticmaps" diff --git a/staticmaps/object.py b/staticmaps/object.py index 7620005..9156f3b 100644 --- a/staticmaps/object.py +++ b/staticmaps/object.py @@ -1,4 +1,4 @@ -# py-staticmaps +"""py-staticmaps - object""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information from abc import ABC, abstractmethod @@ -16,6 +16,10 @@ class Object(ABC): + """ + Object A base class for objects + """ + def __init__(self) -> None: pass diff --git a/staticmaps/pillow_renderer.py b/staticmaps/pillow_renderer.py index 38827bf..93be15a 100644 --- a/staticmaps/pillow_renderer.py +++ b/staticmaps/pillow_renderer.py @@ -1,4 +1,4 @@ -# py-staticmaps +"""py-staticmaps - pillow_renderer""" # Copyright (c) 2021 Florian Pigorsch; see /LICENSE for licensing information import io @@ -27,15 +27,39 @@ def __init__(self, transformer: Transformer) -> None: self._offset_x = 0 def draw(self) -> PIL_ImageDraw.Draw: + """ + draw Call PIL_ImageDraw.Draw() + + Returns: + PIL_ImageDraw.Draw: An PIL_Image draw object + """ return self._draw def image(self) -> PIL_Image.Image: + """ + image Call PIL_Image.new() + + Returns: + PIL_Image.Image: A PIL_Image image object + """ return self._image def offset_x(self) -> int: + """ + offset_x Return the offset in x direction + + Returns: + int: Offset in x direction + """ return self._offset_x def alpha_compose(self, image: PIL_Image.Image) -> None: + """ + alpha_compose Call PIL_Image.alpha_composite() + + Parameters: + image (PIL_Image.Image): A PIL_Image image object + """ assert image.size == self._image.size self._image = PIL_Image.alpha_composite(self._image, image) self._draw = PIL_ImageDraw.Draw(self._image) @@ -43,8 +67,8 @@ def alpha_compose(self, image: PIL_Image.Image) -> None: def render_objects(self, objects: typing.List["Object"]) -> None: """Render all objects of static map - :param objects: objects of static map - :type objects: typing.List["Object"] + Parameters: + objects (typing.List["Object"]): objects of static map """ x_count = math.ceil(self._trans.image_width() / (2 * self._trans.world_width())) for obj in objects: @@ -55,8 +79,8 @@ def render_objects(self, objects: typing.List["Object"]) -> None: def render_background(self, color: typing.Optional[Color]) -> None: """Render background of static map - :param color: background color - :type color: typing.Optional[Color] + Parameters: + color (typing.Optional[Color]): background color """ if color is None: return @@ -65,8 +89,8 @@ def render_background(self, color: typing.Optional[Color]) -> None: def render_tiles(self, download: typing.Callable[[int, int, int], typing.Optional[bytes]]) -> None: """Render background of static map - :param download: url of tiles provider - :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] + Parameters: + download (typing.Callable[[int, int, int], typing.Optional[bytes]]): url of tiles provider """ for yy in range(0, self._trans.tiles_y()): y = self._trans.first_tile_y() + yy @@ -91,8 +115,8 @@ def render_tiles(self, download: typing.Callable[[int, int, int], typing.Optiona def render_attribution(self, attribution: typing.Optional[str]) -> None: """Render attribution from given tiles provider - :param attribution: Attribution for the given tiles provider - :type attribution: typing.Optional[str]: + Parameters: + attribution (typing.Optional[str]:): Attribution for the given tiles provider """ if (attribution is None) or (attribution == ""): return @@ -111,15 +135,13 @@ def fetch_tile( ) -> typing.Optional[PIL_Image.Image]: """Fetch tiles from given tiles provider - :param download: callable - :param x: width - :param y: height - :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] - :type x: int - :type y: int + Parameters: + download (typing.Callable[[int, int, int], typing.Optional[bytes]]): callable + x (int): width + y (int): height - :return: pillow image - :rtype: typing.Optional[PIL_Image.Image] + Returns: + typing.Optional[PIL_Image.Image]: pillow image """ image_data = download(self._trans.zoom(), x, y) if image_data is None: @@ -130,10 +152,10 @@ def fetch_tile( def create_image(image_data: bytes) -> PIL_Image: """Create a pillow image - :param image_data: Image data - :type image_data: bytes + Parameters: + image_data (bytes): Image data - :return: pillow image - :rtype: PIL.Image + Returns: + PIL.Image: pillow image """ return PIL_Image.open(io.BytesIO(image_data)).convert("RGBA") diff --git a/staticmaps/renderer.py b/staticmaps/renderer.py index a94177d..cbf60ca 100644 --- a/staticmaps/renderer.py +++ b/staticmaps/renderer.py @@ -1,4 +1,4 @@ -# py-staticmaps +"""py-staticmaps - renderer""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information from abc import ABC, abstractmethod @@ -26,8 +26,8 @@ def __init__(self, transformer: Transformer) -> None: def transformer(self) -> Transformer: """Return transformer object - :return: transformer - :rtype: Transformer + Returns: + Transformer: transformer """ return self._trans @@ -35,58 +35,60 @@ def transformer(self) -> Transformer: def render_objects(self, objects: typing.List["Object"]) -> None: """Render all objects of static map - :param objects: objects of static map - :type objects: typing.List["Object"] + Parameters: + objects (typing.List["Object"]): objects of static map """ @abstractmethod def render_background(self, color: typing.Optional[Color]) -> None: """Render background of static map - :param color: background color - :type color: typing.Optional[Color] + Parameters: + color (typing.Optional[Color]): background color """ @abstractmethod def render_tiles(self, download: typing.Callable[[int, int, int], typing.Optional[bytes]]) -> None: """Render background of static map - :param download: url of tiles provider - :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] + Parameters: + download (typing.Callable[[int, int, int], typing.Optional[bytes]]): + url of tiles provider """ def render_marker_object(self, marker: "Marker") -> None: """Render marker object of static map - :param marker: marker object - :type marker: Marker + Parameters: + marker (Marker): marker object """ def render_image_marker_object(self, marker: "ImageMarker") -> None: """Render image marker object of static map - :param marker: image marker object - :type marker: ImageMarker + Parameters: + marker (ImageMarker): image marker object """ def render_line_object(self, line: "Line") -> None: """Render line object of static map - :param line: line object - :type line: Line + Parameters: + line (Line): line object """ def render_area_object(self, area: "Area") -> None: """Render area object of static map - :param area: area object - :type area: Area + Parameters: + area (Area): area object """ @abstractmethod def render_attribution(self, attribution: typing.Optional[str]) -> None: """Render attribution from given tiles provider - :param attribution: Attribution for the given tiles provider - :type attribution: typing.Optional[str]: + Parameters: + attribution (typing.Optional[str]:): Attribution for the + given tiles provider """ diff --git a/staticmaps/svg_renderer.py b/staticmaps/svg_renderer.py index 3a4a69f..3902bf2 100644 --- a/staticmaps/svg_renderer.py +++ b/staticmaps/svg_renderer.py @@ -1,4 +1,4 @@ -# py-staticmaps +"""py-staticmaps - svg_renderer""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import base64 @@ -32,16 +32,16 @@ def __init__(self, transformer: Transformer) -> None: def drawing(self) -> svgwrite.Drawing: """Return the svg drawing for the image - :return: svg drawing - :rtype: svgwrite.Drawing + Returns: + svgwrite.Drawing: svg drawing """ return self._draw def group(self) -> svgwrite.container.Group: """Return the svg group for the image - :return: svg group - :rtype: svgwrite.container.Group + Returns: + svgwrite.container.Group: svg group """ assert self._group is not None return self._group @@ -49,8 +49,8 @@ def group(self) -> svgwrite.container.Group: def render_objects(self, objects: typing.List["Object"]) -> None: """Render all objects of static map - :param objects: objects of static map - :type objects: typing.List["Object"] + Parameters: + objects (typing.List["Object"]): objects of static map """ x_count = math.ceil(self._trans.image_width() / (2 * self._trans.world_width())) for obj in objects: @@ -65,8 +65,8 @@ def render_objects(self, objects: typing.List["Object"]) -> None: def render_background(self, color: typing.Optional[Color]) -> None: """Render background of static map - :param color: background color - :type color: typing.Optional[Color] + Parameters: + color (typing.Optional[Color]): background color """ if color is None: return @@ -77,8 +77,9 @@ def render_background(self, color: typing.Optional[Color]) -> None: def render_tiles(self, download: typing.Callable[[int, int, int], typing.Optional[bytes]]) -> None: """Render background of static map - :param download: url of tiles provider - :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] + Parameters: + download (typing.Callable[[int, int, int], typing.Optional[bytes]]): + url of tiles provider """ group = self._draw.g(clip_path="url(#page)") for yy in range(0, self._trans.tiles_y()): @@ -108,8 +109,9 @@ def render_tiles(self, download: typing.Callable[[int, int, int], typing.Optiona def render_attribution(self, attribution: typing.Optional[str]) -> None: """Render attribution from given tiles provider - :param attribution: Attribution for the given tiles provider - :type attribution: typing.Optional[str]: + Parameters: + attribution (typing.Optional[str]:): Attribution for the + given tiles provider """ if (attribution is None) or (attribution == ""): return @@ -140,15 +142,14 @@ def fetch_tile( ) -> typing.Optional[str]: """Fetch tiles from given tiles provider - :param download: callable - :param x: width - :param y: height - :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] - :type x: int - :type y: int + Parameters: + download (typing.Callable[[int, int, int], typing.Optional[bytes]]): + callable + x (int): width + y (int): height - :return: svg drawing - :rtype: typing.Optional[str] + Returns: + typing.Optional[str]: svg drawing """ image_data = download(self._trans.zoom(), x, y) if image_data is None: @@ -159,10 +160,11 @@ def fetch_tile( def guess_image_mime_type(data: bytes) -> str: """Guess mime type from image data - :param data: image data - :type data: bytes - :return: mime type - :rtype: str + Parameters: + data (bytes): image data + + Returns: + str: mime type """ if data[:4] == b"\xff\xd8\xff\xe0" and data[6:11] == b"JFIF\0": return "image/jpeg" @@ -174,11 +176,11 @@ def guess_image_mime_type(data: bytes) -> str: def create_inline_image(image_data: bytes) -> str: """Create an svg inline image - :param image_data: Image data - :type image_data: bytes + Parameters: + image_data (bytes): Image data - :return: svg inline image - :rtype: str + Returns: + str: svg inline image """ image_type = SvgRenderer.guess_image_mime_type(image_data) return f"data:{image_type};base64,{base64.b64encode(image_data).decode('utf-8')}" diff --git a/staticmaps/tile_downloader.py b/staticmaps/tile_downloader.py index 353f1ba..d7f7449 100644 --- a/staticmaps/tile_downloader.py +++ b/staticmaps/tile_downloader.py @@ -1,11 +1,11 @@ -# py-staticmaps +"""py-staticmaps - tile_downloader""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import os import pathlib import typing -import requests +import requests # type: ignore import slugify # type: ignore from .meta import GITHUB_URL, LIB_NAME, VERSION @@ -22,27 +22,27 @@ def __init__(self) -> None: def set_user_agent(self, user_agent: str) -> None: """Set the user agent for the downloader - :param user_agent: user agent - :type user_agent: str + Parameters: + user_agent (str): user agent """ self._user_agent = user_agent def get(self, provider: TileProvider, cache_dir: str, zoom: int, x: int, y: int) -> typing.Optional[bytes]: """Get tiles - :param provider: tile provider - :type provider: TileProvider - :param cache_dir: cache directory for tiles - :type cache_dir: str - :param zoom: zoom for static map - :type zoom: int - :param x: x value of center for the static map - :type x: int - :param y: y value of center for the static map - :type y: int - :return: tiles - :rtype: typing.Optional[bytes] - :raises RuntimeError: raises a runtime error if the the server response status is not 200 + Parameters: + provider (TileProvider): tile provider + cache_dir (str): cache directory for tiles + zoom (int): zoom for static map + x (int): x value of center for the static map + y (int): y value of center for the static map + + Returns: + typing.Optional[bytes]: tiles + + Raises: + RuntimeError: raises a runtime error if the the server + response status is not 200 """ file_name = None if cache_dir is not None: @@ -54,7 +54,7 @@ def get(self, provider: TileProvider, cache_dir: str, zoom: int, x: int, y: int) url = provider.url(zoom, x, y) if url is None: return None - res = requests.get(url, headers={"user-agent": self._user_agent}) + res = requests.get(url, headers={"user-agent": self._user_agent}, timeout=3600) if res.status_code == 200: data = res.content else: @@ -69,10 +69,11 @@ def get(self, provider: TileProvider, cache_dir: str, zoom: int, x: int, y: int) def sanitized_name(self, name: str) -> str: """Return sanitized name - :param name: name to sanitize - :type name: str - :return: sanitized name - :rtype: str + Parameters: + name (str): name to sanitize + + Returns: + str: sanitized name """ if name in self._sanitized_name_cache: return self._sanitized_name_cache[name] @@ -85,17 +86,14 @@ def sanitized_name(self, name: str) -> str: def cache_file_name(self, provider: TileProvider, cache_dir: str, zoom: int, x: int, y: int) -> str: """Return a cache file name - :param provider: tile provider - :type provider: TileProvider - :param cache_dir: cache directory for tiles - :type cache_dir: str - :param zoom: zoom for static map - :type zoom: int - :param x: x value of center for the static map - :type x: int - :param y: y value of center for the static map - :type y: int - :return: cache file name - :rtype: str + Parameters: + provider (TileProvider): tile provider + cache_dir (str): cache directory for tiles + zoom (int): zoom for static map + x (int): x value of center for the static map + y (int): y value of center for the static map + + Returns: + str: cache file name """ return os.path.join(cache_dir, self.sanitized_name(provider.name()), str(zoom), str(x), f"{y}.png") diff --git a/staticmaps/tile_provider.py b/staticmaps/tile_provider.py index f18af38..5821762 100644 --- a/staticmaps/tile_provider.py +++ b/staticmaps/tile_provider.py @@ -1,4 +1,4 @@ -# py-staticmaps +"""py-staticmaps - tile_provider""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import string @@ -24,27 +24,42 @@ def __init__( self._attribution = attribution self._max_zoom = max_zoom if ((max_zoom is not None) and (max_zoom <= 20)) else 20 + def __eq__(self, other: object) -> bool: + if not isinstance(other, TileProvider): + # don't attempt to compare against unrelated types + return NotImplemented + + return ( + self._name == other._name + and str(self._url_pattern) == str(other._url_pattern) + and self._shards == other._shards + and self._api_key == other._api_key + and self._attribution == other._attribution + and self._max_zoom == other._max_zoom + ) + def set_api_key(self, key: str) -> None: """Set an api key - :param key: api key - :type key: str + Parameters: + key (str): api key """ self._api_key = key def name(self) -> str: """Return the name of the tile provider - :return: name of tile provider - :rtype: str + Returns: + str: name of tile provider """ return self._name def attribution(self) -> typing.Optional[str]: """Return the attribution of the tile provider - :return: attribution of tile provider if available - :rtype: typing.Optional[str] + Returns: + typing.Optional[str]: attribution of tile provider if + available """ return self._attribution @@ -52,30 +67,29 @@ def attribution(self) -> typing.Optional[str]: def tile_size() -> int: """Return the tile size - :return: tile size - :rtype: int + Returns: + int: tile size """ return 256 def max_zoom(self) -> int: """Return the maximum zoom of the tile provider - :return: maximum zoom - :rtype: int + Returns: + int: maximum zoom """ return self._max_zoom def url(self, zoom: int, x: int, y: int) -> typing.Optional[str]: """Return the url of the tile provider - :param zoom: zoom for static map - :type zoom: int - :param x: x value of center for the static map - :type x: int - :param y: y value of center for the static map - :type y: int - :return: url with zoom, x and y values - :rtype: typing.Optional[str] + Parameters: + zoom (int): zoom for static map + x (int): x value of center for the static map + y (int): y value of center for the static map + + Returns: + typing.Optional[str]: url with zoom, x and y values """ if len(self._url_pattern.template) == 0: return None diff --git a/staticmaps/transformer.py b/staticmaps/transformer.py index 3732bbe..2b22823 100644 --- a/staticmaps/transformer.py +++ b/staticmaps/transformer.py @@ -1,4 +1,4 @@ -# py-staticmaps +"""py-staticmaps - transformer""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import math @@ -39,32 +39,32 @@ def __init__(self, width: int, height: int, zoom: int, center: s2sphere.LatLng, def world_width(self) -> int: """Return the width of the world in pixels depending on tiles provider - :return: width of the world in pixels - :rtype: int + Returns: + int: width of the world in pixels """ return self._number_of_tiles * self._tile_size def image_width(self) -> int: """Return the width of the image in pixels - :return: width of the image in pixels - :rtype: int + Returns: + int: width of the image in pixels """ return self._width def image_height(self) -> int: """Return the height of the image in pixels - :return: height of the image in pixels - :rtype: int + Returns: + int: height of the image in pixels """ return self._height def zoom(self) -> int: """Return the zoom of the static map - :return: zoom of the static map - :rtype: int + Returns: + int: zoom of the static map """ return self._zoom @@ -72,8 +72,8 @@ def zoom(self) -> int: def image_size(self) -> typing.Tuple[int, int]: """Return the size of the image as tuple of width and height - :return: width and height of the image in pixels - :rtype: tuple + Returns: + tuple: width and height of the image in pixels """ return self._width, self._height @@ -81,64 +81,64 @@ def image_size(self) -> typing.Tuple[int, int]: def number_of_tiles(self) -> int: """Return number of tiles of static map - :return: number of tiles - :rtype: int + Returns: + int: number of tiles """ return self._number_of_tiles def first_tile_x(self) -> int: """Return number of first tile in x - :return: number of first tile - :rtype: int + Returns: + int: number of first tile """ return self._first_tile_x def first_tile_y(self) -> int: """Return number of first tile in y - :return: number of first tile - :rtype: int + Returns: + int: number of first tile """ return self._first_tile_y def tiles_x(self) -> int: """Return number of tiles in x - :return: number of tiles - :rtype: int + Returns: + int: number of tiles """ return self._tiles_x def tiles_y(self) -> int: """Return number of tiles in y - :return: number of tiles - :rtype: int + Returns: + int: number of tiles """ return self._tiles_y def tile_offset_x(self) -> float: """Return tile offset in x - :return: tile offset - :rtype: int + Returns: + int: tile offset """ return self._tile_offset_x def tile_offset_y(self) -> float: """Return tile offset in y - :return: tile offset - :rtype: int + Returns: + int: tile offset """ return self._tile_offset_y def tile_size(self) -> int: """Return tile size - :return: tile size - :rtype: int + Returns: + int: tile size """ return self._tile_size @@ -146,10 +146,11 @@ def tile_size(self) -> int: def mercator(latlng: s2sphere.LatLng) -> typing.Tuple[float, float]: """Mercator projection - :param latlng: LatLng object - :type latlng: s2sphere.LatLng - :return: tile values of given LatLng - :rtype: tuple + Parameters: + latlng (s2sphere.LatLng): LatLng object + + Returns: + tuple: tile values of given LatLng """ lat = latlng.lat().radians lng = latlng.lng().radians @@ -159,12 +160,12 @@ def mercator(latlng: s2sphere.LatLng) -> typing.Tuple[float, float]: def mercator_inv(x: float, y: float) -> s2sphere.LatLng: """Inverse Mercator projection - :param x: x value - :type x: float - :param y: x value - :type y: float - :return: LatLng values of given values - :rtype: s2sphere.LatLng + Parameters: + x (float): x value + y (float): x value + + Returns: + s2sphere.LatLng: LatLng values of given values """ x = 2 * math.pi * (x - 0.5) k = math.exp(4 * math.pi * (0.5 - y)) @@ -174,10 +175,11 @@ def mercator_inv(x: float, y: float) -> s2sphere.LatLng: def ll2t(self, latlng: s2sphere.LatLng) -> typing.Tuple[float, float]: """Transform LatLng values into tiles - :param latlng: LatLng object - :type latlng: s2sphere.LatLng - :return: tile values of given LatLng - :rtype: tuple + Parameters: + latlng (s2sphere.LatLng): LatLng object + + Returns: + tuple: tile values of given LatLng """ x, y = self.mercator(latlng) return self._number_of_tiles * x, self._number_of_tiles * y @@ -185,22 +187,23 @@ def ll2t(self, latlng: s2sphere.LatLng) -> typing.Tuple[float, float]: def t2ll(self, x: float, y: float) -> s2sphere.LatLng: """Transform tile values into LatLng values - :param x: x tile - :type x: float - :param y: x tile - :type y: float - :return: LatLng values of given tile values - :rtype: s2sphere.LatLng + Parameters: + x (float): x tile + y (float): x tile + + Returns: + s2sphere.LatLng: LatLng values of given tile values """ return self.mercator_inv(x / self._number_of_tiles, y / self._number_of_tiles) def ll2pixel(self, latlng: s2sphere.LatLng) -> typing.Tuple[float, float]: """Transform LatLng values into pixel values - :param latlng: LatLng object - :type latlng: s2sphere.LatLng - :return: pixel values of given LatLng - :rtype: tuple + Parameters: + latlng (s2sphere.LatLng): LatLng object + + Returns: + tuple: pixel values of given LatLng """ x, y = self.ll2t(latlng) s = self._tile_size @@ -211,12 +214,12 @@ def ll2pixel(self, latlng: s2sphere.LatLng) -> typing.Tuple[float, float]: def pixel2ll(self, x: float, y: float) -> s2sphere.LatLng: """Transform pixel values into LatLng values - :param x: x pixel - :type x: float - :param y: x pixel - :type y: float - :return: LatLng values of given pixel values - :rtype: s2sphere.LatLng + Parameters: + x (float): x pixel + y (float): x pixel + + Returns: + s2sphere.LatLng: LatLng values of given pixel values """ s = self._tile_size x = (x - self._width / 2) / s + self._tile_center_x diff --git a/tests/test_tile_provider.py b/tests/test_tile_provider.py index d18e285..fde192c 100644 --- a/tests/test_tile_provider.py +++ b/tests/test_tile_provider.py @@ -1,4 +1,4 @@ -# py-staticmaps +"""py-staticmaps - Test TileProvider""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import staticmaps @@ -18,3 +18,21 @@ def test_sharding() -> None: for s in shard_counts: assert (third * 0.9) < s assert s < (third * 1.1) + + +def test_tile_provider_init() -> None: + t1 = staticmaps.tile_provider.tile_provider_JawgLight + t1.set_api_key("0123456789876543210") + + t2 = staticmaps.TileProvider( + "jawg-light", + url_pattern="https://$s.tile.jawg.io/jawg-light/$z/$x/$y.png?access-token=$k", + shards=["a", "b", "c", "d"], + attribution="Maps (C) Jawg Maps (C) OpenStreetMap.org contributors", + max_zoom=20, + api_key="0123456789876543210", + ) + assert t1.name() == t2.name() == "jawg-light" + assert t1.attribution() == t2.attribution() == "Maps (C) Jawg Maps (C) OpenStreetMap.org contributors" + assert t1.tile_size() == t2.tile_size() == 256 + assert t1.max_zoom() == t2.max_zoom() == 20 From ea692a64cb25b415882a935e25c2b7692f3db6cc Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 10 Oct 2022 17:51:08 +0200 Subject: [PATCH 055/147] Drop support for version 3.6 --- .github/workflows/main.yml | 3 ++- test_1.py | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 test_1.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index dac565d..61593cf 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,7 +9,8 @@ jobs: max-parallel: 4 matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.6, 3.7, 3.8, 3.9] +# python-version: [3.6, 3.7, 3.8, 3.9] + python-version: [3.7, 3.8, 3.9] steps: - name: Git config run: git config --global core.autocrlf input diff --git a/test_1.py b/test_1.py new file mode 100644 index 0000000..9ffc621 --- /dev/null +++ b/test_1.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +"""py-staticmaps - Example Frankfurt-New York""" + +# py-staticmaps +# Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information + +import math +import staticmaps + +colors = [((0, 0, 0, 0), 0.0), + ((255, 255, 255, 255), 1.0), + ((0, 0, 0), 1.0), + ((255, 255, 255), 1.0), + ((255, 255, 255, 100), 0.392156627), + ((255, 255, 255, 200), 0.781250000), + ] +for rgb, float_alpha in colors: + color = staticmaps.Color(*rgb) + print(math.isclose(float_alpha, color.float_a(), rel_tol=0.10)) From 075f2ce9c515ee2cf1c43e8f9d4b7f67d393dd13 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 10 Oct 2022 17:58:37 +0200 Subject: [PATCH 056/147] remove temp file --- test_1.py | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 test_1.py diff --git a/test_1.py b/test_1.py deleted file mode 100644 index 9ffc621..0000000 --- a/test_1.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python -"""py-staticmaps - Example Frankfurt-New York""" - -# py-staticmaps -# Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information - -import math -import staticmaps - -colors = [((0, 0, 0, 0), 0.0), - ((255, 255, 255, 255), 1.0), - ((0, 0, 0), 1.0), - ((255, 255, 255), 1.0), - ((255, 255, 255, 100), 0.392156627), - ((255, 255, 255, 200), 0.781250000), - ] -for rgb, float_alpha in colors: - color = staticmaps.Color(*rgb) - print(math.isclose(float_alpha, color.float_a(), rel_tol=0.10)) From b1c3e5a96d4023f1caf89fb1fbc6f43a9b6b90a5 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 10 Oct 2022 18:16:19 +0200 Subject: [PATCH 057/147] pylint issues --- staticmaps/cairo_renderer.py | 4 ++-- staticmaps/cli.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/staticmaps/cairo_renderer.py b/staticmaps/cairo_renderer.py index 148456d..897e156 100644 --- a/staticmaps/cairo_renderer.py +++ b/staticmaps/cairo_renderer.py @@ -1,4 +1,4 @@ -# py-staticmaps +"""py-staticmaps cairo_renderer""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import io @@ -169,7 +169,7 @@ def render_attribution(self, attribution: typing.Optional[str]) -> None: t_width = self._context.text_extents(attribution)[3] if t_width < width - 4: break - font_size = font_size - 0.25 + font_size -= 0.25 self._context.set_source_rgba(*WHITE.float_rgb(), 0.8) self._context.rectangle(0, height - f_height - f_descent - 2, width, height) self._context.fill() diff --git a/staticmaps/cli.py b/staticmaps/cli.py index 55eb4de..bc2d2be 100755 --- a/staticmaps/cli.py +++ b/staticmaps/cli.py @@ -1,7 +1,6 @@ #!/usr/bin/env python """py-staticmaps - cli.py - entry point""" -"""py-staticmaps cli""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import argparse From 429ff77537c27e883ccc03f11f05629dcc3c3d94 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Tue, 11 Oct 2022 10:37:36 +0200 Subject: [PATCH 058/147] - extend tests - enable coverage --- Makefile | 23 ++++-- requirements-dev.txt | 3 + setup.py | 2 +- tests/test_color.py | 152 ++++++++++++++++++++++++++++++++++++-- tests/test_coordinates.py | 18 +++-- 5 files changed, 176 insertions(+), 22 deletions(-) diff --git a/Makefile b/Makefile index 8cd8634..a375d77 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,10 @@ -PYTHON=python3 +#PYTHON=python3 +PYTHON=/applbin/python_vw .PHONY: setup setup: $(PYTHON) -m venv .env - .env/bin/pip install --upgrade pip + .env/bin/pip install --upgrade pip wheel .env/bin/pip install --upgrade --requirement requirements.txt .env/bin/pip install --upgrade --requirement requirements-dev.txt .env/bin/pip install --upgrade --requirement requirements-examples.txt @@ -14,17 +15,19 @@ install: setup .PHONY: lint lint: - .env/bin/pylint \ - setup.py staticmaps examples tests - .env/bin/flake8 \ - setup.py staticmaps examples tests - .env/bin/mypy \ - setup.py staticmaps examples tests .env/bin/black \ --line-length 120 \ --check \ --diff \ setup.py staticmaps examples tests + .env/bin/flake8 \ + setup.py staticmaps examples tests + .env/bin/pylint \ + setup.py staticmaps examples tests + .env/bin/mypy \ + setup.py staticmaps examples tests + .env/bin/codespell \ + README.md staticmaps/*.py tests/*.py examples/*.py .PHONY: format format: @@ -49,6 +52,10 @@ run-examples: test: PYTHONPATH=. .env/bin/python -m pytest tests +.PHONY: coverage +coverage: + PYTHONPATH=. .env/bin/python -m pytest --cov=staticmaps --cov-branch --cov-report=term --cov-report=html tests + .PHONY: build-package build-package: rm -rf dist diff --git a/requirements-dev.txt b/requirements-dev.txt index b9efa9c..f6a2fc8 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,12 +3,15 @@ # Linting/Tooling black +codespell flake8 mypy pylint # Testing +coverage pytest +pytest-cov # Building twine diff --git a/setup.py b/setup.py index a02118f..197f943 100644 --- a/setup.py +++ b/setup.py @@ -55,7 +55,7 @@ def _read_reqs(rel_path: str) -> typing.List[str]: "Topic :: Scientific/Engineering :: GIS", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", + # "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", diff --git a/tests/test_color.py b/tests/test_color.py index fc578bb..6734add 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -1,11 +1,135 @@ -# py-staticmaps +"""py-staticmaps - test_color""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information +import math import pytest # type: ignore import staticmaps +def test_text_color() -> None: + white = [ + (0, 0, 0), + (255, 0, 0), + (0, 0, 255), + (0, 0, 0, 0), + (0, 0, 0, 255), + ] + for rgb in white: + color = staticmaps.Color(*rgb) + assert staticmaps.WHITE == color.text_color() + assert staticmaps.WHITE.int_rgb() == color.text_color().int_rgb() + + black = [ + (0, 255, 0), + (255, 255, 0), + (0, 255, 255), + (255, 255, 255), + ] + for rgb in black: + color = staticmaps.Color(*rgb) + assert staticmaps.BLACK == color.text_color() + assert staticmaps.BLACK.int_rgb() == color.text_color().int_rgb() + + +def test_hex_rgb() -> None: + colors = [ + ((0, 0, 0), "#000000"), + ((255, 0, 0), "#ff0000"), + ((0, 255, 0), "#00ff00"), + ((0, 0, 255), "#0000ff"), + ((255, 255, 0), "#ffff00"), + ((0, 255, 255), "#00ffff"), + ((255, 255, 255), "#ffffff"), + ] + for rgb, hex_color in colors: + color = staticmaps.Color(*rgb) + assert hex_color == color.hex_rgb() + + +def test_int_rgb() -> None: + colors = [ + (0, 0, 0), + (255, 0, 0), + (0, 0, 255), + (0, 255, 0), + (255, 255, 0), + (0, 255, 255), + (255, 255, 255), + ] + for rgb in colors: + color = staticmaps.Color(*rgb) + assert rgb == color.int_rgb() + + +def test_int_rgba() -> None: + colors = [ + (0, 0, 0, 0), + (255, 0, 0, 0), + (0, 0, 255, 255), + (0, 255, 0, 255), + (255, 255, 0, 0), + (0, 255, 255, 0), + (255, 255, 255, 255), + ] + for rgb in colors: + color = staticmaps.Color(*rgb) + assert rgb == color.int_rgba() + + +def test_float_rgb() -> None: + colors = [ + ((0, 0, 0), (0.0, 0.0, 0.0)), + ((255, 0, 0), (1.0, 0.0, 0.0)), + ((0, 255, 0), (0.0, 1.0, 0.0)), + ((0, 0, 255), (0.0, 0.0, 1.0)), + ((255, 255, 0), (1.0, 1.0, 0.0)), + ((0, 255, 255), (0.0, 1.0, 1.0)), + ((255, 255, 255), (1.0, 1.0, 1.0)), + ] + for rgb, float_color in colors: + color = staticmaps.Color(*rgb) + assert float_color == color.float_rgb() + + +def test_float_rgba() -> None: + colors = [ + ((0, 0, 0), (0.0, 0.0, 0.0, 1.0)), + ((0, 0, 0, 0), (0.0, 0.0, 0.0, 0.0)), + ((255, 0, 0, 0), (1.0, 0.0, 0.0, 0.0)), + ((0, 255, 0, 255), (0.0, 1.0, 0.0, 1.0)), + ((0, 0, 255, 255), (0.0, 0.0, 1.0, 1.0)), + ((255, 255, 0, 0), (1.0, 1.0, 0.0, 0.0)), + ((0, 255, 255, 0), (0.0, 1.0, 1.0, 0.0)), + ((255, 255, 255, 255), (1.0, 1.0, 1.0, 1.0)), + ((0, 0, 0), (0.0, 0.0, 0.0, 1.0)), + ((255, 255, 255), (1.0, 1.0, 1.0, 1.0)), + ] + for rgb, float_color in colors: + color = staticmaps.Color(*rgb) + assert float_color == color.float_rgba() + + +def test_float_a() -> None: + colors = [ + ((0, 0, 0, 0), 0.0), + ((255, 255, 255, 255), 1.0), + ((0, 0, 0), 1.0), + ((255, 255, 255), 1.0), + ((255, 255, 255, 100), 0.39215663), + ((255, 255, 255, 200), 0.78431373), + ] + for rgb, float_alpha in colors: + color = staticmaps.Color(*rgb) + assert math.isclose(float_alpha, color.float_a(), rel_tol=0.0001) + + def test_parse_color() -> None: + good = ["0x1a2b3c", "0x1A2B3C", "#1a2b3c", "0x1A2B3C", "0x1A2B3C4D", "black", "RED", "Green", "transparent"] + for s in good: + staticmaps.parse_color(s) + + +def test_parse_color_raises_value_error() -> None: bad = [ "", "aaa", @@ -18,12 +142,13 @@ def test_parse_color() -> None: with pytest.raises(ValueError): staticmaps.parse_color(s) - good = ["0x1a2b3c", "0x1A2B3C", "#1a2b3c", "0x1A2B3C", "0x1A2B3C4D", "black", "RED", "Green", "transparent"] - for s in good: - staticmaps.parse_color(s) - def test_create() -> None: + staticmaps.Color(1, 2, 3) + staticmaps.Color(1, 2, 3, 4) + + +def test_create_raises_value_error() -> None: bad = [ (-1, 0, 0), (256, 0, 0), @@ -38,5 +163,18 @@ def test_create() -> None: with pytest.raises(ValueError): staticmaps.Color(*rgb) - staticmaps.Color(1, 2, 3) - staticmaps.Color(1, 2, 3, 4) + +def test_random_color() -> None: + colors = [ + staticmaps.BLACK, + staticmaps.BLUE, + staticmaps.BROWN, + staticmaps.GREEN, + staticmaps.ORANGE, + staticmaps.PURPLE, + staticmaps.RED, + staticmaps.YELLOW, + staticmaps.WHITE, + ] + for _ in [0, 10]: + assert staticmaps.random_color() in colors diff --git a/tests/test_coordinates.py b/tests/test_coordinates.py index 1793166..af97014 100644 --- a/tests/test_coordinates.py +++ b/tests/test_coordinates.py @@ -1,4 +1,4 @@ -# py-staticmaps +"""py-staticmaps - test_coordinates""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import pytest # type: ignore @@ -7,6 +7,13 @@ def test_parse_latlng() -> None: + good = ["48,8", " 48 , 8 ", "-48,8", "+48,8", "48,-8", "48,+8", "48.123,8.456"] + for s in good: + c = staticmaps.parse_latlng(s) + assert c.is_valid() + + +def test_parse_latlng_raises_value_error() -> None: bad = [ "", "aaa", @@ -24,11 +31,6 @@ def test_parse_latlng() -> None: with pytest.raises(ValueError): staticmaps.parse_latlng(s) - good = ["48,8", " 48 , 8 ", "-48,8", "+48,8", "48,-8", "48,+8", "48.123,8.456"] - for s in good: - c = staticmaps.parse_latlng(s) - assert c.is_valid() - def test_parse_latlngs() -> None: good = [("", 0), ("48,8", 1), ("48,8 47,7", 2), (" 48,8 47,7 ", 2), ("48,7 48,8 47,7", 3)] @@ -36,6 +38,8 @@ def test_parse_latlngs() -> None: a = staticmaps.parse_latlngs(s) assert len(a) == expected_len + +def test_parse_latlngs_raises_value_error() -> None: bad = ["xyz", "48,8 xyz", "48,8 48,181"] for s in bad: with pytest.raises(ValueError): @@ -48,6 +52,8 @@ def test_parse_latlngs2rect() -> None: r = staticmaps.parse_latlngs2rect(s) assert r.is_valid() + +def test_parse_latlngs2rect_raises_value_error() -> None: bad = ["xyz", "48,8 xyz", "48,8 48,181", "48,7", "48,7 48,8 47,7"] for s in bad: with pytest.raises(ValueError): From b12f9dbca7af1d1cf6a4fe34fa0a3b8a0bd5a45d Mon Sep 17 00:00:00 2001 From: Lowtower Date: Tue, 11 Oct 2022 10:58:05 +0200 Subject: [PATCH 059/147] some small docstring issues --- staticmaps/object.py | 41 ++++++++++++++++++++--------------- staticmaps/tile_downloader.py | 3 +-- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/staticmaps/object.py b/staticmaps/object.py index 9156f3b..093552c 100644 --- a/staticmaps/object.py +++ b/staticmaps/object.py @@ -27,8 +27,8 @@ def __init__(self) -> None: def extra_pixel_bounds(self) -> PixelBoundsT: """Return extra pixel bounds from object - :return: extra pixel bounds - :rtype: PixelBoundsT + Returns: + PixelBoundsT: extra pixel bounds """ return 0, 0, 0, 0 @@ -36,17 +36,19 @@ def extra_pixel_bounds(self) -> PixelBoundsT: def bounds(self) -> s2sphere.LatLngRect: """Return bounds of object - :return: bounds of object - :rtype: s2sphere.LatLngRect + Returns: + s2sphere.LatLngRect: bounds of object """ return s2sphere.LatLngRect() def render_pillow(self, renderer: PillowRenderer) -> None: """Render object using PILLOW - :param renderer: pillow renderer - :type renderer: PillowRenderer - :raises RuntimeError: raises runtime error if a not implemented method is called + Parameters: + renderer (PillowRenderer): pillow renderer + + Raises: + RuntimeError: raises runtime error if a not implemented method is called """ # pylint: disable=unused-argument t = "Pillow" @@ -57,9 +59,11 @@ def render_pillow(self, renderer: PillowRenderer) -> None: def render_svg(self, renderer: SvgRenderer) -> None: """Render object using svgwrite - :param renderer: svg renderer - :type renderer: SvgRenderer - :raises RuntimeError: raises runtime error if a not implemented method is called + Parameters: + renderer (SvgRenderer): svg renderer + + Raises: + RuntimeError: raises runtime error if a not implemented method is called """ # pylint: disable=unused-argument t = "SVG" @@ -70,9 +74,11 @@ def render_svg(self, renderer: SvgRenderer) -> None: def render_cairo(self, renderer: CairoRenderer) -> None: """Render object using cairo - :param renderer: cairo renderer - :type renderer: CairoRenderer - :raises RuntimeError: raises runtime error if a not implemented method is called + Parameters: + renderer (CairoRenderer): cairo renderer + + Raises: + RuntimeError: raises runtime error if a not implemented method is called """ # pylint: disable=unused-argument t = "Cairo" @@ -83,10 +89,11 @@ def render_cairo(self, renderer: CairoRenderer) -> None: def pixel_rect(self, trans: Transformer) -> typing.Tuple[float, float, float, float]: """Return the pixel rect (left, top, right, bottom) of the object when using the supplied Transformer. - :param trans: - :type trans: Transformer - :return: pixel rectangle of object - :rtype: typing.Tuple[float, float, float, float] + Parameters: + trans (Transformer): transformer + + Returns: + typing.Tuple[float, float, float, float]: pixel rectangle of object """ bounds = self.bounds() se_x, se_y = trans.ll2pixel(bounds.get_vertex(1)) diff --git a/staticmaps/tile_downloader.py b/staticmaps/tile_downloader.py index d7f7449..70c8e41 100644 --- a/staticmaps/tile_downloader.py +++ b/staticmaps/tile_downloader.py @@ -41,8 +41,7 @@ def get(self, provider: TileProvider, cache_dir: str, zoom: int, x: int, y: int) typing.Optional[bytes]: tiles Raises: - RuntimeError: raises a runtime error if the the server - response status is not 200 + RuntimeError: raises a runtime error if the server response status is not 200 """ file_name = None if cache_dir is not None: From 3309cee977cf8808cc6ba58feef1356348065c5d Mon Sep 17 00:00:00 2001 From: Lowtower Date: Tue, 11 Oct 2022 11:20:44 +0200 Subject: [PATCH 060/147] python version in Makefile --- Makefile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Makefile b/Makefile index a375d77..c57a541 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,4 @@ -#PYTHON=python3 -PYTHON=/applbin/python_vw +PYTHON=python3 .PHONY: setup setup: From 6e14ee78305679ad4cd7f13c243919ccf874378e Mon Sep 17 00:00:00 2001 From: Lowtower Date: Tue, 11 Oct 2022 11:52:26 +0200 Subject: [PATCH 061/147] pillow deprectaion warning --- examples/custom_objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/custom_objects.py b/examples/custom_objects.py index 91487d6..cb0ef3e 100644 --- a/examples/custom_objects.py +++ b/examples/custom_objects.py @@ -37,7 +37,7 @@ def render_pillow(self, renderer: staticmaps.PillowRenderer) -> None: x, y = renderer.transformer().ll2pixel(self.latlng()) x = x + renderer.offset_x() - tw, th = renderer.draw().textsize(self._text) + tw, th = renderer.draw().textlength(self._text) w = max(self._arrow, tw + 2 * self._margin) h = th + 2 * self._margin From d356e452bf4651d1251b10b0fb4f0b9fa7a6f8bc Mon Sep 17 00:00:00 2001 From: Lowtower Date: Tue, 11 Oct 2022 11:53:55 +0200 Subject: [PATCH 062/147] make use of node.js 16 in GitHub workflow --- .github/workflows/main.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 61593cf..ceb404d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,9 +14,9 @@ jobs: steps: - name: Git config run: git config --global core.autocrlf input - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python v${{matrix.python-version}} - ${{runner.os}} - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{matrix.python-version}} cache: pip @@ -48,9 +48,9 @@ jobs: steps: - name: Git config run: git config --global core.autocrlf input - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python v${{matrix.python-version}} - ${{runner.os}} - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{matrix.python-version}} cache: pip @@ -75,7 +75,7 @@ jobs: (ls *cairo*.png && mv *cairo*.png build/.) || echo "no cairo png files found!" cd - - name: Archive examples - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: build_examples path: examples/build From 4f70baaaeede7170dbfde53fdb6fdaa7f57d37a2 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Tue, 11 Oct 2022 12:18:01 +0200 Subject: [PATCH 063/147] pillow deprectaion warning #2 --- examples/custom_objects.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/custom_objects.py b/examples/custom_objects.py index cb0ef3e..a019af2 100644 --- a/examples/custom_objects.py +++ b/examples/custom_objects.py @@ -37,7 +37,8 @@ def render_pillow(self, renderer: staticmaps.PillowRenderer) -> None: x, y = renderer.transformer().ll2pixel(self.latlng()) x = x + renderer.offset_x() - tw, th = renderer.draw().textlength(self._text) + textbox = renderer.draw().textbbox((0, 0, 0, 0), self._text) + tw, th = (textbox[2] - textbox[0], textbox[3] - textbox[1]) w = max(self._arrow, tw + 2 * self._margin) h = th + 2 * self._margin From 1ff484cc207b87014748fc596b6a2e81b3819b1c Mon Sep 17 00:00:00 2001 From: Lowtower Date: Tue, 11 Oct 2022 12:54:29 +0200 Subject: [PATCH 064/147] examples pylint --- examples/custom_objects.py | 46 +++++++++++++++++++++++++++++++---- examples/draw_gpx.py | 2 +- examples/frankfurt_newyork.py | 3 +-- examples/freiburg_area.py | 3 +-- examples/geodesic_circles.py | 4 +-- examples/tile_providers.py | 2 +- examples/us_capitals.py | 3 +-- 7 files changed, 48 insertions(+), 15 deletions(-) diff --git a/examples/custom_objects.py b/examples/custom_objects.py index a019af2..03e7476 100644 --- a/examples/custom_objects.py +++ b/examples/custom_objects.py @@ -1,17 +1,23 @@ #!/usr/bin/env python -# py-staticmaps +"""py-staticmaps - Example Custom Objects""" # Copyright (c) 2021 Florian Pigorsch; see /LICENSE for licensing information try: import cairo # type: ignore except ImportError: pass + import s2sphere # type: ignore + import staticmaps class TextLabel(staticmaps.Object): + """ + TextLabel Custom object, inherits from staticmaps.Object + """ + def __init__(self, latlng: s2sphere.LatLng, text: str) -> None: staticmaps.Object.__init__(self) self._latlng = latlng @@ -21,21 +27,41 @@ def __init__(self, latlng: s2sphere.LatLng, text: str) -> None: self._font_size = 12 def latlng(self) -> s2sphere.LatLng: + """Return latlng of object + + Returns: + s2sphere.LatLng: latlng of object + """ return self._latlng def bounds(self) -> s2sphere.LatLngRect: + """Return bounds of object + + Returns: + s2sphere.LatLngRect: bounds of object + """ return s2sphere.LatLngRect.from_point(self._latlng) def extra_pixel_bounds(self) -> staticmaps.PixelBoundsT: + """Return extra pixel bounds from object + + Returns: + PixelBoundsT: extra pixel bounds + """ # Guess text extents. tw = len(self._text) * self._font_size * 0.5 th = self._font_size * 1.2 - w = max(self._arrow, tw + 2.0 * self._margin) - return (int(w / 2.0), int(th + 2.0 * self._margin + self._arrow), int(w / 2), 0) + w = max(self._arrow, int(tw + 2.0 * self._margin)) + return int(w / 2.0), int(th + 2.0 * self._margin + self._arrow), int(w / 2), 0 def render_pillow(self, renderer: staticmaps.PillowRenderer) -> None: + """Render object using PILLOW + + Parameters: + renderer (PillowRenderer): pillow renderer + """ x, y = renderer.transformer().ll2pixel(self.latlng()) - x = x + renderer.offset_x() + x += renderer.offset_x() textbox = renderer.draw().textbbox((0, 0, 0, 0), self._text) tw, th = (textbox[2] - textbox[0], textbox[3] - textbox[1]) @@ -57,6 +83,11 @@ def render_pillow(self, renderer: staticmaps.PillowRenderer) -> None: renderer.draw().text((x - tw / 2, y - self._arrow - h / 2 - th / 2), self._text, fill=(0, 0, 0, 255)) def render_cairo(self, renderer: staticmaps.CairoRenderer) -> None: + """Render object using cairo + + Parameters: + renderer (CairoRenderer): cairo renderer + """ x, y = renderer.transformer().ll2pixel(self.latlng()) ctx = renderer.context() @@ -100,13 +131,18 @@ def render_cairo(self, renderer: staticmaps.CairoRenderer) -> None: ctx.stroke() def render_svg(self, renderer: staticmaps.SvgRenderer) -> None: + """Render object using svgwrite + + Parameters: + renderer (SvgRenderer): svg renderer + """ x, y = renderer.transformer().ll2pixel(self.latlng()) # guess text extents tw = len(self._text) * self._font_size * 0.5 th = self._font_size * 1.2 - w = max(self._arrow, tw + 2 * self._margin) + w = max(self._arrow, int(tw + 2 * self._margin)) h = th + 2 * self._margin path = renderer.drawing().path( diff --git a/examples/draw_gpx.py b/examples/draw_gpx.py index f7e71e6..a121248 100644 --- a/examples/draw_gpx.py +++ b/examples/draw_gpx.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# py-staticmaps +"""py-staticmaps - Example Draw GPX""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import sys diff --git a/examples/frankfurt_newyork.py b/examples/frankfurt_newyork.py index 5bb1998..d857da0 100644 --- a/examples/frankfurt_newyork.py +++ b/examples/frankfurt_newyork.py @@ -1,7 +1,6 @@ #!/usr/bin/env python -"""py-staticmaps - Example Frankfurt-New York""" -# py-staticmaps +"""py-staticmaps - Example Frankfurt-New York""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import staticmaps diff --git a/examples/freiburg_area.py b/examples/freiburg_area.py index 82b23d6..a2bf469 100644 --- a/examples/freiburg_area.py +++ b/examples/freiburg_area.py @@ -1,7 +1,6 @@ #!/usr/bin/env python -"""py-staticmaps - Example Freiburg Area""" -# py-staticmaps +"""py-staticmaps - Example Freiburg Area""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import staticmaps diff --git a/examples/geodesic_circles.py b/examples/geodesic_circles.py index 9cd90da..b768be3 100644 --- a/examples/geodesic_circles.py +++ b/examples/geodesic_circles.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# py-staticmaps +"""py-staticmaps - Example Geodesic Circles""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import staticmaps @@ -13,7 +13,7 @@ context.add_object(staticmaps.Circle(center1, 2000, fill_color=staticmaps.TRANSPARENT, color=staticmaps.RED, width=2)) context.add_object(staticmaps.Circle(center2, 2000, fill_color=staticmaps.TRANSPARENT, color=staticmaps.GREEN, width=2)) -context.add_object(staticmaps.Marker(center1, color=staticmaps.RED)) +context.add_object(staticmaps.Marker(center1)) context.add_object(staticmaps.Marker(center2, color=staticmaps.GREEN)) # render png via pillow diff --git a/examples/tile_providers.py b/examples/tile_providers.py index 3977430..03b8853 100644 --- a/examples/tile_providers.py +++ b/examples/tile_providers.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# py-staticmaps +"""py-staticmaps - Example Tile Providers""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import staticmaps diff --git a/examples/us_capitals.py b/examples/us_capitals.py index 96af961..9335ffe 100644 --- a/examples/us_capitals.py +++ b/examples/us_capitals.py @@ -1,7 +1,6 @@ #!/usr/bin/env python -"""py-staticmaps - Example US capitals""" -# py-staticmaps +"""py-staticmaps - Example US capitals""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import json From 617f572b9d0d219480741350efa1c894883a7bd5 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Tue, 6 Dec 2022 15:04:44 +0100 Subject: [PATCH 065/147] modifications due to pylint update --- .pylintrc | 32 ++++++++++++++++---------------- examples/us_capitals.py | 2 +- staticmaps/tile_downloader.py | 4 +++- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/.pylintrc b/.pylintrc index b07c3ce..2471c16 100644 --- a/.pylintrc +++ b/.pylintrc @@ -16,7 +16,7 @@ persistent=yes # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. -load-plugins = pylint.extensions.check_docs +load-plugins = pylint.extensions.docparams # Use multiple processes to speed up Pylint. jobs=0 @@ -52,10 +52,10 @@ confidence= # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" disable= - bad-continuation, +# bad-continuation, duplicate-code, missing-docstring, - no-init, +# no-init, no-member, no-value-for-parameter, too-few-public-methods, @@ -72,7 +72,7 @@ output-format=colorized # Put messages in a separate file for each module / package specified on the # command line instead of printing them on stdout. Reports (if any) will be # written in a file name "pylint_global.[txt|html]". -files-output=no +# files-output=no # Tells whether to display a full report or only the messages reports=no @@ -151,7 +151,7 @@ single-line-if-stmt=no # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. # `trailing-comma` allows a space between comma and closing bracket: (a, ). # `empty-line` allows space-only lines. -no-space-check=trailing-comma +# no-space-check=trailing-comma # Maximum number of lines in a module max-module-lines=500 @@ -170,7 +170,7 @@ expected-line-ending-format=LF [BASIC] # List of builtins function names that should not be used, separated by a comma -bad-functions=map,filter,input +# bad-functions=map,filter,input # Good variable names which should always be accepted, separated by a comma good-names=i,_ @@ -189,61 +189,61 @@ include-naming-hint=yes function-rgx=([a-z_][a-z0-9_]{1,40}|test_[A-Za-z0-9_]{3,70})$ # Naming hint for function names -function-name-hint=[a-z_][a-z0-9_]{1,40}$ +# function-name-hint=[a-z_][a-z0-9_]{1,40}$ # Regular expression matching correct variable names variable-rgx=[a-z_][a-z0-9_]{0,40}$ # Naming hint for variable names -variable-name-hint=[a-z_][a-z0-9_]{0,40}$ +# variable-name-hint=[a-z_][a-z0-9_]{0,40}$ # Regular expression matching correct constant names const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__)|(urls|urlpatterns|register))$ # Naming hint for constant names -const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ +# const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ # Regular expression matching correct attribute names attr-rgx=[a-z_][a-z0-9_]{0,30}$ # Naming hint for attribute names -attr-name-hint=[a-z_][a-z0-9_]{0,30}$ +# attr-name-hint=[a-z_][a-z0-9_]{0,30}$ # Regular expression matching correct argument names argument-rgx=[a-z_][a-z0-9_]{0,30}$ # Naming hint for argument names -argument-name-hint=[a-z_][a-z0-9_]{0,30}$ +# argument-name-hint=[a-z_][a-z0-9_]{0,30}$ # Regular expression matching correct class attribute names class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{1,40}|(__.*__))$ # Naming hint for class attribute names -class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{1,40}|(__.*__))$ +# class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{1,40}|(__.*__))$ # Regular expression matching correct inline iteration names inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ # Naming hint for inline iteration names -inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ +# inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ # Regular expression matching correct class names class-rgx=[A-Z_][a-zA-Z0-9]+$ # Naming hint for class names -class-name-hint=[A-Z_][a-zA-Z0-9]+$ +# class-name-hint=[A-Z_][a-zA-Z0-9]+$ # Regular expression matching correct module names module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Naming hint for module names -module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ +# module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Regular expression matching correct method names method-rgx=[a-z_][a-z0-9_]{1,30}$ # Naming hint for method names -method-name-hint=[a-z_][a-z0-9_]{1,30}$ +# method-name-hint=[a-z_][a-z0-9_]{1,30}$ # Regular expression which should only match function or class names that do # not require a docstring. diff --git a/examples/us_capitals.py b/examples/us_capitals.py index 98c086b..b9a357c 100644 --- a/examples/us_capitals.py +++ b/examples/us_capitals.py @@ -14,7 +14,7 @@ "https://gist.githubusercontent.com/jpriebe/d62a45e29f24e843c974/" "raw/b1d3066d245e742018bce56e41788ac7afa60e29/us_state_capitals.json" ) -response = requests.get(URL) +response = requests.get(URL, timeout=10) for _, data in json.loads(response.text).items(): capital = staticmaps.create_latlng(float(data["lat"]), float(data["long"])) context.add_object(staticmaps.Marker(capital, size=5)) diff --git a/staticmaps/tile_downloader.py b/staticmaps/tile_downloader.py index 353f1ba..ed62b47 100644 --- a/staticmaps/tile_downloader.py +++ b/staticmaps/tile_downloader.py @@ -11,6 +11,8 @@ from .meta import GITHUB_URL, LIB_NAME, VERSION from .tile_provider import TileProvider +REQUEST_TIMEOUT = 10 + class TileDownloader: """A tile downloader class""" @@ -54,7 +56,7 @@ def get(self, provider: TileProvider, cache_dir: str, zoom: int, x: int, y: int) url = provider.url(zoom, x, y) if url is None: return None - res = requests.get(url, headers={"user-agent": self._user_agent}) + res = requests.get(url, headers={"user-agent": self._user_agent}, timeout=REQUEST_TIMEOUT) if res.status_code == 200: data = res.content else: From 3504fa3d63c3f64eceecfeef28e293224b922a03 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Tue, 6 Dec 2022 15:10:33 +0100 Subject: [PATCH 066/147] parametrize tests --- tests/test_color.py | 153 ++++++++++++++++++++++---------------- tests/test_coordinates.py | 61 ++++++++------- 2 files changed, 118 insertions(+), 96 deletions(-) diff --git a/tests/test_color.py b/tests/test_color.py index 8900073..9fa9cac 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -6,33 +6,40 @@ import staticmaps -def test_text_color() -> None: - white = [ +@pytest.mark.parametrize( + "rgb", + [ (0, 0, 0), (255, 0, 0), (0, 0, 255), (0, 0, 0, 0), (0, 0, 0, 255), - ] - for rgb in white: - color = staticmaps.Color(*rgb) - assert staticmaps.WHITE == color.text_color() - assert staticmaps.WHITE.int_rgb() == color.text_color().int_rgb() + ], +) +def test_text_color_white(rgb: tuple) -> None: + color = staticmaps.Color(*rgb) + assert staticmaps.WHITE == color.text_color() + assert staticmaps.WHITE.int_rgb() == color.text_color().int_rgb() + - black = [ +@pytest.mark.parametrize( + "rgb", + [ (0, 255, 0), (255, 255, 0), (0, 255, 255), (255, 255, 255), - ] - for rgb in black: - color = staticmaps.Color(*rgb) - assert staticmaps.BLACK == color.text_color() - assert staticmaps.BLACK.int_rgb() == color.text_color().int_rgb() + ], +) +def test_text_color_black(rgb: tuple) -> None: + color = staticmaps.Color(*rgb) + assert staticmaps.BLACK == color.text_color() + assert staticmaps.BLACK.int_rgb() == color.text_color().int_rgb() -def test_hex_rgb() -> None: - colors = [ +@pytest.mark.parametrize( + "rgb, hex_color", + [ ((0, 0, 0), "#000000"), ((255, 0, 0), "#ff0000"), ((0, 255, 0), "#00ff00"), @@ -40,14 +47,16 @@ def test_hex_rgb() -> None: ((255, 255, 0), "#ffff00"), ((0, 255, 255), "#00ffff"), ((255, 255, 255), "#ffffff"), - ] - for rgb, hex_color in colors: - color = staticmaps.Color(*rgb) - assert hex_color == color.hex_rgb() + ], +) +def test_hex_rgb(rgb: tuple, hex_color: str) -> None: + color = staticmaps.Color(*rgb) + assert hex_color == color.hex_rgb() -def test_int_rgb() -> None: - colors = [ +@pytest.mark.parametrize( + "rgb", + [ (0, 0, 0), (255, 0, 0), (0, 0, 255), @@ -55,14 +64,16 @@ def test_int_rgb() -> None: (255, 255, 0), (0, 255, 255), (255, 255, 255), - ] - for rgb in colors: - color = staticmaps.Color(*rgb) - assert rgb == color.int_rgb() + ], +) +def test_int_rgb(rgb: tuple) -> None: + color = staticmaps.Color(*rgb) + assert rgb == color.int_rgb() -def test_int_rgba() -> None: - colors = [ +@pytest.mark.parametrize( + "rgb", + [ (0, 0, 0, 0), (255, 0, 0, 0), (0, 0, 255, 255), @@ -70,14 +81,16 @@ def test_int_rgba() -> None: (255, 255, 0, 0), (0, 255, 255, 0), (255, 255, 255, 255), - ] - for rgb in colors: - color = staticmaps.Color(*rgb) - assert rgb == color.int_rgba() + ], +) +def test_int_rgba(rgb: tuple) -> None: + color = staticmaps.Color(*rgb) + assert rgb == color.int_rgba() -def test_float_rgb() -> None: - colors = [ +@pytest.mark.parametrize( + "rgb, float_color", + [ ((0, 0, 0), (0.0, 0.0, 0.0)), ((255, 0, 0), (1.0, 0.0, 0.0)), ((0, 255, 0), (0.0, 1.0, 0.0)), @@ -85,14 +98,16 @@ def test_float_rgb() -> None: ((255, 255, 0), (1.0, 1.0, 0.0)), ((0, 255, 255), (0.0, 1.0, 1.0)), ((255, 255, 255), (1.0, 1.0, 1.0)), - ] - for rgb, float_color in colors: - color = staticmaps.Color(*rgb) - assert float_color == color.float_rgb() + ], +) +def test_float_rgb(rgb: tuple, float_color: tuple) -> None: + color = staticmaps.Color(*rgb) + assert float_color == color.float_rgb() -def test_float_rgba() -> None: - colors = [ +@pytest.mark.parametrize( + "rgb, float_color", + [ ((0, 0, 0), (0.0, 0.0, 0.0, 1.0)), ((0, 0, 0, 0), (0.0, 0.0, 0.0, 0.0)), ((255, 0, 0, 0), (1.0, 0.0, 0.0, 0.0)), @@ -103,44 +118,50 @@ def test_float_rgba() -> None: ((255, 255, 255, 255), (1.0, 1.0, 1.0, 1.0)), ((0, 0, 0), (0.0, 0.0, 0.0, 1.0)), ((255, 255, 255), (1.0, 1.0, 1.0, 1.0)), - ] - for rgb, float_color in colors: - color = staticmaps.Color(*rgb) - assert float_color == color.float_rgba() + ], +) +def test_float_rgba(rgb: tuple, float_color: tuple) -> None: + color = staticmaps.Color(*rgb) + assert float_color == color.float_rgba() -def test_float_a() -> None: - colors = [ +@pytest.mark.parametrize( + "rgb, float_alpha", + [ ((0, 0, 0, 0), 0.0), ((255, 255, 255, 255), 1.0), ((0, 0, 0), 1.0), ((255, 255, 255), 1.0), ((255, 255, 255, 100), 0.39215663), ((255, 255, 255, 200), 0.78431373), - ] - for rgb, float_alpha in colors: - color = staticmaps.Color(*rgb) - assert math.isclose(float_alpha, color.float_a(), rel_tol=0.0001) + ], +) +def test_float_a(rgb: tuple, float_alpha: float) -> None: + color = staticmaps.Color(*rgb) + assert math.isclose(float_alpha, color.float_a(), rel_tol=0.0001) -def test_parse_color() -> None: - good = ["0x1a2b3c", "0x1A2B3C", "#1a2b3c", "0x1A2B3C", "0x1A2B3C4D", "black", "RED", "Green", "transparent"] - for s in good: - staticmaps.parse_color(s) +@pytest.mark.parametrize( + "good", ["0x1a2b3c", "0x1A2B3C", "#1a2b3c", "0x1A2B3C", "0x1A2B3C4D", "black", "RED", "Green", "transparent"] +) +def test_parse_color(good: str) -> None: + staticmaps.parse_color(good) -def test_parse_color_raises_value_error() -> None: - bad = [ +@pytest.mark.parametrize( + "bad", + [ "", "aaa", "midnightblack", "#123", "#12345", "#1234567", - ] - for s in bad: - with pytest.raises(ValueError): - staticmaps.parse_color(s) + ], +) +def test_parse_color_raises_value_error(bad: str) -> None: + with pytest.raises(ValueError): + staticmaps.parse_color(bad) def test_create() -> None: @@ -148,8 +169,9 @@ def test_create() -> None: staticmaps.Color(1, 2, 3, 4) -def test_create_raises_value_error() -> None: - bad = [ +@pytest.mark.parametrize( + "rgb", + [ (-1, 0, 0), (256, 0, 0), (0, -1, 0), @@ -158,10 +180,11 @@ def test_create_raises_value_error() -> None: (0, 0, 256), (0, 0, 0, -1), (0, 0, 0, 256), - ] - for rgb in bad: - with pytest.raises(ValueError): - staticmaps.Color(*rgb) + ], +) +def test_create_raises_value_error(rgb: tuple) -> None: + with pytest.raises(ValueError): + staticmaps.Color(*rgb) def test_random_color() -> None: diff --git a/tests/test_coordinates.py b/tests/test_coordinates.py index 596f8fd..59dc915 100644 --- a/tests/test_coordinates.py +++ b/tests/test_coordinates.py @@ -6,15 +6,15 @@ import staticmaps -def test_parse_latlng() -> None: - good = ["48,8", " 48 , 8 ", "-48,8", "+48,8", "48,-8", "48,+8", "48.123,8.456"] - for s in good: - c = staticmaps.parse_latlng(s) - assert c.is_valid() +@pytest.mark.parametrize("good", ["48,8", " 48 , 8 ", "-48,8", "+48,8", "48,-8", "48,+8", "48.123,8.456"]) +def test_parse_latlng(good: str) -> None: + c = staticmaps.parse_latlng(good) + assert c.is_valid() -def test_parse_latlng_raises_value_error() -> None: - bad = [ +@pytest.mark.parametrize( + "bad", + [ "", "aaa", "12", @@ -26,35 +26,34 @@ def test_parse_latlng_raises_value_error() -> None: "-91,8", "48,-181", "48,181", - ] - for s in bad: - with pytest.raises(ValueError): - staticmaps.parse_latlng(s) + ], +) +def test_parse_latlng_raises_value_error(bad: str) -> None: + with pytest.raises(ValueError): + staticmaps.parse_latlng(bad) -def test_parse_latlngs() -> None: - good = [("", 0), ("48,8", 1), ("48,8 47,7", 2), (" 48,8 47,7 ", 2), ("48,7 48,8 47,7", 3)] - for s, expected_len in good: - a = staticmaps.parse_latlngs(s) - assert len(a) == expected_len +@pytest.mark.parametrize( + "good, expected_len", [("", 0), ("48,8", 1), ("48,8 47,7", 2), (" 48,8 47,7 ", 2), ("48,7 48,8 47,7", 3)] +) +def test_parse_latlngs(good: str, expected_len: int) -> None: + a = staticmaps.parse_latlngs(good) + assert len(a) == expected_len -def test_parse_latlngs_raises_value_error() -> None: - bad = ["xyz", "48,8 xyz", "48,8 48,181"] - for s in bad: - with pytest.raises(ValueError): - staticmaps.parse_latlngs(s) +@pytest.mark.parametrize("bad", ["xyz", "48,8 xyz", "48,8 48,181"]) +def test_parse_latlngs_raises_value_error(bad: str) -> None: + with pytest.raises(ValueError): + staticmaps.parse_latlngs(bad) -def test_parse_latlngs2rect() -> None: - good = ["48,8 47,7", " 48,8 47,7 "] - for s in good: - r = staticmaps.parse_latlngs2rect(s) - assert r.is_valid() +@pytest.mark.parametrize("good", ["48,8 47,7", " 48,8 47,7 "]) +def test_parse_latlngs2rect(good: str) -> None: + r = staticmaps.parse_latlngs2rect(good) + assert r.is_valid() -def test_parse_latlngs2rect_raises_value_error() -> None: - bad = ["xyz", "48,8 xyz", "48,8 48,181", "48,7", "48,7 48,8 47,7"] - for s in bad: - with pytest.raises(ValueError): - staticmaps.parse_latlngs2rect(s) +@pytest.mark.parametrize("bad", ["xyz", "48,8 xyz", "48,8 48,181", "48,7", "48,7 48,8 47,7"]) +def test_parse_latlngs2rect_raises_value_error(bad: str) -> None: + with pytest.raises(ValueError): + staticmaps.parse_latlngs2rect(bad) From 28e4109edebddd0a7397c3841a2a1581fc671dad Mon Sep 17 00:00:00 2001 From: Lowtower Date: Tue, 6 Dec 2022 15:19:21 +0100 Subject: [PATCH 067/147] Node.js 16 --- .github/workflows/main.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index dac565d..9f8b941 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,13 +9,13 @@ jobs: max-parallel: 4 matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: [3.6, 3.7, 3.8, 3.9, 3.10] steps: - name: Git config run: git config --global core.autocrlf input - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python v${{matrix.python-version}} - ${{runner.os}} - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{matrix.python-version}} cache: pip @@ -47,9 +47,9 @@ jobs: steps: - name: Git config run: git config --global core.autocrlf input - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python v${{matrix.python-version}} - ${{runner.os}} - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{matrix.python-version}} cache: pip @@ -74,7 +74,7 @@ jobs: (ls *cairo*.png && mv *cairo*.png build/.) || echo "no cairo png files found!" cd - - name: Archive examples - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: build_examples path: examples/build From b670c12908824dc31dcf3bdc1dd7266a08196f73 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Tue, 6 Dec 2022 15:23:24 +0100 Subject: [PATCH 068/147] Python version --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9f8b941..392f7cc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,7 +9,7 @@ jobs: max-parallel: 4 matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.6, 3.7, 3.8, 3.9, 3.10] + python-version: [3.6.15, 3.7, 3.8, 3.9, 3.10] steps: - name: Git config run: git config --global core.autocrlf input From 83da579c3823d5a450d23c2c5f5b1be313a299a1 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Tue, 6 Dec 2022 15:27:32 +0100 Subject: [PATCH 069/147] Drop python version 3,6, add 3.10 and 3.11 --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 392f7cc..09f5365 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,7 +9,7 @@ jobs: max-parallel: 4 matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.6.15, 3.7, 3.8, 3.9, 3.10] + python-version: [3.7, 3.8, 3.9, 3.10, 3.11] steps: - name: Git config run: git config --global core.autocrlf input From 8927ffa87b768e04a87335cb9b9b27855d7ebeb2 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Tue, 6 Dec 2022 15:28:40 +0100 Subject: [PATCH 070/147] Drop python version 3,6, add 3.10 and 3.11 --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 09f5365..766a832 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,7 +9,7 @@ jobs: max-parallel: 4 matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.7, 3.8, 3.9, 3.10, 3.11] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] steps: - name: Git config run: git config --global core.autocrlf input From 593b2b2e67175f552423c85b0062d616473a41a1 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Tue, 6 Dec 2022 15:32:04 +0100 Subject: [PATCH 071/147] Drop python version 3.11 --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 766a832..35e4425 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,7 +9,7 @@ jobs: max-parallel: 4 matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.7", "3.8", "3.9", "3.10"] steps: - name: Git config run: git config --global core.autocrlf input From 21e1f309a450805fb82c7ff242dcb87a83433766 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Tue, 6 Dec 2022 16:14:48 +0100 Subject: [PATCH 072/147] several changes --- staticmaps/cairo_renderer.py | 79 ++++++++++++-------- staticmaps/pillow_renderer.py | 89 +++++++++++++++------- staticmaps/renderer.py | 65 ++++++++++------ staticmaps/svg_renderer.py | 136 ++++++++++++++++++++++++---------- 4 files changed, 246 insertions(+), 123 deletions(-) diff --git a/staticmaps/cairo_renderer.py b/staticmaps/cairo_renderer.py index d27d151..ce9d483 100644 --- a/staticmaps/cairo_renderer.py +++ b/staticmaps/cairo_renderer.py @@ -1,4 +1,4 @@ -# py-staticmaps +"""py-staticmaps cairo_renderer""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import io @@ -6,6 +6,8 @@ import sys import typing +import s2sphere # type: ignore + try: import cairo # type: ignore except ImportError: @@ -25,8 +27,8 @@ def cairo_is_supported() -> bool: """Check whether cairo is supported - :return: Is cairo supported - :rtype: bool + Returns: + bool: Is cairo supported """ return "cairo" in sys.modules @@ -50,17 +52,15 @@ def __init__(self, transformer: Transformer) -> None: def image_surface(self) -> cairo_ImageSurface: """ - - :return: cairo image surface - :rtype: cairo.ImageSurface + Returns: + cairo.ImageSurface: cairo image surface """ return self._surface def context(self) -> cairo_Context: """ - - :return: cairo context - :rtype: cairo.Context + Returns: + cairo.Context: cairo context """ return self._context @@ -68,11 +68,11 @@ def context(self) -> cairo_Context: def create_image(image_data: bytes) -> cairo_ImageSurface: """Create a cairo image - :param image_data: Image data - :type image_data: bytes + Parameters: + image_data (bytes): Image data - :return: cairo image surface - :rtype: cairo.ImageSurface + Returns: + cairo.ImageSurface: cairo image surface """ image = PIL_Image.open(io.BytesIO(image_data)) if image.format == "PNG": @@ -83,11 +83,18 @@ def create_image(image_data: bytes) -> cairo_ImageSurface: png_bytes.seek(0) return cairo.ImageSurface.create_from_png(png_bytes) - def render_objects(self, objects: typing.List["Object"]) -> None: + def render_objects( + self, + objects: typing.List["Object"], + bbox: typing.Optional[s2sphere.LatLngRect] = None, + epb: typing.Optional[typing.Tuple[int, int, int, int]] = None, + ) -> None: """Render all objects of static map - :param objects: objects of static map - :type objects: typing.List["Object"] + Parameters: + objects (typing.List["Object"]): objects of static map + bbox (s2sphere.LatLngRect): boundary box of all objects + epb (typing.Tuple[int, int, int, int]): extra pixel bounds """ x_count = math.ceil(self._trans.image_width() / (2 * self._trans.world_width())) for obj in objects: @@ -100,8 +107,8 @@ def render_objects(self, objects: typing.List["Object"]) -> None: def render_background(self, color: typing.Optional[Color]) -> None: """Render background of static map - :param color: background color - :type color: typing.Optional[Color] + Parameters: + color (typing.Optional[Color]): background color """ if color is None: return @@ -109,11 +116,19 @@ def render_background(self, color: typing.Optional[Color]) -> None: self._context.rectangle(0, 0, *self._trans.image_size()) self._context.fill() - def render_tiles(self, download: typing.Callable[[int, int, int], typing.Optional[bytes]]) -> None: + def render_tiles( + self, + download: typing.Callable[[int, int, int], typing.Optional[bytes]], + bbox: typing.Optional[s2sphere.LatLngRect] = None, + epb: typing.Optional[typing.Tuple[int, int, int, int]] = None, + ) -> None: """Render background of static map - :param download: url of tiles provider - :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] + Parameters: + download (typing.Callable[[int, int, int], typing.Optional[bytes]]): + url of tiles provider + bbox (s2sphere.LatLngRect): boundary box of all objects + epb (typing.Tuple[int, int, int, int]): extra pixel bounds """ for yy in range(0, self._trans.tiles_y()): y = self._trans.first_tile_y() + yy @@ -139,8 +154,9 @@ def render_tiles(self, download: typing.Callable[[int, int, int], typing.Optiona def render_attribution(self, attribution: typing.Optional[str]) -> None: """Render attribution from given tiles provider - :param attribution: Attribution for the given tiles provider - :type attribution: typing.Optional[str]: + Parameters: + attribution (typing.Optional[str]:): Attribution for the + given tiles provider """ if (attribution is None) or (attribution == ""): return @@ -153,7 +169,7 @@ def render_attribution(self, attribution: typing.Optional[str]) -> None: t_width = self._context.text_extents(attribution)[3] if t_width < width - 4: break - font_size = font_size - 0.25 + font_size -= 0.25 self._context.set_source_rgba(*WHITE.float_rgb(), 0.8) self._context.rectangle(0, height - f_height - f_descent - 2, width, height) self._context.fill() @@ -168,15 +184,14 @@ def fetch_tile( ) -> typing.Optional[cairo_ImageSurface]: """Fetch tiles from given tiles provider - :param download: callable - :param x: width - :param y: height - :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] - :type x: int - :type y: int + Parameters: + download (typing.Callable[[int, int, int], typing.Optional[bytes]]): + callable + x (int): width + y (int): height - :return: cairo image surface - :rtype: typing.Optional[cairo_ImageSurface] + Returns: + typing.Optional[cairo_ImageSurface]: cairo image surface """ image_data = download(self._trans.zoom(), x, y) if image_data is None: diff --git a/staticmaps/pillow_renderer.py b/staticmaps/pillow_renderer.py index 38827bf..48895b2 100644 --- a/staticmaps/pillow_renderer.py +++ b/staticmaps/pillow_renderer.py @@ -1,10 +1,11 @@ -# py-staticmaps +"""py-staticmaps - pillow_renderer""" # Copyright (c) 2021 Florian Pigorsch; see /LICENSE for licensing information import io import math import typing +import s2sphere # type: ignore from PIL import Image as PIL_Image # type: ignore from PIL import ImageDraw as PIL_ImageDraw # type: ignore @@ -27,24 +28,55 @@ def __init__(self, transformer: Transformer) -> None: self._offset_x = 0 def draw(self) -> PIL_ImageDraw.Draw: + """ + draw Call PIL_ImageDraw.Draw() + + Returns: + PIL_ImageDraw.Draw: An PIL_Image draw object + """ return self._draw def image(self) -> PIL_Image.Image: + """ + image Call PIL_Image.new() + + Returns: + PIL_Image.Image: A PIL_Image image object + """ return self._image def offset_x(self) -> int: + """ + offset_x Return the offset in x direction + + Returns: + int: Offset in x direction + """ return self._offset_x def alpha_compose(self, image: PIL_Image.Image) -> None: + """ + alpha_compose Call PIL_Image.alpha_composite() + + Parameters: + image (PIL_Image.Image): A PIL_Image image object + """ assert image.size == self._image.size self._image = PIL_Image.alpha_composite(self._image, image) self._draw = PIL_ImageDraw.Draw(self._image) - def render_objects(self, objects: typing.List["Object"]) -> None: + def render_objects( + self, + objects: typing.List["Object"], + bbox: typing.Optional[s2sphere.LatLngRect] = None, + epb: typing.Optional[typing.Tuple[int, int, int, int]] = None, + ) -> None: """Render all objects of static map - :param objects: objects of static map - :type objects: typing.List["Object"] + Parameters: + objects (typing.List["Object"]): objects of static map + bbox (s2sphere.LatLngRect): boundary box of all objects + epb (typing.Tuple[int, int, int, int]): extra pixel bounds """ x_count = math.ceil(self._trans.image_width() / (2 * self._trans.world_width())) for obj in objects: @@ -55,18 +87,25 @@ def render_objects(self, objects: typing.List["Object"]) -> None: def render_background(self, color: typing.Optional[Color]) -> None: """Render background of static map - :param color: background color - :type color: typing.Optional[Color] + Parameters: + color (typing.Optional[Color]): background color """ if color is None: return - self.draw().rectangle([(0, 0), self.image().size], fill=color.int_rgba()) - - def render_tiles(self, download: typing.Callable[[int, int, int], typing.Optional[bytes]]) -> None: + self.draw().rectangle(((0, 0), self.image().size), fill=color.int_rgba()) + + def render_tiles( + self, + download: typing.Callable[[int, int, int], typing.Optional[bytes]], + bbox: typing.Optional[s2sphere.LatLngRect] = None, + epb: typing.Optional[typing.Tuple[int, int, int, int]] = None, + ) -> None: """Render background of static map - :param download: url of tiles provider - :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] + Parameters: + download (typing.Callable[[int, int, int], typing.Optional[bytes]]): url of tiles provider + bbox (s2sphere.LatLngRect): boundary box of all objects + epb (typing.Tuple[int, int, int, int]): extra pixel bounds """ for yy in range(0, self._trans.tiles_y()): y = self._trans.first_tile_y() + yy @@ -91,8 +130,8 @@ def render_tiles(self, download: typing.Callable[[int, int, int], typing.Optiona def render_attribution(self, attribution: typing.Optional[str]) -> None: """Render attribution from given tiles provider - :param attribution: Attribution for the given tiles provider - :type attribution: typing.Optional[str]: + Parameters: + attribution (typing.Optional[str]:): Attribution for the given tiles provider """ if (attribution is None) or (attribution == ""): return @@ -102,7 +141,7 @@ def render_attribution(self, attribution: typing.Optional[str]) -> None: h = self._trans.image_height() overlay = PIL_Image.new("RGBA", self._image.size, (255, 255, 255, 0)) draw = PIL_ImageDraw.Draw(overlay) - draw.rectangle([(0, h - th - 2 * margin), (w, h)], fill=(255, 255, 255, 204)) + draw.rectangle(((0, h - th - 2 * margin), (w, h)), fill=(255, 255, 255, 204)) self.alpha_compose(overlay) self.draw().text((margin, h - th - margin), attribution, fill=(0, 0, 0, 255)) @@ -111,15 +150,13 @@ def fetch_tile( ) -> typing.Optional[PIL_Image.Image]: """Fetch tiles from given tiles provider - :param download: callable - :param x: width - :param y: height - :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] - :type x: int - :type y: int + Parameters: + download (typing.Callable[[int, int, int], typing.Optional[bytes]]): callable + x (int): width + y (int): height - :return: pillow image - :rtype: typing.Optional[PIL_Image.Image] + Returns: + typing.Optional[PIL_Image.Image]: pillow image """ image_data = download(self._trans.zoom(), x, y) if image_data is None: @@ -130,10 +167,10 @@ def fetch_tile( def create_image(image_data: bytes) -> PIL_Image: """Create a pillow image - :param image_data: Image data - :type image_data: bytes + Parameters: + image_data (bytes): Image data - :return: pillow image - :rtype: PIL.Image + Returns: + PIL.Image: pillow image """ return PIL_Image.open(io.BytesIO(image_data)).convert("RGBA") diff --git a/staticmaps/renderer.py b/staticmaps/renderer.py index a94177d..f05af69 100644 --- a/staticmaps/renderer.py +++ b/staticmaps/renderer.py @@ -1,8 +1,10 @@ -# py-staticmaps +"""py-staticmaps - renderer""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information -from abc import ABC, abstractmethod import typing +from abc import ABC, abstractmethod + +import s2sphere # type: ignore from .color import Color from .transformer import Transformer @@ -26,67 +28,82 @@ def __init__(self, transformer: Transformer) -> None: def transformer(self) -> Transformer: """Return transformer object - :return: transformer - :rtype: Transformer + Returns: + Transformer: transformer """ return self._trans @abstractmethod - def render_objects(self, objects: typing.List["Object"]) -> None: + def render_objects( + self, + objects: typing.List["Object"], + bbox: s2sphere.LatLngRect, + epb: typing.Optional[typing.Tuple[int, int, int, int]] = None, + ) -> None: """Render all objects of static map - :param objects: objects of static map - :type objects: typing.List["Object"] + Parameters: + objects (typing.List["Object"]): objects of static map + bbox (s2sphere.LatLngRect): boundary box of all objects + epb (typing.Tuple[int, int, int, int]): extra pixel bounds """ @abstractmethod def render_background(self, color: typing.Optional[Color]) -> None: """Render background of static map - :param color: background color - :type color: typing.Optional[Color] + Parameters: + color (typing.Optional[Color]): background color """ @abstractmethod - def render_tiles(self, download: typing.Callable[[int, int, int], typing.Optional[bytes]]) -> None: - """Render background of static map - - :param download: url of tiles provider - :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] + def render_tiles( + self, + download: typing.Callable[[int, int, int], typing.Optional[bytes]], + bbox: s2sphere.LatLngRect, + epb: typing.Optional[typing.Tuple[int, int, int, int]] = None, + ) -> None: + """Render tiles of static map + + Parameters: + download (typing.Callable[[int, int, int], typing.Optional[bytes]]): url of tiles provider + bbox (s2sphere.LatLngRect): boundary box of all objects + epb (typing.Tuple[int, int, int, int]): extra pixel bounds """ def render_marker_object(self, marker: "Marker") -> None: """Render marker object of static map - :param marker: marker object - :type marker: Marker + Parameters: + marker (Marker): marker object """ def render_image_marker_object(self, marker: "ImageMarker") -> None: """Render image marker object of static map - :param marker: image marker object - :type marker: ImageMarker + Parameters: + marker (ImageMarker): image marker object """ def render_line_object(self, line: "Line") -> None: """Render line object of static map - :param line: line object - :type line: Line + Parameters: + line (Line): line object """ def render_area_object(self, area: "Area") -> None: """Render area object of static map - :param area: area object - :type area: Area + Parameters: + area (Area): area object """ @abstractmethod def render_attribution(self, attribution: typing.Optional[str]) -> None: """Render attribution from given tiles provider - :param attribution: Attribution for the given tiles provider - :type attribution: typing.Optional[str]: + Parameters: + attribution (typing.Optional[str]:): Attribution for the + given tiles provider """ diff --git a/staticmaps/svg_renderer.py b/staticmaps/svg_renderer.py index 3a4a69f..fdfb74b 100644 --- a/staticmaps/svg_renderer.py +++ b/staticmaps/svg_renderer.py @@ -1,10 +1,13 @@ +"""py-staticmaps - SvgRenderer""" + # py-staticmaps -# Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information +# Copyright (c) 2022 Florian Pigorsch; see /LICENSE for licensing information import base64 import math import typing +import s2sphere # type: ignore import svgwrite # type: ignore from .color import Color, BLACK, WHITE @@ -32,41 +35,49 @@ def __init__(self, transformer: Transformer) -> None: def drawing(self) -> svgwrite.Drawing: """Return the svg drawing for the image - :return: svg drawing - :rtype: svgwrite.Drawing + Returns: + svgwrite.Drawing: svg drawing """ return self._draw def group(self) -> svgwrite.container.Group: """Return the svg group for the image - :return: svg group - :rtype: svgwrite.container.Group + Returns: + svgwrite.container.Group: svg group """ assert self._group is not None return self._group - def render_objects(self, objects: typing.List["Object"]) -> None: + def render_objects( + self, + objects: typing.List["Object"], + bbox: typing.Optional[s2sphere.LatLngRect] = None, + epb: typing.Optional[typing.Tuple[int, int, int, int]] = None, + ) -> None: """Render all objects of static map - :param objects: objects of static map - :type objects: typing.List["Object"] + Parameters: + objects (typing.List["Object"]): objects of static map + bbox (s2sphere.LatLngRect): boundary box of all objects + epb (typing.Tuple[int, int, int, int]): extra pixel bounds """ + self._group = self._draw.g(clip_path="url(#page)") x_count = math.ceil(self._trans.image_width() / (2 * self._trans.world_width())) for obj in objects: for p in range(-x_count, x_count + 1): - self._group = self._draw.g( - clip_path="url(#page)", transform=f"translate({p * self._trans.world_width()}, 0)" - ) + group = self._draw.g(clip_path="url(#page)", transform=f"translate({p * self._trans.world_width()}, 0)") obj.render_svg(self) - self._draw.add(self._group) - self._group = None + self._group.add(group) + objects_group = self._tighten_to_boundary(self._group, bbox, epb) + self._draw.add(objects_group) + self._group = None def render_background(self, color: typing.Optional[Color]) -> None: """Render background of static map - :param color: background color - :type color: typing.Optional[Color] + Parameters: + color (typing.Optional[Color]): background color """ if color is None: return @@ -74,13 +85,20 @@ def render_background(self, color: typing.Optional[Color]) -> None: group.add(self._draw.rect(insert=(0, 0), size=self._trans.image_size(), rx=None, ry=None, fill=color.hex_rgb())) self._draw.add(group) - def render_tiles(self, download: typing.Callable[[int, int, int], typing.Optional[bytes]]) -> None: - """Render background of static map - - :param download: url of tiles provider - :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] + def render_tiles( + self, + download: typing.Callable[[int, int, int], typing.Optional[bytes]], + bbox: typing.Optional[s2sphere.LatLngRect] = None, + epb: typing.Optional[typing.Tuple[int, int, int, int]] = None, + ) -> None: + """Render tiles of static map + + Parameters: + download (typing.Callable[[int, int, int], typing.Optional[bytes]]): url of tiles provider + bbox (s2sphere.LatLngRect): boundary box of all objects + epb (typing.Tuple[int, int, int, int]): extra pixel bounds """ - group = self._draw.g(clip_path="url(#page)") + self._group = self._draw.g(clip_path="url(#page)") for yy in range(0, self._trans.tiles_y()): y = self._trans.first_tile_y() + yy if y < 0 or y >= self._trans.number_of_tiles(): @@ -91,7 +109,7 @@ def render_tiles(self, download: typing.Callable[[int, int, int], typing.Optiona tile_img = self.fetch_tile(download, x, y) if tile_img is None: continue - group.add( + self._group.add( self._draw.image( tile_img, insert=( @@ -103,13 +121,49 @@ def render_tiles(self, download: typing.Callable[[int, int, int], typing.Optiona ) except RuntimeError: pass - self._draw.add(group) + tiles_group = self._tighten_to_boundary(self._group, bbox, epb) + self._draw.add(tiles_group) + self._group = None + + def _tighten_to_boundary( + self, + group: svgwrite.container.Group, + bbox: typing.Optional[s2sphere.LatLngRect] = None, + epb: typing.Optional[typing.Tuple[int, int, int, int]] = None, + ) -> svgwrite.container.Group: + """Calculate scale and offset for tight rendering on the boundary""" + # pylint: disable=too-many-locals + if not bbox or not epb: + return group + # boundary points + nw_x, nw_y = self._trans.ll2pixel(s2sphere.LatLng.from_angles(bbox.lat_lo(), bbox.lng_lo())) + se_x, se_y = self._trans.ll2pixel(s2sphere.LatLng.from_angles(bbox.lat_hi(), bbox.lng_hi())) + epb_l, epb_t, epb_r, epb_b = 0, 0, 0, 0 + if epb: + epb_l, epb_t, epb_r, epb_b = epb + # boundary size + size_x = se_x - nw_x + epb_r + epb_l + size_y = nw_y - se_y + epb_t + epb_b + # scale to boundaries + width = self._trans.image_width() + height = self._trans.image_height() + scale_x = size_x / width + scale_y = size_y / height + scale = 1 / max(scale_x, scale_y) + # translate new center to old center + off_x = -0.5 * width * (scale - 1) + off_y = -0.5 * height * (scale - 1) + # finally, translate and scale + group.translate(off_x, off_y) + group.scale(scale) + return group def render_attribution(self, attribution: typing.Optional[str]) -> None: """Render attribution from given tiles provider - :param attribution: Attribution for the given tiles provider - :type attribution: typing.Optional[str]: + Parameters: + attribution (typing.Optional[str]:): Attribution for the + given tiles provider """ if (attribution is None) or (attribution == ""): return @@ -140,15 +194,14 @@ def fetch_tile( ) -> typing.Optional[str]: """Fetch tiles from given tiles provider - :param download: callable - :param x: width - :param y: height - :type download: typing.Callable[[int, int, int], typing.Optional[bytes]] - :type x: int - :type y: int + Parameters: + download (typing.Callable[[int, int, int], typing.Optional[bytes]]): + callable + x (int): width + y (int): height - :return: svg drawing - :rtype: typing.Optional[str] + Returns: + typing.Optional[str]: svg drawing """ image_data = download(self._trans.zoom(), x, y) if image_data is None: @@ -159,10 +212,11 @@ def fetch_tile( def guess_image_mime_type(data: bytes) -> str: """Guess mime type from image data - :param data: image data - :type data: bytes - :return: mime type - :rtype: str + Parameters: + data (bytes): image data + + Returns: + str: mime type """ if data[:4] == b"\xff\xd8\xff\xe0" and data[6:11] == b"JFIF\0": return "image/jpeg" @@ -174,11 +228,11 @@ def guess_image_mime_type(data: bytes) -> str: def create_inline_image(image_data: bytes) -> str: """Create an svg inline image - :param image_data: Image data - :type image_data: bytes + Parameters: + image_data (bytes): Image data - :return: svg inline image - :rtype: str + Returns: + str: svg inline image """ image_type = SvgRenderer.guess_image_mime_type(image_data) return f"data:{image_type};base64,{base64.b64encode(image_data).decode('utf-8')}" From 6b20a949e3ef0d6c8a761a666b32ae02e858a4a5 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Wed, 7 Dec 2022 13:33:51 +0100 Subject: [PATCH 073/147] v0.1: enable installing cairo and cairo examples --- .github/workflows/main.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 35e4425..79b7b62 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,8 +8,10 @@ jobs: strategy: max-parallel: 4 matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ["3.7", "3.8", "3.9", "3.10"] +# os: [ubuntu-latest, macos-latest, windows-latest] +# python-version: ["3.7", "3.8", "3.9", "3.10"] + os: [ubuntu-latest] + python-version: ["3.7"] steps: - name: Git config run: git config --global core.autocrlf input @@ -45,6 +47,8 @@ jobs: python-version: [3.7] needs: "lint_and_test" steps: + - name: Install cairo + run: apt-get install libcairo2-dev - name: Git config run: git config --global core.autocrlf input - uses: actions/checkout@v3 @@ -57,6 +61,7 @@ jobs: run: | python -m pip install --upgrade pip setuptools wheel pip install -r requirements.txt + pip install -r requirements-cairo.txt pip install -r requirements-examples.txt - name: Build examples run: | From b6f99e7c0724edf834f1776eb271c32b5e8ba154 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Wed, 7 Dec 2022 15:23:53 +0100 Subject: [PATCH 074/147] v0.2: enable installing cairo and cairo examples: add sudo --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 79b7b62..793bca7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -48,7 +48,7 @@ jobs: needs: "lint_and_test" steps: - name: Install cairo - run: apt-get install libcairo2-dev + run: sudo apt-get install libcairo2-dev - name: Git config run: git config --global core.autocrlf input - uses: actions/checkout@v3 From 6c8ab5a94b8c8eaf0727e793318b5fdc5613bbf9 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Wed, 7 Dec 2022 15:28:32 +0100 Subject: [PATCH 075/147] v0.3: enable installing cairo and cairo examples: increase request timeout to 600 --- examples/us_capitals.py | 2 +- staticmaps/tile_downloader.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/us_capitals.py b/examples/us_capitals.py index aee6dc2..c0f3490 100644 --- a/examples/us_capitals.py +++ b/examples/us_capitals.py @@ -14,7 +14,7 @@ "https://gist.githubusercontent.com/jpriebe/d62a45e29f24e843c974/" "raw/b1d3066d245e742018bce56e41788ac7afa60e29/us_state_capitals.json" ) -response = requests.get(URL, timeout=10) +response = requests.get(URL, timeout=600) for _, data in json.loads(response.text).items(): capital = staticmaps.create_latlng(float(data["lat"]), float(data["long"])) context.add_object(staticmaps.Marker(capital, size=5)) diff --git a/staticmaps/tile_downloader.py b/staticmaps/tile_downloader.py index 3978913..84f80ce 100644 --- a/staticmaps/tile_downloader.py +++ b/staticmaps/tile_downloader.py @@ -11,7 +11,7 @@ from .meta import GITHUB_URL, LIB_NAME, VERSION from .tile_provider import TileProvider -REQUEST_TIMEOUT = 10 +REQUEST_TIMEOUT = 600 class TileDownloader: From 6fe985c97d8c01e8e73af6fdf2d20994cce091a9 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Wed, 7 Dec 2022 15:44:38 +0100 Subject: [PATCH 076/147] v0.4: enable installing cairo and cairo examples: re-enable python and os versions, python v3.8 for building examples --- .github/workflows/main.yml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 793bca7..86568df 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,16 +8,14 @@ jobs: strategy: max-parallel: 4 matrix: -# os: [ubuntu-latest, macos-latest, windows-latest] -# python-version: ["3.7", "3.8", "3.9", "3.10"] - os: [ubuntu-latest] - python-version: ["3.7"] + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ["3.7", "3.8", "3.9", "3.10"] steps: - name: Git config run: git config --global core.autocrlf input - uses: actions/checkout@v3 - name: Set up Python v${{matrix.python-version}} - ${{runner.os}} - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: ${{matrix.python-version}} cache: pip @@ -44,7 +42,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - python-version: [3.7] + python-version: [3.8] needs: "lint_and_test" steps: - name: Install cairo @@ -53,7 +51,7 @@ jobs: run: git config --global core.autocrlf input - uses: actions/checkout@v3 - name: Set up Python v${{matrix.python-version}} - ${{runner.os}} - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: ${{matrix.python-version}} cache: pip From 2c4c903658d6df2bfea16ac1fc3b911cb630a728 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Wed, 7 Dec 2022 16:46:30 +0100 Subject: [PATCH 077/147] v0.5: reorganize Makefile, formatting, linting --- .editorconfig | 46 +++++ .flake8 | 2 - .pylintrc | 376 ---------------------------------- Makefile | 86 ++++++-- docs/gen_ref_pages.py | 1 + examples/draw_gpx.py | 1 + examples/us_capitals.py | 2 + mkdocs.yml | 2 +- requirements-dev.txt | 5 + setup.cfg | 102 +++++++++ setup.py | 1 + staticmaps/__init__.py | 65 +++++- staticmaps/area.py | 4 +- staticmaps/cairo_renderer.py | 2 +- staticmaps/circle.py | 4 +- staticmaps/line.py | 6 +- staticmaps/marker.py | 4 +- staticmaps/object.py | 3 +- staticmaps/pillow_renderer.py | 4 +- staticmaps/renderer.py | 1 - staticmaps/svg_renderer.py | 2 +- tests/mock_tile_downloader.py | 1 + tests/test_color.py | 2 + 23 files changed, 297 insertions(+), 425 deletions(-) create mode 100644 .editorconfig delete mode 100644 .flake8 delete mode 100644 .pylintrc create mode 100644 setup.cfg diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..aa982ed --- /dev/null +++ b/.editorconfig @@ -0,0 +1,46 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +# Matches multiple files with brace expansion notation +# Set default charset +[*.{js,py}] +charset = utf-8 + +# 4 space indentation +[*.py] +max_line_length = 120 +indent_style = space +indent_size = 4 + +[*.md] +trim_trailing_whitespace = false + +[mkdocs.yml] +indent_size = 2 + +# Tab indentation (no size specified) +[Makefile] +max_line_length = 150 +indent_style = tab +trim_trailing_whitespace = false + +# Indentation override for all JS under lib directory +[lib/**.js] +indent_style = space +indent_size = 2 + +# Matches the exact files either package.json or .travis.yml +[{package.json,.travis.yml}] +indent_style = space +indent_size = 2 diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 6deafc2..0000000 --- a/.flake8 +++ /dev/null @@ -1,2 +0,0 @@ -[flake8] -max-line-length = 120 diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index 393c43b..0000000 --- a/.pylintrc +++ /dev/null @@ -1,376 +0,0 @@ -[MASTER] - -# Specify a configuration file. -#rcfile= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=.tox,.env,.venv,.eggs,build,migrations,south_migrations,examples,tests - -# Pickle collected data for later comparisons. -persistent=yes - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins = pylint.extensions.docparams - -# Use multiple processes to speed up Pylint. -jobs=0 - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code -extension-pkg-whitelist= - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time. See also the "--disable" option for examples. -#enable= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -disable= -# bad-continuation, - duplicate-code, - missing-docstring, -# no-init, - no-member, - no-value-for-parameter, - too-few-public-methods, - too-many-arguments - - -[REPORTS] - -# Set the output format. Available formats are text, parseable, colorized, msvs -# (visual studio) and html. You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=colorized - -# Put messages in a separate file for each module / package specified on the -# command line instead of printing them on stdout. Reports (if any) will be -# written in a file name "pylint_global.[txt|html]". -# files-output=no - -# Tells whether to display a full report or only the messages -reports=no - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -#msg-template= - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME,XXX,TODO - - -[SIMILARITIES] - -# Minimum lines number of a similarity. -min-similarity-lines=5 - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=no - -# Ignore imports when computing similarities. -ignore-imports=yes - - -[VARIABLES] - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=_$|dummy|tmp$ - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_,_cb - - -[FORMAT] - -# Maximum number of characters on a single line. -max-line-length=120 - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -# no-space-check=trailing-comma - -# Maximum number of lines in a module -max-module-lines=500 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format=LF - - -[BASIC] - -# List of builtins function names that should not be used, separated by a comma -# bad-functions=map,filter,input - -# Good variable names which should always be accepted, separated by a comma -good-names=i,_ - -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata,wtf - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Include a hint for the correct naming format with invalid-name -include-naming-hint=yes - -# Regular expression matching correct function names -function-rgx=([a-z_][a-z0-9_]{1,40}|test_[A-Za-z0-9_]{3,70})$ - -# Naming hint for function names -# function-name-hint=[a-z_][a-z0-9_]{1,40}$ - -# Regular expression matching correct variable names -variable-rgx=[a-z_][a-z0-9_]{0,40}$ - -# Naming hint for variable names -# variable-name-hint=[a-z_][a-z0-9_]{0,40}$ - -# Regular expression matching correct constant names -const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__)|(urls|urlpatterns|register))$ - -# Naming hint for constant names -# const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Regular expression matching correct attribute names -attr-rgx=[a-z_][a-z0-9_]{0,30}$ - -# Naming hint for attribute names -# attr-name-hint=[a-z_][a-z0-9_]{0,30}$ - -# Regular expression matching correct argument names -argument-rgx=[a-z_][a-z0-9_]{0,30}$ - -# Naming hint for argument names -# argument-name-hint=[a-z_][a-z0-9_]{0,30}$ - -# Regular expression matching correct class attribute names -class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{1,40}|(__.*__))$ - -# Naming hint for class attribute names -# class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{1,40}|(__.*__))$ - -# Regular expression matching correct inline iteration names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ - -# Naming hint for inline iteration names -# inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ - -# Regular expression matching correct class names -class-rgx=[A-Z_][a-zA-Z0-9]+$ - -# Naming hint for class names -# class-name-hint=[A-Z_][a-zA-Z0-9]+$ - -# Regular expression matching correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Naming hint for module names -# module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Regular expression matching correct method names -method-rgx=[a-z_][a-z0-9_]{1,30}$ - -# Naming hint for method names -# method-name-hint=[a-z_][a-z0-9_]{1,30}$ - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - - -[ELIF] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - - -[TYPECHECK] - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. -ignored-modules = - -# List of classes names for which member attributes should not be checked -# (useful for classes with attributes dynamically set). -ignored-classes= - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - - -[SPELLING] - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[DESIGN] - -# Maximum number of arguments for function / method -max-args=5 - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.* - -# Maximum number of locals for function / method body -max-locals=15 - -# Maximum number of return / yield for function / method body -max-returns=6 - -# Maximum number of branch for function / method body -max-branches=12 - -# Maximum number of statements in function / method body -max-statements=50 - -# Maximum number of parents for a class (see R0901). -max-parents=8 - -# Maximum number of attributes for a class (see R0902). -max-attributes=7 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=1 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=24 - -# Maximum number of boolean expressions in a if statement -max-bool-expr=5 - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_meta - - -[IMPORTS] - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=regsub,TERMIOS,Bastion,rexec - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=Exception diff --git a/Makefile b/Makefile index c57a541..60c053d 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,37 @@ +PROJECT=staticmaps +SRC_CORE=staticmaps +SRC_TEST=tests +SRC_EXAMPLES=examples +SRC_COMPLETE=$(SRC_CORE) $(SRC_TEST) $(SRC_EXAMPLES) docs/gen_ref_pages.py PYTHON=python3 +PIP=$(PYTHON) -m pip + +help: ## Print help for each target + $(info Makefile low-level Python API.) + $(info =============================) + $(info ) + $(info Available commands:) + $(info ) + @grep '^[[:alnum:]_-]*:.* ##' $(MAKEFILE_LIST) \ + | sort | awk 'BEGIN {FS=":.* ## "}; {printf "%-25s %s\n", $$1, $$2};' + +clean: ## Cleanup + @rm -f ./*.pyc + @rm -rf ./__pycache__ + @rm -f $(SRC_CORE)/*.pyc + @rm -rf $(SRC_CORE)/__pycache__ + @rm -f $(SRC_TEST)/*.pyc + @rm -rf $(SRC_TEST)/__pycache__ + @rm -f $(SRC_EXAMPLES)/*.pyc + @rm -rf $(SRC_EXAMPLES)/__pycache__ + @rm -rf ./.coverage + @rm -rf ./coverage.xml + @rm -rf ./.pytest_cache + @rm -rf ./.mypy_cache + @rm -rf ./site .PHONY: setup -setup: +setup: ## Setup virtual environment $(PYTHON) -m venv .env .env/bin/pip install --upgrade pip wheel .env/bin/pip install --upgrade --requirement requirements.txt @@ -9,33 +39,46 @@ setup: .env/bin/pip install --upgrade --requirement requirements-examples.txt .PHONY: install -install: setup +install: setup ## install package .env/bin/pip install . .PHONY: lint -lint: +lint: ## Lint the code + .env/bin/pycodestyle \ + --max-line-length=120 \ + setup.py $(SRC_COMPLETE) + .env/bin/isort \ + setup.py $(SRC_COMPLETE) \ + --check --diff .env/bin/black \ - --line-length 120 \ - --check \ - --diff \ - setup.py staticmaps examples tests + --line-length 120 \ + --check \ + --diff \ + setup.py $(SRC_COMPLETE) + .env/bin/pyflakes \ + setup.py $(SRC_COMPLETE) .env/bin/flake8 \ - setup.py staticmaps examples tests + setup.py $(SRC_COMPLETE) .env/bin/pylint \ - setup.py staticmaps examples tests + setup.py $(SRC_COMPLETE) .env/bin/mypy \ - setup.py staticmaps examples tests + setup.py $(SRC_COMPLETE) .env/bin/codespell \ - README.md staticmaps/*.py tests/*.py examples/*.py + README.md staticmaps/*.py tests/*.py examples/*.py .PHONY: format -format: +format: ## Format the code + .env/bin/isort \ + setup.py $(SRC_COMPLETE) + .env/bin/autopep8 \ + -i -r \ + setup.py $(SRC_COMPLETE) .env/bin/black \ - --line-length 120 \ - setup.py staticmaps examples tests + --line-length 120 \ + setup.py $(SRC_COMPLETE) .PHONY: run-examples -run-examples: +run-examples: ## Generate example images (cd examples && PYTHONPATH=.. ../.env/bin/python custom_objects.py) (cd examples && PYTHONPATH=.. ../.env/bin/python draw_gpx.py running.gpx) (cd examples && PYTHONPATH=.. ../.env/bin/python frankfurt_newyork.py) @@ -47,30 +90,31 @@ run-examples: (cd examples && mv *pillow*png build/.) (cd examples && mv *cairo*png build/.) (cd -) + .PHONY: test -test: +test: ## Test the code PYTHONPATH=. .env/bin/python -m pytest tests .PHONY: coverage -coverage: +coverage: ## Generate coverage report for the code PYTHONPATH=. .env/bin/python -m pytest --cov=staticmaps --cov-branch --cov-report=term --cov-report=html tests .PHONY: build-package -build-package: +build-package: ## Build the package rm -rf dist PYTHONPATH=. .env/bin/python setup.py sdist PYTHONPATH=. .env/bin/twine check dist/* .PHONY: upload-package-test -upload-package-test: +upload-package-test: ## Upload package test PYTHONPATH=. .env/bin/twine upload --repository-url https://test.pypi.org/legacy/ dist/* .PHONY: upload-package -upload-package: +upload-package: ## Upload package PYTHONPATH=. .env/bin/twine upload --repository py-staticmaps dist/* .PHONY: documentation -documentation: +documentation: ## Generate documentation @if type mkdocs >/dev/null 2>&1 ; then .env/bin/python -m mkdocs build --clean --verbose ; \ else echo "SKIPPED. Run '$(PIP) install mkdocs' first." >&2 ; fi diff --git a/docs/gen_ref_pages.py b/docs/gen_ref_pages.py index 655e8c2..222be2f 100644 --- a/docs/gen_ref_pages.py +++ b/docs/gen_ref_pages.py @@ -3,6 +3,7 @@ from pathlib import Path import mkdocs_gen_files + TOP_LEVEL_NAME = "staticmaps" DIRECTORY = "reference" SRC = "staticmaps" diff --git a/examples/draw_gpx.py b/examples/draw_gpx.py index a121248..3e99497 100644 --- a/examples/draw_gpx.py +++ b/examples/draw_gpx.py @@ -6,6 +6,7 @@ import sys import gpxpy # type: ignore + import staticmaps context = staticmaps.Context() diff --git a/examples/us_capitals.py b/examples/us_capitals.py index c0f3490..1e3b006 100644 --- a/examples/us_capitals.py +++ b/examples/us_capitals.py @@ -4,7 +4,9 @@ # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import json + import requests + import staticmaps context = staticmaps.Context() diff --git a/mkdocs.yml b/mkdocs.yml index d0d05f8..6e43780 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -44,7 +44,7 @@ plugins: show_root_heading: true show_source: true watch: - - src/sta_run_all_loadcases + - staticmaps markdown_extensions: - pymdownx.highlight diff --git a/requirements-dev.txt b/requirements-dev.txt index 492f3de..ae5ca50 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,11 +2,16 @@ --requirement requirements.txt # Linting/Tooling +autopep8 black codespell coverage flake8 +isort mypy +pur +pycodestyle +pyflakes pylint # Testing diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..f39259a --- /dev/null +++ b/setup.cfg @@ -0,0 +1,102 @@ +[tool:pytest] +addopts = -v -ra -s +pythonpath = src +testpaths = tests +markers = + debug: debugging tests + +[coverage:report] +exclude_lines = + pragma: no cover + def __repr__ + if self\.debug + raise AssertionError + raise NotImplementedError + if __name__ == .__main__.: + +[coverage:paths] +source = ./staticmaps/* + +[coverage:html] +directory = reports + +[coverage:run] +branch = True +parallel = True +omit = + staticmaps/__init__.py + +[isort] +profile = black + +known_third_party = + numpy, + pandas, + keras, + tensorflow, + sklearn, + matplotlib, + scipy, + gnspy + +[flake8] +exclude = .git,__pycache__,docs,old,build,dist +max-complexity = 30 +max-line-length = 120 +#ignore=W504,F401,E402,E266,E203,W503,C408,C416,B001 + +[mypy] +check_untyped_defs = True +disallow_incomplete_defs = True +disallow_untyped_defs = True +#warn_return_any = true +warn_unused_configs = true +#ignore_missing_imports = true +#follow_imports = silent +#check_untyped_defs = false +#disallow_incomplete_defs = true +#disallow_untyped_defs = false +#disallow_subclassing_any = false +#strict_optional = false +#no_implicit_optional = false +warn_no_return = true +warn_unreachable = true + +[pycodestyle] +count = False +#ignore = E226,E302,E41 +#max-line-length = 120 +statistics = True +# exclude = + +[pylint.config] +extension-pkg-whitelist= + numpy, + pandas, + keras, + tensorflow, + sklearn, + matplotlib, + scipy + +[pylint.MESSAGES CONTROL] +disable= +# duplicate-code, + missing-docstring, +# no-member, +# no-value-for-parameter, +# too-few-public-methods, +# too-many-arguments, + invalid-name, +#enable=E,W +jobs=1 +confidence=HIGH + +[pylint.FORMAT] +max-line-length = 120 +max-module-lines = 2000 + +[codespell] +skip = *.po,*.ts,content_master_gui_xml.py,content_sta_wrapper.py +count = +quiet-level = 3 diff --git a/setup.py b/setup.py index 197f943..77bcd7c 100644 --- a/setup.py +++ b/setup.py @@ -59,6 +59,7 @@ def _read_reqs(rel_path: str) -> typing.List[str]: "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", ], keywords="map staticmap osm markers", packages=[PACKAGE], diff --git a/staticmaps/__init__.py b/staticmaps/__init__.py index 90d48bc..43dad80 100644 --- a/staticmaps/__init__.py +++ b/staticmaps/__init__.py @@ -6,9 +6,6 @@ from .cairo_renderer import CairoRenderer, cairo_is_supported from .circle import Circle from .color import ( - parse_color, - random_color, - Color, BLACK, BLUE, BROWN, @@ -16,9 +13,12 @@ ORANGE, PURPLE, RED, - YELLOW, - WHITE, TRANSPARENT, + WHITE, + YELLOW, + Color, + parse_color, + random_color, ) from .context import Context from .coordinates import create_latlng, parse_latlng, parse_latlngs, parse_latlngs2rect @@ -33,13 +33,60 @@ from .tile_provider import ( TileProvider, default_tile_providers, + tile_provider_ArcGISWorldImagery, + tile_provider_CartoDarkNoLabels, + tile_provider_CartoNoLabels, + tile_provider_None, tile_provider_OSM, tile_provider_StamenTerrain, tile_provider_StamenToner, tile_provider_StamenTonerLite, - tile_provider_ArcGISWorldImagery, - tile_provider_CartoNoLabels, - tile_provider_CartoDarkNoLabels, - tile_provider_None, ) from .transformer import Transformer + +__all__ = [ + "Area", + "CairoRenderer", + "cairo_is_supported", + "Circle", + "BLACK", + "BLUE", + "BROWN", + "GREEN", + "ORANGE", + "PURPLE", + "RED", + "TRANSPARENT", + "WHITE", + "YELLOW", + "Color", + "parse_color", + "random_color", + "Context", + "create_latlng", + "parse_latlng", + "parse_latlngs", + "parse_latlngs2rect", + "ImageMarker", + "Line", + "Marker", + "GITHUB_URL", + "LIB_NAME", + "VERSION", + "Object", + "PixelBoundsT", + "PillowRenderer", + "SvgRenderer", + "TileDownloader", + "TileProvider", + "default_tile_providers", + "tile_provider_ArcGISWorldImagery", + "tile_provider_CartoDarkNoLabels", + "tile_provider_CartoNoLabels", + "tile_provider_None", + "tile_provider_OSM", + "tile_provider_StamenTerrain", + "tile_provider_StamenToner", + "tile_provider_StamenTonerLite", + "Transformer", +] diff --git a/staticmaps/area.py b/staticmaps/area.py index b694620..6988cf0 100644 --- a/staticmaps/area.py +++ b/staticmaps/area.py @@ -3,12 +3,12 @@ import typing +import s2sphere # type: ignore from PIL import Image as PIL_Image # type: ignore from PIL import ImageDraw as PIL_ImageDraw # type: ignore -import s2sphere # type: ignore from .cairo_renderer import CairoRenderer -from .color import Color, RED, TRANSPARENT +from .color import RED, TRANSPARENT, Color from .line import Line from .pillow_renderer import PillowRenderer from .svg_renderer import SvgRenderer diff --git a/staticmaps/cairo_renderer.py b/staticmaps/cairo_renderer.py index ce9d483..b7fb8e5 100644 --- a/staticmaps/cairo_renderer.py +++ b/staticmaps/cairo_renderer.py @@ -15,7 +15,7 @@ from PIL import Image as PIL_Image # type: ignore -from .color import Color, BLACK, WHITE +from .color import BLACK, WHITE, Color from .renderer import Renderer from .transformer import Transformer diff --git a/staticmaps/circle.py b/staticmaps/circle.py index f1ec2dc..37423be 100644 --- a/staticmaps/circle.py +++ b/staticmaps/circle.py @@ -3,11 +3,11 @@ import typing -from geographiclib.geodesic import Geodesic # type: ignore import s2sphere # type: ignore +from geographiclib.geodesic import Geodesic # type: ignore from .area import Area -from .color import Color, RED, TRANSPARENT +from .color import RED, TRANSPARENT, Color from .coordinates import create_latlng diff --git a/staticmaps/line.py b/staticmaps/line.py index a7cf510..d3de6d6 100644 --- a/staticmaps/line.py +++ b/staticmaps/line.py @@ -4,13 +4,13 @@ import math import typing -from geographiclib.geodesic import Geodesic # type: ignore import s2sphere # type: ignore +from geographiclib.geodesic import Geodesic # type: ignore -from .color import Color, RED +from .cairo_renderer import CairoRenderer +from .color import RED, Color from .coordinates import create_latlng from .object import Object, PixelBoundsT -from .cairo_renderer import CairoRenderer from .pillow_renderer import PillowRenderer from .svg_renderer import SvgRenderer diff --git a/staticmaps/marker.py b/staticmaps/marker.py index f69e5ae..31bd150 100644 --- a/staticmaps/marker.py +++ b/staticmaps/marker.py @@ -5,9 +5,9 @@ import s2sphere # type: ignore -from .color import Color, RED -from .object import Object, PixelBoundsT from .cairo_renderer import CairoRenderer +from .color import RED, Color +from .object import Object, PixelBoundsT from .pillow_renderer import PillowRenderer from .svg_renderer import SvgRenderer diff --git a/staticmaps/object.py b/staticmaps/object.py index 093552c..d6b7305 100644 --- a/staticmaps/object.py +++ b/staticmaps/object.py @@ -1,8 +1,8 @@ """py-staticmaps - object""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information -from abc import ABC, abstractmethod import typing +from abc import ABC, abstractmethod import s2sphere # type: ignore @@ -11,7 +11,6 @@ from .svg_renderer import SvgRenderer from .transformer import Transformer - PixelBoundsT = typing.Tuple[int, int, int, int] diff --git a/staticmaps/pillow_renderer.py b/staticmaps/pillow_renderer.py index 6691420..d93d4ec 100644 --- a/staticmaps/pillow_renderer.py +++ b/staticmaps/pillow_renderer.py @@ -1,5 +1,5 @@ -# py-staticmaps -# Copyright (c) 2021 Florian Pigorsch; see /LICENSE for licensing information +"""py-staticmaps pillow_renderer""" +# Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import io import math diff --git a/staticmaps/renderer.py b/staticmaps/renderer.py index f05af69..efecd4d 100644 --- a/staticmaps/renderer.py +++ b/staticmaps/renderer.py @@ -9,7 +9,6 @@ from .color import Color from .transformer import Transformer - if typing.TYPE_CHECKING: # avoid circlic import from .area import Area # pylint: disable=cyclic-import diff --git a/staticmaps/svg_renderer.py b/staticmaps/svg_renderer.py index fdfb74b..37621b9 100644 --- a/staticmaps/svg_renderer.py +++ b/staticmaps/svg_renderer.py @@ -10,7 +10,7 @@ import s2sphere # type: ignore import svgwrite # type: ignore -from .color import Color, BLACK, WHITE +from .color import BLACK, WHITE, Color from .renderer import Renderer from .transformer import Transformer diff --git a/tests/mock_tile_downloader.py b/tests/mock_tile_downloader.py index c9f6f1c..f9a0470 100644 --- a/tests/mock_tile_downloader.py +++ b/tests/mock_tile_downloader.py @@ -2,6 +2,7 @@ # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import typing + import staticmaps diff --git a/tests/test_color.py b/tests/test_color.py index 9fa9cac..f547455 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -2,7 +2,9 @@ # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import math + import pytest # type: ignore + import staticmaps From b80d5bca679fd280e51e89760707226a5720fc0f Mon Sep 17 00:00:00 2001 From: Lowtower Date: Wed, 7 Dec 2022 16:53:23 +0100 Subject: [PATCH 078/147] v0.6: remove mypy.ini -> setup.cfg --- mypy.ini | 4 ---- setup.cfg | 16 +--------------- 2 files changed, 1 insertion(+), 19 deletions(-) delete mode 100644 mypy.ini diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index f66bccb..0000000 --- a/mypy.ini +++ /dev/null @@ -1,4 +0,0 @@ -[mypy] -check_untyped_defs = True -disallow_incomplete_defs = True -disallow_untyped_defs = True \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index f39259a..fa3d7fd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -49,16 +49,7 @@ max-line-length = 120 check_untyped_defs = True disallow_incomplete_defs = True disallow_untyped_defs = True -#warn_return_any = true warn_unused_configs = true -#ignore_missing_imports = true -#follow_imports = silent -#check_untyped_defs = false -#disallow_incomplete_defs = true -#disallow_untyped_defs = false -#disallow_subclassing_any = false -#strict_optional = false -#no_implicit_optional = false warn_no_return = true warn_unreachable = true @@ -81,12 +72,7 @@ extension-pkg-whitelist= [pylint.MESSAGES CONTROL] disable= -# duplicate-code, missing-docstring, -# no-member, -# no-value-for-parameter, -# too-few-public-methods, -# too-many-arguments, invalid-name, #enable=E,W jobs=1 @@ -97,6 +83,6 @@ max-line-length = 120 max-module-lines = 2000 [codespell] -skip = *.po,*.ts,content_master_gui_xml.py,content_sta_wrapper.py +skip = *.po,*.ts count = quiet-level = 3 From 4bbbcdb5b3580364332d51034eb1d52a1ec1c3c6 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 08:48:56 +0100 Subject: [PATCH 079/147] pull-request: https://github.com/flopp/py-staticmaps/pull/29/commits/ad0c4dd05ce2947495417364f3db60d13bde0c1a --- staticmaps/cairo_renderer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/staticmaps/cairo_renderer.py b/staticmaps/cairo_renderer.py index b7fb8e5..4f4448e 100644 --- a/staticmaps/cairo_renderer.py +++ b/staticmaps/cairo_renderer.py @@ -166,7 +166,7 @@ def render_attribution(self, attribution: typing.Optional[str]) -> None: while True: self._context.set_font_size(font_size) _, f_descent, f_height, _, _ = self._context.font_extents() - t_width = self._context.text_extents(attribution)[3] + t_width = self._context.text_extents(attribution).width if t_width < width - 4: break font_size -= 0.25 From 2f9338baa2e542c347aa98eaa137683c5fb383f7 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 08:49:32 +0100 Subject: [PATCH 080/147] typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a1d1912..9ea05e7 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ with open("freiburg_area.svg", "w", encoding="utf-8") as f: svg_image.write(f, pretty=True) ``` -![draw_gpx](../assets/freiburg_area.png?raw=true) +![freiburg_area](../assets/freiburg_area.png?raw=true) ### Drawing a GPX Track + Image Marker (PNG) From 7fac3cc325e2da895d60d87fda63329b41c3f4ff Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 08:49:47 +0100 Subject: [PATCH 081/147] remove duplicate coverage --- requirements-dev.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index ae5ca50..67f32d1 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -5,7 +5,6 @@ autopep8 black codespell -coverage flake8 isort mypy From b0402fca438168b5ed159878e5ff7e2138be4cdb Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 08:56:58 +0100 Subject: [PATCH 082/147] remove reports directory in "make clean" --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 60c053d..8dcdd4b 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,7 @@ clean: ## Cleanup @rm -rf ./.pytest_cache @rm -rf ./.mypy_cache @rm -rf ./site + @rm -rf ./reports .PHONY: setup setup: ## Setup virtual environment From 1ffde2271acc25ceec9113b3fe27649f3369c991 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 09:36:01 +0100 Subject: [PATCH 083/147] v0.1: test push pictures to assets --- .github/workflows/main.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 86568df..aaaa598 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,8 +8,10 @@ jobs: strategy: max-parallel: 4 matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ["3.7", "3.8", "3.9", "3.10"] +# os: [ubuntu-latest, macos-latest, windows-latest] +# python-version: ["3.7", "3.8", "3.9", "3.10"] + os: [ubuntu-latest] + python-version: ["3.8"] steps: - name: Git config run: git config --global core.autocrlf input @@ -81,3 +83,11 @@ jobs: with: name: build_examples path: examples/build + - name: Display structure + run: ls -R + - name: Push to assets + uses: actions/download-artifact@v3 + with: + name: build_examples + - name: Display structure of downloaded files + run: ls -R From 122741dcaeecbc17f2cfc3775d5b5955cfb841ac Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 09:46:20 +0100 Subject: [PATCH 084/147] v0.2: mypy config --- setup.cfg | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index fa3d7fd..037160d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -49,9 +49,9 @@ max-line-length = 120 check_untyped_defs = True disallow_incomplete_defs = True disallow_untyped_defs = True -warn_unused_configs = true -warn_no_return = true -warn_unreachable = true +warn_unused_configs = True +warn_no_return = True +warn_unreachable = False [pycodestyle] count = False From 55ec1d47a2e92e57bca581a24e68a940b7dd548d Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 09:57:38 +0100 Subject: [PATCH 085/147] v0.3: deploy step --- .github/workflows/main.yml | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index aaaa598..237e030 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -83,9 +83,22 @@ jobs: with: name: build_examples path: examples/build - - name: Display structure - run: ls -R - - name: Push to assets + deploy: + runs-on: ${{matrix.os}} + strategy: + matrix: + os: [ubuntu-latest] + python-version: [3.8] + needs: "build" + steps: + - name: Git config + run: git config --global core.autocrlf input + - name: Git clone + run: | + git clone https://github.com/lowtower/py-staticmaps --branch assets assets + cd assets + pwd + - name: Download artifacts uses: actions/download-artifact@v3 with: name: build_examples From 0c4542a49c0c480604034e0f7193507dae035ecd Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 10:06:46 +0100 Subject: [PATCH 086/147] v0.4: move pictures --- .github/workflows/main.yml | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 237e030..57373dd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -94,13 +94,24 @@ jobs: - name: Git config run: git config --global core.autocrlf input - name: Git clone - run: | - git clone https://github.com/lowtower/py-staticmaps --branch assets assets - cd assets - pwd + run: git clone https://github.com/lowtower/py-staticmaps --branch assets assets - name: Download artifacts uses: actions/download-artifact@v3 with: name: build_examples - name: Display structure of downloaded files - run: ls -R + run: | + pwd + mv *png assets/. + mv *svg assets/. + cd assets + pwd + ls -R + cd + - name: Git push + run: | + pwd + ls -R + cd + pwd + ls -R From f04fef956b650d245d2ece5291b8f455ca90da37 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 10:14:17 +0100 Subject: [PATCH 087/147] v0.5: git push --- .github/workflows/main.yml | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 57373dd..0a76974 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -91,6 +91,9 @@ jobs: python-version: [3.8] needs: "build" steps: +# - uses: actions/checkout@v3 +# with: +# token: ${{secrets.PAT}} - name: Git config run: git config --global core.autocrlf input - name: Git clone @@ -99,19 +102,10 @@ jobs: uses: actions/download-artifact@v3 with: name: build_examples - - name: Display structure of downloaded files + - name: Move pictures run: | - pwd mv *png assets/. mv *svg assets/. - cd assets - pwd - ls -R - cd - name: Git push run: | - pwd - ls -R - cd - pwd - ls -R + git push From 035e4d95d61429e89f2d7d8b5740ad69c560c3c6 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 10:18:49 +0100 Subject: [PATCH 088/147] v0.6: git push - cd --- .github/workflows/main.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0a76974..e9daab4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -102,10 +102,9 @@ jobs: uses: actions/download-artifact@v3 with: name: build_examples - - name: Move pictures + - name: Move pictures and push run: | mv *png assets/. mv *svg assets/. - - name: Git push - run: | + cd assets git push From 33e881854b9cd66147cb78487fb3be99ef542474 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 10:25:23 +0100 Subject: [PATCH 089/147] v0.7: git config user --- .github/workflows/main.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e9daab4..d248ebd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -107,4 +107,6 @@ jobs: mv *png assets/. mv *svg assets/. cd assets + git config --global user.name "github-actions[bot]" + git config --global user.email "${{ secrets.GH_MAIL }}" git push From 4f939a2f1d444a09c1052391133c74202891e5d2 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 10:37:19 +0100 Subject: [PATCH 090/147] v0.8: git config actor --- .github/workflows/main.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d248ebd..23cc7fb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -91,9 +91,6 @@ jobs: python-version: [3.8] needs: "build" steps: -# - uses: actions/checkout@v3 -# with: -# token: ${{secrets.PAT}} - name: Git config run: git config --global core.autocrlf input - name: Git clone @@ -103,10 +100,12 @@ jobs: with: name: build_examples - name: Move pictures and push + with: + global: true + actor: '${{ GITHUB_ACTOR }}' + token: '${{ GITHUB_TOKEN }}' run: | mv *png assets/. mv *svg assets/. cd assets - git config --global user.name "github-actions[bot]" - git config --global user.email "${{ secrets.GH_MAIL }}" git push From de172ef672a883da57663edf65b7d01a9c59027b Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 10:46:19 +0100 Subject: [PATCH 091/147] v0.9: git config GITHUB_ACTOR --- .github/workflows/main.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 23cc7fb..0e5beb3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -100,12 +100,9 @@ jobs: with: name: build_examples - name: Move pictures and push - with: - global: true - actor: '${{ GITHUB_ACTOR }}' - token: '${{ GITHUB_TOKEN }}' run: | mv *png assets/. mv *svg assets/. cd assets + git config --global user.name "${{ GITHUB_ACTOR }}" git push From 87c67b1954f79b69319ee6405235ec516abb33d2 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 10:50:14 +0100 Subject: [PATCH 092/147] v0.10: git config $env.GITHUB_ACTOR --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0e5beb3..805bd35 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -104,5 +104,5 @@ jobs: mv *png assets/. mv *svg assets/. cd assets - git config --global user.name "${{ GITHUB_ACTOR }}" + git config --global user.name $env:GITHUB_ACTOR git push From 2310d8c53ebf2a47b408660815d2b30c3f826a2e Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 10:57:57 +0100 Subject: [PATCH 093/147] v0.11: echo env variables --- .github/workflows/main.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 805bd35..d15b2b6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -104,5 +104,9 @@ jobs: mv *png assets/. mv *svg assets/. cd assets + echo "$env:GITHUB_ACTOR" + echo "$env:GITHUB_SERVER_URL" + echo "$env:GITHUB_REPOSITORY" + echo "{{ $env:GITHUB_SERVER_URL }} / {{ $env:GITHUB_REPOSITORY }}" git config --global user.name $env:GITHUB_ACTOR - git push +# git push From 1f4ee2afc63150f732e2b01513057066ed16f5de Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 11:07:42 +0100 Subject: [PATCH 094/147] v0.12: echo env variables --- .github/workflows/main.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d15b2b6..e90f091 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -104,9 +104,10 @@ jobs: mv *png assets/. mv *svg assets/. cd assets - echo "$env:GITHUB_ACTOR" - echo "$env:GITHUB_SERVER_URL" - echo "$env:GITHUB_REPOSITORY" - echo "{{ $env:GITHUB_SERVER_URL }} / {{ $env:GITHUB_REPOSITORY }}" + echo "$env.GITHUB_ACTOR" + echo "$env.GITHUB_SERVER_URL" + echo "$env.GITHUB_REPOSITORY" + echo "{{ $env.GITHUB_SERVER_URL }} / {{ $env.GITHUB_REPOSITORY }}" + echo "$env.GITHUB_SERVER_URL/$env.GITHUB_REPOSITORY" git config --global user.name $env:GITHUB_ACTOR # git push From af92189327f3ba3759c0bcce5a7116aacdf020bb Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 13:17:01 +0100 Subject: [PATCH 095/147] v0.12a: echo env variables --- .github/workflows/main.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e90f091..097bd7f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -104,10 +104,10 @@ jobs: mv *png assets/. mv *svg assets/. cd assets - echo "$env.GITHUB_ACTOR" - echo "$env.GITHUB_SERVER_URL" - echo "$env.GITHUB_REPOSITORY" - echo "{{ $env.GITHUB_SERVER_URL }} / {{ $env.GITHUB_REPOSITORY }}" - echo "$env.GITHUB_SERVER_URL/$env.GITHUB_REPOSITORY" - git config --global user.name $env:GITHUB_ACTOR + echo "${{env.GITHUB_ACTOR}}" + echo "${{env.GITHUB_SERVER_URL}}" + echo "${{env.GITHUB_REPOSITORY}}" + echo "${{env.GITHUB_SERVER_URL}}/${{env.GITHUB_REPOSITOR}}" + echo "git config --global user.name '${{env.GITHUB_ACTOR}}'" + git config --global user.name "${{env.GITHUB_ACTOR}}" # git push From 7d07e0fd313eea96300c97cb051c2c10e400a429 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 13:23:28 +0100 Subject: [PATCH 096/147] v0.12b: echo env variables --- .github/workflows/main.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 097bd7f..49bc1bb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -104,10 +104,13 @@ jobs: mv *png assets/. mv *svg assets/. cd assets - echo "${{env.GITHUB_ACTOR}}" - echo "${{env.GITHUB_SERVER_URL}}" - echo "${{env.GITHUB_REPOSITORY}}" - echo "${{env.GITHUB_SERVER_URL}}/${{env.GITHUB_REPOSITOR}}" + echo "${{ env.GITHUB_ACTOR }}" + echo "${{ env.GITHUB_SERVER_URL }}" + echo "${{ env.GITHUB_REPOSITORY }}" + echo "${{ env.GITHUB_SERVER_URL}}/${{env.GITHUB_REPOSITOR }}" echo "git config --global user.name '${{env.GITHUB_ACTOR}}'" - git config --global user.name "${{env.GITHUB_ACTOR}}" + echo "GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}" + echo "OWNER: ${{ github.repository_owner }}" + echo "REPO: ${{ github.event.repository.name }}" + git config --global user.name "${{ env.GITHUB_ACTOR }}" # git push From 7aaaef52221d03830a7e18a8d63d57f5c5a94597 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 13:29:54 +0100 Subject: [PATCH 097/147] v0.12c: echo env variables --- .github/workflows/main.yml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 49bc1bb..1d95222 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -104,13 +104,15 @@ jobs: mv *png assets/. mv *svg assets/. cd assets - echo "${{ env.GITHUB_ACTOR }}" - echo "${{ env.GITHUB_SERVER_URL }}" - echo "${{ env.GITHUB_REPOSITORY }}" - echo "${{ env.GITHUB_SERVER_URL}}/${{env.GITHUB_REPOSITOR }}" - echo "git config --global user.name '${{env.GITHUB_ACTOR}}'" + echo "git config --global user.name '${{ github.actor }}'" echo "GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}" echo "OWNER: ${{ github.repository_owner }}" echo "REPO: ${{ github.event.repository.name }}" - git config --global user.name "${{ env.GITHUB_ACTOR }}" + echo "ACTOR: ${{ github.actor }}" + echo "HEAD_REF: ${{ github.head_ref }}" + echo "REF: ${{ github.ref }}" + echo "RUN_ID: ${{ github.run_id }}" + echo "GITHUB: ${{ github.repository_url }}" + echo "GITHUB: ${{ github }}" + git config --global user.name "${{ github.actor }}" # git push From d0ee62fa8417b185f4de299d6fefe588e59cac7a Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 13:35:29 +0100 Subject: [PATCH 098/147] v0.13: git push --- .github/workflows/main.yml | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1d95222..f1608eb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -104,15 +104,6 @@ jobs: mv *png assets/. mv *svg assets/. cd assets - echo "git config --global user.name '${{ github.actor }}'" - echo "GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}" - echo "OWNER: ${{ github.repository_owner }}" - echo "REPO: ${{ github.event.repository.name }}" - echo "ACTOR: ${{ github.actor }}" - echo "HEAD_REF: ${{ github.head_ref }}" - echo "REF: ${{ github.ref }}" - echo "RUN_ID: ${{ github.run_id }}" - echo "GITHUB: ${{ github.repository_url }}" - echo "GITHUB: ${{ github }}" + # echo "GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}" git config --global user.name "${{ github.actor }}" -# git push + git push From dd277e4c19d6e33f9df56c83d5be63e7d233ce0b Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 13:45:04 +0100 Subject: [PATCH 099/147] v0.13a: git push --- .github/workflows/main.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f1608eb..a00f471 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -100,10 +100,13 @@ jobs: with: name: build_examples - name: Move pictures and push + credentials: + username: ${{ github.ACTOR }} + password: ${{ secrets.GITHUB_TOKEN }} run: | mv *png assets/. mv *svg assets/. cd assets - # echo "GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}" git config --global user.name "${{ github.actor }}" +# git config --global url."git@github.com:".insteadOf "https://github.com/" git push From 21341aede863564af6286abd9563d9ca1f98d278 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 13:52:34 +0100 Subject: [PATCH 100/147] v0.13b: git push --- .github/workflows/main.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a00f471..1112c99 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -100,13 +100,14 @@ jobs: with: name: build_examples - name: Move pictures and push - credentials: - username: ${{ github.ACTOR }} - password: ${{ secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} +# username: ${{ github.ACTOR }} +# password: ${{ secrets.GITHUB_TOKEN }} run: | mv *png assets/. mv *svg assets/. cd assets - git config --global user.name "${{ github.actor }}" +# git config --global user.name "${{ github.actor }}" # git config --global url."git@github.com:".insteadOf "https://github.com/" git push From 0c89926d5f3a7dfeedfafdcba2937706e05c4ab0 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 13:55:18 +0100 Subject: [PATCH 101/147] v0.13c: git push --- .github/workflows/main.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1112c99..8c2cbf2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -91,6 +91,10 @@ jobs: python-version: [3.8] needs: "build" steps: + - with: + github-token: ${{ secrets.GITHUB_TOKEN }} +# username: ${{ github.ACTOR }} +# password: ${{ secrets.GITHUB_TOKEN }} - name: Git config run: git config --global core.autocrlf input - name: Git clone @@ -100,14 +104,10 @@ jobs: with: name: build_examples - name: Move pictures and push - with: - github-token: ${{ secrets.GITHUB_TOKEN }} -# username: ${{ github.ACTOR }} -# password: ${{ secrets.GITHUB_TOKEN }} run: | mv *png assets/. mv *svg assets/. cd assets + git push # git config --global user.name "${{ github.actor }}" # git config --global url."git@github.com:".insteadOf "https://github.com/" - git push From c8228208d0fb8908b6d4936fe4e2f78cf576ae2d Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 13:57:39 +0100 Subject: [PATCH 102/147] v0.13d: git push --- .github/workflows/main.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8c2cbf2..ca11106 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -91,10 +91,6 @@ jobs: python-version: [3.8] needs: "build" steps: - - with: - github-token: ${{ secrets.GITHUB_TOKEN }} -# username: ${{ github.ACTOR }} -# password: ${{ secrets.GITHUB_TOKEN }} - name: Git config run: git config --global core.autocrlf input - name: Git clone @@ -104,10 +100,14 @@ jobs: with: name: build_examples - name: Move pictures and push + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +# username: ${{ github.ACTOR }} +# password: ${{ secrets.GITHUB_TOKEN }} run: | mv *png assets/. mv *svg assets/. cd assets - git push # git config --global user.name "${{ github.actor }}" # git config --global url."git@github.com:".insteadOf "https://github.com/" + git push From 27f1e38926e0674a5a3f18096d02ba312467634d Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 13:58:46 +0100 Subject: [PATCH 103/147] v0.13e: git push --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ca11106..b7fdef3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -108,6 +108,6 @@ jobs: mv *png assets/. mv *svg assets/. cd assets -# git config --global user.name "${{ github.actor }}" -# git config --global url."git@github.com:".insteadOf "https://github.com/" + git config --global user.name "${{ github.actor }}" + git config --global url."git@github.com:".insteadOf "https://github.com/" git push From 60f8eb8b9babfe4589fa770e886ab53a02b22463 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 14:03:40 +0100 Subject: [PATCH 104/147] v0.13f: git push --- .github/workflows/main.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b7fdef3..e6e52e5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -101,13 +101,13 @@ jobs: name: build_examples - name: Move pictures and push env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -# username: ${{ github.ACTOR }} -# password: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + username: ${{ github.ACTOR }} + password: ${{ secrets.GITHUB_TOKEN }} run: | mv *png assets/. mv *svg assets/. cd assets git config --global user.name "${{ github.actor }}" - git config --global url."git@github.com:".insteadOf "https://github.com/" +# git config --global url."git@github.com:".insteadOf "https://github.com/" git push From b2e6e21a6a3c280a5b13222ce4703b01a6ef9480 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 14:04:08 +0100 Subject: [PATCH 105/147] v0.13g: git push --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e6e52e5..ee3e4fd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -109,5 +109,5 @@ jobs: mv *svg assets/. cd assets git config --global user.name "${{ github.actor }}" -# git config --global url."git@github.com:".insteadOf "https://github.com/" git push +# git config --global url."git@github.com:".insteadOf "https://github.com/" From 34768a5864cd49f65565f1fdfe4ea0216fff6fa3 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 14:11:51 +0100 Subject: [PATCH 106/147] v0.13h: git push --- .github/workflows/main.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ee3e4fd..78a6839 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -94,7 +94,7 @@ jobs: - name: Git config run: git config --global core.autocrlf input - name: Git clone - run: git clone https://github.com/lowtower/py-staticmaps --branch assets assets + run: git clone git@github.com:lowtower/py-staticmaps.git --branch assets assets - name: Download artifacts uses: actions/download-artifact@v3 with: @@ -102,12 +102,11 @@ jobs: - name: Move pictures and push env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - username: ${{ github.ACTOR }} - password: ${{ secrets.GITHUB_TOKEN }} + ACTOR: ${{ github.ACTOR }} run: | mv *png assets/. mv *svg assets/. cd assets - git config --global user.name "${{ github.actor }}" + git config --global user.name "${{ github.ACTOR }}" git push # git config --global url."git@github.com:".insteadOf "https://github.com/" From e5a2125f11dfc6d06a209bcdf1e6413edf700852 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 14:16:41 +0100 Subject: [PATCH 107/147] v0.13i: git push --- .github/workflows/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 78a6839..3239e0d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -90,6 +90,9 @@ jobs: os: [ubuntu-latest] python-version: [3.8] needs: "build" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ACTOR: ${{ github.ACTOR }} steps: - name: Git config run: git config --global core.autocrlf input @@ -100,9 +103,6 @@ jobs: with: name: build_examples - name: Move pictures and push - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - ACTOR: ${{ github.ACTOR }} run: | mv *png assets/. mv *svg assets/. From bb37cec801d22524a1377e786606c962adf7e92d Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 14:26:09 +0100 Subject: [PATCH 108/147] v0.13j: git push --- .github/workflows/main.yml | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3239e0d..c56b815 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -90,23 +90,37 @@ jobs: os: [ubuntu-latest] python-version: [3.8] needs: "build" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - ACTOR: ${{ github.ACTOR }} steps: - - name: Git config - run: git config --global core.autocrlf input + - uses: actions/checkout@v3 + - name: Setup SSH Keys and known_hosts + env: + SSH_AUTH_SOCK: /tmp/ssh_agent.sock + run: | + mkdir -p ~/.ssh + ssh-keyscan git.something >> ~/.ssh/known_hosts + ssh-agent -a ${{ env.SSH_AUTH_SOCK }} > /dev/null + ssh-add - <<< "${{ secrets.SSH_PRIVATE_KEY }}" + - name: Setup git and push to remote + env: + SSH_AUTH_SOCK: /tmp/ssh_agent.sock + run: | + git config --global core.autocrlf input + git config --global user.name "github-actions[bot]" +# git config --global user.name "${{ github.ACTOR }}" + git config --global user.email "${{ secrets.GH_MAIL }}" + git config --global url."git@github.com:".insteadOf "https://github.com/" - name: Git clone - run: git clone git@github.com:lowtower/py-staticmaps.git --branch assets assets + run: git clone https://github.com/lowtower/py-staticmaps --branch assets assets - name: Download artifacts uses: actions/download-artifact@v3 with: name: build_examples - name: Move pictures and push +# env: +# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +# ACTOR: ${{ github.ACTOR }} run: | mv *png assets/. mv *svg assets/. cd assets - git config --global user.name "${{ github.ACTOR }}" git push -# git config --global url."git@github.com:".insteadOf "https://github.com/" From dc88a8fe6dd8179b3fcf5e26feb0f48c441163ff Mon Sep 17 00:00:00 2001 From: lowtower Date: Thu, 8 Dec 2022 14:27:30 +0100 Subject: [PATCH 109/147] v0.13k: git push --- .github/workflows/main.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c56b815..dbd4cff 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -106,7 +106,6 @@ jobs: run: | git config --global core.autocrlf input git config --global user.name "github-actions[bot]" -# git config --global user.name "${{ github.ACTOR }}" git config --global user.email "${{ secrets.GH_MAIL }}" git config --global url."git@github.com:".insteadOf "https://github.com/" - name: Git clone @@ -116,9 +115,6 @@ jobs: with: name: build_examples - name: Move pictures and push -# env: -# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -# ACTOR: ${{ github.ACTOR }} run: | mv *png assets/. mv *svg assets/. From e5be5c2d50a0c602c202400f30587c6da92b57b1 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 14:34:02 +0100 Subject: [PATCH 110/147] v0.13l: git push --- .github/workflows/main.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index dbd4cff..f404784 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -92,12 +92,14 @@ jobs: needs: "build" steps: - uses: actions/checkout@v3 + with: + fetch-depth: 0 - name: Setup SSH Keys and known_hosts env: SSH_AUTH_SOCK: /tmp/ssh_agent.sock run: | mkdir -p ~/.ssh - ssh-keyscan git.something >> ~/.ssh/known_hosts + ssh-keyscan git.github.com >> ~/.ssh/known_hosts ssh-agent -a ${{ env.SSH_AUTH_SOCK }} > /dev/null ssh-add - <<< "${{ secrets.SSH_PRIVATE_KEY }}" - name: Setup git and push to remote From daa83cba24234cf65cd2a25ebb3443c1f5c4d242 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 14:43:52 +0100 Subject: [PATCH 111/147] v0.13m: git push --- .github/workflows/main.yml | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f404784..4ebbca3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -94,20 +94,14 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 0 - - name: Setup SSH Keys and known_hosts - env: - SSH_AUTH_SOCK: /tmp/ssh_agent.sock - run: | - mkdir -p ~/.ssh - ssh-keyscan git.github.com >> ~/.ssh/known_hosts - ssh-agent -a ${{ env.SSH_AUTH_SOCK }} > /dev/null - ssh-add - <<< "${{ secrets.SSH_PRIVATE_KEY }}" + - uses: fusion-engineering/setup-git-credentials@v2 + with: + credentials: ${{ secrets.GIT_CREDENTIAL }} - name: Setup git and push to remote - env: - SSH_AUTH_SOCK: /tmp/ssh_agent.sock run: | + export GIT_USER=${{ secrets.GIT_USER }} git config --global core.autocrlf input - git config --global user.name "github-actions[bot]" + git config --global user.name "$GIT_USER" git config --global user.email "${{ secrets.GH_MAIL }}" git config --global url."git@github.com:".insteadOf "https://github.com/" - name: Git clone @@ -122,3 +116,5 @@ jobs: mv *svg assets/. cd assets git push + env: + CI: True From 078f92baa90a56c66f96643fbf8efeb573fd3007 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 14:52:10 +0100 Subject: [PATCH 112/147] v0.13n: git push --- .github/workflows/main.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4ebbca3..f16c088 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -99,6 +99,11 @@ jobs: credentials: ${{ secrets.GIT_CREDENTIAL }} - name: Setup git and push to remote run: | + echo "${{ secrets.UPLOAD_EXAMPLES }}" + echo "${{ secrets.GIT_CREDENTIAL }}" + echo "${{ secrets.GIT_USER }}" + echo "${{ secrets.GH_USER }}" + echo "${{ secrets.GH_MAIL }}" export GIT_USER=${{ secrets.GIT_USER }} git config --global core.autocrlf input git config --global user.name "$GIT_USER" From 2076394cabc9420515080fb73d91e00a2f1f2191 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 14:56:47 +0100 Subject: [PATCH 113/147] v0.13o: git push --- .github/workflows/main.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f16c088..0d68352 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -94,9 +94,6 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 0 - - uses: fusion-engineering/setup-git-credentials@v2 - with: - credentials: ${{ secrets.GIT_CREDENTIAL }} - name: Setup git and push to remote run: | echo "${{ secrets.UPLOAD_EXAMPLES }}" From 423132e845b5081f3695f2aad34959b0b23f26ea Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 15:06:49 +0100 Subject: [PATCH 114/147] v0.13p: git push --- .github/workflows/main.yml | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0d68352..0a2e492 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -91,21 +91,24 @@ jobs: python-version: [3.8] needs: "build" steps: + - env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ACTOR: ${{ github.actor }} + OWNER: ${{ github.repository_owner }} + REPO: ${{ github.event.repository.name }} - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Setup git and push to remote run: | echo "${{ secrets.UPLOAD_EXAMPLES }}" - echo "${{ secrets.GIT_CREDENTIAL }}" - echo "${{ secrets.GIT_USER }}" - echo "${{ secrets.GH_USER }}" - echo "${{ secrets.GH_MAIL }}" - export GIT_USER=${{ secrets.GIT_USER }} + echo "${{ secrets.GITHUB_TOKEN }}" + echo "${{ github.actor }}" + echo "${{ github.repository_owner }}" + echo "${{ github.event.repository.name }}" + export GIT_USER=${{ github.actor }} git config --global core.autocrlf input - git config --global user.name "$GIT_USER" - git config --global user.email "${{ secrets.GH_MAIL }}" - git config --global url."git@github.com:".insteadOf "https://github.com/" + git config --global user.name "${{ github.actor }}" - name: Git clone run: git clone https://github.com/lowtower/py-staticmaps --branch assets assets - name: Download artifacts From 4bc93bcfe9c117ded7aceed5527e36eeb487ba4e Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 15:10:13 +0100 Subject: [PATCH 115/147] v0.13q: git push --- .github/workflows/main.yml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0a2e492..6d39977 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -91,15 +91,15 @@ jobs: python-version: [3.8] needs: "build" steps: - - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - ACTOR: ${{ github.actor }} - OWNER: ${{ github.repository_owner }} - REPO: ${{ github.event.repository.name }} - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Setup git and push to remote + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ACTOR: ${{ github.actor }} + OWNER: ${{ github.repository_owner }} + REPO: ${{ github.event.repository.name }} run: | echo "${{ secrets.UPLOAD_EXAMPLES }}" echo "${{ secrets.GITHUB_TOKEN }}" @@ -121,5 +121,3 @@ jobs: mv *svg assets/. cd assets git push - env: - CI: True From cf3f2fd8b0cf222789eb851ae5a027b06e80475c Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 15:16:49 +0100 Subject: [PATCH 116/147] v0.13r: git push --- .github/workflows/main.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6d39977..954f052 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -95,17 +95,7 @@ jobs: with: fetch-depth: 0 - name: Setup git and push to remote - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - ACTOR: ${{ github.actor }} - OWNER: ${{ github.repository_owner }} - REPO: ${{ github.event.repository.name }} run: | - echo "${{ secrets.UPLOAD_EXAMPLES }}" - echo "${{ secrets.GITHUB_TOKEN }}" - echo "${{ github.actor }}" - echo "${{ github.repository_owner }}" - echo "${{ github.event.repository.name }}" export GIT_USER=${{ github.actor }} git config --global core.autocrlf input git config --global user.name "${{ github.actor }}" @@ -116,7 +106,17 @@ jobs: with: name: build_examples - name: Move pictures and push + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ACTOR: ${{ github.actor }} + OWNER: ${{ github.repository_owner }} + REPO: ${{ github.event.repository.name }} run: | + echo "${{ secrets.UPLOAD_EXAMPLES }}" + echo "${{ secrets.GITHUB_TOKEN }}" + echo "${{ github.actor }}" + echo "${{ github.repository_owner }}" + echo "${{ github.event.repository.name }}" mv *png assets/. mv *svg assets/. cd assets From e373ec51d0721576eb33ee6a776afa1451e1aef8 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 15:36:04 +0100 Subject: [PATCH 117/147] v0.13s: git push --- .github/workflows/main.yml | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 954f052..693f268 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -105,19 +105,17 @@ jobs: uses: actions/download-artifact@v3 with: name: build_examples - - name: Move pictures and push - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - ACTOR: ${{ github.actor }} - OWNER: ${{ github.repository_owner }} - REPO: ${{ github.event.repository.name }} + - name: Move pictures and commit run: | - echo "${{ secrets.UPLOAD_EXAMPLES }}" - echo "${{ secrets.GITHUB_TOKEN }}" - echo "${{ github.actor }}" - echo "${{ github.repository_owner }}" - echo "${{ github.event.repository.name }}" - mv *png assets/. - mv *svg assets/. + mv *.png assets/. + mv *.svg assets/. cd assets - git push + git add *.png + git add *.svg + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git commit -a -m "Automatic update of example image files" + - name: Push changes + uses: ad-m/github-push-action@master + with: + branch: "assets" From 2c87c63ab8496f6ac2221528c929ece2ae551971 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 8 Dec 2022 15:41:32 +0100 Subject: [PATCH 118/147] v0.13t: git push --- .github/workflows/main.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 693f268..a0ead77 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -98,7 +98,6 @@ jobs: run: | export GIT_USER=${{ github.actor }} git config --global core.autocrlf input - git config --global user.name "${{ github.actor }}" - name: Git clone run: git clone https://github.com/lowtower/py-staticmaps --branch assets assets - name: Download artifacts @@ -117,5 +116,3 @@ jobs: git commit -a -m "Automatic update of example image files" - name: Push changes uses: ad-m/github-push-action@master - with: - branch: "assets" From 7f72dd80b6be49c2aabb4a48824aa1859293d03c Mon Sep 17 00:00:00 2001 From: lowtower Date: Thu, 8 Dec 2022 16:36:11 +0100 Subject: [PATCH 119/147] Feature/GitHub workflow cairo (#5) * v0.13u: git push * v0.13v: git push * v0.13w: git push * v0.13x: git push only if on main branch * v0.13y: update README.md with new example pictures * v0.13z: build job only if pushed on main branch, activate full matrix Co-authored-by: Lowtower --- .github/workflows/main.yml | 23 ++++++++++++++++------- README.md | 15 ++++++++++----- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a0ead77..990131e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,10 +8,8 @@ jobs: strategy: max-parallel: 4 matrix: -# os: [ubuntu-latest, macos-latest, windows-latest] -# python-version: ["3.7", "3.8", "3.9", "3.10"] - os: [ubuntu-latest] - python-version: ["3.8"] + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ["3.7", "3.8", "3.9", "3.10"] steps: - name: Git config run: git config --global core.autocrlf input @@ -46,6 +44,7 @@ jobs: os: [ubuntu-latest] python-version: [3.8] needs: "lint_and_test" + if: github.ref == 'refs/heads/main' steps: - name: Install cairo run: sudo apt-get install libcairo2-dev @@ -90,6 +89,7 @@ jobs: os: [ubuntu-latest] python-version: [3.8] needs: "build" + if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v3 with: @@ -109,10 +109,19 @@ jobs: mv *.png assets/. mv *.svg assets/. cd assets - git add *.png - git add *.svg git config --local user.email "github-actions[bot]@users.noreply.github.com" git config --local user.name "github-actions[bot]" - git commit -a -m "Automatic update of example image files" + git fetch + git pull + git add *.png + git add *.svg + git commit -m "Automatic update of example image files" + git status + pwd + ls -lrt - name: Push changes uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + directory: assets + branch: assets diff --git a/README.md b/README.md index 9ea05e7..46c8bc4 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ with open("frankfurt_newyork.svg", "w", encoding="utf-8") as f: svg_image.write(f, pretty=True) ``` -![franfurt_newyork](../assets/frankfurt_newyork.png?raw=true) +![franfurt_newyork](../assets/frankfurt_newyork.cairo.png?raw=true) ### Transparent Polygons @@ -110,7 +110,7 @@ with open("freiburg_area.svg", "w", encoding="utf-8") as f: svg_image.write(f, pretty=True) ``` -![freiburg_area](../assets/freiburg_area.png?raw=true) +![freiburg_area](../assets/freiburg_area.cairo.png?raw=true) ### Drawing a GPX Track + Image Marker (PNG) @@ -147,7 +147,7 @@ image = context.render_cairo(800, 500) image.write_to_png("draw_gpx.cairo.png") ``` -![draw_gpx](../assets/draw_gpx.png?raw=true) +![draw_gpx](../assets/draw_gpx.cairo.png?raw=true) ### US State Capitals @@ -178,7 +178,7 @@ image = context.render_cairo(800, 500) image.write_to_png("us_capitals.cairo.png") ``` -![us_capitals](../assets/us_capitals.png?raw=true) +![us_capitals](../assets/us_capitals.cairo.png?raw=true) ### Geodesic Circles @@ -206,7 +206,12 @@ image = context.render_cairo(800, 600) image.write_to_png("geodesic_circles.cairo.png") ``` -![geodesic_circles](../assets/geodesic_circles.png?raw=true) +#### Cairo example +![geodesic_circles](../assets/geodesic_circles.cairo.png?raw=true) +#### Pillow example +![geodesic_circles_pillow](../assets/geodesic_circles.pillow.png?raw=true) +#### SVG example +![geodesic_circles_svg](../assets/geodesic_circles.svg?raw=true) ### Other Examples From 106f64d8f9db954382a1b1013ad240c0a40afaae Mon Sep 17 00:00:00 2001 From: Lowtower Date: Fri, 3 Feb 2023 12:27:29 +0100 Subject: [PATCH 120/147] make setup working again by ignoring "--require" in requirements-dev.txt --- setup.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 77bcd7c..ca0be7d 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,11 @@ def _read_descr(rel_path: str) -> str: def _read_reqs(rel_path: str) -> typing.List[str]: abs_path = os.path.join(os.path.dirname(__file__), rel_path) with open(abs_path, encoding="utf-8") as f: - return [s.strip() for s in f.readlines() if s.strip() and not s.strip().startswith("#")] + return [ + s.strip() + for s in f.readlines() + if s.strip() and not s.strip().startswith("#") and not s.strip().startswith("--requirement") + ] PACKAGE = "staticmaps" From 9ca00f5c22fbdae73a329b1941b5151a4366dca4 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Fri, 3 Feb 2023 12:52:16 +0100 Subject: [PATCH 121/147] drop python v3.7 support --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 990131e..6b87543 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,7 +9,7 @@ jobs: max-parallel: 4 matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ["3.7", "3.8", "3.9", "3.10"] + python-version: ["3.8", "3.9", "3.10"] steps: - name: Git config run: git config --global core.autocrlf input From cdefc860ed1cc018e34814accc4f798b9ecee5a3 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Fri, 3 Feb 2023 12:53:40 +0100 Subject: [PATCH 122/147] add python v3.11 support --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6b87543..9263b6d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,7 +9,7 @@ jobs: max-parallel: 4 matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ["3.8", "3.9", "3.10"] + python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - name: Git config run: git config --global core.autocrlf input From 57aac717a50089fa924c4d2704878b0c77ec64f1 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Fri, 3 Feb 2023 12:53:40 +0100 Subject: [PATCH 123/147] add python v3.11 support --- .github/workflows/main.yml | 2 +- setup.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6b87543..9263b6d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,7 +9,7 @@ jobs: max-parallel: 4 matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ["3.8", "3.9", "3.10"] + python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - name: Git config run: git config --global core.autocrlf input diff --git a/setup.py b/setup.py index ca0be7d..8e1a677 100644 --- a/setup.py +++ b/setup.py @@ -60,10 +60,11 @@ def _read_reqs(rel_path: str) -> typing.List[str]: "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", # "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", + # "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", ], keywords="map staticmap osm markers", packages=[PACKAGE], From e226b4bba6424e5f5774ada59e270b6457454095 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Tue, 18 Jul 2023 13:32:06 +0200 Subject: [PATCH 124/147] ImageDraw.textlength() instead of deprecated method ImageDraw.textsize() --- staticmaps/pillow_renderer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/staticmaps/pillow_renderer.py b/staticmaps/pillow_renderer.py index d93d4ec..da1a0b1 100644 --- a/staticmaps/pillow_renderer.py +++ b/staticmaps/pillow_renderer.py @@ -136,7 +136,7 @@ def render_attribution(self, attribution: typing.Optional[str]) -> None: if (attribution is None) or (attribution == ""): return margin = 2 - _, th = self.draw().textsize(attribution) + _, th = self.draw().textlength(attribution) w = self._trans.image_width() h = self._trans.image_height() overlay = PIL_Image.new("RGBA", self._image.size, (255, 255, 255, 0)) From 5dbebabd0f193eee6b10022fbe2abd15b2678203 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Tue, 18 Jul 2023 14:48:19 +0200 Subject: [PATCH 125/147] use ImageDraw.textbbox() to retrieve the textbox height --- staticmaps/pillow_renderer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/staticmaps/pillow_renderer.py b/staticmaps/pillow_renderer.py index da1a0b1..0a8c801 100644 --- a/staticmaps/pillow_renderer.py +++ b/staticmaps/pillow_renderer.py @@ -136,9 +136,10 @@ def render_attribution(self, attribution: typing.Optional[str]) -> None: if (attribution is None) or (attribution == ""): return margin = 2 - _, th = self.draw().textlength(attribution) w = self._trans.image_width() h = self._trans.image_height() + left, top, right, bottom = self.draw().textbbox((margin, h - margin), attribution) + th = bottom - top overlay = PIL_Image.new("RGBA", self._image.size, (255, 255, 255, 0)) draw = PIL_ImageDraw.Draw(overlay) draw.rectangle(((0, h - th - 2 * margin), (w, h)), fill=(255, 255, 255, 204)) From 2eca499a6b1e1fc0db41ab714058964c9b30edca Mon Sep 17 00:00:00 2001 From: Lowtower Date: Tue, 18 Jul 2023 15:13:53 +0200 Subject: [PATCH 126/147] improvement in run-examples --- Makefile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 8dcdd4b..591ae1c 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,7 @@ clean: ## Cleanup @rm -rf $(SRC_TEST)/__pycache__ @rm -f $(SRC_EXAMPLES)/*.pyc @rm -rf $(SRC_EXAMPLES)/__pycache__ + @rm -rf $(SRC_EXAMPLES)/build @rm -rf ./.coverage @rm -rf ./coverage.xml @rm -rf ./.pytest_cache @@ -87,10 +88,10 @@ run-examples: ## Generate example images (cd examples && PYTHONPATH=.. ../.env/bin/python geodesic_circles.py) (cd examples && PYTHONPATH=.. ../.env/bin/python tile_providers.py) (cd examples && PYTHONPATH=.. ../.env/bin/python us_capitals.py) - (cd examples && mv *.svg build/.) - (cd examples && mv *pillow*png build/.) - (cd examples && mv *cairo*png build/.) - (cd -) + (cd examples && mkdir -p build) + (cd examples && ls *.svg 2>/dev/null && mv *.svg build/.) || echo "no svg files found!" + (cd examples && ls *pillow*.png 2>/dev/null && mv *pillow*.png build/.) || echo "no pillow png files found!" + (cd examples && ls *cairo*.png 2>/dev/null && mv *cairo*.png build/.) || echo "no cairo png files found!" .PHONY: test test: ## Test the code From 5c4be45a23ef2e481a135b34aab53ab4dd792393 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Wed, 19 Jul 2023 16:21:53 +0200 Subject: [PATCH 127/147] changes for rendering svg images with tightening to object boundaries --- Makefile | 9 ++--- README.md | 10 ++++++ examples/custom_objects.py | 6 ++++ examples/draw_gpx.py | 6 ++++ examples/frankfurt_newyork.py | 35 +++++++++++++++++-- examples/geodesic_circles.py | 6 ++++ examples/tile_providers.py | 7 ++++ staticmaps/cairo_renderer.py | 32 ++++++++--------- staticmaps/context.py | 17 ++++----- staticmaps/line.py | 2 +- staticmaps/marker.py | 28 ++++++++++++--- staticmaps/object.py | 14 ++++++++ staticmaps/pillow_renderer.py | 25 +++++++------- staticmaps/renderer.py | 37 ++++++++++++++------ staticmaps/svg_renderer.py | 65 +++++++++++++++++++---------------- 15 files changed, 207 insertions(+), 92 deletions(-) diff --git a/Makefile b/Makefile index 8dcdd4b..591ae1c 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,7 @@ clean: ## Cleanup @rm -rf $(SRC_TEST)/__pycache__ @rm -f $(SRC_EXAMPLES)/*.pyc @rm -rf $(SRC_EXAMPLES)/__pycache__ + @rm -rf $(SRC_EXAMPLES)/build @rm -rf ./.coverage @rm -rf ./coverage.xml @rm -rf ./.pytest_cache @@ -87,10 +88,10 @@ run-examples: ## Generate example images (cd examples && PYTHONPATH=.. ../.env/bin/python geodesic_circles.py) (cd examples && PYTHONPATH=.. ../.env/bin/python tile_providers.py) (cd examples && PYTHONPATH=.. ../.env/bin/python us_capitals.py) - (cd examples && mv *.svg build/.) - (cd examples && mv *pillow*png build/.) - (cd examples && mv *cairo*png build/.) - (cd -) + (cd examples && mkdir -p build) + (cd examples && ls *.svg 2>/dev/null && mv *.svg build/.) || echo "no svg files found!" + (cd examples && ls *pillow*.png 2>/dev/null && mv *pillow*.png build/.) || echo "no pillow png files found!" + (cd examples && ls *cairo*.png 2>/dev/null && mv *cairo*.png build/.) || echo "no cairo png files found!" .PHONY: test test: ## Test the code diff --git a/README.md b/README.md index 46c8bc4..59321ca 100644 --- a/README.md +++ b/README.md @@ -67,10 +67,20 @@ image.write_to_png("frankfurt_newyork.cairo.png") svg_image = context.render_svg(800, 500) with open("frankfurt_newyork.svg", "w", encoding="utf-8") as f: svg_image.write(f, pretty=True) + +# render svg - tight boundaries +context.set_tighten_to_bounds(True) +svg_image = context.render_svg(800, 500) +with open("frankfurt_newyork.tight.svg", "w", encoding="utf-8") as f: + svg_image.write(f, pretty=True) ``` ![franfurt_newyork](../assets/frankfurt_newyork.cairo.png?raw=true) +![franfurt_newyork](../assets/frankfurt_newyork.svg?raw=true) + +![franfurt_newyork](../assets/frankfurt_newyork.tight.svg?raw=true) + ### Transparent Polygons diff --git a/examples/custom_objects.py b/examples/custom_objects.py index 03e7476..1110e7b 100644 --- a/examples/custom_objects.py +++ b/examples/custom_objects.py @@ -199,3 +199,9 @@ def render_svg(self, renderer: staticmaps.SvgRenderer) -> None: svg_image = context.render_svg(800, 500) with open("custom_objects.svg", "w", encoding="utf-8") as f: svg_image.write(f, pretty=True) + +# render svg - tight boundaries +context.set_tighten_to_bounds(True) +svg_image = context.render_svg(800, 500) +with open("custom_objects.tight.svg", "w", encoding="utf-8") as f: + svg_image.write(f, pretty=True) diff --git a/examples/draw_gpx.py b/examples/draw_gpx.py index 3e99497..04ff3f5 100644 --- a/examples/draw_gpx.py +++ b/examples/draw_gpx.py @@ -40,3 +40,9 @@ svg_image = context.render_svg(800, 500) with open("running.svg", "w", encoding="utf-8") as f: svg_image.write(f, pretty=True) + +# render svg - tight boundaries +context.set_tighten_to_bounds(True) +svg_image = context.render_svg(800, 500) +with open("running.tight.svg", "w", encoding="utf-8") as f: + svg_image.write(f, pretty=True) diff --git a/examples/frankfurt_newyork.py b/examples/frankfurt_newyork.py index d857da0..0bf7ce8 100644 --- a/examples/frankfurt_newyork.py +++ b/examples/frankfurt_newyork.py @@ -3,6 +3,8 @@ """py-staticmaps - Example Frankfurt-New York""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information +import s2sphere # type: ignore + import staticmaps context = staticmaps.Context() @@ -12,8 +14,26 @@ newyork = staticmaps.create_latlng(40.712728, -74.006015) context.add_object(staticmaps.Line([frankfurt, newyork], color=staticmaps.BLUE, width=4)) -context.add_object(staticmaps.Marker(frankfurt, color=staticmaps.GREEN, size=12)) -context.add_object(staticmaps.Marker(newyork, color=staticmaps.RED, size=12)) +context.add_object(staticmaps.Marker(frankfurt, color=staticmaps.BLUE, size=20)) +context.add_object(staticmaps.Marker(newyork, color=staticmaps.GREEN, size=12, stroke_width=2)) + +# TODOX: remove testing code + +bbox = context.object_bounds() +assert bbox +context.add_object(staticmaps.Line([bbox.lo(), bbox.hi()], width=1)) +line = staticmaps.Line( + [ + s2sphere.LatLng.from_angles(bbox.lat_lo(), bbox.lng_lo()), + s2sphere.LatLng.from_angles(bbox.lat_lo(), bbox.lng_hi()), + s2sphere.LatLng.from_angles(bbox.lat_hi(), bbox.lng_hi()), + s2sphere.LatLng.from_angles(bbox.lat_hi(), bbox.lng_lo()), + s2sphere.LatLng.from_angles(bbox.lat_lo(), bbox.lng_lo()), + ], + staticmaps.GREEN, + width=1, +) +context.add_object(line) # render png via pillow image = context.render_pillow(800, 500) @@ -29,6 +49,17 @@ with open("frankfurt_newyork.svg", "w", encoding="utf-8") as f: svg_image.write(f, pretty=True) +# render png via pillow - tight boundaries +context.set_tighten_to_bounds(True) +image = context.render_pillow(800, 500) +image.save("frankfurt_newyork.tight.pillow.png") + +# render png via cairo - tight boundaries +if staticmaps.cairo_is_supported(): + context.set_tighten_to_bounds(True) + image = context.render_cairo(800, 500) + image.write_to_png("frankfurt_newyork.tight.cairo.png") + # render svg - tight boundaries context.set_tighten_to_bounds(True) svg_image = context.render_svg(800, 500) diff --git a/examples/geodesic_circles.py b/examples/geodesic_circles.py index b768be3..d6359cb 100644 --- a/examples/geodesic_circles.py +++ b/examples/geodesic_circles.py @@ -29,3 +29,9 @@ svg_image = context.render_svg(800, 600) with open("geodesic_circles.svg", "w", encoding="utf-8") as f: svg_image.write(f, pretty=True) + +# render svg - tight boundaries +context.set_tighten_to_bounds(True) +svg_image = context.render_svg(800, 500) +with open("geodesic_circles.tight.svg", "w", encoding="utf-8") as f: + svg_image.write(f, pretty=True) diff --git a/examples/tile_providers.py b/examples/tile_providers.py index 03b8853..8dea113 100644 --- a/examples/tile_providers.py +++ b/examples/tile_providers.py @@ -29,6 +29,13 @@ image.write_to_png(f"provider_{name}.cairo.png") # render svg + context.set_tighten_to_bounds() svg_image = context.render_svg(800, 500) with open(f"provider_{name}.svg", "w", encoding="utf-8") as f: svg_image.write(f, pretty=True) + + # render svg - tight boundaries + context.set_tighten_to_bounds(True) + svg_image = context.render_svg(800, 500) + with open(f"provider_{name}.tight.svg", "w", encoding="utf-8") as f: + svg_image.write(f, pretty=True) diff --git a/staticmaps/cairo_renderer.py b/staticmaps/cairo_renderer.py index 4f4448e..bee01fd 100644 --- a/staticmaps/cairo_renderer.py +++ b/staticmaps/cairo_renderer.py @@ -1,4 +1,6 @@ -"""py-staticmaps cairo_renderer""" +"""py-staticmaps CairoRenderer""" + +# py-staticmaps # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import io @@ -6,7 +8,7 @@ import sys import typing -import s2sphere # type: ignore +# import s2sphere # type: ignore try: import cairo # type: ignore @@ -86,15 +88,13 @@ def create_image(image_data: bytes) -> cairo_ImageSurface: def render_objects( self, objects: typing.List["Object"], - bbox: typing.Optional[s2sphere.LatLngRect] = None, - epb: typing.Optional[typing.Tuple[int, int, int, int]] = None, + tighten: bool, ) -> None: """Render all objects of static map Parameters: objects (typing.List["Object"]): objects of static map - bbox (s2sphere.LatLngRect): boundary box of all objects - epb (typing.Tuple[int, int, int, int]): extra pixel bounds + tighten (bool): tighten to boundaries """ x_count = math.ceil(self._trans.image_width() / (2 * self._trans.world_width())) for obj in objects: @@ -119,16 +119,15 @@ def render_background(self, color: typing.Optional[Color]) -> None: def render_tiles( self, download: typing.Callable[[int, int, int], typing.Optional[bytes]], - bbox: typing.Optional[s2sphere.LatLngRect] = None, - epb: typing.Optional[typing.Tuple[int, int, int, int]] = None, + objects: typing.List["Object"], + tighten: bool, ) -> None: - """Render background of static map + """Render tiles of static map Parameters: - download (typing.Callable[[int, int, int], typing.Optional[bytes]]): - url of tiles provider - bbox (s2sphere.LatLngRect): boundary box of all objects - epb (typing.Tuple[int, int, int, int]): extra pixel bounds + download (typing.Callable[[int, int, int], typing.Optional[bytes]]): url of tiles provider + objects (typing.List["Object"]): objects of static map + tighten (bool): tighten to boundaries """ for yy in range(0, self._trans.tiles_y()): y = self._trans.first_tile_y() + yy @@ -142,8 +141,8 @@ def render_tiles( continue self._context.save() self._context.translate( - xx * self._trans.tile_size() + self._trans.tile_offset_x(), - yy * self._trans.tile_size() + self._trans.tile_offset_y(), + int(xx * self._trans.tile_size() + self._trans.tile_offset_x()), + int(yy * self._trans.tile_size() + self._trans.tile_offset_y()), ) self._context.set_source_surface(tile_img) self._context.paint() @@ -155,8 +154,7 @@ def render_attribution(self, attribution: typing.Optional[str]) -> None: """Render attribution from given tiles provider Parameters: - attribution (typing.Optional[str]:): Attribution for the - given tiles provider + attribution (typing.Optional[str]:): Attribution for the given tiles provider """ if (attribution is None) or (attribution == ""): return diff --git a/staticmaps/context.py b/staticmaps/context.py index 7006978..927b65a 100644 --- a/staticmaps/context.py +++ b/staticmaps/context.py @@ -164,8 +164,8 @@ def render_cairo(self, width: int, height: int) -> typing.Any: renderer = CairoRenderer(trans) renderer.render_background(self._background_color) - renderer.render_tiles(self._fetch_tile) - renderer.render_objects(self._objects) + renderer.render_tiles(self._fetch_tile, self._objects, self._tighten_to_bounds) + renderer.render_objects(self._objects, self._tighten_to_bounds) renderer.render_attribution(self._tile_provider.attribution()) return renderer.image_surface() @@ -191,8 +191,8 @@ def render_pillow(self, width: int, height: int) -> PIL_Image: renderer = PillowRenderer(trans) renderer.render_background(self._background_color) - renderer.render_tiles(self._fetch_tile) - renderer.render_objects(self._objects) + renderer.render_tiles(self._fetch_tile, self._objects, self._tighten_to_bounds) + renderer.render_objects(self._objects, self._tighten_to_bounds) renderer.render_attribution(self._tile_provider.attribution()) return renderer.image() @@ -215,16 +215,11 @@ def render_svg(self, width: int, height: int) -> svgwrite.Drawing: raise RuntimeError("Cannot render map without center/zoom.") trans = Transformer(width, height, zoom, center, self._tile_provider.tile_size()) - bbox = None - epb = None - if self._tighten_to_bounds: - bbox = self.object_bounds() - epb = self.extra_pixel_bounds() renderer = SvgRenderer(trans) renderer.render_background(self._background_color) - renderer.render_tiles(self._fetch_tile, bbox, epb) - renderer.render_objects(self._objects, bbox, epb) + renderer.render_tiles(self._fetch_tile, self._objects, self._tighten_to_bounds) + renderer.render_objects(self._objects, self._tighten_to_bounds) renderer.render_attribution(self._tile_provider.attribution()) return renderer.drawing() diff --git a/staticmaps/line.py b/staticmaps/line.py index d3de6d6..5a4033d 100644 --- a/staticmaps/line.py +++ b/staticmaps/line.py @@ -65,7 +65,7 @@ def extra_pixel_bounds(self) -> PixelBoundsT: Returns: PixelBoundsT: extra pixel bounds """ - return self._width, self._width, self._width, self._width + return int(0.5 * self._width), int(0.5 * self._width), int(0.5 * self._width), int(0.5 * self._width) def interpolate(self) -> typing.List[s2sphere.LatLng]: """Interpolate bounds diff --git a/staticmaps/marker.py b/staticmaps/marker.py index 31bd150..dd27119 100644 --- a/staticmaps/marker.py +++ b/staticmaps/marker.py @@ -14,14 +14,19 @@ class Marker(Object): """ - Marker A marker object + Marker A marker object. + The given parameter size is the radius of the rounded head of the marker + The final marker object size is: + width = 2 * size + height = 3 * size """ - def __init__(self, latlng: s2sphere.LatLng, color: Color = RED, size: int = 10) -> None: + def __init__(self, latlng: s2sphere.LatLng, color: Color = RED, size: int = 10, stroke_width: int = 1) -> None: Object.__init__(self) self._latlng = latlng self._color = color self._size = size + self._stroke_width = stroke_width def latlng(self) -> s2sphere.LatLng: """Return LatLng of the marker @@ -47,13 +52,21 @@ def size(self) -> int: """ return self._size + def stroke_width(self) -> int: + """Return stroke width of the marker + + Returns: + int: stroke width of the marker + """ + return self._stroke_width + def bounds(self) -> s2sphere.LatLngRect: """Return bounds of the marker Returns: s2sphere.LatLngRect: bounds of the marker """ - return s2sphere.LatLngRect.from_point(self._latlng) + return s2sphere.LatLngRect.from_point(self.latlng()) def extra_pixel_bounds(self) -> PixelBoundsT: """Return extra pixel bounds of the marker @@ -61,7 +74,12 @@ def extra_pixel_bounds(self) -> PixelBoundsT: Returns: PixelBoundsT: extra pixel bounds of the marker """ - return self._size, self._size, self._size, 0 + return ( + int(self.size() + 0.5 * self.stroke_width()), + int(3 * self.size() + 0.5 * self.stroke_width()), + int(self.size() + 0.5 * self.stroke_width()), + int(0.5 * self.stroke_width()), + ) def render_pillow(self, renderer: PillowRenderer) -> None: """Render marker using PILLOW @@ -103,7 +121,7 @@ def render_svg(self, renderer: SvgRenderer) -> None: path = renderer.drawing().path( fill=self.color().hex_rgb(), stroke=self.color().text_color().hex_rgb(), - stroke_width=1, + stroke_width=self.stroke_width(), opacity=self.color().float_a(), ) path.push(f"M {x} {y}") diff --git a/staticmaps/object.py b/staticmaps/object.py index d6b7305..b842d43 100644 --- a/staticmaps/object.py +++ b/staticmaps/object.py @@ -99,3 +99,17 @@ def pixel_rect(self, trans: Transformer) -> typing.Tuple[float, float, float, fl nw_x, nw_y = trans.ll2pixel(bounds.get_vertex(3)) l, t, r, b = self.extra_pixel_bounds() return nw_x - l, nw_y - t, se_x + r, se_y + b + + def bounds_epb(self, trans: Transformer) -> s2sphere.LatLngRect: + """Return the object bounds including extra pixel bounds of the object when using the supplied Transformer. + + Parameters: + trans (Transformer): transformer + + Returns: + s2sphere.LatLngRect: bounds of object + """ + pixel_bounds = self.pixel_rect(trans) + return s2sphere.LatLngRect.from_point_pair( + trans.pixel2ll(pixel_bounds[0], pixel_bounds[1]), trans.pixel2ll(pixel_bounds[2], pixel_bounds[3]) + ) diff --git a/staticmaps/pillow_renderer.py b/staticmaps/pillow_renderer.py index d93d4ec..cba02e8 100644 --- a/staticmaps/pillow_renderer.py +++ b/staticmaps/pillow_renderer.py @@ -1,11 +1,13 @@ -"""py-staticmaps pillow_renderer""" +"""py-staticmaps PillowRenderer""" + +# py-staticmaps # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import io import math import typing -import s2sphere # type: ignore +# import s2sphere # type: ignore from PIL import Image as PIL_Image # type: ignore from PIL import ImageDraw as PIL_ImageDraw # type: ignore @@ -68,15 +70,13 @@ def alpha_compose(self, image: PIL_Image.Image) -> None: def render_objects( self, objects: typing.List["Object"], - bbox: typing.Optional[s2sphere.LatLngRect] = None, - epb: typing.Optional[typing.Tuple[int, int, int, int]] = None, + tighten: bool, ) -> None: """Render all objects of static map Parameters: objects (typing.List["Object"]): objects of static map - bbox (s2sphere.LatLngRect): boundary box of all objects - epb (typing.Tuple[int, int, int, int]): extra pixel bounds + tighten (bool): tighten to boundaries """ x_count = math.ceil(self._trans.image_width() / (2 * self._trans.world_width())) for obj in objects: @@ -97,15 +97,15 @@ def render_background(self, color: typing.Optional[Color]) -> None: def render_tiles( self, download: typing.Callable[[int, int, int], typing.Optional[bytes]], - bbox: typing.Optional[s2sphere.LatLngRect] = None, - epb: typing.Optional[typing.Tuple[int, int, int, int]] = None, + objects: typing.List["Object"], + tighten: bool, ) -> None: - """Render background of static map + """Render tiles of static map Parameters: download (typing.Callable[[int, int, int], typing.Optional[bytes]]): url of tiles provider - bbox (s2sphere.LatLngRect): boundary box of all objects - epb (typing.Tuple[int, int, int, int]): extra pixel bounds + objects (typing.List["Object"]): objects of static map + tighten (bool): tighten to boundaries """ for yy in range(0, self._trans.tiles_y()): y = self._trans.first_tile_y() + yy @@ -136,9 +136,10 @@ def render_attribution(self, attribution: typing.Optional[str]) -> None: if (attribution is None) or (attribution == ""): return margin = 2 - _, th = self.draw().textsize(attribution) w = self._trans.image_width() h = self._trans.image_height() + left, top, right, bottom = self.draw().textbbox((margin, h - margin), attribution) + th = bottom - top overlay = PIL_Image.new("RGBA", self._image.size, (255, 255, 255, 0)) draw = PIL_ImageDraw.Draw(overlay) draw.rectangle(((0, h - th - 2 * margin), (w, h)), fill=(255, 255, 255, 204)) diff --git a/staticmaps/renderer.py b/staticmaps/renderer.py index efecd4d..4587425 100644 --- a/staticmaps/renderer.py +++ b/staticmaps/renderer.py @@ -36,15 +36,13 @@ def transformer(self) -> Transformer: def render_objects( self, objects: typing.List["Object"], - bbox: s2sphere.LatLngRect, - epb: typing.Optional[typing.Tuple[int, int, int, int]] = None, + tighten: bool, ) -> None: """Render all objects of static map Parameters: objects (typing.List["Object"]): objects of static map - bbox (s2sphere.LatLngRect): boundary box of all objects - epb (typing.Tuple[int, int, int, int]): extra pixel bounds + tighten (bool): tighten to boundaries """ @abstractmethod @@ -59,15 +57,15 @@ def render_background(self, color: typing.Optional[Color]) -> None: def render_tiles( self, download: typing.Callable[[int, int, int], typing.Optional[bytes]], - bbox: s2sphere.LatLngRect, - epb: typing.Optional[typing.Tuple[int, int, int, int]] = None, + objects: typing.List["Object"], + tighten: bool, ) -> None: """Render tiles of static map Parameters: download (typing.Callable[[int, int, int], typing.Optional[bytes]]): url of tiles provider - bbox (s2sphere.LatLngRect): boundary box of all objects - epb (typing.Tuple[int, int, int, int]): extra pixel bounds + objects (typing.List["Object"]): objects of static map + tighten (bool): tighten to boundaries """ def render_marker_object(self, marker: "Marker") -> None: @@ -103,6 +101,25 @@ def render_attribution(self, attribution: typing.Optional[str]) -> None: """Render attribution from given tiles provider Parameters: - attribution (typing.Optional[str]:): Attribution for the - given tiles provider + attribution (typing.Optional[str]): Attribution for the given tiles provider + """ + + def get_object_bounds(self, objects: typing.List["Object"]) -> s2sphere.LatLngRect: + """Return "cumulated" boundaries of all objects + + Parameters: + objects typing.List["Object"]): list of all objects to be rendered in the static map + + Returns: + s2sphere.LatLngRect: LatLngRect object with "cumulated" boundaries of all objects """ + + new_bounds: typing.Optional[s2sphere.LatLngRect] = None + for obj in objects: + bounds_epb = obj.bounds_epb(self._trans) + if new_bounds: + new_bounds = new_bounds.union(bounds_epb) + else: + new_bounds = bounds_epb + + return new_bounds diff --git a/staticmaps/svg_renderer.py b/staticmaps/svg_renderer.py index 37621b9..2c9c3f6 100644 --- a/staticmaps/svg_renderer.py +++ b/staticmaps/svg_renderer.py @@ -52,15 +52,13 @@ def group(self) -> svgwrite.container.Group: def render_objects( self, objects: typing.List["Object"], - bbox: typing.Optional[s2sphere.LatLngRect] = None, - epb: typing.Optional[typing.Tuple[int, int, int, int]] = None, + tighten: bool, ) -> None: """Render all objects of static map Parameters: objects (typing.List["Object"]): objects of static map - bbox (s2sphere.LatLngRect): boundary box of all objects - epb (typing.Tuple[int, int, int, int]): extra pixel bounds + tighten (bool): tighten to boundaries """ self._group = self._draw.g(clip_path="url(#page)") x_count = math.ceil(self._trans.image_width() / (2 * self._trans.world_width())) @@ -69,7 +67,7 @@ def render_objects( group = self._draw.g(clip_path="url(#page)", transform=f"translate({p * self._trans.world_width()}, 0)") obj.render_svg(self) self._group.add(group) - objects_group = self._tighten_to_boundary(self._group, bbox, epb) + objects_group = self._tighten_to_boundary(self._group, objects, tighten) self._draw.add(objects_group) self._group = None @@ -88,15 +86,15 @@ def render_background(self, color: typing.Optional[Color]) -> None: def render_tiles( self, download: typing.Callable[[int, int, int], typing.Optional[bytes]], - bbox: typing.Optional[s2sphere.LatLngRect] = None, - epb: typing.Optional[typing.Tuple[int, int, int, int]] = None, + objects: typing.List["Object"], + tighten: bool, ) -> None: """Render tiles of static map Parameters: download (typing.Callable[[int, int, int], typing.Optional[bytes]]): url of tiles provider - bbox (s2sphere.LatLngRect): boundary box of all objects - epb (typing.Tuple[int, int, int, int]): extra pixel bounds + objects (typing.List["Object"]): objects of static map + tighten (bool): tighten to boundaries """ self._group = self._draw.g(clip_path="url(#page)") for yy in range(0, self._trans.tiles_y()): @@ -113,43 +111,51 @@ def render_tiles( self._draw.image( tile_img, insert=( - xx * self._trans.tile_size() + self._trans.tile_offset_x(), - yy * self._trans.tile_size() + self._trans.tile_offset_y(), + int(xx * self._trans.tile_size() + self._trans.tile_offset_x()), + int(yy * self._trans.tile_size() + self._trans.tile_offset_y()), ), size=(self._trans.tile_size(), self._trans.tile_size()), ) ) except RuntimeError: pass - tiles_group = self._tighten_to_boundary(self._group, bbox, epb) + tiles_group = self._tighten_to_boundary(self._group, objects, tighten) self._draw.add(tiles_group) self._group = None def _tighten_to_boundary( - self, - group: svgwrite.container.Group, - bbox: typing.Optional[s2sphere.LatLngRect] = None, - epb: typing.Optional[typing.Tuple[int, int, int, int]] = None, + self, group: svgwrite.container.Group, objects: typing.List["Object"], tighten: bool = False ) -> svgwrite.container.Group: - """Calculate scale and offset for tight rendering on the boundary""" + """Calculate scale and offset for tight rendering on the boundary + + Parameters: + group (svgwrite.container.Group): svg group + objects (typing.List["Object"]): objects of static map + tighten (bool): tighten to boundaries + Returns: + svgwrite.container.Group: svg group + """ # pylint: disable=too-many-locals - if not bbox or not epb: + if not tighten: return group + # boundary points - nw_x, nw_y = self._trans.ll2pixel(s2sphere.LatLng.from_angles(bbox.lat_lo(), bbox.lng_lo())) - se_x, se_y = self._trans.ll2pixel(s2sphere.LatLng.from_angles(bbox.lat_hi(), bbox.lng_hi())) - epb_l, epb_t, epb_r, epb_b = 0, 0, 0, 0 - if epb: - epb_l, epb_t, epb_r, epb_b = epb + bounds = self.get_object_bounds(objects) + nw_x, nw_y = self._trans.ll2pixel(s2sphere.LatLng.from_angles(bounds.lat_lo(), bounds.lng_lo())) + se_x, se_y = self._trans.ll2pixel(s2sphere.LatLng.from_angles(bounds.lat_hi(), bounds.lng_hi())) # boundary size - size_x = se_x - nw_x + epb_r + epb_l - size_y = nw_y - se_y + epb_t + epb_b + size_x = se_x - nw_x + size_y = nw_y - se_y # scale to boundaries width = self._trans.image_width() height = self._trans.image_height() - scale_x = size_x / width - scale_y = size_y / height - scale = 1 / max(scale_x, scale_y) + # scale = 1, if division by zero + try: + scale_x = size_x / width + scale_y = size_y / height + scale = 1 / max(scale_x, scale_y) + except ZeroDivisionError: + scale = 1 # translate new center to old center off_x = -0.5 * width * (scale - 1) off_y = -0.5 * height * (scale - 1) @@ -162,8 +168,7 @@ def render_attribution(self, attribution: typing.Optional[str]) -> None: """Render attribution from given tiles provider Parameters: - attribution (typing.Optional[str]:): Attribution for the - given tiles provider + attribution (typing.Optional[str]:): Attribution for the given tiles provider """ if (attribution is None) or (attribution == ""): return From f4006e6c73bf9d8b5bff8f92e1d133aa7eea1825 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Fri, 21 Jul 2023 17:02:45 +0200 Subject: [PATCH 128/147] add new bounds object --- staticmaps/__init__.py | 2 ++ staticmaps/bounds.py | 68 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 staticmaps/bounds.py diff --git a/staticmaps/__init__.py b/staticmaps/__init__.py index 43dad80..52f8c7b 100644 --- a/staticmaps/__init__.py +++ b/staticmaps/__init__.py @@ -3,6 +3,7 @@ # flake8: noqa from .area import Area +from .bounds import Bounds from .cairo_renderer import CairoRenderer, cairo_is_supported from .circle import Circle from .color import ( @@ -46,6 +47,7 @@ __all__ = [ "Area", + "Bounds", "CairoRenderer", "cairo_is_supported", "Circle", diff --git a/staticmaps/bounds.py b/staticmaps/bounds.py new file mode 100644 index 0000000..956d8e9 --- /dev/null +++ b/staticmaps/bounds.py @@ -0,0 +1,68 @@ +"""py-staticmaps - bounds""" +# Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information + +import typing + +import s2sphere # type: ignore + +from .cairo_renderer import CairoRenderer +from .object import Object, PixelBoundsT +from .pillow_renderer import PillowRenderer +from .svg_renderer import SvgRenderer + + +class Bounds(Object): + """ + Custom bounds object to be respected for the final static map. Nothing is being rendered. + """ + + def __init__(self, latlngs: typing.List[s2sphere.LatLng], extra_pixel_bounds: PixelBoundsT = (0, 0, 0, 0)) -> None: + Object.__init__(self) + if latlngs is None or len(latlngs) < 2: + raise ValueError("Trying to create custom bounds with less than 2 coordinates") + + self._latlngs = latlngs + self._extra_pixel_bounds = extra_pixel_bounds + + def bounds(self) -> s2sphere.LatLngRect: + """Return bounds of bounds object + + Returns: + s2sphere.LatLngRect: bounds of bounds object + """ + b = s2sphere.LatLngRect() + for latlng in self._latlngs: + b = b.union(s2sphere.LatLngRect.from_point(latlng.normalized())) + return b + + def extra_pixel_bounds(self) -> PixelBoundsT: + """Return extra pixel bounds of bounds object + + Returns: + PixelBoundsT: extra pixel bounds + """ + return self._extra_pixel_bounds + + def render_pillow(self, renderer: PillowRenderer) -> None: + """Do not render custom bounds + + Parameters: + renderer (PillowRenderer): pillow renderer + """ + return + + def render_svg(self, renderer: SvgRenderer) -> None: + """Do not render custom bounds + + Parameters: + renderer (SvgRenderer): svg renderer + """ + return + + def render_cairo(self, renderer: CairoRenderer) -> None: + """Do not render custom bounds + + Parameters: + renderer (CairoRenderer): cairo renderer + """ + return From 68b0c8047ca53a68df872c4c7c7c42e034956a41 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Fri, 21 Jul 2023 17:16:37 +0200 Subject: [PATCH 129/147] add bounds object, change tests, ... --- README.md | 5 ++-- examples/frankfurt_newyork.py | 26 +++---------------- staticmaps/bounds.py | 6 ++--- staticmaps/cli.py | 2 +- staticmaps/context.py | 48 ++--------------------------------- staticmaps/line.py | 3 ++- tests/test_context.py | 6 ++--- 7 files changed, 17 insertions(+), 79 deletions(-) diff --git a/README.md b/README.md index 59321ca..f4139b2 100644 --- a/README.md +++ b/README.md @@ -60,8 +60,9 @@ image = context.render_pillow(800, 500) image.save("frankfurt_newyork.pillow.png") # render anti-aliased png (this only works if pycairo is installed) -image = context.render_cairo(800, 500) -image.write_to_png("frankfurt_newyork.cairo.png") +if staticmaps.cairo_is_supported(): + image = context.render_cairo(800, 500) + image.write_to_png("frankfurt_newyork.cairo.png") # render svg svg_image = context.render_svg(800, 500) diff --git a/examples/frankfurt_newyork.py b/examples/frankfurt_newyork.py index 0bf7ce8..a4ff132 100644 --- a/examples/frankfurt_newyork.py +++ b/examples/frankfurt_newyork.py @@ -3,8 +3,6 @@ """py-staticmaps - Example Frankfurt-New York""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information -import s2sphere # type: ignore - import staticmaps context = staticmaps.Context() @@ -14,32 +12,14 @@ newyork = staticmaps.create_latlng(40.712728, -74.006015) context.add_object(staticmaps.Line([frankfurt, newyork], color=staticmaps.BLUE, width=4)) -context.add_object(staticmaps.Marker(frankfurt, color=staticmaps.BLUE, size=20)) -context.add_object(staticmaps.Marker(newyork, color=staticmaps.GREEN, size=12, stroke_width=2)) - -# TODOX: remove testing code - -bbox = context.object_bounds() -assert bbox -context.add_object(staticmaps.Line([bbox.lo(), bbox.hi()], width=1)) -line = staticmaps.Line( - [ - s2sphere.LatLng.from_angles(bbox.lat_lo(), bbox.lng_lo()), - s2sphere.LatLng.from_angles(bbox.lat_lo(), bbox.lng_hi()), - s2sphere.LatLng.from_angles(bbox.lat_hi(), bbox.lng_hi()), - s2sphere.LatLng.from_angles(bbox.lat_hi(), bbox.lng_lo()), - s2sphere.LatLng.from_angles(bbox.lat_lo(), bbox.lng_lo()), - ], - staticmaps.GREEN, - width=1, -) -context.add_object(line) +context.add_object(staticmaps.Marker(frankfurt, color=staticmaps.GREEN, size=12)) +context.add_object(staticmaps.Marker(newyork, color=staticmaps.RED, size=12)) # render png via pillow image = context.render_pillow(800, 500) image.save("frankfurt_newyork.pillow.png") -# render png via cairo +# render anti-aliased png (this only works if pycairo is installed) if staticmaps.cairo_is_supported(): image = context.render_cairo(800, 500) image.write_to_png("frankfurt_newyork.cairo.png") diff --git a/staticmaps/bounds.py b/staticmaps/bounds.py index 956d8e9..f90e30b 100644 --- a/staticmaps/bounds.py +++ b/staticmaps/bounds.py @@ -44,7 +44,7 @@ def extra_pixel_bounds(self) -> PixelBoundsT: return self._extra_pixel_bounds def render_pillow(self, renderer: PillowRenderer) -> None: - """Do not render custom bounds + """Do not render custom bounds for pillow Parameters: renderer (PillowRenderer): pillow renderer @@ -52,7 +52,7 @@ def render_pillow(self, renderer: PillowRenderer) -> None: return def render_svg(self, renderer: SvgRenderer) -> None: - """Do not render custom bounds + """Do not render custom bounds for svg Parameters: renderer (SvgRenderer): svg renderer @@ -60,7 +60,7 @@ def render_svg(self, renderer: SvgRenderer) -> None: return def render_cairo(self, renderer: CairoRenderer) -> None: - """Do not render custom bounds + """Do not render custom bounds for cairo Parameters: renderer (CairoRenderer): cairo renderer diff --git a/staticmaps/cli.py b/staticmaps/cli.py index bc2d2be..b7a9000 100755 --- a/staticmaps/cli.py +++ b/staticmaps/cli.py @@ -151,7 +151,7 @@ def main() -> None: for coords in args.marker: context.add_object(staticmaps.Marker(staticmaps.parse_latlng(coords))) if args.bounds is not None: - context.add_bounds(staticmaps.parse_latlngs2rect(args.bounds)) + context.add_object(staticmaps.Bounds(staticmaps.parse_latlngs(args.bounds))) context.set_tighten_to_bounds(args.tighten_to_bounds) file_name = args.filename[0] diff --git a/staticmaps/context.py b/staticmaps/context.py index 927b65a..9fb7f0d 100644 --- a/staticmaps/context.py +++ b/staticmaps/context.py @@ -31,8 +31,6 @@ def __init__(self) -> None: self._background_color: typing.Optional[Color] = None self._objects: typing.List[Object] = [] self._center: typing.Optional[s2sphere.LatLng] = None - self._bounds: typing.Optional[s2sphere.LatLngRect] = None - self._extra_pixel_bounds: typing.Tuple[int, int, int, int] = (0, 0, 0, 0) self._zoom: typing.Optional[int] = None self._tile_provider = tile_provider_OSM self._tile_downloader = TileDownloader() @@ -111,33 +109,6 @@ def add_object(self, obj: Object) -> None: """ self._objects.append(obj) - def add_bounds( - self, - latlngrect: s2sphere.LatLngRect, - extra_pixel_bounds: typing.Optional[typing.Union[int, typing.Tuple[int, int, int, int]]] = None, - ) -> None: - """Add boundaries that shall be respected by the static map - - Parameters: - latlngrect (s2sphere.LatLngRect): boundaries to be respected - extra_pixel_bounds (int, tuple): extra pixel bounds to be - respected - """ - if self._bounds: - self._bounds = self._bounds.union(latlngrect) - else: - self._bounds = latlngrect - if extra_pixel_bounds: - if isinstance(extra_pixel_bounds, tuple): - self._extra_pixel_bounds = extra_pixel_bounds - else: - self._extra_pixel_bounds = ( - extra_pixel_bounds, - extra_pixel_bounds, - extra_pixel_bounds, - extra_pixel_bounds, - ) - def render_cairo(self, width: int, height: int) -> typing.Any: """Render area using cairo @@ -236,22 +207,7 @@ def object_bounds(self) -> typing.Optional[s2sphere.LatLngRect]: for obj in self._objects: assert bounds bounds = bounds.union(obj.bounds()) - return self._custom_bounds(bounds) - - def _custom_bounds(self, bounds: typing.Optional[s2sphere.LatLngRect]) -> typing.Optional[s2sphere.LatLngRect]: - """check for additional bounds and return the union with object bounds - - Parameters: - bounds (s2sphere.LatLngRect): boundaries from objects - - Returns: - s2sphere.LatLngRect: maximum of additional and object bounds - """ - if not self._bounds: - return bounds - if not bounds: - return self._bounds - return bounds.union(self._bounds) + return bounds def extra_pixel_bounds(self) -> PixelBoundsT: """return extra pixel bounds from all objects @@ -259,7 +215,7 @@ def extra_pixel_bounds(self) -> PixelBoundsT: Returns: PixelBoundsT: extra pixel object bounds """ - max_l, max_t, max_r, max_b = self._extra_pixel_bounds + max_l, max_t, max_r, max_b = 0, 0, 0, 0 attribution = self._tile_provider.attribution() if (attribution is None) or (attribution == ""): max_b = max(max_b, 12) diff --git a/staticmaps/line.py b/staticmaps/line.py index 5a4033d..53ab665 100644 --- a/staticmaps/line.py +++ b/staticmaps/line.py @@ -7,6 +7,7 @@ import s2sphere # type: ignore from geographiclib.geodesic import Geodesic # type: ignore +from .bounds import Bounds from .cairo_renderer import CairoRenderer from .color import RED, Color from .coordinates import create_latlng @@ -15,7 +16,7 @@ from .svg_renderer import SvgRenderer -class Line(Object): +class Line(Bounds): """ Line A line object """ diff --git a/tests/test_context.py b/tests/test_context.py index 27ec16a..090880d 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -107,7 +107,7 @@ def test_bounds() -> None: staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8) ) - context.add_bounds(s2sphere.LatLngRect(staticmaps.create_latlng(46, 6), staticmaps.create_latlng(49, 9))) + context.add_object(staticmaps.Bounds([staticmaps.create_latlng(46, 6), staticmaps.create_latlng(49, 9)])) assert context.object_bounds() is not None assert context.object_bounds() == s2sphere.LatLngRect( staticmaps.create_latlng(46, 6), staticmaps.create_latlng(49, 9) @@ -122,7 +122,7 @@ def test_add_greater_custom_bound_extends_bounds() -> None: context.add_object(staticmaps.Marker(staticmaps.create_latlng(48, 8))) assert context.object_bounds() is not None - context.add_bounds(s2sphere.LatLngRect(staticmaps.create_latlng(49, 7.5), staticmaps.create_latlng(49, 8))) + context.add_object(staticmaps.Bounds([staticmaps.create_latlng(49, 7.5), staticmaps.create_latlng(49, 8)])) assert context.object_bounds() == s2sphere.LatLngRect( staticmaps.create_latlng(47, 7), staticmaps.create_latlng(49, 8) ) @@ -136,7 +136,7 @@ def test_add_smaller_custom_bound_keeps_bounds() -> None: context.add_object(staticmaps.Marker(staticmaps.create_latlng(48, 8))) assert context.object_bounds() is not None - context.add_bounds(s2sphere.LatLngRect(staticmaps.create_latlng(47.5, 7.5), staticmaps.create_latlng(48, 8))) + context.add_object(staticmaps.Bounds([staticmaps.create_latlng(47.5, 7.5), staticmaps.create_latlng(48, 8)])) assert context.object_bounds() is not None assert context.object_bounds() == s2sphere.LatLngRect( staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8) From 0f832f57caaa2ef1be80b2c51351ac7a40ff8a27 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Fri, 21 Jul 2023 18:01:22 +0200 Subject: [PATCH 130/147] add examples to readme, add custom bounds examples --- README.md | 42 +++++++++++++++++++++++++++++------ examples/frankfurt_newyork.py | 15 +++++++++++++ 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f4139b2..3e8a356 100644 --- a/README.md +++ b/README.md @@ -74,13 +74,33 @@ context.set_tighten_to_bounds(True) svg_image = context.render_svg(800, 500) with open("frankfurt_newyork.tight.svg", "w", encoding="utf-8") as f: svg_image.write(f, pretty=True) -``` -![franfurt_newyork](../assets/frankfurt_newyork.cairo.png?raw=true) +context2 = staticmaps.Context() +context2.set_tile_provider(staticmaps.tile_provider_StamenToner) +context2.add_object(staticmaps.Bounds([frankfurt, newyork])) -![franfurt_newyork](../assets/frankfurt_newyork.svg?raw=true) +# render svg +svg_image = context2.render_svg(800, 500) +with open("frankfurt_newyork.bounds.svg", "w", encoding="utf-8") as f: + svg_image.write(f, pretty=True) -![franfurt_newyork](../assets/frankfurt_newyork.tight.svg?raw=true) +# render svg - tight boundaries +context2.set_tighten_to_bounds(True) +svg_image = context2.render_svg(800, 500) +with open("frankfurt_newyork.bounds.tight.svg", "w", encoding="utf-8") as f: + svg_image.write(f, pretty=True) +``` + +#### Cairo example +![frankfurt_newyork](../assets/frankfurt_newyork.cairo.png?raw=true) +#### SVG example +![frankfurt_newyork_svg](../assets/frankfurt_newyork.svg?raw=true) +#### SVG tight example +![frankfurt_newyork_svg_tight](../assets/frankfurt_newyork.tight.svg?raw=true) +#### SVG custom bounds example +![frankfurt_newyork_bounds_svg](../assets/frankfurt_newyork.bounds.svg?raw=true) +#### SVG custom bounds tight example +![frankfurt_newyork_bounds_svg_tight](../assets/frankfurt_newyork.bounds.tight.svg?raw=true) ### Transparent Polygons @@ -120,8 +140,10 @@ svg_image = context.render_svg(800, 500) with open("freiburg_area.svg", "w", encoding="utf-8") as f: svg_image.write(f, pretty=True) ``` - +#### Cairo example ![freiburg_area](../assets/freiburg_area.cairo.png?raw=true) +#### SVG tight example +![freiburg_area_svg_tight](../assets/freiburg_area.tight.svg?raw=true) ### Drawing a GPX Track + Image Marker (PNG) @@ -157,8 +179,10 @@ image.save("draw_gpx.pillow.png") image = context.render_cairo(800, 500) image.write_to_png("draw_gpx.cairo.png") ``` - +#### Cairo example ![draw_gpx](../assets/draw_gpx.cairo.png?raw=true) +#### SVG tight example +![draw_gpx_svg_tight](../assets/draw_gpx.tight.svg?raw=true) ### US State Capitals @@ -188,8 +212,10 @@ image.save("us_capitals.pillow.png") image = context.render_cairo(800, 500) image.write_to_png("us_capitals.cairo.png") ``` - +#### Cairo example ![us_capitals](../assets/us_capitals.cairo.png?raw=true) +#### SVG tight example +![us_capitals_svg_tight](../assets/us_capitals.tight.svg?raw=true) ### Geodesic Circles @@ -223,6 +249,8 @@ image.write_to_png("geodesic_circles.cairo.png") ![geodesic_circles_pillow](../assets/geodesic_circles.pillow.png?raw=true) #### SVG example ![geodesic_circles_svg](../assets/geodesic_circles.svg?raw=true) +#### SVG tight example +![geodesic_circles_svg_tight](../assets/geodesic_circles.tight.svg?raw=true) ### Other Examples diff --git a/examples/frankfurt_newyork.py b/examples/frankfurt_newyork.py index a4ff132..9dda1a3 100644 --- a/examples/frankfurt_newyork.py +++ b/examples/frankfurt_newyork.py @@ -45,3 +45,18 @@ svg_image = context.render_svg(800, 500) with open("frankfurt_newyork.tight.svg", "w", encoding="utf-8") as f: svg_image.write(f, pretty=True) + +context2 = staticmaps.Context() +context2.set_tile_provider(staticmaps.tile_provider_StamenToner) +context2.add_object(staticmaps.Bounds([frankfurt, newyork])) + +# render svg +svg_image = context2.render_svg(800, 500) +with open("frankfurt_newyork.bounds.svg", "w", encoding="utf-8") as f: + svg_image.write(f, pretty=True) + +# render svg - tight boundaries +context2.set_tighten_to_bounds(True) +svg_image = context2.render_svg(800, 500) +with open("frankfurt_newyork.bounds.tight.svg", "w", encoding="utf-8") as f: + svg_image.write(f, pretty=True) From 1bc6ae0a173b285a15f7429ba7dbbf41a4a9989e Mon Sep 17 00:00:00 2001 From: Lowtower Date: Fri, 21 Jul 2023 18:51:49 +0200 Subject: [PATCH 131/147] fix wrong file names for images in draw_gpx --- README.md | 20 ++++++++++++++++---- examples/draw_gpx.py | 8 ++++---- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 3e8a356..33fd103 100644 --- a/README.md +++ b/README.md @@ -171,13 +171,25 @@ for p in gpx.walk(only_points=True): context.add_object(marker) break -# render non-anti-aliased png +# render png via pillow image = context.render_pillow(800, 500) image.save("draw_gpx.pillow.png") -# render anti-aliased png (this only works if pycairo is installed) -image = context.render_cairo(800, 500) -image.write_to_png("draw_gpx.cairo.png") +# render png via cairo +if staticmaps.cairo_is_supported(): + image = context.render_cairo(800, 500) + image.write_to_png("draw_gpx.cairo.png") + +# render svg +svg_image = context.render_svg(800, 500) +with open("draw_gpx.svg", "w", encoding="utf-8") as f: + svg_image.write(f, pretty=True) + +# render svg - tight boundaries +context.set_tighten_to_bounds(True) +svg_image = context.render_svg(800, 500) +with open("draw_gpx.tight.svg", "w", encoding="utf-8") as f: + svg_image.write(f, pretty=True) ``` #### Cairo example ![draw_gpx](../assets/draw_gpx.cairo.png?raw=true) diff --git a/examples/draw_gpx.py b/examples/draw_gpx.py index 04ff3f5..e3f24e8 100644 --- a/examples/draw_gpx.py +++ b/examples/draw_gpx.py @@ -29,20 +29,20 @@ # render png via pillow image = context.render_pillow(800, 500) -image.save("running.pillow.png") +image.save("draw_gpx.pillow.png") # render png via cairo if staticmaps.cairo_is_supported(): image = context.render_cairo(800, 500) - image.write_to_png("running.cairo.png") + image.write_to_png("draw_gpx.cairo.png") # render svg svg_image = context.render_svg(800, 500) -with open("running.svg", "w", encoding="utf-8") as f: +with open("draw_gpx.svg", "w", encoding="utf-8") as f: svg_image.write(f, pretty=True) # render svg - tight boundaries context.set_tighten_to_bounds(True) svg_image = context.render_svg(800, 500) -with open("running.tight.svg", "w", encoding="utf-8") as f: +with open("draw_gpx.tight.svg", "w", encoding="utf-8") as f: svg_image.write(f, pretty=True) From 14cbac608bfc0662c2c51beb9210b59190ea37e2 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Fri, 21 Jul 2023 19:51:00 +0200 Subject: [PATCH 132/147] add tightening to description --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3e8a356..fc0343e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![CI](https://github.com/flopp/py-staticmaps/workflows/CI/badge.svg)](https://github.com/flopp/py-staticmaps/actions?query=workflow%3ACI) +[![CI](https://github.com/lowtower/py-staticmaps/workflows/CI/badge.svg)](https://github.com/lowtower/py-staticmaps/actions?query=workflow%3ACI) [![PyPI Package](https://img.shields.io/pypi/v/py-staticmaps.svg)](https://pypi.org/project/py-staticmaps/) [![Format](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) [![License MIT](https://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat)](LICENSE) @@ -17,6 +17,10 @@ A python module to create static map images (PNG, SVG) with markers, geodesic li - Non-anti-aliased drawing via `PILLOW` - Anti-aliased drawing via `pycairo` (optional; only if `pycairo` is installed properly) - SVG creation via `svgwrite` +- optional tightening of the map to object or custom boundaries + - SVG only + - tiles are being "cropped" to given boundaries + - might lead to reduction of image quality ## Installation From fade0dd4fe346b8baab75ad98a59505e644310d7 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Wed, 26 Jul 2023 20:38:30 +0200 Subject: [PATCH 133/147] Change handling of extra pixel bounds - make integer possible instead of tuple --- staticmaps/bounds.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/staticmaps/bounds.py b/staticmaps/bounds.py index f90e30b..c206030 100644 --- a/staticmaps/bounds.py +++ b/staticmaps/bounds.py @@ -16,13 +16,17 @@ class Bounds(Object): Custom bounds object to be respected for the final static map. Nothing is being rendered. """ - def __init__(self, latlngs: typing.List[s2sphere.LatLng], extra_pixel_bounds: PixelBoundsT = (0, 0, 0, 0)) -> None: + def __init__( + self, latlngs: typing.List[s2sphere.LatLng], extra_pixel_bounds: typing.Union[int, PixelBoundsT] = 0 + ) -> None: Object.__init__(self) if latlngs is None or len(latlngs) < 2: raise ValueError("Trying to create custom bounds with less than 2 coordinates") - self._latlngs = latlngs - self._extra_pixel_bounds = extra_pixel_bounds + if isinstance(extra_pixel_bounds, int): + self._extra_pixel_bounds = (extra_pixel_bounds, extra_pixel_bounds, extra_pixel_bounds, extra_pixel_bounds) + else: + self._extra_pixel_bounds = extra_pixel_bounds def bounds(self) -> s2sphere.LatLngRect: """Return bounds of bounds object From 92e540d994398ff2cb2cf4985229733b1f9fe01d Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 6 Jun 2024 11:42:18 +0200 Subject: [PATCH 134/147] formatting --- setup.py | 1 + staticmaps/__init__.py | 1 + staticmaps/area.py | 1 + staticmaps/bounds.py | 1 + staticmaps/circle.py | 1 + staticmaps/color.py | 1 + staticmaps/coordinates.py | 1 + staticmaps/image_marker.py | 1 + staticmaps/line.py | 1 + staticmaps/marker.py | 1 + staticmaps/meta.py | 1 + staticmaps/object.py | 1 + staticmaps/renderer.py | 1 + staticmaps/tile_downloader.py | 1 + staticmaps/tile_provider.py | 1 + staticmaps/transformer.py | 1 + tests/test_color.py | 1 + tests/test_coordinates.py | 1 + tests/test_tile_provider.py | 1 + 19 files changed, 19 insertions(+) diff --git a/setup.py b/setup.py index 8e1a677..ff9c639 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ """py-staticmaps setup""" + # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import os diff --git a/staticmaps/__init__.py b/staticmaps/__init__.py index 52f8c7b..b8da7f4 100644 --- a/staticmaps/__init__.py +++ b/staticmaps/__init__.py @@ -1,4 +1,5 @@ """py-staticmaps __init__""" + # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information # flake8: noqa diff --git a/staticmaps/area.py b/staticmaps/area.py index 6988cf0..ddd2c0d 100644 --- a/staticmaps/area.py +++ b/staticmaps/area.py @@ -1,4 +1,5 @@ """py-staticmaps - area""" + # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import typing diff --git a/staticmaps/bounds.py b/staticmaps/bounds.py index c206030..897d89b 100644 --- a/staticmaps/bounds.py +++ b/staticmaps/bounds.py @@ -1,4 +1,5 @@ """py-staticmaps - bounds""" + # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import typing diff --git a/staticmaps/circle.py b/staticmaps/circle.py index 37423be..489eeca 100644 --- a/staticmaps/circle.py +++ b/staticmaps/circle.py @@ -1,4 +1,5 @@ """py-staticmaps - circle""" + # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import typing diff --git a/staticmaps/color.py b/staticmaps/color.py index b72fed3..6f984dc 100644 --- a/staticmaps/color.py +++ b/staticmaps/color.py @@ -1,4 +1,5 @@ """py-staticmaps - Color""" + # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import random diff --git a/staticmaps/coordinates.py b/staticmaps/coordinates.py index 02761d6..f2650fb 100644 --- a/staticmaps/coordinates.py +++ b/staticmaps/coordinates.py @@ -1,4 +1,5 @@ """py-staticmaps - Coordinates""" + # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import typing diff --git a/staticmaps/image_marker.py b/staticmaps/image_marker.py index 292e4d2..a4aedea 100644 --- a/staticmaps/image_marker.py +++ b/staticmaps/image_marker.py @@ -1,4 +1,5 @@ """py-staticmaps - image_marker""" + # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import io diff --git a/staticmaps/line.py b/staticmaps/line.py index dd60107..8af84c0 100644 --- a/staticmaps/line.py +++ b/staticmaps/line.py @@ -1,4 +1,5 @@ """py-staticmaps - line""" + # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import math diff --git a/staticmaps/marker.py b/staticmaps/marker.py index dd27119..8924529 100644 --- a/staticmaps/marker.py +++ b/staticmaps/marker.py @@ -1,4 +1,5 @@ """py-staticmaps - marker""" + # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import math diff --git a/staticmaps/meta.py b/staticmaps/meta.py index 13c07b5..c19db2a 100644 --- a/staticmaps/meta.py +++ b/staticmaps/meta.py @@ -1,4 +1,5 @@ """py-staticmaps - meta""" + # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information GITHUB_URL = "https://github.com/flopp/py-staticmaps" diff --git a/staticmaps/object.py b/staticmaps/object.py index b842d43..d8a79f0 100644 --- a/staticmaps/object.py +++ b/staticmaps/object.py @@ -1,4 +1,5 @@ """py-staticmaps - object""" + # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import typing diff --git a/staticmaps/renderer.py b/staticmaps/renderer.py index 4587425..84205f9 100644 --- a/staticmaps/renderer.py +++ b/staticmaps/renderer.py @@ -1,4 +1,5 @@ """py-staticmaps - renderer""" + # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import typing diff --git a/staticmaps/tile_downloader.py b/staticmaps/tile_downloader.py index 3978913..597c7fa 100644 --- a/staticmaps/tile_downloader.py +++ b/staticmaps/tile_downloader.py @@ -1,4 +1,5 @@ """py-staticmaps - tile_downloader""" + # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import os diff --git a/staticmaps/tile_provider.py b/staticmaps/tile_provider.py index 0a50f88..15ad0df 100644 --- a/staticmaps/tile_provider.py +++ b/staticmaps/tile_provider.py @@ -1,4 +1,5 @@ """py-staticmaps - tile_provider""" + # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import string diff --git a/staticmaps/transformer.py b/staticmaps/transformer.py index 2b22823..4134064 100644 --- a/staticmaps/transformer.py +++ b/staticmaps/transformer.py @@ -1,4 +1,5 @@ """py-staticmaps - transformer""" + # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import math diff --git a/tests/test_color.py b/tests/test_color.py index f547455..95fbb41 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -1,4 +1,5 @@ """py-staticmaps - Test Color""" + # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import math diff --git a/tests/test_coordinates.py b/tests/test_coordinates.py index 59dc915..bf555bc 100644 --- a/tests/test_coordinates.py +++ b/tests/test_coordinates.py @@ -1,4 +1,5 @@ """py-staticmaps - Test Coordinates""" + # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import pytest # type: ignore diff --git a/tests/test_tile_provider.py b/tests/test_tile_provider.py index fde192c..146e127 100644 --- a/tests/test_tile_provider.py +++ b/tests/test_tile_provider.py @@ -1,4 +1,5 @@ """py-staticmaps - Test TileProvider""" + # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information import staticmaps From 3a9509b7c296b4019bbbbc58f4daab3c34333e29 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 6 Jun 2024 11:53:01 +0200 Subject: [PATCH 135/147] typo --- examples/frankfurt_newyork.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/frankfurt_newyork.py b/examples/frankfurt_newyork.py index ea12c08..af000d5 100644 --- a/examples/frankfurt_newyork.py +++ b/examples/frankfurt_newyork.py @@ -37,8 +37,8 @@ # render png via cairo - tight boundaries if staticmaps.cairo_is_supported(): context.set_tighten_to_bounds(True) - image = context.render_cairo(800, 500) - image.write_to_png("frankfurt_newyork.tight.cairo.png") + cairo_image = context.render_cairo(800, 500) + cairo_image.write_to_png("frankfurt_newyork.tight.cairo.png") # render svg - tight boundaries context.set_tighten_to_bounds(True) From 43db355f3fcf224b5babc5888c73ad78bb807876 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 6 Jun 2024 11:53:14 +0200 Subject: [PATCH 136/147] pylint disable --- staticmaps/line.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/staticmaps/line.py b/staticmaps/line.py index 8af84c0..55e9997 100644 --- a/staticmaps/line.py +++ b/staticmaps/line.py @@ -22,8 +22,9 @@ class Line(Bounds): Line A line object """ + # pylint: disable=super-init-not-called def __init__(self, latlngs: typing.List[s2sphere.LatLng], color: Color = RED, width: int = 2) -> None: - Object.__init__(self) + Object.__init__(self) # pylint: disable=non-parent-init-called if latlngs is None or len(latlngs) < 2: raise ValueError("Trying to create line with less than 2 coordinates") if width < 0: From 8a492fdcc7c90c1f654a2c384481de99558ca879 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 6 Jun 2024 12:15:25 +0200 Subject: [PATCH 137/147] use approx_equals instead of == --- tests/test_context.py | 52 +++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/tests/test_context.py b/tests/test_context.py index 090880d..5a8331c 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -17,8 +17,8 @@ def test_add_marker_adds_bounds_is_point() -> None: context.add_object(staticmaps.Marker(staticmaps.create_latlng(48, 8))) assert context.object_bounds() is not None - assert context.object_bounds() == s2sphere.LatLngRect( - staticmaps.create_latlng(48, 8), staticmaps.create_latlng(48, 8) + assert context.object_bounds().approx_equals( + s2sphere.LatLngRect(staticmaps.create_latlng(48, 8), staticmaps.create_latlng(48, 8)) ) bounds = context.object_bounds() assert bounds is not None @@ -31,12 +31,12 @@ def test_add_two_markers_adds_bounds_is_not_point() -> None: context.add_object(staticmaps.Marker(staticmaps.create_latlng(47, 7))) assert context.object_bounds() is not None - assert context.object_bounds() == s2sphere.LatLngRect( - staticmaps.create_latlng(47, 7), staticmaps.create_latlng(47, 7) + assert context.object_bounds().approx_equals( + s2sphere.LatLngRect(staticmaps.create_latlng(47, 7), staticmaps.create_latlng(47, 7)) ) context.add_object(staticmaps.Marker(staticmaps.create_latlng(48, 8))) - assert context.object_bounds() == s2sphere.LatLngRect( - staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8) + assert context.object_bounds().approx_equals( + s2sphere.LatLngRect(staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8)) ) bounds = context.object_bounds() assert bounds is not None @@ -49,8 +49,8 @@ def test_add_line_adds_bounds_is_rect() -> None: context.add_object(staticmaps.Line([staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8)])) assert context.object_bounds() is not None - assert context.object_bounds() == s2sphere.LatLngRect( - staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8) + assert context.object_bounds().approx_equals( + s2sphere.LatLngRect(staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8)) ) @@ -60,13 +60,13 @@ def test_add_greater_line_extends_bounds() -> None: context.add_object(staticmaps.Line([staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8)])) assert context.object_bounds() is not None - assert context.object_bounds() == s2sphere.LatLngRect( - staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8) + assert context.object_bounds().approx_equals( + s2sphere.LatLngRect(staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8)) ) context.add_object(staticmaps.Line([staticmaps.create_latlng(46, 6), staticmaps.create_latlng(49, 9)])) assert context.object_bounds() is not None - assert context.object_bounds() == s2sphere.LatLngRect( - staticmaps.create_latlng(46, 6), staticmaps.create_latlng(49, 9) + assert context.object_bounds().approx_equals( + s2sphere.LatLngRect(staticmaps.create_latlng(46, 6), staticmaps.create_latlng(49, 9)) ) @@ -76,13 +76,13 @@ def test_add_smaller_line_keeps_bounds() -> None: context.add_object(staticmaps.Line([staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8)])) assert context.object_bounds() is not None - assert context.object_bounds() == s2sphere.LatLngRect( - staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8) + assert context.object_bounds().approx_equals( + s2sphere.LatLngRect(staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8)) ) context.add_object(staticmaps.Line([staticmaps.create_latlng(47.5, 7.5), staticmaps.create_latlng(48, 8)])) assert context.object_bounds() is not None - assert context.object_bounds() == s2sphere.LatLngRect( - staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8) + assert context.object_bounds().approx_equals( + s2sphere.LatLngRect(staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8)) ) @@ -97,20 +97,20 @@ def test_bounds() -> None: context.add_object(staticmaps.Marker(staticmaps.create_latlng(47, 7))) assert context.object_bounds() is not None - assert context.object_bounds() == s2sphere.LatLngRect( - staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8) + assert context.object_bounds().approx_equals( + s2sphere.LatLngRect(staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8)) ) context.add_object(staticmaps.Marker(staticmaps.create_latlng(47.5, 7.5))) assert context.object_bounds() is not None - assert context.object_bounds() == s2sphere.LatLngRect( - staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8) + assert context.object_bounds().approx_equals( + s2sphere.LatLngRect(staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8)) ) context.add_object(staticmaps.Bounds([staticmaps.create_latlng(46, 6), staticmaps.create_latlng(49, 9)])) assert context.object_bounds() is not None - assert context.object_bounds() == s2sphere.LatLngRect( - staticmaps.create_latlng(46, 6), staticmaps.create_latlng(49, 9) + assert context.object_bounds().approx_equals( + s2sphere.LatLngRect(staticmaps.create_latlng(46, 6), staticmaps.create_latlng(49, 9)) ) @@ -123,8 +123,8 @@ def test_add_greater_custom_bound_extends_bounds() -> None: assert context.object_bounds() is not None context.add_object(staticmaps.Bounds([staticmaps.create_latlng(49, 7.5), staticmaps.create_latlng(49, 8)])) - assert context.object_bounds() == s2sphere.LatLngRect( - staticmaps.create_latlng(47, 7), staticmaps.create_latlng(49, 8) + assert context.object_bounds().approx_equals( + s2sphere.LatLngRect(staticmaps.create_latlng(47, 7), staticmaps.create_latlng(49, 8)) ) @@ -138,8 +138,8 @@ def test_add_smaller_custom_bound_keeps_bounds() -> None: context.add_object(staticmaps.Bounds([staticmaps.create_latlng(47.5, 7.5), staticmaps.create_latlng(48, 8)])) assert context.object_bounds() is not None - assert context.object_bounds() == s2sphere.LatLngRect( - staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8) + assert context.object_bounds().approx_equals( + s2sphere.LatLngRect(staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8)) ) From 46726fbf183e7c2b67d12a248cf65f1bd34703ba Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 6 Jun 2024 12:27:43 +0200 Subject: [PATCH 138/147] mypy ignore --- tests/test_context.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/test_context.py b/tests/test_context.py index 5a8331c..9d4aaa3 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -17,7 +17,7 @@ def test_add_marker_adds_bounds_is_point() -> None: context.add_object(staticmaps.Marker(staticmaps.create_latlng(48, 8))) assert context.object_bounds() is not None - assert context.object_bounds().approx_equals( + assert context.object_bounds().approx_equals( # type: ignore[union-attr] s2sphere.LatLngRect(staticmaps.create_latlng(48, 8), staticmaps.create_latlng(48, 8)) ) bounds = context.object_bounds() @@ -31,11 +31,11 @@ def test_add_two_markers_adds_bounds_is_not_point() -> None: context.add_object(staticmaps.Marker(staticmaps.create_latlng(47, 7))) assert context.object_bounds() is not None - assert context.object_bounds().approx_equals( + assert context.object_bounds().approx_equals( # type: ignore[union-attr] s2sphere.LatLngRect(staticmaps.create_latlng(47, 7), staticmaps.create_latlng(47, 7)) ) context.add_object(staticmaps.Marker(staticmaps.create_latlng(48, 8))) - assert context.object_bounds().approx_equals( + assert context.object_bounds().approx_equals( # type: ignore[union-attr] s2sphere.LatLngRect(staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8)) ) bounds = context.object_bounds() @@ -49,7 +49,7 @@ def test_add_line_adds_bounds_is_rect() -> None: context.add_object(staticmaps.Line([staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8)])) assert context.object_bounds() is not None - assert context.object_bounds().approx_equals( + assert context.object_bounds().approx_equals( # type: ignore[union-attr] s2sphere.LatLngRect(staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8)) ) @@ -60,12 +60,12 @@ def test_add_greater_line_extends_bounds() -> None: context.add_object(staticmaps.Line([staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8)])) assert context.object_bounds() is not None - assert context.object_bounds().approx_equals( + assert context.object_bounds().approx_equals( # type: ignore[union-attr] s2sphere.LatLngRect(staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8)) ) context.add_object(staticmaps.Line([staticmaps.create_latlng(46, 6), staticmaps.create_latlng(49, 9)])) assert context.object_bounds() is not None - assert context.object_bounds().approx_equals( + assert context.object_bounds().approx_equals( # type: ignore[union-attr] s2sphere.LatLngRect(staticmaps.create_latlng(46, 6), staticmaps.create_latlng(49, 9)) ) @@ -76,12 +76,12 @@ def test_add_smaller_line_keeps_bounds() -> None: context.add_object(staticmaps.Line([staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8)])) assert context.object_bounds() is not None - assert context.object_bounds().approx_equals( + assert context.object_bounds().approx_equals( # type: ignore[union-attr] s2sphere.LatLngRect(staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8)) ) context.add_object(staticmaps.Line([staticmaps.create_latlng(47.5, 7.5), staticmaps.create_latlng(48, 8)])) assert context.object_bounds() is not None - assert context.object_bounds().approx_equals( + assert context.object_bounds().approx_equals( # type: ignore[union-attr] s2sphere.LatLngRect(staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8)) ) @@ -97,19 +97,19 @@ def test_bounds() -> None: context.add_object(staticmaps.Marker(staticmaps.create_latlng(47, 7))) assert context.object_bounds() is not None - assert context.object_bounds().approx_equals( + assert context.object_bounds().approx_equals( # type: ignore[union-attr] s2sphere.LatLngRect(staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8)) ) context.add_object(staticmaps.Marker(staticmaps.create_latlng(47.5, 7.5))) assert context.object_bounds() is not None - assert context.object_bounds().approx_equals( + assert context.object_bounds().approx_equals( # type: ignore[union-attr] s2sphere.LatLngRect(staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8)) ) context.add_object(staticmaps.Bounds([staticmaps.create_latlng(46, 6), staticmaps.create_latlng(49, 9)])) assert context.object_bounds() is not None - assert context.object_bounds().approx_equals( + assert context.object_bounds().approx_equals( # type: ignore[union-attr] s2sphere.LatLngRect(staticmaps.create_latlng(46, 6), staticmaps.create_latlng(49, 9)) ) @@ -123,7 +123,7 @@ def test_add_greater_custom_bound_extends_bounds() -> None: assert context.object_bounds() is not None context.add_object(staticmaps.Bounds([staticmaps.create_latlng(49, 7.5), staticmaps.create_latlng(49, 8)])) - assert context.object_bounds().approx_equals( + assert context.object_bounds().approx_equals( # type: ignore[union-attr] s2sphere.LatLngRect(staticmaps.create_latlng(47, 7), staticmaps.create_latlng(49, 8)) ) @@ -138,7 +138,7 @@ def test_add_smaller_custom_bound_keeps_bounds() -> None: context.add_object(staticmaps.Bounds([staticmaps.create_latlng(47.5, 7.5), staticmaps.create_latlng(48, 8)])) assert context.object_bounds() is not None - assert context.object_bounds().approx_equals( + assert context.object_bounds().approx_equals( # type: ignore[union-attr] s2sphere.LatLngRect(staticmaps.create_latlng(47, 7), staticmaps.create_latlng(48, 8)) ) From 6f95a25acfc0ca344cb5d0ecfab246c4539f8481 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 6 Jun 2024 12:33:48 +0200 Subject: [PATCH 139/147] update to node.js 20 --- .github/workflows/main.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2198d8d..7b20674 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,9 +13,9 @@ jobs: steps: - name: Git config run: git config --global core.autocrlf input - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python v${{matrix.python-version}} - ${{runner.os}} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{matrix.python-version}} cache: pip @@ -50,9 +50,9 @@ jobs: run: sudo apt-get install libcairo2-dev - name: Git config run: git config --global core.autocrlf input - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python v${{matrix.python-version}} - ${{runner.os}} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{matrix.python-version}} cache: pip @@ -91,7 +91,7 @@ jobs: needs: "build" if: github.ref == 'refs/heads/main' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup git and push to remote From ad29e2d4150d02450b0bfb0781ce49e95a37af7c Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 6 Jun 2024 12:44:18 +0200 Subject: [PATCH 140/147] update to node.js 20 --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7b20674..547adf7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -78,7 +78,7 @@ jobs: (ls *cairo*.png && mv *cairo*.png build/.) || echo "no cairo png files found!" cd - - name: Archive examples - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: build_examples path: examples/build @@ -101,7 +101,7 @@ jobs: - name: Git clone run: git clone https://github.com/lowtower/py-staticmaps --branch assets assets - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: build_examples - name: Move pictures and commit From c073df76deb426c99f06fd1fff1aeedd07e82526 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 6 Jun 2024 13:11:48 +0200 Subject: [PATCH 141/147] remove reference to Stamen --- examples/frankfurt_newyork.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/frankfurt_newyork.py b/examples/frankfurt_newyork.py index af000d5..ba8c2d2 100644 --- a/examples/frankfurt_newyork.py +++ b/examples/frankfurt_newyork.py @@ -47,7 +47,7 @@ svg_image.write(f, pretty=True) context2 = staticmaps.Context() -context2.set_tile_provider(staticmaps.tile_provider_StamenToner) +context2.set_tile_provider(staticmaps.tile_provider_CartoDarkNoLabels) context2.add_object(staticmaps.Bounds([frankfurt, newyork])) # render svg From e252520578cb001d85f5ace2acf5062a563b60d8 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Thu, 6 Jun 2024 14:55:00 +0200 Subject: [PATCH 142/147] commit message in workflow --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 547adf7..a036b35 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -115,7 +115,7 @@ jobs: git pull git add *.png git add *.svg - git commit -m "Automatic update of example image files" + git commit -m "Automatic update of example image files `date +\"%d-%m-%Y %T\"`" git status pwd ls -lrt From a98e15046b4aae2a0874bb68abf64f784e47e990 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 10 Jun 2024 09:57:02 +0200 Subject: [PATCH 143/147] make use of GitHub secrets for api access tokens --- .github/workflows/main.yml | 3 +++ examples/tile_providers.py | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a036b35..fdbf928 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -63,6 +63,9 @@ jobs: pip install -r requirements-cairo.txt pip install -r requirements-examples.txt - name: Build examples + env: + API_KEY_JAWG: ${{ secrets.API_KEY_JAWG }} + API_KEY_STADIA: ${{ secrets.API_KEY_STADIA }} run: | cd examples mkdir build diff --git a/examples/tile_providers.py b/examples/tile_providers.py index eab3adc..ba5c0cf 100644 --- a/examples/tile_providers.py +++ b/examples/tile_providers.py @@ -3,6 +3,8 @@ """py-staticmaps - Example Tile Providers""" # Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information +import os + import staticmaps context = staticmaps.Context() @@ -17,6 +19,17 @@ context.add_object(staticmaps.Marker(p3, color=staticmaps.YELLOW)) for name, provider in staticmaps.default_tile_providers.items(): + # Jawg and Stadia require access tokens + if "jawg" in provider.name(): + if os.environ["API_KEY_JAWG"]: + provider.set_api_key(os.environ["API_KEY_JAWG"]) + else: + continue + if "stadia" in provider.name(): + if os.environ["API_KEY_STADIA"]: + provider.set_api_key(os.environ["API_KEY_STADIA"]) + else: + continue context.set_tile_provider(provider) # render png via pillow From 1163bd5470fab45d9d6c7c056a12f2b2aee82fb5 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 10 Jun 2024 09:57:02 +0200 Subject: [PATCH 144/147] make use of GitHub secrets for api access tokens --- examples/tile_providers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/tile_providers.py b/examples/tile_providers.py index ba5c0cf..d41345a 100644 --- a/examples/tile_providers.py +++ b/examples/tile_providers.py @@ -21,13 +21,13 @@ for name, provider in staticmaps.default_tile_providers.items(): # Jawg and Stadia require access tokens if "jawg" in provider.name(): - if os.environ["API_KEY_JAWG"]: - provider.set_api_key(os.environ["API_KEY_JAWG"]) + if "API_KEY_JAWG" in os.environ: + provider.set_api_key(os.environ.get("API_KEY_JAWG")) else: continue if "stadia" in provider.name(): - if os.environ["API_KEY_STADIA"]: - provider.set_api_key(os.environ["API_KEY_STADIA"]) + if "API_KEY_STADIA" in os.environ: + provider.set_api_key(os.environ.get("API_KEY_STADIA")) else: continue context.set_tile_provider(provider) From 182996b062524385355889da2b71cca515815847 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 10 Jun 2024 09:57:02 +0200 Subject: [PATCH 145/147] make use of GitHub secrets for api access tokens --- examples/tile_providers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/tile_providers.py b/examples/tile_providers.py index ba5c0cf..7553777 100644 --- a/examples/tile_providers.py +++ b/examples/tile_providers.py @@ -21,13 +21,13 @@ for name, provider in staticmaps.default_tile_providers.items(): # Jawg and Stadia require access tokens if "jawg" in provider.name(): - if os.environ["API_KEY_JAWG"]: - provider.set_api_key(os.environ["API_KEY_JAWG"]) + if "API_KEY_JAWG" in os.environ: + provider.set_api_key(os.environ.get("API_KEY_JAWG")) # type: ignore else: continue if "stadia" in provider.name(): - if os.environ["API_KEY_STADIA"]: - provider.set_api_key(os.environ["API_KEY_STADIA"]) + if "API_KEY_STADIA" in os.environ: + provider.set_api_key(os.environ.get("API_KEY_STADIA")) # type: ignore else: continue context.set_tile_provider(provider) From 6a0bb5d84c36b6fee5296b0eb0b7a2576f5b1108 Mon Sep 17 00:00:00 2001 From: Lowtower Date: Mon, 10 Jun 2024 10:41:11 +0200 Subject: [PATCH 146/147] bugfix: link to image gpx --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6e4923c..b5f878a 100644 --- a/README.md +++ b/README.md @@ -196,9 +196,9 @@ with open("draw_gpx.tight.svg", "w", encoding="utf-8") as f: svg_image.write(f, pretty=True) ``` #### Cairo example -![draw_gpx](../assets/draw_gpx.cairo.png?raw=true) +![draw_gpx](../assets/running.cairo.png?raw=true) #### SVG tight example -![draw_gpx_svg_tight](../assets/draw_gpx.tight.svg?raw=true) +![draw_gpx_svg_tight](../assets/running.tight.svg?raw=true) ### US State Capitals From fc5b0dc919bce85c29f266e65ba94a5f459f38ab Mon Sep 17 00:00:00 2001 From: Lowtower Date: Wed, 29 Oct 2025 12:56:11 +0100 Subject: [PATCH 147/147] optimise custom bounds example --- Makefile | 5 +++-- examples/frankfurt_newyork.py | 15 +++++++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 56c2a25..78f1a1f 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,6 @@ SRC_TEST=tests SRC_EXAMPLES=examples SRC_COMPLETE=$(SRC_CORE) $(SRC_TEST) $(SRC_EXAMPLES) docs/gen_ref_pages.py PYTHON=python3 -PIP=$(PYTHON) -m pip help: ## Print help for each target $(info Makefile low-level Python API.) @@ -16,6 +15,7 @@ help: ## Print help for each target | sort | awk 'BEGIN {FS=":.* ## "}; {printf "%-25s %s\n", $$1, $$2};' clean: ## Cleanup + @rm -rf ./.env @rm -f ./*.pyc @rm -rf ./__pycache__ @rm -f $(SRC_CORE)/*.pyc @@ -81,6 +81,7 @@ format: ## Format the code .PHONY: run-examples run-examples: ## Generate example images + (cd examples && rm -r build) (cd examples && PYTHONPATH=.. ../.env/bin/python custom_objects.py) (cd examples && PYTHONPATH=.. ../.env/bin/python draw_gpx.py running.gpx) (cd examples && PYTHONPATH=.. ../.env/bin/python frankfurt_newyork.py) @@ -119,5 +120,5 @@ upload-package: ## Upload package .PHONY: documentation documentation: ## Generate documentation @if type mkdocs >/dev/null 2>&1 ; then .env/bin/python -m mkdocs build --clean --verbose ; \ - else echo "SKIPPED. Run '$(PIP) install mkdocs' first." >&2 ; fi + else echo "SKIPPED. Run '.env/bin/python -m install mkdocs' first." >&2 ; fi diff --git a/examples/frankfurt_newyork.py b/examples/frankfurt_newyork.py index ba8c2d2..9d375e4 100644 --- a/examples/frankfurt_newyork.py +++ b/examples/frankfurt_newyork.py @@ -1,15 +1,20 @@ #!/usr/bin/env python """py-staticmaps - Example Frankfurt-New York""" -# Copyright (c) 2020 Florian Pigorsch; see /LICENSE for licensing information +# Copyright (c) 2020-2025 Florian Pigorsch & Contributors. All rights reserved. +# +# Use of this source code is governed by a MIT-style +# license that can be found in the LICENSE file. import staticmaps context = staticmaps.Context() context.set_tile_provider(staticmaps.tile_provider_ArcGISWorldImagery) +warsaw = staticmaps.create_latlng(52.233207, 21.061419) frankfurt = staticmaps.create_latlng(50.110644, 8.682092) newyork = staticmaps.create_latlng(40.712728, -74.006015) +los_angeles = staticmaps.create_latlng(33.999099, -118.411735) context.add_object(staticmaps.Line([frankfurt, newyork], color=staticmaps.BLUE, width=4)) context.add_object(staticmaps.Marker(frankfurt, color=staticmaps.GREEN, size=12)) @@ -48,15 +53,17 @@ context2 = staticmaps.Context() context2.set_tile_provider(staticmaps.tile_provider_CartoDarkNoLabels) -context2.add_object(staticmaps.Bounds([frankfurt, newyork])) +context2.add_object(staticmaps.Marker(frankfurt, color=staticmaps.GREEN, size=12)) +context2.add_object(staticmaps.Marker(newyork, color=staticmaps.RED, size=12)) +context2.add_object(staticmaps.Bounds([warsaw, los_angeles])) # render svg svg_image = context2.render_svg(800, 500) -with open("frankfurt_newyork.bounds.svg", "w", encoding="utf-8") as f: +with open("frankfurt_newyork.warsaw_los_angeles_bounds.svg", "w", encoding="utf-8") as f: svg_image.write(f, pretty=True) # render svg - tight boundaries context2.set_tighten_to_bounds(True) svg_image = context2.render_svg(800, 500) -with open("frankfurt_newyork.bounds.tight.svg", "w", encoding="utf-8") as f: +with open("frankfurt_newyork.warsaw_los_angeles_bounds.tight.svg", "w", encoding="utf-8") as f: svg_image.write(f, pretty=True)