37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167 | @dataclass
class Tile:
crs: str
transform: affine.Affine # Pixel to crs
shape: Tuple[int, int]
@classmethod
def from_pb(cls, pb: layouts_pb2.Tile):
return cls(pb.crs, affine.Affine(pb.transform.b, pb.transform.c, pb.transform.a,
pb.transform.e, pb.transform.f, pb.transform.d),
shape=(int(pb.size_px.width), int(pb.size_px.height))
)
@classmethod
def from_geotransform(cls, transform: Union[affine.Affine, Tuple[float, float, float, float, float, float]],
crs: Union[str, int], shape: Tuple[int, int]) -> Tile:
"""
Create a tile from a geotransform, a crs and a shape
Args:
transform: geotransform from pixel coordinates to CRS.
crs: Coordinate Reference System of the tile
shape: shape of the tile (in pixel) (@warning shape is the transpose of numpy shape)
Returns:
A new tile
"""
return cls(crs_to_str(crs), Tile._parse_geotransform(transform), shape)
@classmethod
def from_record(cls, record: entities.Record, crs: Union[str, int], resolution: float) -> Tile:
""" Create a tile that cover the record in the crs at a given resolution
Warning: record.aoi must be loaded (with client.load_aoi())
Warning: Check the `result.shape` as the size might be huge !
Warning: the aoi is converted to the crs, but it might be imprecise at the borders
Args:
record:
crs: Coordinate Reference System of the tile
resolution: resolution of the pixel in the CRS
Returns:
A new tile
"""
return Tile.from_aoi(record.aoi, crs, resolution)
@classmethod
def from_bbox(cls, bbox: Tuple[float, float, float, float], crs: Union[str, int],
resolution: Union[float, Tuple[float, float]]) -> Tile:
"""
Create a tile from a bbox, a crs and a resolution
Args:
bbox : (x1, y1, x2, y2) in crs coordinates
crs : (Coordinate Reference System) of the tile
resolution : of the pixel in the CRS
Returns:
A new tile
"""
rx, ry = resolution if isinstance(resolution, tuple) else (resolution, -resolution)
x1, y1, x2, y2 = bbox
if math.copysign(1, rx)*(x2-x1) < 0:
x1, x2 = x2, x1
if math.copysign(1, ry)*(y2-y1) < 0:
y1, y2 = y2, y1
transform = geo_transform(x1, y1, resolution)
sx, sy = (~transform) * (x2, y2)
return cls(crs_to_str(crs), transform, (math.ceil(sx), math.ceil(sy)))
@classmethod
def from_aoi(cls, aoi: geometry.MultiPolygon, crs: Union[str, int],
resolution: Union[float, Tuple[float, float]]) -> Tile:
"""
Args:
aoi : multipolygon in 4326 coordinates
crs : (Coordinate Reference System) of the tile
resolution : of the pixel in the CRS
Returns:
A new tile
"""
return Tile.from_bbox(gpd.GeoSeries(aoi, crs=4326).to_crs(crs).total_bounds, crs=crs, resolution=resolution)
def __str__(self):
return "Tile {}\n" \
" transform: ({}, {} {}, {}, {} {})\n" \
" bounds: {} {}\n" \
" shape: {} \n" \
" crs: {}\n".format(self.shape,
self.transform.c, self.transform.a, self.transform.b,
self.transform.f, self.transform.d, self.transform.e,
self.transform*(0, 0), self.transform*self.shape,
self.shape, self.crs)
def __repr__(self):
return self.__str__()
def geoseries(self) -> gpd.GeoSeries:
x1, y1 = self.transform*(0, 0)
x2, y2 = self.transform*self.shape
p = geometry.Polygon([[x1, y1], [x1, y2], [x2, y2], [x2, y1], [x1, y1]])
return gpd.GeoSeries(p, crs=self.crs)
def geometry(self, to_crs: Union[str, int] = None):
gs = self.geoseries()
if to_crs is not None:
gs = gs.to_crs(crs_to_str(to_crs))
return gs.iloc[0]
def reshape(self, i1, j1, i2, j2) -> entities.Tile:
""" Create a new Tile using the coordinate pixels
@warning inverse of numpy coordinates """
return Tile.from_bbox(self.transform*(i1, j1) + self.transform*(i2, j2),
self.crs, resolution=(self.transform.a, self.transform.e))
@staticmethod
def _parse_geotransform(transform: Union[affine.Affine, Tuple[float, float, float, float, float, float]]) \
-> affine.Affine:
if isinstance(transform, affine.Affine):
return transform
return affine.Affine.from_gdal(*transform)
@staticmethod
def to_geoseries(tiles: List[Tile]):
""" return list of Tiles as geoseries """
return gpd.GeoSeries(pd.concat([t.geoseries().to_crs("epsg:4326") for t in tiles]))
@staticmethod
def plot(tiles: List[Tile], **kwargs):
""" kwargs: additional arguments for utils.plot_aoi """
return utils.plot_aoi(Tile.to_geoseries(tiles), **kwargs)
|