speedup geometry serialization by smart wrapping magic

This commit is contained in:
Laura Klünder 2018-12-22 22:27:19 +01:00
parent 4819d67f99
commit f1e379aea8
3 changed files with 54 additions and 9 deletions

View file

@ -14,7 +14,7 @@ from shapely import validation
from shapely.geometry import LineString, MultiPolygon, Point, Polygon, mapping, shape from shapely.geometry import LineString, MultiPolygon, Point, Polygon, mapping, shape
from shapely.geometry.base import BaseGeometry 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 from c3nav.mapdata.utils.json import format_geojson
logger = logging.getLogger('c3nav') logger = logging.getLogger('c3nav')
@ -52,7 +52,7 @@ class GeometryField(models.TextField):
def from_db_value(self, value, expression, connection): def from_db_value(self, value, expression, connection):
if value is None: if value is None:
return value return value
return shape(json.loads(value)) return WrappedGeometry(json.loads(value))
def to_python(self, value): def to_python(self, value):
if value is None or value == '': if value is None or value == '':

View file

@ -4,11 +4,11 @@ from collections import OrderedDict
from django.db import models from django.db import models
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _ 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 shapely.ops import unary_union
from c3nav.mapdata.models.base import SerializableMixin 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 from c3nav.mapdata.utils.json import format_geojson
geometry_affecting_fields = ('height', 'width', 'access_restriction') geometry_affecting_fields = ('height', 'width', 'access_restriction')
@ -45,11 +45,11 @@ class GeometryMixin(SerializableMixin):
result = OrderedDict(( result = OrderedDict((
('type', 'Feature'), ('type', 'Feature'),
('properties', self.get_geojson_properties(instance=instance)), ('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) original_geometry = getattr(self, 'original_geometry', None)
if original_geometry: 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 return result
@classmethod @classmethod
@ -72,7 +72,7 @@ class GeometryMixin(SerializableMixin):
def _serialize(self, geometry=True, simple_geometry=False, **kwargs): def _serialize(self, geometry=True, simple_geometry=False, **kwargs):
result = super()._serialize(simple_geometry=simple_geometry, **kwargs) result = super()._serialize(simple_geometry=simple_geometry, **kwargs)
if geometry: if geometry:
result['geometry'] = format_geojson(mapping(self.geometry), round=False) result['geometry'] = format_geojson(smart_mapping(self.geometry), round=False)
if simple_geometry: if simple_geometry:
result['point'] = (self.level_id, ) + tuple(round(i, 2) for i in self.point.coords[0]) result['point'] = (self.level_id, ) + tuple(round(i, 2) for i in self.point.coords[0])
if not isinstance(self.geometry, Point): if not isinstance(self.geometry, Point):
@ -84,9 +84,9 @@ class GeometryMixin(SerializableMixin):
def details_display(self, detailed_geometry=True, **kwargs): def details_display(self, detailed_geometry=True, **kwargs):
result = super().details_display(**kwargs) result = super().details_display(**kwargs)
if detailed_geometry: if detailed_geometry:
result['geometry'] = format_geojson(mapping(self.geometry), round=False) result['geometry'] = format_geojson(smart_mapping(self.geometry), round=False)
else: 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 return result
def get_shadow_geojson(self): def get_shadow_geojson(self):

View file

@ -6,10 +6,13 @@ from typing import List, Sequence, Union
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from django.core import checks from django.core import checks
from django.utils.functional import cached_property
from matplotlib.patches import PathPatch from matplotlib.patches import PathPatch
from matplotlib.path import Path from matplotlib.path import Path
from shapely import prepared, speedups from shapely import prepared, speedups
from shapely.geometry import GeometryCollection, LinearRing, LineString, MultiLineString, MultiPolygon, Point, Polygon 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: if speedups.available:
speedups.enable() speedups.enable()
@ -29,6 +32,48 @@ def check_speedups(app_configs, **kwargs):
return errors 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): 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) if the given geometry is a Polygon and invalid, try to make it valid if it results in a Polygon (not MultiPolygon)