Skip to content

Commit

Permalink
Handle the mix of single and multi lines and polygons in shapefiles.
Browse files Browse the repository at this point in the history
Use some of the code provided by olt in #18, add several tests, see the
LineWritingTest in test_collection.py.
  • Loading branch information
sgillies committed Feb 3, 2013
1 parent 8a3e677 commit 46123bc
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 15 deletions.
1 change: 1 addition & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ CHANGES
- Raise ValueError for unsupported drivers and modes.
- Remove asserts from ogrext.pyx.
- Add validate_record method to collections.
- Handle Shapefile's mix of LineString/Polygon and multis (#18).

0.8 (2012-02-21)
----------------
Expand Down
14 changes: 13 additions & 1 deletion src/fiona/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,19 @@ def validate_record(self, record):
# values.
return not set(record['properties'].keys()
).symmetric_difference(set(self.schema['properties'].keys())) \
and record['geometry']['type'] == self.schema['geometry']
and self.validate_record_geometry(record)

def validate_record_geometry(self, record):
"""Compares the record's geometry to the collection's schema.
Returns ``True`` if the record matches, else ``False``.
"""
# Shapefiles welcome mixes of geometry and their multi- types.
if self.driver == "ESRI Shapefile":
return record['geometry']['type'].lstrip(
"Multi") == self.schema['geometry'].lstrip("Multi")
else:
return record['geometry']['type'] == self.schema['geometry']

def _flushbuffer(self):
if self.session is not None and len(self._buffer) > 0:
Expand Down
40 changes: 27 additions & 13 deletions src/fiona/ogrext.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -734,27 +734,41 @@ cdef class WritingSession(Session):

def writerecs(self, records, collection):
"""Writes buffered records to OGR."""

cdef void *cogr_driver
cdef void *cogr_feature

cdef void *cogr_layer = self.cogr_layer
if cogr_layer is NULL:
raise ValueError("Null layer")
cdef void *cogr_feature

schema_geom_type = collection.schema['geometry']
cogr_driver = ograpi.OGR_DS_GetDriver(self.cogr_ds)
if ograpi.OGR_Dr_GetName(cogr_driver) == b"ESRI Shapefile" \
and "Point" not in collection.schema['geometry']:
schema_geom_type = collection.schema['geometry'].lstrip("Multi")
def validate_geometry_type(rec):
return rec['geometry']['type'].lstrip(
"Multi") == schema_geom_type
else:
schema_geom_type = collection.schema['geometry']
def validate_geometry_type(rec):
return rec['geometry']['type'] == schema_geom_type

schema_props_keys = set(collection.schema['properties'].keys())
for record in records:
log.debug("Creating feature in layer: %s" % record)
# Validate against collection's schema.
if (
set(record['properties'].keys()) -
set(collection.schema['properties'].keys())
) or (
record['geometry']['type'] != \
collection.schema['geometry'] ):
if set(record['properties'].keys()) != schema_props_keys:
raise ValueError(
"Record does not match collection schema: %r != %r" % (
record['properties'].keys(),
list(schema_props_keys) ))
if not validate_geometry_type(record):
raise ValueError(
"Record (%s) not match collection schema (%s)" % (
{'properties': record['properties'].keys(),
'geometry': record['geometry']['type']},
{'properties': collection.schema['properties'].keys(),
'geometry': collection.schema['geometry']}, ))
"Record's geometry type does not match "
"collection schema's geometry type: %r != %r" % (
record['geometry']['type'],
collection.schema['geometry'] ))

cogr_feature = OGRFeatureBuilder().build(record, collection)
result = ograpi.OGR_L_CreateFeature(cogr_layer, cogr_feature)
Expand Down
55 changes: 54 additions & 1 deletion tests/test_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,55 @@ def test_validate_record(self):
self.assertTrue(self.sink.validate_record(fvalid))
self.assertFalse(self.sink.validate_record(finvalid))

def test_validate_record_shapefile_multi(self):
fvalid = {
'geometry': {'type': 'MultiPoint', 'coordinates': [(0.0, 0.1)]},
'properties': {'title': 'point one', 'date': "2012-01-29"}}
self.assertTrue(self.sink.validate_record(fvalid))

class LineWritingTest(unittest.TestCase):

def setUp(self):
self.sink = collection(
"/tmp/line_writing_test.shp",
"w",
driver="ESRI Shapefile",
schema={
'geometry': 'LineString',
'properties': {'title': 'str', 'date': 'date'}},
crs={'init': "epsg:4326", 'no_defs': True})

def tearDown(self):
self.sink.close()

def test_write_one(self):
self.failUnlessEqual(len(self.sink), 0)
self.failUnlessEqual(self.sink.bounds, (0.0, 0.0, 0.0, 0.0))
f = {
'geometry': {'type': 'LineString',
'coordinates': [(0.0, 0.1), (0.0, 0.2)]},
'properties': {'title': 'line one', 'date': "2012-01-29"}}
self.sink.writerecords([f])
self.failUnlessEqual(len(self.sink), 1)
self.failUnlessEqual(self.sink.bounds, (0.0, 0.1, 0.0, 0.2))

def test_write_two(self):
self.failUnlessEqual(len(self.sink), 0)
self.failUnlessEqual(self.sink.bounds, (0.0, 0.0, 0.0, 0.0))
f1 = {
'geometry': {'type': 'LineString',
'coordinates': [(0.0, 0.1), (0.0, 0.2)]},
'properties': {'title': 'line one', 'date': "2012-01-29"}}
f2 = {
'geometry': {'type': 'MultiLineString',
'coordinates': [
[(0.0, 0.0), (0.0, -0.1)],
[(0.0, -0.1), (0.0, -0.2)] ]},
'properties': {'title': 'line two', 'date': "2012-01-29"}}
self.sink.writerecords([f1, f2])
self.failUnlessEqual(len(self.sink), 2)
self.failUnlessEqual(self.sink.bounds, (0.0, -0.2, 0.0, 0.2))

class AppendingTest(unittest.TestCase):

def setUp(self):
Expand All @@ -240,7 +289,11 @@ def test_append_point(self):
with collection("append-test/test_append_point.shp", "a") as c:
self.assertEqual(c.schema['geometry'], 'Point')
c.write({'geometry': {'type': 'Point', 'coordinates': (0.0, 45.0)},
'properties': {'FIPS_CNTRY': 'UK'}})
'properties': { 'FIPS_CNTRY': 'UK',
'AREA': 0.0,
'CAT': 1.0,
'POP_CNTRY': 0,
'CNTRY_NAME': u'Foo'} })
self.assertEqual(len(c), 8)

class CollectionTest(unittest.TestCase):
Expand Down

0 comments on commit 46123bc

Please sign in to comment.