Coverage for src/rok4/TileMatrixSet.py: 99%
71 statements
« prev ^ index » next coverage.py v7.4.1, created at 2024-01-29 10:29 +0100
« prev ^ index » next coverage.py v7.4.1, created at 2024-01-29 10:29 +0100
1"""Provide classes to use a tile matrix set.
3The module contains the following classes:
5- `TileMatrixSet` - Multi level grid
6- `TileMatrix` - A tile matrix set level
8Loading a tile matrix set requires environment variables :
9- ROK4_TMS_DIRECTORY
10"""
12from rok4.Exceptions import *
13from rok4.Storage import get_data_str
14from rok4.Utils import *
16from typing import Dict, List, Tuple
17from json.decoder import JSONDecodeError
18import json
19import os
22class TileMatrix:
23 """A tile matrix is a tile matrix set's level.
25 Attributes:
26 id (str): TM identifiant (no underscore).
27 tms (TileMatrixSet): TMS to whom it belong
28 resolution (float): Ground size of a pixel, using unity of the TMS's coordinate system.
29 origin (Tuple[float, float]): X,Y coordinates of the upper left corner for the level, the grid's origin.
30 tile_size (Tuple[int, int]): Pixel width and height of a tile.
31 matrix_size (Tuple[int, int]): Number of tile in the level, widthwise and heightwise.
32 """
34 def __init__(self, level: Dict, tms: "TileMatrixSet") -> None:
35 """Constructor method
37 Args:
38 level: Level attributes, according to JSON structure
39 tms: TMS object containing the level to create
41 Raises:
42 MissingAttributeError: Attribute is missing in the content
43 """
45 self.tms = tms
46 try:
47 self.id = level["id"]
48 if self.id.find("_") != -1:
49 raise Exception(
50 f"TMS {tms.path} owns a level whom id contains an underscore ({self.id})"
51 )
52 self.resolution = level["cellSize"]
53 self.origin = (
54 level["pointOfOrigin"][0],
55 level["pointOfOrigin"][1],
56 )
57 self.tile_size = (
58 level["tileWidth"],
59 level["tileHeight"],
60 )
61 self.matrix_size = (
62 level["matrixWidth"],
63 level["matrixHeight"],
64 )
65 self.__latlon = (
66 self.tms.sr.EPSGTreatsAsLatLong() or self.tms.sr.EPSGTreatsAsNorthingEasting()
67 )
68 except KeyError as e:
69 raise MissingAttributeError(tms.path, f"tileMatrices[].{e}")
71 def x_to_column(self, x: float) -> int:
72 """Convert west-east coordinate to tile's column
74 Args:
75 x (float): west-east coordinate (TMS coordinates system)
77 Returns:
78 int: tile's column
79 """
80 return int((x - self.origin[0]) / (self.resolution * self.tile_size[0]))
82 def y_to_row(self, y: float) -> int:
83 """Convert north-south coordinate to tile's row
85 Args:
86 y (float): north-south coordinate (TMS coordinates system)
88 Returns:
89 int: tile's row
90 """
91 return int((self.origin[1] - y) / (self.resolution * self.tile_size[1]))
93 def tile_to_bbox(self, tile_col: int, tile_row: int) -> Tuple[float, float, float, float]:
94 """Get tile terrain extent (xmin, ymin, xmax, ymax), in TMS coordinates system
96 TMS spatial reference is Lat / Lon case is handled.
98 Args:
99 tile_col (int): column indice
100 tile_row (int): row indice
102 Returns:
103 Tuple[float, float, float, float]: terrain extent (xmin, ymin, xmax, ymax)
104 """
105 if self.__latlon:
106 return (
107 self.origin[1] - self.resolution * (tile_row + 1) * self.tile_size[1],
108 self.origin[0] + self.resolution * tile_col * self.tile_size[0],
109 self.origin[1] - self.resolution * tile_row * self.tile_size[1],
110 self.origin[0] + self.resolution * (tile_col + 1) * self.tile_size[0],
111 )
112 else:
113 return (
114 self.origin[0] + self.resolution * tile_col * self.tile_size[0],
115 self.origin[1] - self.resolution * (tile_row + 1) * self.tile_size[1],
116 self.origin[0] + self.resolution * (tile_col + 1) * self.tile_size[0],
117 self.origin[1] - self.resolution * tile_row * self.tile_size[1],
118 )
120 def bbox_to_tiles(self, bbox: Tuple[float, float, float, float]) -> Tuple[int, int, int, int]:
121 """Get extrems tile columns and rows corresponding to provided bounding box
123 TMS spatial reference is Lat / Lon case is handled.
125 Args:
126 bbox (Tuple[float, float, float, float]): bounding box (xmin, ymin, xmax, ymax), in TMS coordinates system
128 Returns:
129 Tuple[int, int, int, int]: extrem tiles (col_min, row_min, col_max, row_max)
130 """
132 if self.__latlon:
133 return (
134 self.x_to_column(bbox[1]),
135 self.y_to_row(bbox[2]),
136 self.x_to_column(bbox[3]),
137 self.y_to_row(bbox[0]),
138 )
139 else:
140 return (
141 self.x_to_column(bbox[0]),
142 self.y_to_row(bbox[3]),
143 self.x_to_column(bbox[2]),
144 self.y_to_row(bbox[1]),
145 )
147 def point_to_indices(self, x: float, y: float) -> Tuple[int, int, int, int]:
148 """Get pyramid's tile and pixel indices from point's coordinates
150 TMS spatial reference with Lat / Lon order is handled.
152 Args:
153 x (float): point's x
154 y (float): point's y
156 Returns:
157 Tuple[int, int, int, int]: tile's column, tile's row, pixel's (in the tile) column, pixel's row
158 """
160 if self.__latlon:
161 absolute_pixel_column = int((y - self.origin[0]) / self.resolution)
162 absolute_pixel_row = int((self.origin[1] - x) / self.resolution)
163 else:
164 absolute_pixel_column = int((x - self.origin[0]) / self.resolution)
165 absolute_pixel_row = int((self.origin[1] - y) / self.resolution)
167 return (
168 absolute_pixel_column // self.tile_size[0],
169 absolute_pixel_row // self.tile_size[1],
170 absolute_pixel_column % self.tile_size[0],
171 absolute_pixel_row % self.tile_size[1],
172 )
175class TileMatrixSet:
176 """A tile matrix set is multi levels grid definition
178 Attributes:
179 name (str): TMS's name
180 path (str): TMS origin path (JSON)
181 id (str): TMS identifier
182 srs (str): TMS coordinates system
183 sr (osgeo.osr.SpatialReference): TMS OSR spatial reference
184 levels (Dict[str, TileMatrix]): TMS levels
185 """
187 def __init__(self, name: str) -> None:
188 """Constructor method
190 Args:
191 name: TMS's name
193 Raises:
194 MissingEnvironmentError: Missing object storage informations
195 Exception: No level in the TMS, CRS not recognized by OSR
196 StorageError: Storage read issue
197 FormatError: Provided path is not a well formed JSON
198 MissingAttributeError: Attribute is missing in the content
199 """
201 self.name = name
203 try:
204 self.path = os.path.join(os.environ["ROK4_TMS_DIRECTORY"], f"{self.name}.json")
205 except KeyError as e:
206 raise MissingEnvironmentError(e)
208 try:
209 data = json.loads(get_data_str(self.path))
211 self.id = data["id"]
212 self.srs = data["crs"]
213 self.sr = srs_to_spatialreference(self.srs)
214 self.levels = {}
215 for l in data["tileMatrices"]:
216 lev = TileMatrix(l, self)
217 self.levels[lev.id] = lev
219 if len(self.levels.keys()) == 0:
220 raise Exception(f"TMS '{self.path}' has no level")
222 if data["orderedAxes"] != ["X", "Y"] and data["orderedAxes"] != ["Lon", "Lat"]:
223 raise Exception(
224 f"TMS '{self.path}' own invalid axes order : only X/Y or Lon/Lat are handled"
225 )
227 except JSONDecodeError as e:
228 raise FormatError("JSON", self.path, e)
230 except KeyError as e:
231 raise MissingAttributeError(self.path, e)
233 except RuntimeError as e:
234 raise Exception(
235 f"Wrong attribute 'crs' ('{self.srs}') in '{self.path}', not recognize by OSR"
236 )
238 def get_level(self, level_id: str) -> "TileMatrix":
239 """Get one level according to its identifier
241 Args:
242 level_id: Level identifier
244 Returns:
245 The corresponding tile matrix, None if not present
246 """
248 return self.levels.get(level_id, None)
250 @property
251 def sorted_levels(self) -> List[TileMatrix]:
252 return sorted(self.levels.values(), key=lambda l: l.resolution)