Skip to content

Commit

Permalink
initial support for geopackage metatata issue#713
Browse files Browse the repository at this point in the history
  • Loading branch information
snowman2 committed Mar 7, 2019
1 parent 51b0714 commit 844d77b
Show file tree
Hide file tree
Showing 3 changed files with 280 additions and 1 deletion.
81 changes: 81 additions & 0 deletions fiona/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,87 @@ def crs_wkt(self):
self._crs_wkt = self.session.get_crs_wkt()
return self._crs_wkt

def tags(self, ns=None):
"""Returns a dict containing copies of the dataset or layers's
tags. Tags are pairs of key and value strings. Tags belong to
namespaces. The standard namespaces are: default (None) and
'IMAGE_STRUCTURE'. Applications can create their own additional
namespaces.
Parameters
----------
ns: str, optional
Can be used to select a namespace other than the default.
Returns
-------
dict
"""
if self.session:
return self.session.tags(ns=ns)
return None

def get_tag_item(self, key, ns=None):
"""Returns tag item value
Parameters
----------
key: str
The key for the metadata item to fetch.
ns: str, optional
Used to select a namespace other than the default.
Returns
-------
str
"""
if self.session:
return self.session.get_tag_item(key=key, ns=ns)
return None

def set_tags(self, tags, ns=None):
"""Writes a dict containing the dataset or layers's tags.
Tags are pairs of key and value strings. Tags belong to
namespaces. The standard namespaces are: default (None) and
'IMAGE_STRUCTURE'. Applications can create their own additional
namespaces.
Parameters
----------
tags: dict
The dict of metadata items to set.
ns: str, optional
Used to select a namespace other than the default.
Returns
-------
int
"""
if isinstance(self.session, WritingSession):
return self.session.set_tags(tags, ns=ns)
raise RuntimeError("Unable to set tags as not in writing mode.")

def set_tag_item(self, key, tag, ns=None):
"""Sets the tag item value
Parameters
----------
key: str
The key for the metadata item to set.
tag: str
The value of the metadata item to set.
ns: str, optional
Used to select a namespace other than the default.
Returns
-------
int
"""
if isinstance(self.session, WritingSession):
return self.session.set_tag_item(key=key, tag=tag, ns=ns)
raise RuntimeError("Unable to set tag item as not in writing mode.")


@property
def meta(self):
"""Returns a mapping with the driver, schema, crs, and additional
Expand Down
147 changes: 146 additions & 1 deletion fiona/ogrext.pyx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# These are extension functions and classes using the OGR C API.

from __future__ import absolute_import

include "gdal.pxi"

import datetime
import json
import locale
Expand Down Expand Up @@ -822,6 +823,76 @@ cdef class Session:
return 0


def tags(self, ns=None):
"""Returns a dict containing copies of the dataset or layers's
tags. Tags are pairs of key and value strings. Tags belong to
namespaces. The standard namespaces are: default (None) and
'IMAGE_STRUCTURE'. Applications can create their own additional
namespaces.
Parameters
----------
ns: str, optional
Can be used to select a namespace other than the default.
Returns
-------
dict
"""
cdef GDALMajorObjectH obj = NULL
if self.cogr_layer != NULL:
obj = self.cogr_layer
else:
obj = self.cogr_ds

cdef const char *domain = NULL
if ns:
ns = ns.encode('utf-8')
domain = ns

cdef char **metadata = NULL
metadata = GDALGetMetadata(obj, domain)
num_items = CSLCount(metadata)

return dict(metadata[i].decode('utf-8').split('=', 1) for i in range(num_items))


def get_tag_item(self, key, ns=None):
"""Returns tag item value
Parameters
----------
key: str
The key for the metadata item to fetch.
ns: str, optional
Used to select a namespace other than the default.
Returns
-------
str
"""

key = key.encode('utf-8')
cdef const char *name = key

cdef const char *domain = NULL
if ns:
ns = ns.encode('utf-8')
domain = ns

cdef GDALMajorObjectH obj = NULL
if self.cogr_layer != NULL:
obj = self.cogr_layer
else:
obj = self.cogr_ds

cdef char *value = NULL
value = GDALGetMetadataItem(obj, name, domain)
if value == NULL:
return None
return value.decode("utf-8")


cdef class WritingSession(Session):

cdef object _schema_mapping
Expand Down Expand Up @@ -1210,6 +1281,80 @@ cdef class WritingSession(Session):
gdal_flush_cache(cogr_ds)
log.debug("Flushed data source cache")

def set_tags(self, tags, ns=None):
"""Writes a dict containing the dataset or layers's tags.
Tags are pairs of key and value strings. Tags belong to
namespaces. The standard namespaces are: default (None) and
'IMAGE_STRUCTURE'. Applications can create their own additional
namespaces.
Parameters
----------
tags: dict
The dict of metadata items to set.
ns: str, optional
Used to select a namespace other than the default.
Returns
-------
int
"""
cdef GDALMajorObjectH obj = NULL
if self.cogr_layer != NULL:
obj = self.cogr_layer
else:
obj = self.cogr_ds

cdef const char *domain = NULL
if ns:
ns = ns.encode('utf-8')
domain = ns

cdef char **metadata = NULL
try:
for key, value in tags.items():
key = key.encode("utf-8")
value = value.encode("utf-8")
metadata = CSLAddNameValue(metadata, <const char *>key, <const char *>value)
return GDALSetMetadata(obj, metadata, domain)
finally:
CSLDestroy(metadata)

def set_tag_item(self, key, tag, ns=None):
"""Sets the tag item value
Parameters
----------
key: str
The key for the metadata item to set.
tag: str
The value of the metadata item to set.
ns: str
Used to select a namespace other than the default.
Returns
-------
int
"""
key = key.encode('utf-8')
cdef const char *name = key
tag = tag.encode("utf-8")
cdef char *value = tag

cdef const char *domain = NULL
if ns:
ns = ns.encode('utf-8')
domain = ns

cdef GDALMajorObjectH obj = NULL
if self.cogr_layer != NULL:
obj = self.cogr_layer
else:
obj = self.cogr_ds

return GDALSetMetadataItem(obj, name, value, domain)


cdef class Iterator:

"""Provides iterated access to feature data.
Expand Down
53 changes: 53 additions & 0 deletions tests/test_session.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,62 @@
"""Tests of the ogrext.Session class"""
import pytest

import fiona

from .conftest import gdal_version


def test_get(path_coutwildrnp_shp):
with fiona.open(path_coutwildrnp_shp) as col:
feat3 = col.get(2)
assert feat3['properties']['NAME'] == 'Mount Zirkel Wilderness'


@pytest.mark.parametrize("layer, namespace, tags", [
(None, None, {"test_tag1": "test_value1", "test_tag2": "test_value2"}),
(None, "test", {"test_tag1": "test_value1", "test_tag2": "test_value2"}),
(None, None, {}),
(None, "test", {}),
("layer", None, {"test_tag1": "test_value1", "test_tag2": "test_value2"}),
("layer", "test", {"test_tag1": "test_value1", "test_tag2": "test_value2"}),
("layer", None, {}),
("layer", "test", {}),
])
@pytest.mark.xfail(not gdal_version.major >= 2, reason="Broken on GDAL 1.x")
def test_set_tags(layer, namespace, tags, tmpdir):
test_geopackage = str(tmpdir.join("test.gpkg"))
schema = {'properties': {'CDATA1': 'str:254'}, 'geometry': 'Polygon'}
with fiona.Env(), fiona.open(
test_geopackage, "w", driver="GPKG", schema=schema, layer=layer) as gpkg:
assert gpkg.tags() == {}
gpkg.set_tags(tags, ns=namespace)

with fiona.Env(), fiona.open(test_geopackage, layer=layer) as gpkg:
assert gpkg.tags(ns=namespace) == tags
if namespace is not None:
assert gpkg.tags() == {}
with pytest.raises(RuntimeError):
gpkg.set_tags({}, ns=namespace)


@pytest.mark.parametrize("layer, namespace", [
(None, None),
(None, "test"),
("test", None),
("test", "test"),
])
@pytest.mark.xfail(not gdal_version.major >= 2, reason="Broken on GDAL 1.x")
def test_set_tag_item(layer, namespace, tmpdir):
test_geopackage = str(tmpdir.join("test.gpkg"))
schema = {'properties': {'CDATA1': 'str:254'}, 'geometry': 'Polygon'}
with fiona.Env(), fiona.open(
test_geopackage, "w", driver="GPKG", schema=schema, layer=layer) as gpkg:
assert gpkg.get_tag_item("test_tag1", ns=namespace) is None
gpkg.set_tag_item("test_tag1", "test_value1", ns=namespace)

with fiona.Env(), fiona.open(test_geopackage, layer=layer) as gpkg:
if namespace is not None:
assert gpkg.get_tag_item("test_tag1") is None
assert gpkg.get_tag_item("test_tag1", ns=namespace) == "test_value1"
with pytest.raises(RuntimeError):
gpkg.set_tag_item("test_tag1", "test_value1", ns=namespace)

0 comments on commit 844d77b

Please sign in to comment.