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

1"""Provide processor tranforming data. Output data is emitted as the input processor reading progresses 

2 

3The module contains the following classes: 

4 

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""" 

10 

11import sys 

12from typing import Dict, List, Tuple, Union, Iterator 

13from math import floor 

14from urllib.parse import urlparse, parse_qs 

15 

16from osgeo import gdal, ogr, osr 

17ogr.UseExceptions() 

18osr.UseExceptions() 

19gdal.UseExceptions() 

20 

21from rok4.tile_matrix_set import TileMatrixSet 

22from rok4.utils import bbox_to_geometry 

23 

24from rok4_tools.tmsizer_utils.processors.processor import Processor 

25 

26class Gettile2tileindexProcessor(Processor): 

27 """Processor extracting tile index from a WMTS GetTile URL 

28 

29 Accepted input format is "GETTILE_PARAMS" and output format is "TILE_INDEX" 

30 

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 """ 

36 

37 input_formats_allowed = ["GETTILE_PARAMS"] 

38 

39 def __init__(self, input: Processor, **options): 

40 """Constructor method 

41 

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 

46 

47 Raises: 

48 ValueError: Input format is not allowed 

49 ValueError: Provided level is not in the pivot TMS 

50 """ 

51 

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}") 

54 

55 super().__init__("TILE_INDEX") 

56 

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 

65 

66 self.__input = input 

67 if "layers" in options.keys(): 

68 self.__layers = options["layers"].split(",") 

69 else: 

70 self.__layers = None 

71 

72 def process(self) -> Iterator[Tuple[str, int, int]]: 

73 """Read an item from the input processor and extract tile index 

74 

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. 

78 

79 Examples: 

80 

81 Get tile index 

82 

83 from rok4_tools.tmsizer_utils.processors.map import Gettile2tileindexProcessor 

84 

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 

90 

91 except Exception as e: 

92 print("{e}") 

93 

94 Yields: 

95 Iterator[Tuple[str, int, int]]: Tile index (level, col, row) 

96 """ 

97 

98 if self.__input.format == "GETTILE_PARAMS": 

99 for item in self.__input.process(): 

100 self._processed += 1 

101 

102 qs = parse_qs(urlparse(item.upper()).query) 

103 try: 

104 

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 

108 

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 

112 

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 

116 

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 

120 

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 

126 

127 def __str__(self) -> str: 

128 return f"Gettile2tileindexProcessor : {self._processed} {self.__input.format} items processed, extracting tile's indices" 

129 

130 

131class Tileindex2gettileProcessor(Processor): 

132 """Processor generating WMTS GetTile query parameters from tile index 

133 

134 Accepted input format is "TILE_INDEX" and output format is "GETTILE_PARAMS" 

135 

136 Attributes: 

137 __input (Processor): Processor from which data is read 

138 """ 

139 

140 input_formats_allowed = ["TILE_INDEX"] 

141 

142 def __init__(self, input: Processor): 

143 """Constructor method 

144 

145 Args: 

146 input (Processor): Processor from which data is read 

147 

148 Raises: 

149 ValueError: Input format is not allowed 

150 """ 

151 

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}") 

154 

155 super().__init__("GETTILE_PARAMS") 

156 

157 self.__input = input 

158 

159 def process(self) -> Iterator[str]: 

160 """Read a tile index from the input processor and generate WMTS GetTile query parameters 

161 

162 Examples: 

163 

164 Get GetTile query parameters 

165 

166 from rok4_tools.tmsizer_utils.processors.map import Tileindex2gettileProcessor 

167 

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 

173 

174 except Exception as e: 

175 print("{e}") 

176 

177 Yields: 

178 Iterator[str]: GetTile query parameters TILEMATRIXSET=<tms>&TILEMATRIX=<level>&TILECOL=<col>&TILEROW=<row> 

179 """ 

180 

181 if self.__input.format == "TILE_INDEX": 

182 for item in self.__input.process(): 

183 self._processed += 1 

184 

185 (level, col, row) = item 

186 

187 yield f"TILEMATRIXSET={self.tms.name}&TILEMATRIX={level}&TILECOL={col}&TILEROW={row}" 

188 

189 def __str__(self) -> str: 

190 return f"Tileindex2gettileProcessor : {self._processed} {self.__input.format} items processed, generating GetTile's query parameters" 

191 

192class Tileindex2pointProcessor(Processor): 

193 """Processor generating the tile's center coordinates from tile index 

194 

195 Accepted input format is "TILE_INDEX" and output format is "POINT" 

196 

197 Attributes: 

198 __input (Processor): Processor from which data is read 

199 """ 

200 

201 input_formats_allowed = ["TILE_INDEX"] 

202 

203 def __init__(self, input: Processor): 

204 """Constructor method 

205 

206 Args: 

207 input (Processor): Processor from which data is read 

208 

209 Raises: 

210 ValueError: Input format is not allowed 

211 """ 

212 

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}") 

215 

216 super().__init__("POINT") 

217 

218 self.__input = input 

219 

220 def process(self) -> Iterator[Tuple[float, float]]: 

221 """Read a tile index from the input processor and generate the tile's center coordinates 

222 

223 Examples: 

224 

225 Get tile's center coordinates 

226 

227 from rok4_tools.tmsizer_utils.processors.map import Tileindex2pointProcessor 

228 

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 

234 

235 except Exception as e: 

236 print("{e}") 

237 

238 Yields: 

239 Iterator[Tuple[float, float]]: point coordinates (x,y) 

240 """ 

241 

242 if self.__input.format == "TILE_INDEX": 

243 for item in self.__input.process(): 

244 self._processed += 1 

245 

246 (level, col, row) = item 

247 try: 

248 bb = self.tms.get_level(level).tile_to_bbox(col, row) 

249 

250 x_center = bb[0] + (bb[2] - bb[0]) / 2; 

251 y_center = bb[1] + (bb[3] - bb[1]) / 2; 

252 

253 yield (x_center, y_center) 

254 except Exception as e: 

255 # Le niveau n'est pas valide, on passe simplement 

256 pass 

257 

258 def __str__(self) -> str: 

259 return f"Tileindex2pointProcessor : {self._processed} {self.__input.format} items processed, extracting tile's center coordinates" 

260 

261 

262class Geometry2tileindexProcessor(Processor): 

263 """Processor generating the tile's center coordinates from tile index 

264 

265 Accepted input format is "GEOMETRY" and output format is "TILE_INDEX" 

266 

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 """ 

272 

273 input_formats_allowed = ["GEOMETRY"] 

274 geometry_formats_allowed = ["WKT", "WKB", "GeoJSON"] 

275 

276 def __init__(self, input: Processor, **options): 

277 """Constructor method 

278 

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 

283 

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 """ 

290 

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}") 

293 

294 super().__init__("TILE_INDEX") 

295 

296 self.__input = input 

297 

298 try: 

299 

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}") 

302 

303 self.__geometry_format = options["format"] 

304 

305 if self.tms.get_level(options["level"]) is None: 

306 raise ValueError(f"Provided level is not in the TMS") 

307 

308 self.__level = options["level"] 

309 

310 except KeyError as e: 

311 raise KeyError(f"Option {e} is required to generate tile indices from geometries") 

312 

313 def process(self) -> Iterator[Tuple[str, int, int]]: 

314 """Read a geometry from the input processor and extract tile index 

315 

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 

318 

319 Examples: 

320 

321 Get intersecting tiles' indices 

322 

323 from rok4_tools.tmsizer_utils.processors.map import Geometry2tileindexProcessor 

324 

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 

330 

331 except Exception as e: 

332 print("{e}") 

333 

334 Yields: 

335 Iterator[Tuple[str, int, int]]: Tile index (level, col, row) 

336 """ 

337 

338 tile_matrix = self.tms.get_level(self.__level) 

339 

340 if self.__input.format == "GEOMETRY": 

341 

342 for item in self.__input.process(): 

343 self._processed += 1 

344 

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)) 

353 

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)) 

358 

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) 

364 

365 except Exception as e: 

366 # La géométrie n'est pas valide, on la passe simplement 

367 print(e) 

368 continue 

369 

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"