Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: Added support for geopackage metatata #821

Merged
merged 5 commits into from
Apr 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 107 additions & 4 deletions fiona/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@
from fiona._crs import crs_to_wkt
from fiona._env import get_gdal_release_name, get_gdal_version_tuple
from fiona.env import env_ctx_if_needed
from fiona.errors import FionaDeprecationWarning
from fiona.errors import FionaDeprecationWarning, GDALVersionError, UnsupportedOperation
from fiona.drvsupport import supported_drivers
from fiona.path import Path, vsi_path, parse_path
from six import string_types, binary_type


_GDAL_VERSION_TUPLE = get_gdal_version_tuple()
_GDAL_RELEASE_NAME = get_gdal_release_name()

log = logging.getLogger(__name__)


Expand Down Expand Up @@ -76,10 +79,10 @@ def __init__(self, path, mode='r', driver=None, schema=None, crs=None,
raise TypeError("invalid archive: %r" % archive)

# Check GDAL version against drivers
if (driver == "GPKG" and get_gdal_version_tuple() < (1, 11, 0)):
if (driver == "GPKG" and _GDAL_VERSION_TUPLE < (1, 11, 0)):
raise DriverError(
"GPKG driver requires GDAL 1.11.0, fiona was compiled "
"against: {}".format(get_gdal_release_name()))
"against: {}".format(_GDAL_RELEASE_NAME))

self.session = None
self.iterator = None
Expand Down Expand Up @@ -213,6 +216,106 @@ 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 _GDAL_VERSION_TUPLE.major < 2:
raise GDALVersionError(
"tags requires GDAL 2+, fiona was compiled "
"against: {}".format(_GDAL_RELEASE_NAME)
)
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 _GDAL_VERSION_TUPLE.major < 2:
raise GDALVersionError(
"get_tag_item requires GDAL 2+, fiona was compiled "
"against: {}".format(_GDAL_RELEASE_NAME)
)
if self.session:
return self.session.get_tag_item(key=key, ns=ns)
return None

def update_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 _GDAL_VERSION_TUPLE.major < 2:
raise GDALVersionError(
"update_tags requires GDAL 2+, fiona was compiled "
"against: {}".format(_GDAL_RELEASE_NAME)
)
if not isinstance(self.session, WritingSession):
raise UnsupportedOperation("Unable to update tags as not in writing mode.")
return self.session.update_tags(tags, ns=ns)

def update_tag_item(self, key, tag, ns=None):
"""Updates 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 _GDAL_VERSION_TUPLE.major < 2:
raise GDALVersionError(
"update_tag_item requires GDAL 2+, fiona was compiled "
"against: {}".format(_GDAL_RELEASE_NAME)
)
if not isinstance(self.session, WritingSession):
raise UnsupportedOperation("Unable to update tag as not in writing mode.")
return self.session.update_tag_item(key=key, tag=tag, ns=ns)

@property
def meta(self):
"""Returns a mapping with the driver, schema, crs, and additional
Expand Down Expand Up @@ -398,7 +501,7 @@ def _check_schema_driver_support(self):

See GH#572 for discussion.
"""
gdal_version_major = get_gdal_version_tuple().major
gdal_version_major = _GDAL_VERSION_TUPLE.major

for field in self._schema["properties"].values():
field_type = field.split(":")[0]
Expand Down
4 changes: 4 additions & 0 deletions fiona/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ class CRSError(FionaValueError):
"""When a crs mapping has neither init or proj items."""


class UnsupportedOperation(FionaError):
"""Raised when reading from a file opened in 'w' mode"""


class DataIOError(IOError):
"""IO errors involving driver registration or availability."""

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 @@ -861,6 +862,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 @@ -1207,6 +1278,80 @@ cdef class WritingSession(Session):
gdal_flush_cache(cogr_ds)
log.debug("Flushed data source cache")

def update_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 update_tag_item(self, key, tag, ns=None):
"""Updates 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
Loading