register updated geometries for tile cache invalidation
This commit is contained in:
parent
cd09cbab68
commit
5af314d282
9 changed files with 160 additions and 0 deletions
|
@ -0,0 +1,3 @@
|
|||
|
||||
|
||||
default_app_config = 'c3nav.mapdata.apps.MapdataConfig'
|
9
src/c3nav/mapdata/apps.py
Normal file
9
src/c3nav/mapdata/apps.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class MapdataConfig(AppConfig):
|
||||
name = 'c3nav.mapdata'
|
||||
|
||||
def ready(self):
|
||||
from c3nav.mapdata.render.cache import register_signals
|
||||
register_signals()
|
|
@ -28,6 +28,10 @@ class GeometryMixin(SerializableMixin):
|
|||
abstract = True
|
||||
base_manager_name = 'objects'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.orig_geometry = None if 'geometry' in self.get_deferred_fields() else self.geometry
|
||||
|
||||
def get_geojson_properties(self, *args, **kwargs) -> dict:
|
||||
result = OrderedDict((
|
||||
('type', self.__class__.__name__.lower()),
|
||||
|
@ -76,6 +80,14 @@ class GeometryMixin(SerializableMixin):
|
|||
def recalculate_bounds(self):
|
||||
self.minx, self.miny, self.maxx, self.maxy = self.geometry.bounds
|
||||
|
||||
@property
|
||||
def geometry_changed(self):
|
||||
return self.orig_geometry is None or (self.geometry is not self.orig_geometry and
|
||||
not self.geometry.almost_equals(self.orig_geometry, 2))
|
||||
|
||||
def get_changed_geometry(self):
|
||||
return self.geometry if self.orig_geometry is None else self.geometry.symmetric_difference(self.orig_geometry)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.recalculate_bounds()
|
||||
super().save(*args, **kwargs)
|
||||
|
|
|
@ -15,6 +15,7 @@ from c3nav.mapdata.models import Level
|
|||
from c3nav.mapdata.models.access import AccessRestrictionMixin
|
||||
from c3nav.mapdata.models.geometry.base import GeometryMixin
|
||||
from c3nav.mapdata.models.locations import SpecificLocation
|
||||
from c3nav.mapdata.render.cache import changed_geometries
|
||||
from c3nav.mapdata.utils.geometry import assert_multilinestring, assert_multipolygon, clean_geometry
|
||||
|
||||
|
||||
|
@ -41,6 +42,17 @@ class LevelGeometryMixin(GeometryMixin):
|
|||
result['level'] = self.level_id
|
||||
return result
|
||||
|
||||
def register_change(self, force=False):
|
||||
if force or self.geometry_changed:
|
||||
changed_geometries.register(self.level_id, self.geometry if force else self.get_changed_geometry())
|
||||
|
||||
def register_delete(self):
|
||||
changed_geometries.register(self.level_id, self.geometry)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.register_change()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class Building(LevelGeometryMixin, models.Model):
|
||||
"""
|
||||
|
|
|
@ -5,6 +5,7 @@ from shapely.geometry import CAP_STYLE, JOIN_STYLE, mapping
|
|||
from c3nav.mapdata.fields import GeometryField
|
||||
from c3nav.mapdata.models.geometry.base import GeometryMixin
|
||||
from c3nav.mapdata.models.locations import SpecificLocation
|
||||
from c3nav.mapdata.render.cache import changed_geometries
|
||||
from c3nav.mapdata.utils.json import format_geojson
|
||||
|
||||
|
||||
|
@ -28,6 +29,21 @@ class SpaceGeometryMixin(GeometryMixin):
|
|||
result['color'] = color
|
||||
return result
|
||||
|
||||
def register_change(self, force=True):
|
||||
space = self.space
|
||||
if force or self.geometry_changed:
|
||||
changed_geometries.register(space.level_id, space.geometry.intersection(
|
||||
self.geometry if force else self.get_changed_geometry()
|
||||
))
|
||||
|
||||
def register_delete(self):
|
||||
space = self.space
|
||||
changed_geometries.register(space.level_id, space.geometry.intersection(self.geometry))
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.register_change()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class Column(SpaceGeometryMixin, models.Model):
|
||||
"""
|
||||
|
|
|
@ -160,6 +160,10 @@ class LocationGroupCategory(TitledMixin, models.Model):
|
|||
allow_pois = models.BooleanField(_('allow pois'), db_index=True, default=True)
|
||||
priority = models.IntegerField(default=0, db_index=True)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.orig_priority = self.priority
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Location Group Category')
|
||||
verbose_name_plural = _('Location Group Categories')
|
||||
|
@ -173,6 +177,23 @@ class LocationGroupCategory(TitledMixin, models.Model):
|
|||
result.move_to_end('id', last=False)
|
||||
return result
|
||||
|
||||
def register_changed_geometries(self):
|
||||
from c3nav.mapdata.models.geometry.space import SpaceGeometryMixin
|
||||
query = self.locationgroups.all()
|
||||
for model in get_submodels(SpecificLocation):
|
||||
related_name = SpecificLocation._meta.default_related_name
|
||||
query.prefetch_related('locationgroup__'+related_name)
|
||||
if issubclass(model, SpaceGeometryMixin):
|
||||
query = query.select_related('locationgorups__'+related_name+'__space')
|
||||
|
||||
for group in query:
|
||||
group.register_changed_geometries(do_query=False)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.priority != self.orig_priority:
|
||||
self.register_changed_geometries()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class LocationGroupManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
|
@ -193,6 +214,12 @@ class LocationGroup(Location, models.Model):
|
|||
default_related_name = 'locationgroups'
|
||||
ordering = ('-category__priority', '-priority')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.orig_priority = self.priority
|
||||
self.orig_category = self.category
|
||||
self.orig_color = self.color
|
||||
|
||||
def _serialize(self, **kwargs):
|
||||
result = super()._serialize(**kwargs)
|
||||
result['category'] = self.category_id
|
||||
|
@ -212,6 +239,21 @@ class LocationGroup(Location, models.Model):
|
|||
attributes.append(_('internal'))
|
||||
return self.title + ' ('+', '.join(str(s) for s in attributes)+')'
|
||||
|
||||
def register_changed_geometries(self, do_query=True):
|
||||
from c3nav.mapdata.models.geometry.space import SpaceGeometryMixin
|
||||
for model in get_submodels(SpecificLocation):
|
||||
query = getattr(self, SpecificLocation._meta.default_related_name).objects.all()
|
||||
if do_query:
|
||||
if issubclass(model, SpaceGeometryMixin):
|
||||
query = query.select_related('space')
|
||||
for obj in query:
|
||||
obj.register_change(force=True)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.orig_color != self.color or self.priority != self.orig_priority or self.category != self.orig_category:
|
||||
self.register_changed_geometries()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class LocationRedirect(LocationSlug):
|
||||
target = models.ForeignKey(LocationSlug, related_name='redirects', on_delete=models.CASCADE,
|
||||
|
|
|
@ -50,8 +50,10 @@ class MapUpdate(models.Model):
|
|||
raise TypeError
|
||||
|
||||
from c3nav.mapdata.models import AltitudeArea
|
||||
from c3nav.mapdata.render.cache import GeometryChangeTracker
|
||||
from c3nav.mapdata.render.base import LevelRenderData
|
||||
AltitudeArea.recalculate()
|
||||
GeometryChangeTracker()
|
||||
LevelRenderData.rebuild()
|
||||
super().save(**kwargs)
|
||||
cache.set('mapdata:last_update', (self.pk, self.datetime), 900)
|
||||
|
|
53
src/c3nav/mapdata/render/cache.py
Normal file
53
src/c3nav/mapdata/render/cache.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
from django.db.models.signals import m2m_changed, post_delete
|
||||
|
||||
from c3nav.mapdata.utils.models import get_submodels
|
||||
|
||||
|
||||
class GeometryChangeTracker:
|
||||
def __init__(self):
|
||||
self._geometries_by_level = {}
|
||||
self._deleted_levels = set()
|
||||
|
||||
def register(self, level_id, geometry):
|
||||
self._geometries_by_level.setdefault(level_id, []).append(geometry)
|
||||
|
||||
def level_deleted(self, level_id):
|
||||
self._deleted_levels.add(level_id)
|
||||
|
||||
def reset(self):
|
||||
self._geometries_by_level = {}
|
||||
self._deleted_levels = set()
|
||||
|
||||
|
||||
changed_geometries = GeometryChangeTracker()
|
||||
|
||||
|
||||
def geometry_deleted(sender, instance, **kwargs):
|
||||
instance.register_delete()
|
||||
|
||||
|
||||
def locationgroup_changed(sender, instance, action, reverse, model, pk_set, using, **kwargs):
|
||||
if action not in ('post_add', 'post_remove', 'post_clear'):
|
||||
return
|
||||
|
||||
if not reverse:
|
||||
instance.register_change(force=True)
|
||||
else:
|
||||
if action not in 'post_clear':
|
||||
raise NotImplementedError
|
||||
query = model.objects.filter(pk__in=pk_set)
|
||||
from c3nav.mapdata.models.geometry.space import SpaceGeometryMixin
|
||||
if issubclass(model, SpaceGeometryMixin):
|
||||
query = query.select_related('space')
|
||||
for obj in query:
|
||||
obj.register_change(force=True)
|
||||
|
||||
|
||||
def register_signals():
|
||||
from c3nav.mapdata.models.geometry.base import GeometryMixin
|
||||
for model in get_submodels(GeometryMixin):
|
||||
post_delete.connect(geometry_deleted, sender=model)
|
||||
|
||||
from c3nav.mapdata.models.locations import SpecificLocation
|
||||
for model in get_submodels(SpecificLocation):
|
||||
m2m_changed.connect(locationgroup_changed, sender=model.groups.through)
|
|
@ -9,6 +9,11 @@ from c3nav.mapdata.render.svg import SVGRenderer
|
|||
|
||||
|
||||
def tile(request, level, zoom, x, y, format):
|
||||
import cProfile
|
||||
import pstats
|
||||
pr = cProfile.Profile()
|
||||
pr.enable()
|
||||
|
||||
zoom = int(zoom)
|
||||
if not (0 <= zoom <= 10):
|
||||
raise Http404
|
||||
|
@ -72,6 +77,12 @@ def tile(request, level, zoom, x, y, format):
|
|||
else:
|
||||
data = f.read()
|
||||
|
||||
pr.disable()
|
||||
s = open('/tmp/profiled', 'w')
|
||||
sortby = 'cumulative'
|
||||
ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
|
||||
ps.print_stats()
|
||||
|
||||
response = HttpResponse(data, content_type)
|
||||
response['ETag'] = etag
|
||||
response['Cache-Control'] = 'no-cache'
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue