diff --git a/src/c3nav/mapdata/fields.py b/src/c3nav/mapdata/fields.py index 096c079c..49de0d9f 100644 --- a/src/c3nav/mapdata/fields.py +++ b/src/c3nav/mapdata/fields.py @@ -1,19 +1,32 @@ import json +from django.core.exceptions import ValidationError from django.db import models +from shapely import validation from shapely.geometry import mapping, shape +from shapely.geometry.base import BaseGeometry -from c3nav.mapdata.utils import format_geojson +from c3nav.mapdata.utils import clean_geometry, format_geojson + + +def validate_geometry(geometry): + if not isinstance(geometry, BaseGeometry): + raise ValidationError('GeometryField expexted a Shapely BaseGeometry child-class.') + + if not geometry.is_valid: + raise ValidationError('Invalid geometry: %s' % validation.explain_validity(geometry)) class GeometryField(models.TextField): + default_validators = [validate_geometry] + def from_db_value(self, value, expression, connection, context): if value is None: return value return shape(json.loads(value)) def to_python(self, value): - return shape(json.loads(value)) + return clean_geometry(shape(json.loads(value))) def get_prep_value(self, value): return json.dumps(format_geojson(mapping(value))) diff --git a/src/c3nav/mapdata/utils.py b/src/c3nav/mapdata/utils.py index 518d6a4e..2dc7a973 100644 --- a/src/c3nav/mapdata/utils.py +++ b/src/c3nav/mapdata/utils.py @@ -1,6 +1,8 @@ import json from collections import OrderedDict +from shapely.geometry import Polygon + def _preencode(data, magic_marker, in_coords=False): if isinstance(data, dict): @@ -45,3 +47,18 @@ def round_coordinates(data): return tuple(round_coordinates(item) for item in data) else: return round(data, 2) + + +def clean_geometry(geometry): + if geometry.is_valid: + return geometry + + if isinstance(geometry, Polygon): + p = Polygon(list(geometry.exterior.coords)) + for interior in geometry.interiors: + p = p.difference(Polygon(list(interior.coords))) + + if p.is_valid: + return p + + return geometry