From 6b29b2e15cdd93013341dd5f4f4a6bb367acc251 Mon Sep 17 00:00:00 2001 From: Sean Gillies Date: Fri, 7 Feb 2014 23:33:44 -0700 Subject: [PATCH] Add support for dataset and band tags (#32) GDAL calls these items "metadata" but I think "tags" suits rasterio better. --- CHANGES.txt | 4 ++ rasterio/_gdal.pxd | 8 ++++ rasterio/_io.pyx | 74 +++++++++++++++++++++++++++++++++++++ rasterio/tests/test_tags.py | 29 +++++++++++++++ 4 files changed, 115 insertions(+) create mode 100644 rasterio/tests/test_tags.py diff --git a/CHANGES.txt b/CHANGES.txt index 9f5611e7..6b8d1c11 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,10 @@ Changes ======= +Next +------------------ +- Add support for dataset and band tags (#32). + 0.5.1 (2014-02-02) ------------------ - Add mask option to shapes() function (#26). diff --git a/rasterio/_gdal.pxd b/rasterio/_gdal.pxd index 414d23bf..4220c603 100644 --- a/rasterio/_gdal.pxd +++ b/rasterio/_gdal.pxd @@ -6,6 +6,9 @@ cdef extern from "cpl_conv.h": void CPLSetThreadLocalConfigOption (char *key, char *val) cdef extern from "cpl_string.h": + int CSLCount (char **papszStrList) + char ** CSLAddNameValue (char **papszStrList, const char *pszName, const char *pszValue) + int CSLFindName (char **papszStrList, const char *pszName) char ** CSLSetNameValue (char **list, char *name, char *val) void CSLDestroy (char **list) @@ -48,6 +51,11 @@ cdef extern from "gdal.h": const char * GDALGetDriverShortName(void *driver) const char * GDALGetDriverLongName(void *driver) + char** GDALGetMetadata (void *hObject, const char *pszDomain) + int GDALSetMetadata (void *hObject, char **papszMD, const char *pszDomain) + const char* GDALGetMetadataItem(void *hObject, const char *pszName, const char *pszDomain) + int GDALSetMetadataItem (void *hObject, const char *pszName, const char *pszValue, const char *pszDomain) + cdef extern from "gdal_alg.h": int GDALPolygonize(void *src_band, void *mask_band, void *layer, int fidx, char **options, void *progress_func, void *progress_data) int GDALSieveFilter(void *src_band, void *mask_band, void *dst_band, int size, int connectivity, char **options, void *progress_func, void *progress_data) diff --git a/rasterio/_io.pyx b/rasterio/_io.pyx index cc2c9544..514a1b5e 100644 --- a/rasterio/_io.pyx +++ b/rasterio/_io.pyx @@ -546,6 +546,41 @@ cdef class RasterReader: return out + def tags(self, bidx=0, domain=None): + """Get the dataset's tags. + + The optional domain and band arguments select a specific + GDAL metadata domain and tags for a specific band. + """ + cdef char *item_c + cdef void *hobj + cdef const char *domain_c + cdef char **papszStrList + + if not self._hds: + raise ValueError("can't read closed raster file") + if bidx > 0: + hobj = _gdal.GDALGetRasterBand(self._hds, bidx) + if hobj is NULL: + raise ValueError("NULL band") + else: + hobj = self._hds + if domain: + domain_b = domain.encode('utf-8') + domain_c = domain_b + else: + domain_c = NULL + papszStrList = _gdal.GDALGetMetadata(hobj, domain_c) + num_items = _gdal.CSLCount(papszStrList) + retval = {} + for i in range(num_items): + item_c = papszStrList[i] + item_b = item_c + item = item_b.decode('utf-8') + key, value = item.split('=') + retval[key] = value + return retval + cdef class RasterUpdater(RasterReader): # Read-write access to raster data and metadata. @@ -774,3 +809,42 @@ cdef class RasterUpdater(RasterReader): raise ValueError("Invalid dtype") # TODO: handle errors (by retval). + def update_tags(self, bidx=0, domain=None, **kwargs): + """Update the dataset's tags from items in kwargs. + + The optional domain and band arguments select a specific + GDAL metadata domain and tags for a specific band. + + Only keys and values that can be converted to strings are + allowed. + """ + cdef char *key_c, *value_c + cdef void *hobj + cdef const char *domain_c + + if not self._hds: + raise ValueError("can't read closed raster file") + if bidx > 0: + hobj = _gdal.GDALGetRasterBand(self._hds, bidx) + if hobj is NULL: + raise ValueError("NULL band") + else: + hobj = self._hds + if domain: + domain_b = domain.encode('utf-8') + domain_c = domain_b + else: + domain_c = NULL + cdef char **papszStrList = _gdal.GDALGetMetadata(hobj, NULL) + for key, value in kwargs.items(): + key_b = str(key).encode('utf-8') + value_b = str(value).encode('utf-8') + key_c = key_b + value_c = value_b + i = _gdal.CSLFindName(papszStrList, key_c) + if i < 0: + papszStrList = _gdal.CSLAddNameValue(papszStrList, key_c, value_c) + else: + papszStrList = _gdal.CSLSetNameValue(papszStrList, key_c, value_c) + retval = _gdal.GDALSetMetadata(hobj, papszStrList, domain_c) + diff --git a/rasterio/tests/test_tags.py b/rasterio/tests/test_tags.py new file mode 100644 index 00000000..45957df9 --- /dev/null +++ b/rasterio/tests/test_tags.py @@ -0,0 +1,29 @@ + +import pytest +import rasterio + +def test_tags_read(): + with rasterio.open('rasterio/tests/data/RGB.byte.tif') as src: + assert src.tags() == {'AREA_OR_POINT': 'Area'} + assert src.tags(domain='IMAGE_STRUCTURE') == {'INTERLEAVE': 'PIXEL'} + assert 'STATISTICS_MAXIMUM' in src.tags(1) + +def test_tags_update(tmpdir): + tiffname = str(tmpdir.join('foo.tif')) + with rasterio.open( + tiffname, + 'w', + driver='GTiff', + count=1, + dtype=rasterio.uint8, + width=10, + height=10) as dst: + dst.update_tags(a='1', b='2') + dst.update_tags(1, c=3) + assert dst.tags() == {'a': '1', 'b': '2'} + assert dst.tags(1) == {'c': '3'} + + with rasterio.open(tiffname) as src: + assert src.tags() == {'a': '1', 'b': '2'} + assert src.tags(1) == {'c': '3'} +