diff --git a/src/c3nav/mapdata/fields.py b/src/c3nav/mapdata/fields.py index ec0548cc..b8f2718a 100644 --- a/src/c3nav/mapdata/fields.py +++ b/src/c3nav/mapdata/fields.py @@ -14,7 +14,7 @@ from shapely import validation from shapely.geometry import LineString, MultiPolygon, Point, Polygon, mapping, shape from shapely.geometry.base import BaseGeometry -from c3nav.mapdata.utils.geometry import clean_geometry +from c3nav.mapdata.utils.geometry import WrappedGeometry, clean_geometry from c3nav.mapdata.utils.json import format_geojson logger = logging.getLogger('c3nav') @@ -52,7 +52,7 @@ class GeometryField(models.TextField): def from_db_value(self, value, expression, connection): if value is None: return value - return shape(json.loads(value)) + return WrappedGeometry(json.loads(value)) def to_python(self, value): if value is None or value == '': diff --git a/src/c3nav/mapdata/models/geometry/base.py b/src/c3nav/mapdata/models/geometry/base.py index 1131ead3..696da40c 100644 --- a/src/c3nav/mapdata/models/geometry/base.py +++ b/src/c3nav/mapdata/models/geometry/base.py @@ -4,11 +4,11 @@ from collections import OrderedDict from django.db import models from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ -from shapely.geometry import Point, box, mapping +from shapely.geometry import Point, box from shapely.ops import unary_union from c3nav.mapdata.models.base import SerializableMixin -from c3nav.mapdata.utils.geometry import assert_multipolygon, good_representative_point +from c3nav.mapdata.utils.geometry import assert_multipolygon, good_representative_point, smart_mapping from c3nav.mapdata.utils.json import format_geojson geometry_affecting_fields = ('height', 'width', 'access_restriction') @@ -45,11 +45,11 @@ class GeometryMixin(SerializableMixin): result = OrderedDict(( ('type', 'Feature'), ('properties', self.get_geojson_properties(instance=instance)), - ('geometry', format_geojson(mapping(self.geometry), round=False)), + ('geometry', format_geojson(smart_mapping(self.geometry), round=False)), )) original_geometry = getattr(self, 'original_geometry', None) if original_geometry: - result['original_geometry'] = format_geojson(mapping(original_geometry), round=False) + result['original_geometry'] = format_geojson(smart_mapping(original_geometry), round=False) return result @classmethod @@ -72,7 +72,7 @@ class GeometryMixin(SerializableMixin): def _serialize(self, geometry=True, simple_geometry=False, **kwargs): result = super()._serialize(simple_geometry=simple_geometry, **kwargs) if geometry: - result['geometry'] = format_geojson(mapping(self.geometry), round=False) + result['geometry'] = format_geojson(smart_mapping(self.geometry), round=False) if simple_geometry: result['point'] = (self.level_id, ) + tuple(round(i, 2) for i in self.point.coords[0]) if not isinstance(self.geometry, Point): @@ -84,9 +84,9 @@ class GeometryMixin(SerializableMixin): def details_display(self, detailed_geometry=True, **kwargs): result = super().details_display(**kwargs) if detailed_geometry: - result['geometry'] = format_geojson(mapping(self.geometry), round=False) + result['geometry'] = format_geojson(smart_mapping(self.geometry), round=False) else: - result['geometry'] = format_geojson(mapping(box(*self.geometry.bounds)), round=False) + result['geometry'] = format_geojson(smart_mapping(box(*self.geometry.bounds)), round=False) return result def get_shadow_geojson(self): diff --git a/src/c3nav/mapdata/utils/geometry.py b/src/c3nav/mapdata/utils/geometry.py index d2c3a8d4..10d7a32f 100644 --- a/src/c3nav/mapdata/utils/geometry.py +++ b/src/c3nav/mapdata/utils/geometry.py @@ -6,10 +6,13 @@ from typing import List, Sequence, Union import matplotlib.pyplot as plt from django.core import checks +from django.utils.functional import cached_property from matplotlib.patches import PathPatch from matplotlib.path import Path from shapely import prepared, speedups from shapely.geometry import GeometryCollection, LinearRing, LineString, MultiLineString, MultiPolygon, Point, Polygon +from shapely.geometry import mapping as shapely_mapping +from shapely.geometry import shape as shapely_shape if speedups.available: speedups.enable() @@ -29,6 +32,48 @@ def check_speedups(app_configs, **kwargs): return errors +class WrappedGeometry(): + picklable = False + wrapped_geojson = None + + def __init__(self, geojson): + self.wrapped_geojson = geojson + + @cached_property + def wrapped_geom(self): + return shapely_shape(self.wrapped_geojson) + + def __getattr__(self, name): + if name in ('__getstate__'): + self.picklable = True + # make sure geometry is cached + if self.wrapped_geojson: + # noinspection PyStatementEffect + self.wrapped_geom + raise AttributeError + if name in ('__reduce__', '__getstate__', '__setstate__', '__reduce_ex__', + '__getnewargs__', '__getnewargs_ex__'): + raise AttributeError + return getattr(self.wrapped_geom, name) + + @property + def __class__(self): + result = WrappedGeometry if self.picklable else self.wrapped_geom.__class__ + self.__dict__.pop('picklable', None) + return result + + +def unwrap_geometry(geometry): + return getattr(geometry, 'geom', geometry) + + +def smart_mapping(geometry): + if hasattr(geometry, 'wrapped_geojson'): + print('being smart!') + return geometry.wrapped_geojson + return shapely_mapping(geometry) + + def clean_geometry(geometry): """ if the given geometry is a Polygon and invalid, try to make it valid if it results in a Polygon (not MultiPolygon)