From 9729e231cb7fb4345c31a3cdaa9105ceb480c801 Mon Sep 17 00:00:00 2001 From: snowman2 Date: Thu, 15 Sep 2022 14:53:14 -0500 Subject: [PATCH] ENH: Add support for reading string lists --- fiona/gdal.pxi | 1 + fiona/ogrext.pyx | 18 +++++++++++++++++- fiona/schema.pyx | 7 +++++-- tests/test_schema.py | 31 +++++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 3 deletions(-) diff --git a/fiona/gdal.pxi b/fiona/gdal.pxi index 6fca3e8e9..200f99224 100644 --- a/fiona/gdal.pxi +++ b/fiona/gdal.pxi @@ -314,6 +314,7 @@ cdef extern from "ogr_api.h" nogil: double OGR_F_GetFieldAsDouble(OGRFeatureH feature, int n) int OGR_F_GetFieldAsInteger(OGRFeatureH feature, int n) const char *OGR_F_GetFieldAsString(OGRFeatureH feature, int n) + char **OGR_F_GetFieldAsStringList( OGRFeatureH feature, int n) int OGR_F_GetFieldCount(OGRFeatureH feature) OGRFieldDefnH OGR_F_GetFieldDefnRef(OGRFeatureH feature, int n) int OGR_F_GetFieldIndex(OGRFeatureH feature, const char *name) diff --git a/fiona/ogrext.pyx b/fiona/ogrext.pyx index 2994ecf83..a31f3ee50 100644 --- a/fiona/ogrext.pyx +++ b/fiona/ogrext.pyx @@ -11,6 +11,7 @@ import os import warnings import math from collections import namedtuple, OrderedDict +from typing import List from uuid import uuid4 from six import integer_types, string_types, text_type @@ -168,6 +169,8 @@ cdef class FeatureBuilder: cdef void *fdefn = NULL cdef int i cdef unsigned char *data = NULL + cdef char **string_list = NULL + cdef int string_list_index = 0 cdef int l cdef int retval cdef int fieldsubtype @@ -284,7 +287,20 @@ cdef class FeatureBuilder: elif fieldtype is bytes: data = OGR_F_GetFieldAsBinary(feature, i, &l) props[key] = data[:l] - + elif fieldtype is List[str]: + string_list = OGR_F_GetFieldAsStringList(feature, i) + string_list_index = 0 + props[key] = [] + while string_list[string_list_index] != NULL: + val = string_list[string_list_index] + try: + val = val.decode(encoding) + except UnicodeDecodeError: + log.warning( + "Failed to decode %s using %s codec", val, encoding + ) + props[key].append(val) + string_list_index += 1 else: props[key] = None diff --git a/fiona/schema.pyx b/fiona/schema.pyx index b3c338757..c1526206b 100644 --- a/fiona/schema.pyx +++ b/fiona/schema.pyx @@ -1,5 +1,7 @@ from six import text_type +from typing import List + from fiona.errors import SchemaError from fiona.rfc3339 import FionaDateType, FionaDateTimeType, FionaTimeType @@ -24,7 +26,7 @@ FIELD_TYPES = [ 'float', # OFTReal, Double Precision floating point None, # OFTRealList, List of doubles 'str', # OFTString, String of UTF-8 chars - None, # OFTStringList, Array of strings + 'List[str]', # OFTStringList, Array of strings None, # OFTWideString, deprecated None, # OFTWideStringList, deprecated 'bytes', # OFTBinary, Raw Binary data @@ -45,7 +47,8 @@ FIELD_TYPES_MAP = { 'datetime': FionaDateTimeType, 'bytes': bytes, 'int64': int, - 'int': int + 'int': int, + 'List[str]': List[str], } FIELD_TYPES_MAP_REV = dict([(v, k) for k, v in FIELD_TYPES_MAP.items()]) diff --git a/tests/test_schema.py b/tests/test_schema.py index c49d06fa7..29b997856 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -384,3 +384,34 @@ def test_schema_default_fields_wrong_type(tmpdir): driver="GPX", schema=schema) as c: pass + + +def test_schema_string_list(tmp_path): + output_file = tmp_path / "fio_test.geojson" + schema = { + "properties": { + "time_range": "str", + }, + "geometry": "Point", +} + with fiona.open( + output_file, "w", driver="GeoJSON", schema=schema, crs="EPSG:4326" + ) as fds: + fds.writerecords( + [ + { + "id": 1, + "geometry": {"type": "Point", "coordinates": [0.0, 0.0]}, + "properties": { + "time_range": '["2020-01-01", "2020-01-02"]', + }, + } + ] + ) + + with fiona.open(output_file) as fds: + assert fds.schema["properties"] == {"time_range": "List[str]"} + layers = list(fds) + assert layers[0]["properties"] == { + "time_range": ["2020-01-01", "2020-01-02"] + }