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
|
abstract = True
|
||||||
base_manager_name = 'objects'
|
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:
|
def get_geojson_properties(self, *args, **kwargs) -> dict:
|
||||||
result = OrderedDict((
|
result = OrderedDict((
|
||||||
('type', self.__class__.__name__.lower()),
|
('type', self.__class__.__name__.lower()),
|
||||||
|
@ -76,6 +80,14 @@ class GeometryMixin(SerializableMixin):
|
||||||
def recalculate_bounds(self):
|
def recalculate_bounds(self):
|
||||||
self.minx, self.miny, self.maxx, self.maxy = self.geometry.bounds
|
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):
|
def save(self, *args, **kwargs):
|
||||||
self.recalculate_bounds()
|
self.recalculate_bounds()
|
||||||
super().save(*args, **kwargs)
|
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.access import AccessRestrictionMixin
|
||||||
from c3nav.mapdata.models.geometry.base import GeometryMixin
|
from c3nav.mapdata.models.geometry.base import GeometryMixin
|
||||||
from c3nav.mapdata.models.locations import SpecificLocation
|
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
|
from c3nav.mapdata.utils.geometry import assert_multilinestring, assert_multipolygon, clean_geometry
|
||||||
|
|
||||||
|
|
||||||
|
@ -41,6 +42,17 @@ class LevelGeometryMixin(GeometryMixin):
|
||||||
result['level'] = self.level_id
|
result['level'] = self.level_id
|
||||||
return result
|
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):
|
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.fields import GeometryField
|
||||||
from c3nav.mapdata.models.geometry.base import GeometryMixin
|
from c3nav.mapdata.models.geometry.base import GeometryMixin
|
||||||
from c3nav.mapdata.models.locations import SpecificLocation
|
from c3nav.mapdata.models.locations import SpecificLocation
|
||||||
|
from c3nav.mapdata.render.cache import changed_geometries
|
||||||
from c3nav.mapdata.utils.json import format_geojson
|
from c3nav.mapdata.utils.json import format_geojson
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,6 +29,21 @@ class SpaceGeometryMixin(GeometryMixin):
|
||||||
result['color'] = color
|
result['color'] = color
|
||||||
return result
|
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):
|
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)
|
allow_pois = models.BooleanField(_('allow pois'), db_index=True, default=True)
|
||||||
priority = models.IntegerField(default=0, db_index=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:
|
class Meta:
|
||||||
verbose_name = _('Location Group Category')
|
verbose_name = _('Location Group Category')
|
||||||
verbose_name_plural = _('Location Group Categories')
|
verbose_name_plural = _('Location Group Categories')
|
||||||
|
@ -173,6 +177,23 @@ class LocationGroupCategory(TitledMixin, models.Model):
|
||||||
result.move_to_end('id', last=False)
|
result.move_to_end('id', last=False)
|
||||||
return result
|
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):
|
class LocationGroupManager(models.Manager):
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
@ -193,6 +214,12 @@ class LocationGroup(Location, models.Model):
|
||||||
default_related_name = 'locationgroups'
|
default_related_name = 'locationgroups'
|
||||||
ordering = ('-category__priority', '-priority')
|
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):
|
def _serialize(self, **kwargs):
|
||||||
result = super()._serialize(**kwargs)
|
result = super()._serialize(**kwargs)
|
||||||
result['category'] = self.category_id
|
result['category'] = self.category_id
|
||||||
|
@ -212,6 +239,21 @@ class LocationGroup(Location, models.Model):
|
||||||
attributes.append(_('internal'))
|
attributes.append(_('internal'))
|
||||||
return self.title + ' ('+', '.join(str(s) for s in attributes)+')'
|
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):
|
class LocationRedirect(LocationSlug):
|
||||||
target = models.ForeignKey(LocationSlug, related_name='redirects', on_delete=models.CASCADE,
|
target = models.ForeignKey(LocationSlug, related_name='redirects', on_delete=models.CASCADE,
|
||||||
|
|
|
@ -50,8 +50,10 @@ class MapUpdate(models.Model):
|
||||||
raise TypeError
|
raise TypeError
|
||||||
|
|
||||||
from c3nav.mapdata.models import AltitudeArea
|
from c3nav.mapdata.models import AltitudeArea
|
||||||
|
from c3nav.mapdata.render.cache import GeometryChangeTracker
|
||||||
from c3nav.mapdata.render.base import LevelRenderData
|
from c3nav.mapdata.render.base import LevelRenderData
|
||||||
AltitudeArea.recalculate()
|
AltitudeArea.recalculate()
|
||||||
|
GeometryChangeTracker()
|
||||||
LevelRenderData.rebuild()
|
LevelRenderData.rebuild()
|
||||||
super().save(**kwargs)
|
super().save(**kwargs)
|
||||||
cache.set('mapdata:last_update', (self.pk, self.datetime), 900)
|
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):
|
def tile(request, level, zoom, x, y, format):
|
||||||
|
import cProfile
|
||||||
|
import pstats
|
||||||
|
pr = cProfile.Profile()
|
||||||
|
pr.enable()
|
||||||
|
|
||||||
zoom = int(zoom)
|
zoom = int(zoom)
|
||||||
if not (0 <= zoom <= 10):
|
if not (0 <= zoom <= 10):
|
||||||
raise Http404
|
raise Http404
|
||||||
|
@ -72,6 +77,12 @@ def tile(request, level, zoom, x, y, format):
|
||||||
else:
|
else:
|
||||||
data = f.read()
|
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 = HttpResponse(data, content_type)
|
||||||
response['ETag'] = etag
|
response['ETag'] = etag
|
||||||
response['Cache-Control'] = 'no-cache'
|
response['Cache-Control'] = 'no-cache'
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue