Skip to content

save

save_cog(data_save, path_tiff_save, profile=None, descriptions=None, tags=None, dir_tmpfiles='.', fs=None)

Save data GeoData object as cloud optimized GeoTIFF

Parameters:

Name Type Description Default
data_save GeoData

GeoData (C, H, W) format with geoinformation (crs and transform).

required
descriptions Optional[List[str]]

name of the bands

None
path_tiff_save str

path to save the COG GeoTIFF

required
profile Optional[Dict[str, Any]]

profile dict to save the data. crs and transform will be updated from data_save.

None
tags Optional[Dict[str, Any]]

Dict to save as tags of the image

None
dir_tmpfiles str

dir to create tempfiles if needed

'.'
fs Optional[Any]

fsspec filesystem to save the file

None

Examples:

img = np.random.randn(4,256,256) transform = rasterio.Affine(10, 0, 799980.0, 0, -10, 1900020.0) data = GeoTensor(img, crs="EPSG:32644", transform=transform) save_cog(data, "example.tif", descriptions=["band1", "band2", "band3", "band4"])

Source code in georeader/save.py
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
def save_cog(data_save:GeoData, path_tiff_save:str,
             profile:Optional[Dict[str, Any]]=None,
             descriptions:Optional[List[str]] = None, 
             tags:Optional[Dict[str, Any]]=None,
             dir_tmpfiles:str=".",
             fs:Optional[Any]=None) -> None:
    """
    Save data GeoData object as cloud optimized GeoTIFF

    Args:
        data_save: GeoData (C, H, W) format with geoinformation (crs and transform).
        descriptions: name of the bands
        path_tiff_save: path to save the COG GeoTIFF
        profile: profile dict to save the data. crs and transform will be updated from data_save.
        tags: Dict to save as tags of the image
        dir_tmpfiles: dir to create tempfiles if needed
        fs: fsspec filesystem to save the file


    Examples:
        >> img = np.random.randn(4,256,256)
        >> transform = rasterio.Affine(10, 0, 799980.0, 0, -10, 1900020.0)
        >> data = GeoTensor(img, crs="EPSG:32644", transform=transform)
        >> save_cog(data, "example.tif", descriptions=["band1", "band2", "band3", "band4"])

    """
    if profile is None:
        profile = {
            "compress": "lzw",
            "RESAMPLING": "CUBICSPLINE",  # for pyramids
        }
    if len(data_save.shape) == 3:
        np_data = np.asanyarray(data_save.values)
    elif len(data_save.shape) == 2:
        np_data = np.asanyarray(data_save.values[np.newaxis])
    else:
        raise NotImplementedError(f"Expected data with 2 or 3 dimensions found: {data_save.shape}")

    profile["crs"] = data_save.crs
    profile["transform"] = data_save.transform

    if "nodata" not in profile:
        profile["nodata"] = data_save.fill_value_default

    _save_cog(np_data,
              path_tiff_save, profile, descriptions=descriptions,
              tags=tags, dir_tmpfiles=dir_tmpfiles, fs=fs)

save_tiled_geotiff(data_save, path_tiff_save, profile_arg=None, descriptions=None, tags=None, dir_tmpfiles='.', blocksize=BLOCKSIZE_DEFAULT, fs=None)

Save data GeoData object as tiled GeoTIFF (see save_cog to save as a Cloud Optimized GeoTIFF)

Parameters:

Name Type Description Default
data_save GeoData

GeoData (C, H, W) format with geoinformation (crs and transform).

required
path_tiff_save str

path to save the GeoTIFF

required
profile_arg Optional[Dict[str, Any]]

profile dict to save the data. crs and transform will be updated from data_save.

None
descriptions Optional[List[str]]

name of the bands

None
profile

profile dict to save the data. crs and transform will be updated from data_save.

required
tags Optional[Dict[str, Any]]

Dict to save as tags of the image

None
dir_tmpfiles str

dir to create tempfiles if needed

'.'
blocksize int

blocksize of the GeoTIFF

BLOCKSIZE_DEFAULT
fs Optional[Any]

fsspec filesystem to save the file

None
Source code in georeader/save.py
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 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
def save_tiled_geotiff(data_save:GeoData, path_tiff_save:str,
                       profile_arg:Optional[Dict[str, Any]]=None,
                       descriptions:Optional[List[str]] = None,
                       tags:Optional[Dict[str, Any]]=None,
                       dir_tmpfiles:str=".",
                       blocksize:int=BLOCKSIZE_DEFAULT,
                       fs:Optional[Any]=None) -> None:
    """
    Save data GeoData object as tiled GeoTIFF (see `save_cog` to save as a Cloud Optimized GeoTIFF)

    Args:
        data_save: GeoData (C, H, W) format with geoinformation (crs and transform).
        path_tiff_save: path to save the GeoTIFF
        profile_arg: profile dict to save the data. crs and transform will be updated from data_save.
        descriptions: name of the bands
        profile: profile dict to save the data. crs and transform will be updated from data_save.
        tags: Dict to save as tags of the image
        dir_tmpfiles: dir to create tempfiles if needed
        blocksize: blocksize of the GeoTIFF
        fs: fsspec filesystem to save the file

    """
    profile = PROFILE_TILED_GEOTIFF_DEFAULT.copy()
    profile.update({"blockxsize": blocksize, "blockysize": blocksize})
    if profile_arg is not None:
        profile.update(profile_arg)

    if len(data_save.shape) == 3:
        out_np = np.asanyarray(data_save.values)
    elif len(data_save.shape) == 2:
        out_np = np.asanyarray(data_save.values[np.newaxis])
    else:
        raise NotImplementedError(f"Expected data with 2 or 3 dimensions found: {data_save.shape}")

    profile["crs"] = data_save.crs
    profile["transform"] = data_save.transform

    if "nodata" not in profile:
        profile["nodata"] = data_save.fill_value_default

    if descriptions is not None:
        assert len(descriptions) == out_np.shape[0], f"Unexpected band descriptions {len(descriptions)} expected {out_np.shape[0]}"

    # Set count, height, width
    for idx, c in enumerate(["count", "height", "width"]):
        if c in profile:
            assert profile[c] == out_np.shape[idx], f"Unexpected shape: {profile[c]} {out_np.shape}"
        else:
            profile[c] = out_np.shape[idx]

    if "dtype" not in profile:
        profile["dtype"] = str(out_np.dtype)

    # check blocksize
    for idx, b in enumerate(["blockysize", "blockxsize"]):
        if b in profile:
            profile[b] = min(profile[b], out_np.shape[idx + 1])

    if (out_np.shape[1] > profile["blockysize"]) or (out_np.shape[2] > profile["blockxsize"]):
        profile["tiled"] = True

    profile["driver"] = "GTiff"
    is_remote_file = any((path_tiff_save.startswith(ext) for ext in REMOTE_FILE_EXTENSIONS))

    # Create a tempfile if is a remote file
    if is_remote_file:
        with tempfile.NamedTemporaryFile(dir=dir_tmpfiles, suffix=".tif", delete=True) as fileobj:
            name_save = fileobj.name
    else:
        name_save = path_tiff_save

    with rasterio.open(name_save, "w", **profile) as rst_out:
        if tags is not None:
            rst_out.update_tags(**tags)
        rst_out.write(out_np)
        if descriptions is not None:
            for i in range(1, out_np.shape[0] + 1):
                rst_out.set_band_description(i, descriptions[i - 1])

    if is_remote_file:
        if fs is None:
            import fsspec
            fs = fsspec.filesystem(path_tiff_save.split(":")[0])

        if not os.path.exists(name_save):
            raise FileNotFoundError(f"File {name_save} have not been created")

        fs.put_file(name_save, path_tiff_save, overwrite=True)
        if os.path.exists(name_save):
            os.remove(name_save)