diff --git a/.github/labeler.yml b/.github/labeler.yml index 75c5ceb..aff23b6 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,21 +1,33 @@ ci-cd: - - .github/**/* +- changed-files: + - any-glob-to-any-file: + - .github/** dependencies: - - requirements.txt - - requirements/*.txt +- changed-files: + - any-glob-to-any-file: + - requirements.txt + - requirements/*.txt documentation: - - docs/**/* +- changed-files: + - any-glob-to-any-file: + - docs/** enhancement: - - src/**/* +- changed-files: + - any-glob-to-any-file: + - src/** quality: - - tests/**/* +- changed-files: + - any-glob-to-any-file: + - tests/** tooling: - - .gitignore - - .pre-commit-config.yaml - - setup.cfg - - pyproject.toml +- changed-files: + - any-glob-to-any-file: + - .gitignore + - .pre-commit-config.yaml + - setup.cfg + - pyproject.toml diff --git a/.github/workflows/pr-auto-labeler.yml b/.github/workflows/pr-auto-labeler.yml index cc5272e..ec24fd0 100644 --- a/.github/workflows/pr-auto-labeler.yml +++ b/.github/workflows/pr-auto-labeler.yml @@ -2,14 +2,11 @@ name: "🏷 PR Labeler" on: - pull_request -permissions: - contents: read - pull-requests: write - jobs: - triage: + labeler: + permissions: + contents: read + pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/labeler@v5 - with: - repo-token: "${{ secrets.GITHUB_TOKEN }}" + - uses: actions/labeler@v5 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 452410f..5a39206 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.5.2 + +### [Added] + +* Outil TMSIZER : possibilité de préciser une aire prédéinie à la place de la bbox et un niveau à la place des dimensions pour l'écriture d'une heatmap (une tuile du niveau correspondra à un pixel) + ## 1.5.1 ### [Added] diff --git a/README.md b/README.md index ddfe774..c596c35 100644 --- a/README.md +++ b/README.md @@ -165,13 +165,22 @@ Conversions possibles (paramètres obligatoires en gras, paramètres facultatifs | Format en entrée | Options d'entrée | Format en sortie | Options de sortie | Description | |------------------|------------------|------------------|---------------------------------------------------------------|---------------------------------------------------------------------------------------------------------| | GETTILE_PARAMS | *`levels=[, ...]`*,*`layers=[, ...]`* | COUNT | | Compte le nombre de GetTile dans les URLs en entrée utilisant le TMS pivot et les éventuels niveaux et couches fournies | -| GETTILE_PARAMS | *`levels=[, ...]`*,*`layers=[, ...]`* | HEATMAP | **`bbox=,,,`**, **`dimensions=x`** | Génère une carte de chaleur des tuiles interrogées sur la zone demandée et sur les éventuels niveau et couche fournis | +| GETTILE_PARAMS | *`levels=[, ...]`*,*`layers=[, ...]`* | HEATMAP | **`bbox=,,, or area=`**, **`dimensions=x or level=`** | Génère une carte de chaleur des tuiles interrogées sur la zone demandée et sur les éventuels niveaux et couches fournies. Si un niveau est fourni en sortie, on calera la bbox et les résolutions pour avoir un pixel correpondant à l'étendue d'une tuile du niveau. Certaines aires sont prédéfinies pour certaines projections du TMS | | GEOMETRY | **`format=`**,**`level=`** | GETTILE_PARAMS | | Génére les paramètres de requête GetTile des tuiles du niveau fourni intersectant les géométries en entrée | +Aires prédéfinies pour une carte de chaleur : + +* `EPSG:3857` + * `FXX` (France métropolitaine) + Exemple (GETTILE_PARAMS -> HEATMAP) : `tmsizer -i logs.txt --tms PM -io levels=15,14 -io layer=LAYER.NAME1,LAYER.NAME2,LAYER.NAME3 -if GETTILE_PARAMS -of HEATMAP -oo bbox=65000,6100000,665000,6500000 -oo dimensions=600x400 -o heatmap.tif` +Exemple (GETTILE_PARAMS -> HEATMAP) avec une aire prédéfinie et une correspondance pixel-niveau: + +`tmsizer -i logs.txt --tms PM -if GETTILE_PARAMS -of HEATMAP -oo area=FXX -oo level=15 -o heatmap.tif` + ## Compiler la suite d'outils ```sh diff --git a/README.pypi.md b/README.pypi.md index a5fcafa..0d277c9 100644 --- a/README.pypi.md +++ b/README.pypi.md @@ -164,10 +164,18 @@ Availables conversions (mandatory options in bold, optionnal options in italic) | Input format | Input options | Output format | Output options | Description | |----------------|---------------------------------------------------|----------------|---------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------| | GETTILE_PARAMS | *`levels=[, ...]`*,*`layers=[, ...]`* | COUNT | | Count the GetTiles requests using the pivot TMS and optionnally the provided level and layer | -| GETTILE_PARAMS | *`levels=[, ...]`*,*`layers=[, ...]`* | HEATMAP | **`bbox=,,,`**, **`dimensions=x`** | Create an heat map of requested tiles on the provided area, optionnaly filtering with provided level and layer | +| GETTILE_PARAMS | *`levels=[, ...]`*,*`layers=[, ...]`* | HEATMAP | **`bbox=,,, or area=`**, **`dimensions=x or level=`** | Create an heat map of requested tiles on the provided area, optionnaly filtering with provided level and layer | | GEOMETRY | **`format=`**,**`level=`** | GETTILE_PARAMS | | Generate GetTile query parameters for tiles intersecting input geometries for the provided level | +Available areas for a heatmap : + +* `EPSG:3857` + * `FXX` (European France) + Example (GETTILE_PARAMS -> HEATMAP) : +`tmsizer -i logs.txt --tms PM -io levels=15,14 -io layer=LAYER.NAME1,LAYER.NAME2,LAYER.NAME3 -if GETTILE_PARAMS -of HEATMAP -oo bbox=65000,6100000,665000,6500000 -oo dimensions=600x400 -o heatmap.tif` + +Example (GETTILE_PARAMS -> HEATMAP) with predefined area and pixel-level superposition: -`tmsizer -i logs.txt --tms PM -io levels=15,14 -io layer=LAYER.NAME1,LAYER.NAME2,LAYER.NAME3 -if GETTILE_PARAMS -of HEATMAP -oo bbox=65000,6100000,665000,6500000 -oo dimensions=600x400 -o heatmap.tif` \ No newline at end of file +`tmsizer -i logs.txt --tms PM -if GETTILE_PARAMS -of HEATMAP -oo area=FXX -oo level=15 -o heatmap.tif` \ No newline at end of file diff --git a/src/rok4_tools/tmsizer_utils/processors/reduce.py b/src/rok4_tools/tmsizer_utils/processors/reduce.py index 5827912..88f7a0e 100644 --- a/src/rok4_tools/tmsizer_utils/processors/reduce.py +++ b/src/rok4_tools/tmsizer_utils/processors/reduce.py @@ -85,6 +85,12 @@ class HeatmapProcessor(Processor): input_formats_allowed = ["POINT"] + areas = { + "EPSG:3857": { + "FXX": [-649498, 5048729, 1173394, 6661417] + } + } + def __init__(self, input: Processor, **options): """Constructor method @@ -107,7 +113,7 @@ def __init__(self, input: Processor, **options): self.__input = input - try: + if "bbox" in options: try: self.__bbox = [float(c) for c in options["bbox"].split(",")] self.__bbox = tuple(self.__bbox) @@ -117,6 +123,18 @@ def __init__(self, input: Processor, **options): if len(self.__bbox) != 4 or self.__bbox[0] >= self.__bbox[2] or self.__bbox[1] >= self.__bbox[3]: raise ValueError(f"Option 'bbox' have to be provided with format ,,, (floats, min < max)") + elif "area" in options: + try: + self.__bbox = self.areas[self.tms.srs][options["area"]] + except KeyError as e: + if self.tms.srs in self.areas: + raise ValueError(f"Area '{options['area']}' is not available for the TMS coordinates system ({self.tms.srs}): available areas are {', '.join(self.areas[self.tms.srs].keys())}") + else : + raise ValueError(f"No defined areas for the TMS coordinates system ({self.tms.srs})") + else: + raise KeyError(f"Option 'bbox' or 'area' is required for a heatmap processing") + + if "dimensions" in options: try: self.__dimensions = [int(d) for d in options["dimensions"].split("x")] self.__dimensions = tuple(self.__dimensions) @@ -130,9 +148,39 @@ def __init__(self, input: Processor, **options): (self.__bbox[2] - self.__bbox[0]) / self.__dimensions[0], (self.__bbox[3] - self.__bbox[1]) / self.__dimensions[1] ) + elif "level" in options: + level = self.tms.get_level(options["level"]) + if level is None: + raise ValueError(f"The provided level '{options['dimensions']}' (to have one pixel per tile) is not in the TMS") + + # On va caler la bbox pour qu'elle coïncide avec les limites de tuiles du niveau demandé + (col_min, row_min, col_max, row_max) = level.bbox_to_tiles(self.__bbox) + + # Calage du coin en bas à gauche + (xmin, ymin, xmax, ymax) = level.tile_to_bbox(col_min, row_max) + self.__bbox[0] = xmin + self.__bbox[1] = ymin + + # Calage du coin en haut à droite + (xmin, ymin, xmax, ymax) = level.tile_to_bbox(col_max, row_min) + self.__bbox[2] = xmax + self.__bbox[3] = ymax + + self.__resolutions = ( + xmax - xmin, + ymax - ymin + ) + + self.__dimensions = ( + int((self.__bbox[2] - self.__bbox[0]) / self.__resolutions[0]), + int((self.__bbox[3] - self.__bbox[1]) / self.__resolutions[1]) + ) + + else: + raise KeyError(f"Option 'dimensions' or 'level' is required for a heatmap processing") - except KeyError as e: - raise KeyError(f"Option {e} is required for a heatmap processing") + if self.__dimensions[0] > 10000 or self.__dimensions[1] > 10000: + raise ValueError(f"Heatmap dimensions have to be less than 10 000 x 10 000: here it's {self.__dimensions}") def process(self) -> Iterator[MemoryFile]: """Read point coordinates from the input processor and accumule them as a heat map @@ -194,4 +242,4 @@ def process(self) -> Iterator[MemoryFile]: yield memfile def __str__(self) -> str: - return f"HeatmapProcessor : {self._processed} hits on image with dimensions {self.__dimensions} and bbox {self.__bbox}" + return f"HeatmapProcessor : {self._processed} hits on image with dimensions {self.__dimensions} and bbox {self.__bbox} (resolutions {self.__resolutions})"