From b6667a3abef43687930d883622637a2804acd5ef Mon Sep 17 00:00:00 2001 From: Marc Sommerhalder Date: Wed, 17 Sep 2025 10:34:12 +0200 Subject: [PATCH 1/6] PB-1968: Drop Python 2 support --- README.md | 77 +++++++++-------------------------------- buildspec.yml | 58 ------------------------------- gatilegrid/grid.py | 7 ++-- gatilegrid/tilegrids.py | 19 +++++----- setup.py | 22 ++++-------- tests/__init__.py | 1 - tests/test_grid.py | 2 -- tests/test_tilegrids.py | 2 -- 8 files changed, 33 insertions(+), 155 deletions(-) delete mode 100644 buildspec.yml diff --git a/README.md b/README.md index e5796b5..9032795 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,6 @@ -gatilegrid -=========== +# gatilegrid -![Build Status](https://codebuild.eu-central-1.amazonaws.com/badges?uuid=eyJlbmNyeXB0ZWREYXRhIjoiY1JweHpoMGV5cndNTjFRT1JXc0tNK0tyK0NSL3NoVWhXK3BFNU9DZFBEc3M5clN4RzdZMDc1czk5VUJiV1RFSk5YR0lFR1g4dVBES0FTMmpyVTNCcU0wPSIsIml2UGFyYW1ldGVyU3BlYyI6Im5qQWZtZ0lTWDFIelk0cG0iLCJtYXRlcmlhbFNldFNlcmlhbCI6MX0%3D&branch=master) - -## Geoadmin custom tile grid for web mapping applications - -gatilegrid is compatible with python 2.7, 3.5, 3.6, 3.7 and 3.8 +Geoadmin custom tile grid for web mapping applications. ## Installation @@ -13,7 +8,7 @@ gatilegrid is compatible with python 2.7, 3.5, 3.6, 3.7 and 3.8 $ pip install gatilegrid ``` -### Usage +## Usage Several tile grids are supported, namely 21781, 2056, 3857 and 4326. Here is an exemple using 21781. For 4326, an additional parameter is available (`tmsCompatible=True`). @@ -155,65 +150,25 @@ print(maxRow) >>> 1959 ``` -### Tests - -``` -source .venv/bin/activate -python setup.py test - -``` - -### Publish a new version of the module - -Edit `$HOME/.pypirc` and add (username and password in keepass): - -``` -[distutils] -index-servers = - pypi - pypitest - -[pypi] -repository=https://upload.pypi.org/legacy/ -username=iwi*** -password= - -[pypitest] -repository=https://test.pypi.org/legacy/ -username=iwi*** -password= -``` - -Bump version in `setup.py`. +## Local Development -Build, check and upload the new module to the test repository: +### Setup -``` -pip install --upgrade twine wheel setuptools -python setup.py sdist bdist_wheel -twine upload --repository testpypi dist/* +```bash +python -m venv .venv +source .venv/bin/activate +pip install . +pip install nose2 flake8 ``` -Test local install from test repository. +### Tests -``` -pip install -i https://test.pypi.org/simple/ gatilegrid +```bash +nose2 tests ``` -If everything is ok, push the new version to the default repository. +### Linting +```bash +flake8 --ignore=F401 gatilegrid tests ``` -twine upload --repository pypi dist/* -``` - -Test the newly created module. - -Create a RELEASE in github. - -#### CONTRIBUTORS: - -- [Loic Gasser](https://github.com/loicgasser) -- [Marc Monnerat](https://github.com/procrastinatio) -- [Nadine Piveteau](https://github.com/nadine-piveteau) -- [Marcel Clausen](https://github.com/ltclm) -- [Gilbert Jeinziner](https://github.com/gjn) diff --git a/buildspec.yml b/buildspec.yml deleted file mode 100644 index 186c65a..0000000 --- a/buildspec.yml +++ /dev/null @@ -1,58 +0,0 @@ -version: 0.2 - -env: - shell: bash - variables: - SHELL: /bin/bash - AWS_DEFAULT_REGION: eu-central-1 - USER: "aws_code_build" - -phases: - install: - runtime-versions: - python: 3.7 - commands: - - apt-get update -qq - - apt-get install -yqq python3-pip python2.7 python2.7-dev python3.8 python3.9 - - build: - commands: - # For python 2.7 we need to have a virtualenv in order to have pip and to install the packages - - virtualenv -p python2.7 .venv - # each command in buildspec is run in a separate shell, therefore below we put the whole - # python2.7 into one command in order to activate the venv in this shell and run the test - # inside the venv - - | - . .venv/bin/activate - python --version - pip install -qq flake8 future nose coveralls - echo "Runs flake8..." - python -m flake8 --ignore=F401 gatilegrid tests - echo "Runs coverage" - python -m coverage run --source=gatilegrid setup.py test - python -m coverage report -m - deactivate - # Python 3.7 - - python3.7 --version - - python3.7 -m pip install -qq flake8 future nose coveralls - - echo "Runs flake8..." - - python3.7 -m flake8 --ignore=F401 gatilegrid tests - - echo "Runs coverage" - - python3.7 -m coverage run --source=gatilegrid setup.py test - - python3.7 -m coverage report -m - # Python 3.8 - - python3.8 --version - - python3.8 -m pip install -qq flake8 future nose coveralls - - echo "Runs flake8..." - - python3.8 -m flake8 --ignore=F401 gatilegrid tests - - echo "Runs coverage" - - python3.8 -m coverage run --source=gatilegrid setup.py test - - python3.8 -m coverage report -m - # Python 3.9 - - python3.9 --version - - python3.9 -m pip install -qq flake8 future nose coveralls - - echo "Runs flake8..." - - python3.9 -m flake8 --ignore=F401 gatilegrid tests - - echo "Runs coverage" - - python3.9 -m coverage run --source=gatilegrid setup.py test - - python3.9 -m coverage report -m diff --git a/gatilegrid/grid.py b/gatilegrid/grid.py index 0ad47d2..51a74b9 100644 --- a/gatilegrid/grid.py +++ b/gatilegrid/grid.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- - import math -from past.builtins import xrange class Grid: @@ -34,8 +31,8 @@ def __init__(self, extent, resolutionX, resolutionY): self._setExtentAddress() def __iter__(self): - for col in xrange(0, self.nbCellsX): - for row in xrange(0, self.nbCellsY): + for col in range(0, self.nbCellsX): + for row in range(0, self.nbCellsY): cellExtent = self.cellExtent(col, row) yield (cellExtent, col, row) diff --git a/gatilegrid/tilegrids.py b/gatilegrid/tilegrids.py index 610615d..8148e1b 100644 --- a/gatilegrid/tilegrids.py +++ b/gatilegrid/tilegrids.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- - import math -from past.builtins import xrange EPSG4326_METERS_PER_UNIT = math.pi * 6378137 / 180 @@ -257,10 +254,10 @@ def iterGrid(self, minZoom, maxZoom): assert maxZoom in range(0, len(self.RESOLUTIONS)) assert minZoom <= maxZoom - for zoom in xrange(minZoom, maxZoom + 1): + for zoom in range(minZoom, maxZoom + 1): [minRow, minCol, maxRow, maxCol] = self.getExtentAddress(zoom) - for row in xrange(minRow, maxRow + 1): - for col in xrange(minCol, maxCol + 1): + for row in range(minRow, maxRow + 1): + for col in range(minCol, maxCol + 1): tileBounds = self.tileBounds(zoom, col, row) yield (tileBounds, zoom, col, row) @@ -287,7 +284,7 @@ def totalNumberOfTiles(self, minZoom=None, maxZoom=None): maxZoom = maxZoom + 1 else: maxZoom = len(self.RESOLUTIONS) - for zoom in xrange(minZoom, maxZoom): + for zoom in range(minZoom, maxZoom): nbTiles += self.numberOfTilesAtZoom(zoom) return nbTiles @@ -443,7 +440,7 @@ def __init__(self, originCorner='top-left', useSwissExtent=True): - super(GeoadminTileGridLV03, self).__init__( + super().__init__( extent=extent, tileSizePx=tileSizePx, originCorner=originCorner, @@ -459,7 +456,7 @@ def __init__(self, originCorner='top-left', useSwissExtent=True): - super(GeoadminTileGridLV95, self).__init__( + super().__init__( extent=extent, tileSizePx=tileSizePx, originCorner=originCorner, @@ -475,7 +472,7 @@ def __init__(self, originCorner='top-left', useSwissExtent=True): - super(GlobalMercatorTileGrid, self).__init__( + super().__init__( extent=extent, tileSizePx=tileSizePx, originCorner=originCorner, @@ -492,7 +489,7 @@ def __init__(self, tmsCompatible=True, useSwissExtent=True): - super(GlobalGeodeticTileGrid, self).__init__( + super().__init__( extent=extent, tileSizePx=tileSizePx, originCorner=originCorner, diff --git a/setup.py b/setup.py index 15bce1f..ec5be26 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,3 @@ -# -*- coding: utf-8 -*- - -# HACK for `nose.collector` to work on python 2.7.3 and earlier -import multiprocessing -import os from setuptools import setup @@ -11,7 +6,7 @@ setup(name='gatilegrid', - version='0.2.0', + version='1.0.0', description='Popular tile grids and grids API for web mapping applications', keywords='gis wmts grid map', author='Loic Gasser', @@ -27,20 +22,17 @@ 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', 'Topic :: Scientific/Engineering :: GIS', 'Topic :: Software Development :: Libraries :: Python Modules' ], zip_safe=False, long_description=long_description, long_description_content_type='text/markdown', - test_suite='nose.collector', - install_requires=['future'], - python_requires='>2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4', + install_requires=[], + python_requires='>=3.10', ) diff --git a/tests/__init__.py b/tests/__init__.py index 40a96af..e69de29 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff --git a/tests/test_grid.py b/tests/test_grid.py index 7e3c435..ac3ae41 100644 --- a/tests/test_grid.py +++ b/tests/test_grid.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import unittest from gatilegrid.grid import Grid diff --git a/tests/test_tilegrids.py b/tests/test_tilegrids.py index f67993a..da43806 100644 --- a/tests/test_tilegrids.py +++ b/tests/test_tilegrids.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import math import unittest from gatilegrid import getTileGrid, GeoadminTileGridLV03, \ From a74730f65e07cd37429a5d826f63be5f0db1c70c Mon Sep 17 00:00:00 2001 From: Marc Sommerhalder Date: Wed, 17 Sep 2025 10:43:48 +0200 Subject: [PATCH 2/6] PB-1968: Update build system --- pyproject.toml | 33 +++++++++++++++++++++++++++++++++ setup.cfg | 2 -- setup.py | 38 -------------------------------------- 3 files changed, 33 insertions(+), 40 deletions(-) create mode 100644 pyproject.toml delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b04f5d6 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,33 @@ +[build-system] +requires = ["setuptools", "build"] +build-backend = "setuptools.build_meta" + +[project] +name = "gatilegrid" +version = "1.0.0" +requires-python = ">=3.0" +description = "Popular tile grids and grids API for web mapping applications" +readme = "README.md" +license = {text = "MIT"} +authors = [ + {name = "Loic Gasser", email = "loicgasser4@gmail.com"} +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Topic :: Scientific/Engineering :: GIS", + "Topic :: Software Development :: Libraries :: Python Modules" +] +keywords = ["gis", "wmts", "grid", "map"] + +[project.urls] +Homepage = "https://github.com/geoadmin/lib-gatilegrid" +Documentation = "https://github.com/geoadmin/lib-gatilegrid#readme" +Source = "https://github.com/geoadmin/lib-gatilegrid" +Tracker = "https://github.com/geoadmin/lib-gatilegrid/issues" + +[tool.setuptools.packages.find] +include = ["gatilegrid*"] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index b88034e..0000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[metadata] -description-file = README.md diff --git a/setup.py b/setup.py deleted file mode 100644 index ec5be26..0000000 --- a/setup.py +++ /dev/null @@ -1,38 +0,0 @@ -from setuptools import setup - - -with open("README.md", "r") as fh: - long_description = fh.read() - - -setup(name='gatilegrid', - version='1.0.0', - description='Popular tile grids and grids API for web mapping applications', - keywords='gis wmts grid map', - author='Loic Gasser', - author_email='loicgasser4@gmail.com', - license='MIT', - url='https://github.com/geoadmin/lib-gatilegrid', - packages=['gatilegrid'], - package_dir={'gatilegrid': 'gatilegrid'}, - include_package_data=True, - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', - 'Programming Language :: Python :: 3.13', - 'Topic :: Scientific/Engineering :: GIS', - 'Topic :: Software Development :: Libraries :: Python Modules' - ], - zip_safe=False, - long_description=long_description, - long_description_content_type='text/markdown', - install_requires=[], - python_requires='>=3.10', - ) From 0029ac78df923cfe74b0896d62ea17eb8d7a7710 Mon Sep 17 00:00:00 2001 From: Marc Sommerhalder Date: Wed, 17 Sep 2025 13:48:07 +0200 Subject: [PATCH 3/6] PB-1968: Add pipenv and makefile --- .coveragerc | 2 + .gitignore | 3 + .isort.cfg | 7 ++ .pylintrc | 29 +++++ .style.yapf | 33 ++++++ Makefile | 153 ++++++++++++++++++++++++ Pipfile | 13 +++ Pipfile.lock | 250 ++++++++++++++++++++++++++++++++++++++++ README.md | 14 +-- gatilegrid/__init__.py | 6 +- gatilegrid/grid.py | 8 +- gatilegrid/tilegrids.py | 50 ++++---- tests/test_grid.py | 1 + tests/test_tilegrids.py | 84 +++++++------- tests/unittest.cfg | 15 +++ 15 files changed, 584 insertions(+), 84 deletions(-) create mode 100644 .coveragerc create mode 100644 .isort.cfg create mode 100644 .pylintrc create mode 100644 .style.yapf create mode 100644 Makefile create mode 100644 Pipfile create mode 100644 Pipfile.lock create mode 100644 tests/unittest.cfg diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..013dd20 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,2 @@ +[report] +show_missing = True diff --git a/.gitignore b/.gitignore index 976e661..12d7720 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,6 @@ target/ # Vim temporary files *.swp + +# test report +nose2-junit.xml diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 0000000..07ed404 --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,7 @@ +[settings] +known_third_party=pytest +known_django=django +known_flask=flask +known_gatilegrid=gatilegrid +force_single_line=True +sections=FUTURE,STDLIB,THIRDPARTY,DJANGO,FLASK,GATILEGRID,FIRSTPARTY,LOCALFOLDER diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..6d7bc71 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,29 @@ +[MAIN] +ignore=.venv + +[MESSAGES CONTROL] +disable= + # no convention and refactor rules + C, + R, + # allow some unconventional stuff (assuming the library already works as intended) + unused-variable, + attribute-defined-outside-init, + unused-import, + possibly-used-before-assignment + +generated-members= + # added via mixins + gatilegrid.tilegrids._TileGrid.MINX + gatilegrid.tilegrids._TileGrid.MAXX + gatilegrid.tilegrids._TileGrid.MINY + gatilegrid.tilegrids._TileGrid.MAXY + gatilegrid.tilegrids._TileGrid.RESOLUTIONS + gatilegrid.tilegrids._TileGrid.spatialReference + gatilegrid.tilegrids._TileGrid.tileAddressTemplate + gatilegrid.tilegrids._TileGrid.unit + gatilegrid.tilegrids._TileGrid.metersPerUnit + gatilegrid.tilegrids._TileGrid. + +[REPORTS] +score = no diff --git a/.style.yapf b/.style.yapf new file mode 100644 index 0000000..22a740b --- /dev/null +++ b/.style.yapf @@ -0,0 +1,33 @@ +[style] +based_on_style=google +# Put closing brackets on a separate line, dedented, if the bracketed +# expression can't fit in a single line. Applies to all kinds of brackets, +# including function definitions and calls. For example: +# +# config = { +# 'key1': 'value1', +# 'key2': 'value2', +# } # <--- this bracket is dedented and on a separate line +# +# time_series = self.remote_client.query_entity_counters( +# entity='dev3246.region1', +# key='dns.query_latency_tcp', +# transform=Transformation.AVERAGE(window=timedelta(seconds=60)), +# start_ts=now()-timedelta(days=3), +# end_ts=now(), +# ) # <--- this bracket is dedented and on a separate line +dedent_closing_brackets=True +coalesce_brackets=True + +# This avoid issues with complex dictionary +# see https://github.com/google/yapf/issues/392#issuecomment-407958737 +indent_dictionary_value=True +allow_split_before_dict_value=False + +# Split before arguments, but do not split all sub expressions recursively +# (unless needed). +split_all_top_level_comma_separated_values=True + +# Split lines longer than 100 characters (this only applies to code not to +# comment and docstring) +column_limit=100 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..babd0ae --- /dev/null +++ b/Makefile @@ -0,0 +1,153 @@ +SHELL = /bin/bash + +.DEFAULT_GOAL := help + + +CURRENT_DIR := $(shell pwd) + +# Test reports configuration +TEST_REPORT_DIR ?= $(CURRENT_DIR)/tests/report +TEST_REPORT_FILE ?= nose2-junit.xml + +# general targets timestamps +TIMESTAMPS = .timestamps +REQUIREMENTS := $(TIMESTAMPS) $(PIP_FILE) $(PIP_FILE_LOCK) + +# Find all python files that are not inside a hidden directory (directory starting with .) +PYTHON_FILES := $(shell find ./* -type d \( -path ./build -o -path ./dist \) -prune -false -o -type f -name "*.py" -print) + +# PIPENV files +PIP_FILE = Pipfile +PIP_FILE_LOCK = Pipfile.lock + +# default configuration +ENV_FILE ?= .env.local + +# Commands +PIPENV_RUN := pipenv run +PYTHON := $(PIPENV_RUN) python3 +PIP := $(PIPENV_RUN) pip3 +YAPF := $(PIPENV_RUN) yapf +ISORT := $(PIPENV_RUN) isort +NOSE := $(PIPENV_RUN) nose2 +PYLINT := $(PIPENV_RUN) pylint + +PACKAGE_VERSION = $(shell awk '/^Version:/ {print $$2}' gatilegrid.egg-info/PKG-INFO) + + +all: help + + +.PHONY: help +help: + @echo "Usage: make " + @echo + @echo "Possible targets:" + @echo -e " \033[1mSetup TARGETS\033[0m " + @echo "- setup Create the python virtual environment with developper tools" + @echo -e " \033[1mFORMATING, LINTING AND TESTING TOOLS TARGETS\033[0m " + @echo "- format Format the python source code" + @echo "- lint Lint the python source code" + @echo "- format-lint Format and lint the python source code" + @echo "- test Run the tests" + @echo -e " \033[1mPACKAGING TARGETS\033[0m " + @echo "- package Create package" + @echo "- publish Tag and publish package to PyPI" + @echo -e " \033[1mCLEANING TARGETS\033[0m " + @echo "- clean Clean generated files" + @echo "- clean-venv Clean python venv" + @echo "- clean-all Clean everything" + + +# Build targets. Calling setup is all that is needed for the local files to be installed as needed. + +.PHONY: setup +setup: $(REQUIREMENTS) + pipenv install --dev + +# linting target, calls upon yapf to make sure your code is easier to read and respects some conventions. + +.PHONY: format +format: $(REQUIREMENTS) + $(YAPF) -p -i --style .style.yapf $(PYTHON_FILES) + $(ISORT) $(PYTHON_FILES) + + +.PHONY: ci-check-format +ci-check-format: format + @if [[ -n `git status --porcelain` ]]; then \ + >&2 echo "ERROR: the following files are not formatted correctly:"; \ + >&2 git status --porcelain; \ + exit 1; \ + fi + + +.PHONY: lint +lint: $(REQUIREMENTS) + $(PYLINT) $(PYTHON_FILES) + + +.PHONY: format-lint +format-lint: format lint + + +# Test target + +.PHONY: test +test: $(DEV_REQUIREMENTS_TIMESTAMP) + mkdir -p $(TEST_REPORT_DIR) + $(NOSE) -v -c tests/unittest.cfg --junit-xml-path $(TEST_REPORT_DIR)/$(TEST_REPORT_FILE) -s tests/ + + +# Packaging target + +.PHONY: package +package: $(DEV_REQUIREMENTS_TIMESTAMP) + $(PYTHON) -m build + + +.PHONY: publish +publish: publish-check package + @echo "Upload package version=$(PACKAGE_VERSION)" + $(PYTHON) -m twine upload dist/* + + +# Clean targets + +.PHONY: clean-venv +clean-venv: + pipenv --rm + +.PHONY: clean +clean: clean-venv + @# clean python cache files + find . -name __pycache__ -type d -print0 | xargs -I {} -0 rm -rf "{}" + rm -rf $(TEST_REPORT_DIR) + rm -rf $(TIMESTAMPS) + rm -rf dist + rm -rf build + rm -rf *.egg-info + rm -f .coverage + +.PHONY: clean-all +clean-all: clean + +# Actual builds targets with dependencies + +$(TIMESTAMPS): + mkdir -p $(TIMESTAMPS) + + +$(VENV_TIMESTAMP): + test -d $(VENV) || $(SYSTEM_PYTHON) -m venv $(VENV) && $(PIP) install --upgrade pip setuptools + @touch $(VENV_TIMESTAMP) + + +$(DEV_REQUIREMENTS_TIMESTAMP): $(VENV_TIMESTAMP) $(DEV_REQUIREMENTS) + $(PIP) install -r $(DEV_REQUIREMENTS) + @touch $(DEV_REQUIREMENTS_TIMESTAMP) + + +publish-check: + @echo "Check if publish is allowed" + @if [ -n "`git status --porcelain`" ]; then echo "ERROR: Repo is dirty !" >&2; exit 1; fi diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..3cfae54 --- /dev/null +++ b/Pipfile @@ -0,0 +1,13 @@ +[[source]] +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + +[dev-packages] +flake8 = "*" +nose2 = { version = "*", extras = ["coverage_plugin"] } +yapf = "*" +pylint = "*" +isort = "*" +setuptools = "*" +build = "*" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..a077e10 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,250 @@ +{ + "_meta": { + "hash": { + "sha256": "a2a23c83d187deaae80c01e4a381a82a1678c64ac454d4fa6724e9bfdd762213" + }, + "pipfile-spec": 6, + "requires": {}, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": {}, + "develop": { + "astroid": { + "hashes": [ + "sha256:1e5a5011af2920c7c67a53f65d536d65bfa7116feeaf2354d8b94f29573bb0ce", + "sha256:54c760ae8322ece1abd213057c4b5bba7c49818853fc901ef09719a60dbf9dec" + ], + "markers": "python_full_version >= '3.9.0'", + "version": "==3.3.11" + }, + "build": { + "hashes": [ + "sha256:698edd0ea270bde950f53aed21f3a0135672206f3911e0176261a31e0e07b397", + "sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==1.3.0" + }, + "coverage": { + "hashes": [ + "sha256:073711de3181b2e204e4870ac83a7c4853115b42e9cd4d145f2231e12d670930", + "sha256:081b98395ced0d9bcf60ada7661a0b75f36b78b9d7e39ea0790bb4ed8da14747", + "sha256:0de434f4fbbe5af4fa7989521c655c8c779afb61c53ab561b64dcee6149e4c65", + "sha256:0e93b1476b79eae849dc3872faeb0bf7948fd9ea34869590bc16a2a00b9c82a7", + "sha256:0f3f56e4cb573755e96a16501a98bf211f100463d70275759e73f3cbc00d4f27", + "sha256:0f7cb359a448e043c576f0da00aa8bfd796a01b06aa610ca453d4dde09cc1034", + "sha256:10356fdd33a7cc06e8051413140bbdc6f972137508a3572e3f59f805cd2832fd", + "sha256:137921f2bac5559334ba66122b753db6dc5d1cf01eb7b64eb412bb0d064ef35b", + "sha256:160c00a5e6b6bdf4e5984b0ef21fc860bc94416c41b7df4d63f536d17c38902e", + "sha256:2195f8e16ba1a44651ca684db2ea2b2d4b5345da12f07d9c22a395202a05b23c", + "sha256:282b1b20f45df57cc508c1e033403f02283adfb67d4c9c35a90281d81e5c52c5", + "sha256:28395ca3f71cd103b8c116333fa9db867f3a3e1ad6a084aa3725ae002b6583bc", + "sha256:2904271c80898663c810a6b067920a61dd8d38341244a3605bd31ab55250dad5", + "sha256:2b38261034fda87be356f2c3f42221fdb4171c3ce7658066ae449241485390d5", + "sha256:2e4c33e6378b9d52d3454bd08847a8651f4ed23ddbb4a0520227bd346382bbc6", + "sha256:388d80e56191bf846c485c14ae2bc8898aa3124d9d35903fef7d907780477634", + "sha256:3e23dd5408fe71a356b41baa82892772a4cefcf758f2ca3383d2aa39e1b7a003", + "sha256:3fb99d0786fe17b228eab663d16bee2288e8724d26a199c29325aac4b0319b9b", + "sha256:441c357d55f4936875636ef2cfb3bee36e466dcf50df9afbd398ce79dba1ebb7", + "sha256:4cec13817a651f8804a86e4f79d815b3b28472c910e099e4d5a0e8a3b6a1d4cb", + "sha256:5aea98383463d6e1fa4e95416d8de66f2d0cb588774ee20ae1b28df826bcb619", + "sha256:5b15a87265e96307482746d86995f4bff282f14b027db75469c446da6127433b", + "sha256:5b2dd6059938063a2c9fee1af729d4f2af28fd1a545e9b7652861f0d752ebcea", + "sha256:5e75e37f23eb144e78940b40395b42f2321951206a4f50e23cfd6e8a198d3ceb", + "sha256:6008a021907be8c4c02f37cdc3ffb258493bdebfeaf9a839f9e71dfdc47b018e", + "sha256:61c950fc33d29c91b9e18540e1aed7d9f6787cc870a3e4032493bbbe641d12fc", + "sha256:628055297f3e2aa181464c3808402887643405573eb3d9de060d81531fa79d32", + "sha256:675824a363cc05781b1527b39dc2587b8984965834a748177ee3c37b64ffeafb", + "sha256:689920ecfd60f992cafca4f5477d55720466ad2c7fa29bb56ac8d44a1ac2b47a", + "sha256:692d70ea725f471a547c305f0d0fc6a73480c62fb0da726370c088ab21aed282", + "sha256:6937347c5d7d069ee776b2bf4e1212f912a9f1f141a429c475e6089462fcecc5", + "sha256:6b3039e2ca459a70c79523d39347d83b73f2f06af5624905eba7ec34d64d80b5", + "sha256:6e31b8155150c57e5ac43ccd289d079eb3f825187d7c66e755a055d2c85794c6", + "sha256:70e7bfbd57126b5554aa482691145f798d7df77489a177a6bef80de78860a356", + "sha256:752a3005a1ded28f2f3a6e8787e24f28d6abe176ca64677bcd8d53d6fe2ec08a", + "sha256:7d79dabc0a56f5af990cc6da9ad1e40766e82773c075f09cc571e2076fef882e", + "sha256:7eb68d356ba0cc158ca535ce1381dbf2037fa8cb5b1ae5ddfc302e7317d04144", + "sha256:80b1695cf7c5ebe7b44bf2521221b9bb8cdf69b1f24231149a7e3eb1ae5fa2fb", + "sha256:851430a9a361c7a8484a36126d1d0ff8d529d97385eacc8dfdc9bfc8c2d2cbe4", + "sha256:856986eadf41f52b214176d894a7de05331117f6035a28ac0016c0f63d887629", + "sha256:86b9b59f2b16e981906e9d6383eb6446d5b46c278460ae2c36487667717eccf1", + "sha256:8953746d371e5695405806c46d705a3cd170b9cc2b9f93953ad838f6c1e58612", + "sha256:8cdbe264f11afd69841bd8c0d83ca10b5b32853263ee62e6ac6a0ab63895f972", + "sha256:8dd5af36092430c2b075cee966719898f2ae87b636cefb85a653f1d0ba5d5393", + "sha256:8e0c38dc289e0508ef68ec95834cb5d2e96fdbe792eaccaa1bccac3966bbadcc", + "sha256:90558c35af64971d65fbd935c32010f9a2f52776103a259f1dee865fe8259352", + "sha256:90cb5b1a4670662719591aa92d0095bb41714970c0b065b02a2610172dbf0af6", + "sha256:92be86fcb125e9bda0da7806afd29a3fd33fdf58fba5d60318399adf40bf37d0", + "sha256:92c4ecf6bf11b2e85fd4d8204814dc26e6a19f0c9d938c207c5cb0eadfcabbe3", + "sha256:95d91d7317cde40a1c249d6b7382750b7e6d86fad9d8eaf4fa3f8f44cf171e80", + "sha256:961834e2f2b863a0e14260a9a273aff07ff7818ab6e66d2addf5628590c628f9", + "sha256:9702b59d582ff1e184945d8b501ffdd08d2cee38d93a2206aa5f1365ce0b8d78", + "sha256:98cede73eb83c31e2118ae8d379c12e3e42736903a8afcca92a7218e1f2903b0", + "sha256:99c4283e2a0e147b9c9cc6bc9c96124de9419d6044837e9799763a0e29a7321a", + "sha256:99e1a305c7765631d74b98bf7dbf54eeea931f975e80f115437d23848ee8c27c", + "sha256:a517feaf3a0a3eca1ee985d8373135cfdedfbba3882a5eab4362bda7c7cf518d", + "sha256:a80f7aef9535442bdcf562e5a0d5a5538ce8abe6bb209cfbf170c462ac2c2a32", + "sha256:ac765b026c9f33044419cbba1da913cfb82cca1b60598ac1c7a5ed6aac4621a0", + "sha256:acf36b8268785aad739443fa2780c16260ee3fa09d12b3a70f772ef100939d80", + "sha256:adec1d980fa07e60b6ef865f9e5410ba760e4e1d26f60f7e5772c73b9a5b0713", + "sha256:b0353b0f0850d49ada66fdd7d0c7cdb0f86b900bb9e367024fd14a60cecc1e27", + "sha256:b37201ce4a458c7a758ecc4efa92fa8ed783c66e0fa3c42ae19fc454a0792153", + "sha256:bf9a19f5012dab774628491659646335b1928cfc931bf8d97b0d5918dd58033c", + "sha256:c61fc91ab80b23f5fddbee342d19662f3d3328173229caded831aa0bd7595460", + "sha256:c68018e4fc4e14b5668f1353b41ccf4bc83ba355f0e1b3836861c6f042d89ac1", + "sha256:c706db3cabb7ceef779de68270150665e710b46d56372455cd741184f3868d8f", + "sha256:c83f6afb480eae0313114297d29d7c295670a41c11b274e6bca0c64540c1ce7b", + "sha256:c8a3ec16e34ef980a46f60dc6ad86ec60f763c3f2fa0db6d261e6e754f72e945", + "sha256:c9a8b7a34a4de3ed987f636f71881cd3b8339f61118b1aa311fbda12741bff0b", + "sha256:cd4b2b0707fc55afa160cd5fc33b27ccbf75ca11d81f4ec9863d5793fc6df56a", + "sha256:d6b9ae13d5d3e8aeca9ca94198aa7b3ebbc5acfada557d724f2a1f03d2c0b0df", + "sha256:d8fd7879082953c156d5b13c74aa6cca37f6a6f4747b39538504c3f9c63d043d", + "sha256:d9369a23186d189b2fc95cc08b8160ba242057e887d766864f7adf3c46b2df21", + "sha256:db4a1d897bbbe7339946ffa2fe60c10cc81c43fab8b062d3fcb84188688174a4", + "sha256:df4ec1f8540b0bcbe26ca7dd0f541847cc8a108b35596f9f91f59f0c060bfdd2", + "sha256:e132b9152749bd33534e5bd8565c7576f135f157b4029b975e15ee184325f528", + "sha256:e3fb1fa01d3598002777dd259c0c2e6d9d5e10e7222976fc8e03992f972a2cba", + "sha256:e41be6f0f19da64af13403e52f2dec38bbc2937af54df8ecef10850ff8d35301", + "sha256:ec98435796d2624d6905820a42f82149ee9fc4f2d45c2c5bc5a44481cc50db62", + "sha256:efeda443000aa23f276f4df973cb82beca682fd800bb119d19e80504ffe53ec2", + "sha256:f2a6a8e06bbda06f78739f40bfb56c45d14eb8249d0f0ea6d4b3d48e1f7c695d", + "sha256:f32ff80e7ef6a5b5b606ea69a36e97b219cd9dc799bcf2963018a4d8f788cfbf", + "sha256:f35ed9d945bece26553d5b4c8630453169672bea0050a564456eb88bdffd927e", + "sha256:f644a3ae5933a552a29dbb9aa2f90c677a875f80ebea028e5a52a4f429044b90", + "sha256:f863c08f4ff6b64fa8045b1e3da480f5374779ef187f07b82e0538c68cb4ff8e", + "sha256:fc53ba868875bfbb66ee447d64d6413c2db91fddcfca57025a0e7ab5b07d5862", + "sha256:ff8a991f70f4c0cf53088abf1e3886edcc87d53004c7bb94e78650b4d3dac3b5", + "sha256:ffea0575345e9ee0144dfe5701aa17f3ba546f8c3bb48db62ae101afb740e7d6" + ], + "markers": "python_version >= '3.9'", + "version": "==7.10.6" + }, + "dill": { + "hashes": [ + "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0", + "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049" + ], + "markers": "python_version >= '3.8'", + "version": "==0.4.0" + }, + "flake8": { + "hashes": [ + "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e", + "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==7.3.0" + }, + "isort": { + "hashes": [ + "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450", + "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615" + ], + "index": "pypi", + "markers": "python_full_version >= '3.9.0'", + "version": "==6.0.1" + }, + "mccabe": { + "hashes": [ + "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", + "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" + ], + "markers": "python_version >= '3.6'", + "version": "==0.7.0" + }, + "nose2": { + "extras": [ + "coverage_plugin" + ], + "hashes": [ + "sha256:36770f519df5becd3cbfe0bee4abbfbf9b9f6b4eb4e03361d282b7efcfc4f0df", + "sha256:564450c0c4f1602dfe171902ceb4726cc56658af7a620ae1826f1ffc86b09a86" + ], + "markers": "python_version >= '3.8'", + "version": "==0.15.1" + }, + "packaging": { + "hashes": [ + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + ], + "markers": "python_version >= '3.8'", + "version": "==25.0" + }, + "platformdirs": { + "hashes": [ + "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", + "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf" + ], + "markers": "python_version >= '3.9'", + "version": "==4.4.0" + }, + "pycodestyle": { + "hashes": [ + "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783", + "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d" + ], + "markers": "python_version >= '3.9'", + "version": "==2.14.0" + }, + "pyflakes": { + "hashes": [ + "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58", + "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f" + ], + "markers": "python_version >= '3.9'", + "version": "==3.4.0" + }, + "pylint": { + "hashes": [ + "sha256:26698de19941363037e2937d3db9ed94fb3303fdadf7d98847875345a8bb6b05", + "sha256:7ef94aa692a600e82fabdd17102b73fc226758218c97473c7ad67bd4cb905d83" + ], + "index": "pypi", + "markers": "python_full_version >= '3.9.0'", + "version": "==3.3.8" + }, + "pyproject-hooks": { + "hashes": [ + "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", + "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913" + ], + "markers": "python_version >= '3.7'", + "version": "==1.2.0" + }, + "setuptools": { + "hashes": [ + "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", + "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==80.9.0" + }, + "tomlkit": { + "hashes": [ + "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", + "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0" + ], + "markers": "python_version >= '3.8'", + "version": "==0.13.3" + }, + "yapf": { + "hashes": [ + "sha256:00d3aa24bfedff9420b2e0d5d9f5ab6d9d4268e72afbf59bb3fa542781d5218e", + "sha256:224faffbc39c428cb095818cf6ef5511fdab6f7430a10783fdfb292ccf2852ca" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==0.43.0" + } + } +} diff --git a/README.md b/README.md index 9032795..f86be0f 100644 --- a/README.md +++ b/README.md @@ -155,20 +155,20 @@ print(maxRow) ### Setup ```bash -python -m venv .venv -source .venv/bin/activate -pip install . -pip install nose2 flake8 +make setup ``` ### Tests ```bash -nose2 tests +make test ``` -### Linting +### Formatting and Linting ```bash -flake8 --ignore=F401 gatilegrid tests +make format +make lint +#or +make format-lint ``` diff --git a/gatilegrid/__init__.py b/gatilegrid/__init__.py index d32861d..2241a7d 100644 --- a/gatilegrid/__init__.py +++ b/gatilegrid/__init__.py @@ -1,6 +1,8 @@ -from .tilegrids import GeoadminTileGridLV03, GeoadminTileGridLV95, \ - GlobalMercatorTileGrid, GlobalGeodeticTileGrid from .grid import Grid +from .tilegrids import GeoadminTileGridLV03 +from .tilegrids import GeoadminTileGridLV95 +from .tilegrids import GlobalGeodeticTileGrid +from .tilegrids import GlobalMercatorTileGrid def getTileGrid(srs): diff --git a/gatilegrid/grid.py b/gatilegrid/grid.py index 51a74b9..2f9d8c9 100644 --- a/gatilegrid/grid.py +++ b/gatilegrid/grid.py @@ -75,10 +75,7 @@ def cellAddressFromPointCoordinate(self, pointCoordinate): col = max(0, col - 1) if y in (self.MINY, self.MAXY) and row.is_integer(): row = max(0, row - 1) - return [ - int(math.floor(col)), - int(math.floor(row)) - ] + return [int(math.floor(col)), int(math.floor(row))] def getExtentAddress(self, extent): fromCellCoordinate = extent[:2] @@ -87,8 +84,7 @@ def getExtentAddress(self, extent): self.cellAddressFromPointCoordinate(fromCellCoordinate) colTo, rowTo = \ self.cellAddressFromPointCoordinate(toCellCoordinate) - return [min(colFrom, colTo), min(rowFrom, rowTo), - max(colFrom, colTo), max(rowFrom, rowTo)] + return [min(colFrom, colTo), min(rowFrom, rowTo), max(colFrom, colTo), max(rowFrom, rowTo)] def inExtent(self, pointCoordinate): [x, y] = pointCoordinate diff --git a/gatilegrid/tilegrids.py b/gatilegrid/tilegrids.py index 8148e1b..6596bae 100644 --- a/gatilegrid/tilegrids.py +++ b/gatilegrid/tilegrids.py @@ -1,6 +1,5 @@ import math - EPSG4326_METERS_PER_UNIT = math.pi * 6378137 / 180 # Standard rendered pixel size as defined by OGC standards STANDARD_PIXEL_SIZE = 0.00028 @@ -162,13 +161,19 @@ def resolutions(self, tmsCompatible, tileSizePx): self.resFact = 180.0 / tileSizePx # Cesium terrain else: self.resFact = 360.0 / tileSizePx # OpenLayers - self.RESOLUTIONS = [self.resFact / 2 ** z for z in range(0, 25)] + self.RESOLUTIONS = [self.resFact / 2**z for z in range(0, 25)] class _TileGrid(object): - def __init__(self, extent=None, tileSizePx=256.0, originCorner='top-left', - tmsCompatible=None, useSwissExtent=True): + def __init__( + self, + extent=None, + tileSizePx=256.0, + originCorner='top-left', + tmsCompatible=None, + useSwissExtent=True + ): assert originCorner in ('bottom-left', 'top-left') self.originCorner = originCorner @@ -237,10 +242,7 @@ def tileAddress(self, zoom, point): col = max(0, col - 1) if y in (self.MINY, self.MAXY) and row.is_integer(): row = max(0, row - 1) - return [ - int(math.floor(col)), - int(math.floor(row)) - ] + return [int(math.floor(col)), int(math.floor(row))] def intersectsExtent(self, extent): "Determine if an extent intersects this instance extent" @@ -434,11 +436,7 @@ def ySpan(self): class GeoadminTileGridLV03(_LV03Base, _TileGrid): - def __init__(self, - extent=None, - tileSizePx=256.0, - originCorner='top-left', - useSwissExtent=True): + def __init__(self, extent=None, tileSizePx=256.0, originCorner='top-left', useSwissExtent=True): super().__init__( extent=extent, @@ -450,11 +448,7 @@ def __init__(self, class GeoadminTileGridLV95(_LV95Base, _TileGrid): - def __init__(self, - extent=None, - tileSizePx=256.0, - originCorner='top-left', - useSwissExtent=True): + def __init__(self, extent=None, tileSizePx=256.0, originCorner='top-left', useSwissExtent=True): super().__init__( extent=extent, @@ -466,11 +460,7 @@ def __init__(self, class GlobalMercatorTileGrid(_MercatorBase, _TileGrid): - def __init__(self, - extent=None, - tileSizePx=256.0, - originCorner='top-left', - useSwissExtent=True): + def __init__(self, extent=None, tileSizePx=256.0, originCorner='top-left', useSwissExtent=True): super().__init__( extent=extent, @@ -482,12 +472,14 @@ def __init__(self, class GlobalGeodeticTileGrid(_GeodeticBase, _TileGrid): - def __init__(self, - extent=None, - tileSizePx=256.0, - originCorner='top-left', - tmsCompatible=True, - useSwissExtent=True): + def __init__( + self, + extent=None, + tileSizePx=256.0, + originCorner='top-left', + tmsCompatible=True, + useSwissExtent=True + ): super().__init__( extent=extent, diff --git a/tests/test_grid.py b/tests/test_grid.py index ac3ae41..b80f1a8 100644 --- a/tests/test_grid.py +++ b/tests/test_grid.py @@ -1,4 +1,5 @@ import unittest + from gatilegrid.grid import Grid diff --git a/tests/test_tilegrids.py b/tests/test_tilegrids.py index da43806..76d1b6d 100644 --- a/tests/test_tilegrids.py +++ b/tests/test_tilegrids.py @@ -1,7 +1,11 @@ import math import unittest -from gatilegrid import getTileGrid, GeoadminTileGridLV03, \ - GeoadminTileGridLV95, GlobalMercatorTileGrid, GlobalGeodeticTileGrid + +from gatilegrid import GeoadminTileGridLV03 +from gatilegrid import GeoadminTileGridLV95 +from gatilegrid import GlobalGeodeticTileGrid +from gatilegrid import GlobalMercatorTileGrid +from gatilegrid import getTileGrid class TestGeoadminTileGrid(unittest.TestCase): @@ -29,8 +33,7 @@ def testTileGridWrongExtent(self): GeoadminTileGridLV03(extent=[10.0, 10.0, 20.0, 20.0]) with self.assertRaises(AssertionError): - GeoadminTileGridLV03( - extent=[430000.0, 40000.0, 420000.0, 340000.0]) + GeoadminTileGridLV03(extent=[430000.0, 40000.0, 420000.0, 340000.0]) def testTileGridWrongOrigin(self): with self.assertRaises(AssertionError): @@ -40,8 +43,7 @@ def testTileSize(self): gagrid = GeoadminTileGridLV03() ts = gagrid.tileSize(20) self.assertEqual(ts, 2560.0) - self.assertEqual(gagrid.tileAddressTemplate, - '{zoom}/{tileRow}/{tileCol}') + self.assertEqual(gagrid.tileAddressTemplate, '{zoom}/{tileRow}/{tileCol}') with self.assertRaises(AssertionError): gagrid.tileSize(40) @@ -203,8 +205,12 @@ def testGetScaleLV95(self): def testIterGridWithExtent(self): offset = 20000.0 gagridDefault = GeoadminTileGridLV03() - extent = [gagridDefault.MINX + offset, gagridDefault.MINY + offset, - gagridDefault.MAXX - offset, gagridDefault.MAXY - offset] + extent = [ + gagridDefault.MINX + offset, + gagridDefault.MINY + offset, + gagridDefault.MAXX - offset, + gagridDefault.MAXY - offset + ] gagridExtent = GeoadminTileGridLV03(extent=extent) self.assertGreater(gagridDefault.xSpan, gagridExtent.xSpan) @@ -329,9 +335,9 @@ def testNumberOfTilesMercator(self): self.assertEqual(nb, 16) def testNumberOfTilesGeodetic(self): - grid = GlobalGeodeticTileGrid(originCorner='top-left', - tmsCompatible=False, - useSwissExtent=False) + grid = GlobalGeodeticTileGrid( + originCorner='top-left', tmsCompatible=False, useSwissExtent=False + ) zoom = 0 nb = grid.numberOfTilesAtZoom(zoom) nbx = grid.numberOfXTilesAtZoom(zoom) @@ -360,9 +366,9 @@ def testNumberOfTilesGeodetic(self): self.assertEqual(nb, nbx * nby) self.assertEqual(nb, 32) - grid = GlobalGeodeticTileGrid(originCorner='top-left', - tmsCompatible=True, - useSwissExtent=False) + grid = GlobalGeodeticTileGrid( + originCorner='top-left', tmsCompatible=True, useSwissExtent=False + ) zoom = 0 nb = grid.numberOfTilesAtZoom(zoom) @@ -392,9 +398,9 @@ def testNumberOfTilesGeodetic(self): self.assertEqual(nb, nbx * nby) self.assertEqual(nb, 128) - grid = GlobalGeodeticTileGrid(originCorner='bottom-left', - tmsCompatible=False, - useSwissExtent=False) + grid = GlobalGeodeticTileGrid( + originCorner='bottom-left', tmsCompatible=False, useSwissExtent=False + ) zoom = 0 nb = grid.numberOfTilesAtZoom(zoom) nbx = grid.numberOfXTilesAtZoom(zoom) @@ -413,8 +419,9 @@ def testNumberOfTilesGeodetic(self): self.assertEqual(nb, nbx * nby) self.assertEqual(nb, 8) - grid = GlobalGeodeticTileGrid(originCorner='bottom-left', - tmsCompatible=True, useSwissExtent=False) + grid = GlobalGeodeticTileGrid( + originCorner='bottom-left', tmsCompatible=True, useSwissExtent=False + ) zoom = 0 nb = grid.numberOfTilesAtZoom(zoom) nbx = grid.numberOfXTilesAtZoom(zoom) @@ -472,9 +479,9 @@ def testMercatorGridBoundsAndAddress(self): self.assertEqual(ya, y) def testGeodeticGridBoundsAndAddress(self): - grid = GlobalGeodeticTileGrid(originCorner='top-left', - tmsCompatible=True, - useSwissExtent=False) + grid = GlobalGeodeticTileGrid( + originCorner='top-left', tmsCompatible=True, useSwissExtent=False + ) [z, x, y] = [8, 268, 60] [xmin, ymin, xmax, ymax] = grid.tileBounds(z, x, y) self.assertAlmostEqual(xmin, 8.4375) @@ -489,9 +496,9 @@ def testGeodeticGridBoundsAndAddress(self): self.assertEqual(ya, y) [z, x, y] = [8, 266, 193] - grid = GlobalGeodeticTileGrid(originCorner='bottom-left', - tmsCompatible=True, - useSwissExtent=False) + grid = GlobalGeodeticTileGrid( + originCorner='bottom-left', tmsCompatible=True, useSwissExtent=False + ) [xmin, ymin, xmax, ymax] = grid.tileBounds(z, x, y) self.assertAlmostEqual(xmin, 7.03125) self.assertAlmostEqual(ymin, 45.703125) @@ -506,9 +513,8 @@ def testGeodeticGridBoundsAndAddress(self): def testGetParentTilesGlobalGeodeticTopLeft(self): grid = GlobalGeodeticTileGrid( - originCorner='top-left', - tmsCompatible=False, - useSwissExtent=False) + originCorner='top-left', tmsCompatible=False, useSwissExtent=False + ) nb = grid.numberOfTilesAtZoom(1) self.assertEqual(nb, 2) @@ -535,9 +541,8 @@ def testGetParentTilesGlobalGeodeticTopLeft(self): def testGetParentTilesGlobalGeodeticTopLeftUseSwiss(self): grid = GlobalGeodeticTileGrid( - originCorner='top-left', - tmsCompatible=False, - useSwissExtent=True) + originCorner='top-left', tmsCompatible=False, useSwissExtent=True + ) nb = grid.numberOfTilesAtZoom(1) self.assertEqual(nb, 1) @@ -564,9 +569,8 @@ def testGetParentTilesGlobalGeodeticTopLeftUseSwiss(self): def testGetParentTilesGlobalGeodeticBottomLeft(self): grid = GlobalGeodeticTileGrid( - originCorner='bottom-left', - tmsCompatible=False, - useSwissExtent=False) + originCorner='bottom-left', tmsCompatible=False, useSwissExtent=False + ) nb = grid.numberOfTilesAtZoom(1) self.assertEqual(nb, 2) @@ -609,9 +613,9 @@ def testGetParentTilesLV95(self): # Check that the tile extent intersects the parent tile childExtent = grid.tileBounds(z, x, y) - parentExtent = grid.tileBounds(tileAddresses[0][0], - tileAddresses[0][1], - tileAddresses[0][2]) + parentExtent = grid.tileBounds( + tileAddresses[0][0], tileAddresses[0][1], tileAddresses[0][2] + ) self.assertLessEqual(parentExtent[0], childExtent[2]) self.assertLessEqual(parentExtent[1], childExtent[3]) self.assertGreaterEqual(parentExtent[2], childExtent[0]) @@ -652,9 +656,9 @@ def testGetParentTilesLV03(self): # Check that the tile extent intersects the parent tile childExtent = grid.tileBounds(z, x, y) - parentExtent = grid.tileBounds(tileAddresses[0][0], - tileAddresses[0][1], - tileAddresses[0][2]) + parentExtent = grid.tileBounds( + tileAddresses[0][0], tileAddresses[0][1], tileAddresses[0][2] + ) self.assertLessEqual(parentExtent[0], childExtent[2]) self.assertLessEqual(parentExtent[1], childExtent[3]) self.assertGreaterEqual(parentExtent[2], childExtent[0]) diff --git a/tests/unittest.cfg b/tests/unittest.cfg new file mode 100644 index 0000000..046b53c --- /dev/null +++ b/tests/unittest.cfg @@ -0,0 +1,15 @@ +[unittest] +plugins = nose2.plugins.junitxml + +[junit-xml] +always-on = True + +[log-capture] +always-on = True +# clear the handler to not polute the stdout and stderr with logging messages +clear-handlers = True +log-level = DEBUG + +[coverage] +always-on = True +coverage = gatilegrid From cb1fb53a2a81eb1c48b8cf61d8a5b2d9b1480861 Mon Sep 17 00:00:00 2001 From: Marc Sommerhalder Date: Wed, 17 Sep 2025 14:25:09 +0200 Subject: [PATCH 4/6] PB-1968: Add github workflows --- .github/workflows/pr-auto-semver.yml | 13 +++++++++++++ .github/workflows/semver.yml | 11 +++++++++++ README.md | 8 ++++++++ 3 files changed, 32 insertions(+) create mode 100644 .github/workflows/pr-auto-semver.yml create mode 100644 .github/workflows/semver.yml diff --git a/.github/workflows/pr-auto-semver.yml b/.github/workflows/pr-auto-semver.yml new file mode 100644 index 0000000..de068da --- /dev/null +++ b/.github/workflows/pr-auto-semver.yml @@ -0,0 +1,13 @@ +name: on-pr + +on: + pull_request: + types: + - opened + - reopened + - synchronize + - edited + +jobs: + pr-edit: + uses: geoadmin/.github/.github/workflows/pr-auto-semver.yml@master diff --git a/.github/workflows/semver.yml b/.github/workflows/semver.yml new file mode 100644 index 0000000..ad0c532 --- /dev/null +++ b/.github/workflows/semver.yml @@ -0,0 +1,11 @@ +name: on-push + +on: + push: + branches: + - master + - develop + +jobs: + release: + uses: geoadmin/.github/.github/workflows/semver-release.yml@master diff --git a/README.md b/README.md index f86be0f..a804b4a 100644 --- a/README.md +++ b/README.md @@ -172,3 +172,11 @@ make lint #or make format-lint ``` + +## Release and Publish + +New release and publish on PyPI is done automatically upon PR merge into master branch. For bug fixes and small new features, PR can be directly open on master. Then the PR title define the version bump as follow: + +- PR title and/or commit message contains #major => major version is bumped +- PR title and/or commit message contains #patch or head branch name starts with bug-|hotfix-|bugfix- => patch version is bumped +- Otherwise by default the minor version is bumped From 7a2f30dd1dfbf7a719b96eacf166b11baac73dfa Mon Sep 17 00:00:00 2001 From: Marc Sommerhalder Date: Wed, 17 Sep 2025 16:06:58 +0200 Subject: [PATCH 5/6] PB-1968: Set up git versionioning --- pyproject.toml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b04f5d6..62ec009 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,14 +1,13 @@ [build-system] -requires = ["setuptools", "build"] +requires = ["setuptools>=77.0.0", "build", "setuptools-git-versioning<2"] build-backend = "setuptools.build_meta" [project] name = "gatilegrid" -version = "1.0.0" requires-python = ">=3.0" description = "Popular tile grids and grids API for web mapping applications" readme = "README.md" -license = {text = "MIT"} +license = "MIT" authors = [ {name = "Loic Gasser", email = "loicgasser4@gmail.com"} ] @@ -22,6 +21,7 @@ classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules" ] keywords = ["gis", "wmts", "grid", "map"] +dynamic = ["version"] [project.urls] Homepage = "https://github.com/geoadmin/lib-gatilegrid" @@ -31,3 +31,8 @@ Tracker = "https://github.com/geoadmin/lib-gatilegrid/issues" [tool.setuptools.packages.find] include = ["gatilegrid*"] + +[tool.setuptools-git-versioning] +enabled = true +dev_template = "{tag}+git.{sha}" +dirty_template = "{tag}+git.{sha}.dirty" From 2f916064f628984edac4b1faebf4ecb771d72892 Mon Sep 17 00:00:00 2001 From: Marc Sommerhalder Date: Wed, 17 Sep 2025 16:37:00 +0200 Subject: [PATCH 6/6] PB-1968: Autogenerate help --- Makefile | 56 ++++++++++++++++++-------------------------------------- 1 file changed, 18 insertions(+), 38 deletions(-) diff --git a/Makefile b/Makefile index babd0ae..058e5f5 100644 --- a/Makefile +++ b/Makefile @@ -34,47 +34,22 @@ PYLINT := $(PIPENV_RUN) pylint PACKAGE_VERSION = $(shell awk '/^Version:/ {print $$2}' gatilegrid.egg-info/PKG-INFO) - -all: help - - -.PHONY: help -help: - @echo "Usage: make " - @echo - @echo "Possible targets:" - @echo -e " \033[1mSetup TARGETS\033[0m " - @echo "- setup Create the python virtual environment with developper tools" - @echo -e " \033[1mFORMATING, LINTING AND TESTING TOOLS TARGETS\033[0m " - @echo "- format Format the python source code" - @echo "- lint Lint the python source code" - @echo "- format-lint Format and lint the python source code" - @echo "- test Run the tests" - @echo -e " \033[1mPACKAGING TARGETS\033[0m " - @echo "- package Create package" - @echo "- publish Tag and publish package to PyPI" - @echo -e " \033[1mCLEANING TARGETS\033[0m " - @echo "- clean Clean generated files" - @echo "- clean-venv Clean python venv" - @echo "- clean-all Clean everything" - - # Build targets. Calling setup is all that is needed for the local files to be installed as needed. .PHONY: setup -setup: $(REQUIREMENTS) +setup: $(REQUIREMENTS) ## Create the python virtual environment with developper tools pipenv install --dev # linting target, calls upon yapf to make sure your code is easier to read and respects some conventions. .PHONY: format -format: $(REQUIREMENTS) +format: $(REQUIREMENTS) ## Format the python source code $(YAPF) -p -i --style .style.yapf $(PYTHON_FILES) $(ISORT) $(PYTHON_FILES) .PHONY: ci-check-format -ci-check-format: format +ci-check-format: format ## Check if formatting python source code is required @if [[ -n `git status --porcelain` ]]; then \ >&2 echo "ERROR: the following files are not formatted correctly:"; \ >&2 git status --porcelain; \ @@ -83,18 +58,18 @@ ci-check-format: format .PHONY: lint -lint: $(REQUIREMENTS) +lint: $(REQUIREMENTS) ## Lint the python source code $(PYLINT) $(PYTHON_FILES) .PHONY: format-lint -format-lint: format lint +format-lint: format lint ## Format and lint the python source code # Test target .PHONY: test -test: $(DEV_REQUIREMENTS_TIMESTAMP) +test: $(DEV_REQUIREMENTS_TIMESTAMP) ## Run the tests mkdir -p $(TEST_REPORT_DIR) $(NOSE) -v -c tests/unittest.cfg --junit-xml-path $(TEST_REPORT_DIR)/$(TEST_REPORT_FILE) -s tests/ @@ -102,12 +77,12 @@ test: $(DEV_REQUIREMENTS_TIMESTAMP) # Packaging target .PHONY: package -package: $(DEV_REQUIREMENTS_TIMESTAMP) +package: $(DEV_REQUIREMENTS_TIMESTAMP) ## Create package $(PYTHON) -m build .PHONY: publish -publish: publish-check package +publish: publish-check package ## Tag and publish package to PyPI @echo "Upload package version=$(PACKAGE_VERSION)" $(PYTHON) -m twine upload dist/* @@ -115,11 +90,11 @@ publish: publish-check package # Clean targets .PHONY: clean-venv -clean-venv: +clean-venv: ## Clean python venv pipenv --rm .PHONY: clean -clean: clean-venv +clean: clean-venv ## Clean generated files @# clean python cache files find . -name __pycache__ -type d -print0 | xargs -I {} -0 rm -rf "{}" rm -rf $(TEST_REPORT_DIR) @@ -130,7 +105,7 @@ clean: clean-venv rm -f .coverage .PHONY: clean-all -clean-all: clean +clean-all: clean ## Clean everything # Actual builds targets with dependencies @@ -148,6 +123,11 @@ $(DEV_REQUIREMENTS_TIMESTAMP): $(VENV_TIMESTAMP) $(DEV_REQUIREMENTS) @touch $(DEV_REQUIREMENTS_TIMESTAMP) -publish-check: - @echo "Check if publish is allowed" +publish-check: ## Check if publish is allowed @if [ -n "`git status --porcelain`" ]; then echo "ERROR: Repo is dirty !" >&2; exit 1; fi + +.PHONY: help +help: ## Display this help +# automatically generate the help page based on the documentation after each make target +# from https://gist.github.com/prwhite/8168133 + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[$$()% a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)