Coverage for src/rok4_tools/tmsizer_utils/processors/map.py: 79%
127 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-11-06 17:15 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2024-11-06 17:15 +0000
1"""Provide processor tranforming data. Output data is emitted as the input processor reading progresses
3The module contains the following classes:
5- `Gettile2tileindexProcessor` - Extract tile index from a WMTS GetTile URL
6- `Tileindex2gettileProcessor` - Generate WMTS GetTile query parameters from tile index
7- `Tileindex2pointProcessor` - Generate the tile's center coordinates from tile index
8- `Geometry2tileindexProcessor` - Generate all tiles' indices intersecting the input geometry
9"""
11import sys
12from typing import Dict, List, Tuple, Union, Iterator
13from math import floor
14from urllib.parse import urlparse, parse_qs
16from osgeo import gdal, ogr, osr
17ogr.UseExceptions()
18osr.UseExceptions()
19gdal.UseExceptions()
21from rok4.tile_matrix_set import TileMatrixSet
22from rok4.utils import bbox_to_geometry
24from rok4_tools.tmsizer_utils.processors.processor import Processor
26class Gettile2tileindexProcessor(Processor):
27 """Processor extracting tile index from a WMTS GetTile URL
29 Accepted input format is "GETTILE_PARAMS" and output format is "TILE_INDEX"
31 Attributes:
32 __input (Processor): Processor from which data is read
33 __levels (List[str], optional): Tile matrix identifier(s) to filter data
34 __layers (List[str], optional): Layer(s) to filter data
35 """
37 input_formats_allowed = ["GETTILE_PARAMS"]
39 def __init__(self, input: Processor, **options):
40 """Constructor method
42 Args:
43 input (Processor): Processor from which data is read
44 **levels (str, optional): Tile matrix identifier(s) to filter data
45 **layers (str, optional): Layer(s) to filter data
47 Raises:
48 ValueError: Input format is not allowed
49 ValueError: Provided level is not in the pivot TMS
50 """
52 if input.format not in self.input_formats_allowed:
53 raise ValueError(f"Input format {input.format} is not handled for Gettile2tileindexProcessor : allowed formats are {self.input_formats_allowed}")
55 super().__init__("TILE_INDEX")
57 self.__input = input
58 if "levels" in options.keys():
59 self.__levels = options["levels"].split(",")
60 for l in self.__levels:
61 if self.tms.get_level(l) is None:
62 raise ValueError(f"The provided level '{l}' is not in the TMS")
63 else:
64 self.__levels = None
66 self.__input = input
67 if "layers" in options.keys():
68 self.__layers = options["layers"].split(",")
69 else:
70 self.__layers = None
72 def process(self) -> Iterator[Tuple[str, int, int]]:
73 """Read an item from the input processor and extract tile index
75 Used query parameters are TILEMATRIXSET, TILEMATRIX, TILECOL and TILEROW. If one is missing, item is passed.
76 TILEMATRISET have to be the pivot TMS's name (or just preffixed by its name). TILEMATRIX have to be present in the pivot TMS.
77 If filtering levels are provided, unmatched TILEMATRIX are passed. If filtering layers are provided, unmatched LAYER are passed.
79 Examples:
81 Get tile index
83 from rok4_tools.tmsizer_utils.processors.map import Gettile2tileindexProcessor
85 try:
86 # Creation of Processor source_processor with format GETTILE_PARAMS
87 processor = Gettile2tileindexProcessor(source_processor, level="19" )
88 for item in processor.process():
89 (level, col, row) = item
91 except Exception as e:
92 print("{e}")
94 Yields:
95 Iterator[Tuple[str, int, int]]: Tile index (level, col, row)
96 """
98 if self.__input.format == "GETTILE_PARAMS":
99 for item in self.__input.process():
100 self._processed += 1
102 qs = parse_qs(urlparse(item.upper()).query)
103 try:
105 # On se limite à un niveau et ce n'est pas celui de la requête
106 if self.__levels is not None and qs["TILEMATRIX"][0] not in self.__levels:
107 continue
109 # On se limite à une couche et ce n'est pas celle de la requête
110 if self.__layers is not None and qs["LAYER"][0] not in self.__layers:
111 continue
113 # La requête n'utilise pas le TMS en entrée
114 if qs["TILEMATRIXSET"][0] != self.tms.name and not qs["TILEMATRIXSET"][0].startswith(f"{self.tms.name}_"):
115 continue
117 # La requête demande un niveau que le TMS ne possède pas
118 if self.tms.get_level(qs["TILEMATRIX"][0]) is None:
119 continue
121 yield (str(qs["TILEMATRIX"][0]),int(qs["TILECOL"][0]),int(qs["TILEROW"][0]))
122 except Exception as e:
123 # La requête n'est pas un gettile ou n'est pas valide, il manque un paramètre, ou il a un mauvais format
124 # on la passe simplement
125 pass
127 def __str__(self) -> str:
128 return f"Gettile2tileindexProcessor : {self._processed} {self.__input.format} items processed, extracting tile's indices"
131class Tileindex2gettileProcessor(Processor):
132 """Processor generating WMTS GetTile query parameters from tile index
134 Accepted input format is "TILE_INDEX" and output format is "GETTILE_PARAMS"
136 Attributes:
137 __input (Processor): Processor from which data is read
138 """
140 input_formats_allowed = ["TILE_INDEX"]
142 def __init__(self, input: Processor):
143 """Constructor method
145 Args:
146 input (Processor): Processor from which data is read
148 Raises:
149 ValueError: Input format is not allowed
150 """
152 if input.format not in self.input_formats_allowed:
153 raise ValueError(f"Input format {input.format} is not handled for Tileindex2gettileProcessor : allowed formats are {self.input_formats_allowed}")
155 super().__init__("GETTILE_PARAMS")
157 self.__input = input
159 def process(self) -> Iterator[str]:
160 """Read a tile index from the input processor and generate WMTS GetTile query parameters
162 Examples:
164 Get GetTile query parameters
166 from rok4_tools.tmsizer_utils.processors.map import Tileindex2gettileProcessor
168 try:
169 # Creation of Processor source_processor with format TILE_INDEX
170 processor = Tileindex2gettileProcessor(source_processor)
171 for item in processor.process():
172 query_parameters = item
174 except Exception as e:
175 print("{e}")
177 Yields:
178 Iterator[str]: GetTile query parameters TILEMATRIXSET=<tms>&TILEMATRIX=<level>&TILECOL=<col>&TILEROW=<row>
179 """
181 if self.__input.format == "TILE_INDEX":
182 for item in self.__input.process():
183 self._processed += 1
185 (level, col, row) = item
187 yield f"TILEMATRIXSET={self.tms.name}&TILEMATRIX={level}&TILECOL={col}&TILEROW={row}"
189 def __str__(self) -> str:
190 return f"Tileindex2gettileProcessor : {self._processed} {self.__input.format} items processed, generating GetTile's query parameters"
192class Tileindex2pointProcessor(Processor):
193 """Processor generating the tile's center coordinates from tile index
195 Accepted input format is "TILE_INDEX" and output format is "POINT"
197 Attributes:
198 __input (Processor): Processor from which data is read
199 """
201 input_formats_allowed = ["TILE_INDEX"]
203 def __init__(self, input: Processor):
204 """Constructor method
206 Args:
207 input (Processor): Processor from which data is read
209 Raises:
210 ValueError: Input format is not allowed
211 """
213 if input.format not in self.input_formats_allowed:
214 raise ValueError(f"Input format {input.format} is not handled for Tileindex2pointProcessor : allowed formats are {self.input_formats_allowed}")
216 super().__init__("POINT")
218 self.__input = input
220 def process(self) -> Iterator[Tuple[float, float]]:
221 """Read a tile index from the input processor and generate the tile's center coordinates
223 Examples:
225 Get tile's center coordinates
227 from rok4_tools.tmsizer_utils.processors.map import Tileindex2pointProcessor
229 try:
230 # Creation of Processor source_processor with format TILE_INDEX
231 processor = Tileindex2pointProcessor(source_processor)
232 for item in processor.process():
233 (x, y) = item
235 except Exception as e:
236 print("{e}")
238 Yields:
239 Iterator[Tuple[float, float]]: point coordinates (x,y)
240 """
242 if self.__input.format == "TILE_INDEX":
243 for item in self.__input.process():
244 self._processed += 1
246 (level, col, row) = item
247 try:
248 bb = self.tms.get_level(level).tile_to_bbox(col, row)
250 x_center = bb[0] + (bb[2] - bb[0]) / 2;
251 y_center = bb[1] + (bb[3] - bb[1]) / 2;
253 yield (x_center, y_center)
254 except Exception as e:
255 # Le niveau n'est pas valide, on passe simplement
256 pass
258 def __str__(self) -> str:
259 return f"Tileindex2pointProcessor : {self._processed} {self.__input.format} items processed, extracting tile's center coordinates"
262class Geometry2tileindexProcessor(Processor):
263 """Processor generating the tile's center coordinates from tile index
265 Accepted input format is "GEOMETRY" and output format is "TILE_INDEX"
267 Attributes:
268 __input (Processor): Processor from which data is read
269 __geometry_format (str): Format of input string geometries. "WKT", "WKB" or "GeoJSON"
270 __level (str): Tile matrix identifier to define intersecting tiles
271 """
273 input_formats_allowed = ["GEOMETRY"]
274 geometry_formats_allowed = ["WKT", "WKB", "GeoJSON"]
276 def __init__(self, input: Processor, **options):
277 """Constructor method
279 Args:
280 input (Processor): Processor from which data is read
281 **format (str): Format of input string geometries. "WKT", "WKB" or "GeoJSON"
282 **level (str): Tile matrix identifier to define intersecting tiles
284 Raises:
285 ValueError: Input format is not allowed
286 KeyError: A mandatory option is missing
287 ValueError: A mandatory option is not valid
288 ValueError: Provided level is not in the pivot TMS
289 """
291 if input.format not in self.input_formats_allowed:
292 raise ValueError(f"Input format {input.format} is not handled for Geometry2tileindexProcessor : allowed formats are {self.input_formats_allowed}")
294 super().__init__("TILE_INDEX")
296 self.__input = input
298 try:
300 if options["format"] not in self.geometry_formats_allowed:
301 raise ValueError(f"Option 'format' for an input geometry is not handled ({options['format']}) : allowed formats are {self.geometry_formats_allowed}")
303 self.__geometry_format = options["format"]
305 if self.tms.get_level(options["level"]) is None:
306 raise ValueError(f"Provided level is not in the TMS")
308 self.__level = options["level"]
310 except KeyError as e:
311 raise KeyError(f"Option {e} is required to generate tile indices from geometries")
313 def process(self) -> Iterator[Tuple[str, int, int]]:
314 """Read a geometry from the input processor and extract tile index
316 Geometry is parsed according to provided format. To determine intersecting tiles, geometry have to be a Polygon or a MultiPolygon.
317 For an input geometry, all intersecting tiles for the provided level are yielded
319 Examples:
321 Get intersecting tiles' indices
323 from rok4_tools.tmsizer_utils.processors.map import Geometry2tileindexProcessor
325 try:
326 # Creation of Processor source_processor with format GEOMETRY
327 processor = Geometry2tileindexProcessor(source_processor, level="15", format="GeoJSON" )
328 for item in processor.process():
329 (level, col, row) = item
331 except Exception as e:
332 print("{e}")
334 Yields:
335 Iterator[Tuple[str, int, int]]: Tile index (level, col, row)
336 """
338 tile_matrix = self.tms.get_level(self.__level)
340 if self.__input.format == "GEOMETRY":
342 for item in self.__input.process():
343 self._processed += 1
345 try:
346 geom = None
347 if self.__geometry_format == "WKT":
348 geom = ogr.ForceToMultiPolygon(ogr.CreateGeometryFromWkt(item))
349 elif self.__geometry_format == "GeoJSON":
350 geom = ogr.ForceToMultiPolygon(ogr.CreateGeometryFromJson(item))
351 elif self.__geometry_format == "WKB":
352 geom = ogr.ForceToMultiPolygon(ogr.CreateGeometryFromWkb(item))
354 for i in range(0, geom.GetGeometryCount()):
355 g = geom.GetGeometryRef(i)
356 xmin, xmax, ymin, ymax = g.GetEnvelope()
357 col_min, row_min, col_max, row_max = tile_matrix.bbox_to_tiles((xmin, ymin, xmax, ymax))
359 for col in range(col_min, col_max + 1):
360 for row in range(row_min, row_max + 1):
361 tg = bbox_to_geometry(tile_matrix.tile_to_bbox(col, row))
362 if g.Intersects(tg):
363 yield (self.__level, col, row)
365 except Exception as e:
366 # La géométrie n'est pas valide, on la passe simplement
367 print(e)
368 continue
370 def __str__(self) -> str:
371 return f"Geometry2tileindexProcessor : {self._processed} {self.__input.format} items processed (format {self.__geometry_format}), extracting intersecting tile's indices"