Skip to content

Commit ccbb441

Browse files
authored
Merge pull request #85 from rok4/develop
Release 2.1.4
2 parents 8a5f0b2 + 8ce8fc0 commit ccbb441

File tree

8 files changed

+139
-140
lines changed

8 files changed

+139
-140
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## 2.1.4
2+
3+
### [Fixed]
4+
5+
* Storage : la réponse à un HEAD (test existence en S3) donne un code 404 et non NoSuchKey (confusion avec la lecture d'objet)
6+
* RasterSet: le chargement d'un raster set à partir d'un fichier ou d'un descripteur utilise la librairie Storage et non la librairie GDAL
7+
18
## 2.1.3
29

310
### [Fixed]

src/rok4/pyramid.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ class Pyramid:
382382
__tms (rok4.tile_matrix_set.TileMatrixSet): Used grid
383383
__levels (Dict[str, Level]): Pyramid's levels
384384
__format (str): Data format
385-
__storage (Dict[str, Union[rok4.storage.StorageType,str,int]]): Pyramid's storage informations (type, root and depth if FILE storage)
385+
__storage (Dict[str, Union[rok4.enums.StorageType,str,int]]): Pyramid's storage informations (type, root and depth if FILE storage)
386386
__raster_specifications (Dict): If raster pyramid, raster specifications
387387
__content (Dict): Loading status (loaded) and list content (cache).
388388
@@ -600,12 +600,15 @@ def raster_specifications(self) -> Dict:
600600
"""Get raster specifications for a RASTER pyramid
601601
602602
Example:
603-
{
604-
"channels": 3,
605-
"nodata": "255,0,0",
606-
"photometric": "rgb",
607-
"interpolation": "bicubic"
608-
}
603+
604+
RGB pyramid with red nodata
605+
606+
{
607+
"channels": 3,
608+
"nodata": "255,0,0",
609+
"photometric": "rgb",
610+
"interpolation": "bicubic"
611+
}
609612
610613
Returns:
611614
Dict: Raster specifications, None if VECTOR pyramid

src/rok4/raster.py

Lines changed: 92 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,34 @@
22
33
The module contains the following class :
44
5-
- Raster - Structure describing raster data.
6-
- RasterSet - Structure describing a set of raster data.
5+
- `Raster` - Structure describing raster data.
6+
- `RasterSet` - Structure describing a set of raster data.
77
"""
88

99
# -- IMPORTS --
1010

11-
# standard library
12-
import copy
1311
import json
1412
import re
13+
import tempfile
14+
15+
# standard library
16+
from copy import deepcopy
17+
from json.decoder import JSONDecodeError
1518
from typing import Dict, Tuple
1619

1720
# 3rd party
1821
from osgeo import gdal, ogr
1922

2023
# package
2124
from rok4.enums import ColorFormat
22-
from rok4.storage import exists, get_osgeo_path, put_data_str
25+
from rok4.storage import (
26+
copy,
27+
exists,
28+
get_data_str,
29+
get_osgeo_path,
30+
put_data_str,
31+
remove,
32+
)
2333
from rok4.utils import compute_bbox, compute_format
2434

2535
# -- GLOBALS --
@@ -32,18 +42,13 @@
3242
class Raster:
3343
"""A structure describing raster data
3444
35-
Attributes :
36-
path (str): path to the file/object (ex:
37-
file:///path/to/image.tif or s3://bucket/path/to/image.tif)
38-
bbox (Tuple[float, float, float, float]): bounding rectange
39-
in the data projection
40-
bands (int): number of color bands (or channels)
41-
format (ColorFormat): numeric variable format for color values.
42-
Bit depth, as bits per channel, can be derived from it.
43-
mask (str): path to the associated mask file or object, if any,
44-
or None (same path as the image, but with a ".msk" extension
45-
and TIFF format. ex:
46-
file:///path/to/image.msk or s3://bucket/path/to/image.msk)
45+
Attributes:
46+
path (str): path to the file/object (ex: file:///path/to/image.tif or s3://bucket/path/to/image.tif)
47+
bbox (Tuple[float, float, float, float]): bounding rectange in the data projection
48+
bands (int): number of color bands (or channels) format (ColorFormat). Numeric variable format for color values. Bit depth, as bits per channel,
49+
can be derived from it.
50+
mask (str): path to the associated mask file or object, if any, or None (same path as the image, but with a ".msk" extension and TIFF format.
51+
Ex: file:///path/to/image.msk or s3://bucket/path/to/image.msk)
4752
dimensions (Tuple[int, int]): image width and height, in pixels
4853
"""
4954

@@ -77,14 +82,16 @@ def from_file(cls, path: str) -> "Raster":
7782
print(f"Cannot load information from image : {e}")
7883
7984
Raises:
85+
FormatError: MASK file is not a TIFF
8086
RuntimeError: raised by OGR/GDAL if anything goes wrong
8187
NotImplementedError: Storage type not handled
88+
FileNotFoundError: File or object does not exists
8289
8390
Returns:
8491
Raster: a Raster instance
8592
"""
8693
if not exists(path):
87-
raise Exception(f"No file or object found at path '{path}'.")
94+
raise FileNotFoundError(f"No file or object found at path '{path}'.")
8895

8996
self = cls()
9097

@@ -100,11 +107,8 @@ def from_file(cls, path: str) -> "Raster":
100107
work_mask_path = get_osgeo_path(mask_path)
101108
mask_driver = gdal.IdentifyDriver(work_mask_path).ShortName
102109
if "GTiff" != mask_driver:
103-
message = (
104-
f"Mask file '{mask_path}' is not a TIFF image."
105-
+ f" (GDAL driver : '{mask_driver}'"
106-
)
107-
raise Exception(message)
110+
message = f"Mask file '{mask_path}' use GDAL driver : '{mask_driver}'"
111+
raise FormatError("TIFF", mask_path, message)
108112
self.mask = mask_path
109113
else:
110114
self.mask = None
@@ -129,19 +133,13 @@ def from_parameters(
129133
"""Creates a Raster object from parameters
130134
131135
Args:
132-
path (str): path to the file/object (ex:
133-
file:///path/to/image.tif or s3://bucket/image.tif)
136+
path (str): path to the file/object (ex: file:///path/to/image.tif or s3://bucket/image.tif)
134137
bands (int): number of color bands (or channels)
135-
bbox (Tuple[float, float, float, float]): bounding rectange
136-
in the data projection
137-
dimensions (Tuple[int, int]): image width and height
138-
expressed in pixels
139-
format (ColorFormat): numeric format for color values.
140-
Bit depth, as bits per channel, can be derived from it.
141-
mask (str, optionnal): path to the associated mask, if any,
142-
or None (same path as the image, but with a
143-
".msk" extension and TIFF format. ex:
144-
file:///path/to/image.msk or s3://bucket/image.msk)
138+
bbox (Tuple[float, float, float, float]): bounding rectange in the data projection
139+
dimensions (Tuple[int, int]): image width and height expressed in pixels
140+
format (ColorFormat): numeric format for color values. Bit depth, as bits per channel, can be derived from it.
141+
mask (str, optionnal): path to the associated mask, if any, or None (same path as the image, but with a ".msk"
142+
extension and TIFF format. ex: file:///path/to/image.msk or s3://bucket/image.msk)
145143
146144
Examples:
147145
@@ -152,13 +150,12 @@ def from_parameters(
152150
153151
try:
154152
raster = Raster.from_parameters(
155-
path="file:///data/SC1000/_0040_6150_L93.tif",
156-
mask="file:///data/SC1000/0040_6150_L93.msk",
157-
bands=3,
158-
format=ColorFormat.UINT8,
159-
dimensions=(2000, 2000),
160-
bbox=(40000.000, 5950000.000,
161-
240000.000, 6150000.000)
153+
path="file:///data/SC1000/_0040_6150_L93.tif",
154+
mask="file:///data/SC1000/0040_6150_L93.msk",
155+
bands=3,
156+
format=ColorFormat.UINT8,
157+
dimensions=(2000, 2000),
158+
bbox=(40000.000, 5950000.000, 240000.000, 6150000.000)
162159
)
163160
164161
except Exception as e:
@@ -186,24 +183,16 @@ def from_parameters(
186183
class RasterSet:
187184
"""A structure describing a set of raster data
188185
189-
Attributes :
186+
Attributes:
190187
raster_list (List[Raster]): List of Raster instances in the set
191-
colors (List[Dict]): List of color properties for each raster
192-
instance. Contains only one element if
193-
the set is homogenous.
194-
Element properties:
195-
bands (int): number of color bands (or channels)
196-
format (ColorFormat): numeric variable format for
197-
color values. Bit depth, as bits per channel,
198-
can be derived from it.
188+
colors (Set[Tuple[int, ColorFormat]]): Set (distinct values) of color properties (bands and format) found in the raster set.
199189
srs (str): Name of the set's spatial reference system
200-
bbox (Tuple[float, float, float, float]): bounding rectange
201-
in the data projection, enclosing the whole set
190+
bbox (Tuple[float, float, float, float]): bounding rectange in the data projection, enclosing the whole set
202191
"""
203192

204193
def __init__(self) -> None:
205194
self.bbox = (None, None, None, None)
206-
self.colors = []
195+
self.colors = set()
207196
self.raster_list = []
208197
self.srs = None
209198

@@ -212,9 +201,8 @@ def from_list(cls, path: str, srs: str) -> "RasterSet":
212201
"""Instanciate a RasterSet from an images list path and a srs
213202
214203
Args:
215-
path (str): path to the images list file or object
216-
(each line in this list contains the path to
217-
an image file or object in the set)
204+
path (str): path to the images list file or object (each line in this list contains the path to an image file or object in the set)
205+
srs (str): images' coordinates system
218206
219207
Examples:
220208
@@ -224,13 +212,13 @@ def from_list(cls, path: str, srs: str) -> "RasterSet":
224212
225213
try:
226214
raster_set = RasterSet.from_list(
227-
path="file:///data/SC1000.list",
228-
srs="EPSG:3857"
215+
path="file:///data/SC1000.list",
216+
srs="EPSG:3857"
229217
)
230218
231219
except Exception as e:
232220
print(
233-
f"Cannot load information from list file : {e}"
221+
f"Cannot load information from list file : {e}"
234222
)
235223
236224
Raises:
@@ -243,33 +231,42 @@ def from_list(cls, path: str, srs: str) -> "RasterSet":
243231
self = cls()
244232
self.srs = srs
245233

246-
local_list_path = get_osgeo_path(path)
234+
# Chargement de la liste des images (la liste peut être un fichier ou un objet)
235+
list_obj = tempfile.NamedTemporaryFile(mode="r", delete=False)
236+
list_file = list_obj.name
237+
copy(path, f"file://{list_file}")
238+
list_obj.close()
247239
image_list = []
248-
with open(file=local_list_path) as list_file:
249-
for line in list_file:
240+
with open(list_file) as listin:
241+
for line in listin:
250242
image_path = line.strip(" \t\n\r")
251243
image_list.append(image_path)
252244

253-
temp_bbox = [None, None, None, None]
245+
remove(f"file://{list_file}")
246+
247+
bbox = [None, None, None, None]
254248
for image_path in image_list:
255249
raster = Raster.from_file(image_path)
256250
self.raster_list.append(raster)
257-
if temp_bbox == [None, None, None, None]:
258-
for i in range(0, 4, 1):
259-
temp_bbox[i] = raster.bbox[i]
251+
252+
# Mise à jour de la bbox globale
253+
if bbox == [None, None, None, None]:
254+
bbox = list(raster.bbox)
260255
else:
261-
if temp_bbox[0] > raster.bbox[0]:
262-
temp_bbox[0] = raster.bbox[0]
263-
if temp_bbox[1] > raster.bbox[1]:
264-
temp_bbox[1] = raster.bbox[1]
265-
if temp_bbox[2] < raster.bbox[2]:
266-
temp_bbox[2] = raster.bbox[2]
267-
if temp_bbox[3] < raster.bbox[3]:
268-
temp_bbox[3] = raster.bbox[3]
269-
color_dict = {"bands": raster.bands, "format": raster.format}
270-
if color_dict not in self.colors:
271-
self.colors.append(color_dict)
272-
self.bbox = tuple(temp_bbox)
256+
if bbox[0] > raster.bbox[0]:
257+
bbox[0] = raster.bbox[0]
258+
if bbox[1] > raster.bbox[1]:
259+
bbox[1] = raster.bbox[1]
260+
if bbox[2] < raster.bbox[2]:
261+
bbox[2] = raster.bbox[2]
262+
if bbox[3] < raster.bbox[3]:
263+
bbox[3] = raster.bbox[3]
264+
265+
# Inventaire des colors distinctes
266+
self.colors.add((raster.bands, raster.format))
267+
268+
self.bbox = tuple(bbox)
269+
273270
return self
274271

275272
@classmethod
@@ -287,12 +284,11 @@ def from_descriptor(cls, path: str) -> "RasterSet":
287284
288285
try:
289286
raster_set = RasterSet.from_descriptor(
290-
"file:///data/images/descriptor.json"
287+
"file:///data/images/descriptor.json"
291288
)
292289
293290
except Exception as e:
294-
message = ("Cannot load information from "
295-
+ f"descriptor file : {e}")
291+
message = ("Cannot load information from descriptor file : {e}")
296292
print(message)
297293
298294
Raises:
@@ -303,24 +299,26 @@ def from_descriptor(cls, path: str) -> "RasterSet":
303299
RasterSet: a RasterSet instance
304300
"""
305301
self = cls()
306-
descriptor_path = get_osgeo_path(path)
307-
with open(file=descriptor_path) as file_handle:
308-
raw_content = file_handle.read()
309-
serialization = json.loads(raw_content)
302+
303+
try:
304+
serialization = json.loads(get_data_str(path))
305+
306+
except JSONDecodeError as e:
307+
raise FormatError("JSON", path, e)
308+
310309
self.srs = serialization["srs"]
311310
self.raster_list = []
312311
for raster_dict in serialization["raster_list"]:
313-
parameters = copy.deepcopy(raster_dict)
312+
parameters = deepcopy(raster_dict)
314313
parameters["bbox"] = tuple(raster_dict["bbox"])
315314
parameters["dimensions"] = tuple(raster_dict["dimensions"])
316315
parameters["format"] = ColorFormat[raster_dict["format"]]
317316
self.raster_list.append(Raster.from_parameters(**parameters))
317+
318318
self.bbox = tuple(serialization["bbox"])
319-
self.colors = []
320319
for color_dict in serialization["colors"]:
321-
color_item = copy.deepcopy(color_dict)
322-
color_item["format"] = ColorFormat[color_dict["format"]]
323-
self.colors.append(color_item)
320+
self.colors.add((color_dict["bands"], ColorFormat[color_dict["format"]]))
321+
324322
return self
325323

326324
@property
@@ -332,7 +330,7 @@ def serializable(self) -> Dict:
332330
"""
333331
serialization = {"bbox": list(self.bbox), "srs": self.srs, "colors": [], "raster_list": []}
334332
for color in self.colors:
335-
color_serial = {"bands": color["bands"], "format": color["format"].name}
333+
color_serial = {"bands": color[0], "format": color[1].name}
336334
serialization["colors"].append(color_serial)
337335
for raster in self.raster_list:
338336
raster_dict = {
@@ -345,15 +343,14 @@ def serializable(self) -> Dict:
345343
if raster.mask is not None:
346344
raster_dict["mask"] = raster.mask
347345
serialization["raster_list"].append(raster_dict)
346+
348347
return serialization
349348

350349
def write_descriptor(self, path: str = None) -> None:
351350
"""Print raster set's descriptor as JSON
352351
353352
Args:
354-
path (str, optional): Complete path (file or object)
355-
where to print the raster set's JSON. Defaults to None,
356-
JSON is printed to standard output.
353+
path (str, optional): Complete path (file or object) where to print the raster set's JSON. Defaults to None, JSON is printed to standard output.
357354
"""
358355
content = json.dumps(self.serializable, sort_keys=True)
359356
if path is None:

0 commit comments

Comments
 (0)