diff --git a/src/c3nav/mapdata/management/commands/rebuildgeometries.py b/src/c3nav/mapdata/management/commands/rebuildgeometries.py new file mode 100644 index 00000000..a9da9237 --- /dev/null +++ b/src/c3nav/mapdata/management/commands/rebuildgeometries.py @@ -0,0 +1,9 @@ +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + help = 'rebuild level geometries' + + def handle(self, *args, **options): + from c3nav.mapdata.render.base import LevelGeometries + LevelGeometries.rebuild() diff --git a/src/c3nav/mapdata/migrations/0037_level_geoms_cache.py b/src/c3nav/mapdata/migrations/0037_level_geoms_cache.py new file mode 100644 index 00000000..ba3faa9e --- /dev/null +++ b/src/c3nav/mapdata/migrations/0037_level_geoms_cache.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.6 on 2017-10-19 11:21 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mapdata', '0036_geometry_bounds'), + ] + + operations = [ + migrations.AddField( + model_name='level', + name='geoms_cache', + field=models.BinaryField(default=b''), + preserve_default=False, + ), + ] diff --git a/src/c3nav/mapdata/models/level.py b/src/c3nav/mapdata/models/level.py index 2f3b9eb9..43185602 100644 --- a/src/c3nav/mapdata/models/level.py +++ b/src/c3nav/mapdata/models/level.py @@ -26,6 +26,8 @@ class Level(SpecificLocation, models.Model): on_top_of = models.ForeignKey('mapdata.Level', null=True, on_delete=models.CASCADE, related_name='levels_on_top', verbose_name=_('on top of')) + geoms_cache = models.BinaryField() + class Meta: verbose_name = _('Level') verbose_name_plural = _('Levels') diff --git a/src/c3nav/mapdata/render/base.py b/src/c3nav/mapdata/render/base.py new file mode 100644 index 00000000..2d6a3416 --- /dev/null +++ b/src/c3nav/mapdata/render/base.py @@ -0,0 +1,50 @@ +import pickle + +from django.db import transaction +from django.db.models import Q +from shapely.ops import unary_union + +from c3nav.mapdata.models import Level + + +class LevelGeometries: + def __init__(self): + self.altitudeareas = [] + self.walls = None + self.doors = None + + @staticmethod + def rebuild(): + levels = Level.objects.prefetch_related('altitudeareas', 'buildings', 'doors', 'spaces', + 'spaces__holes', 'spaces__columns') + for level in levels: + geoms = LevelGeometries() + buildings_geom = unary_union([b.geometry for b in level.buildings.all()]) + + for space in level.spaces.all(): + if space.outside: + space.geometry = space.geometry.difference(buildings_geom) + space.geometry = space.geometry.difference(unary_union([c.geometry for c in space.columns.all()])) + space.holes_geom = unary_union([h.geometry for h in space.holes.all()]) + space.walkable_geom = space.geometry.difference(space.holes_geom) + + spaces_geom = unary_union([s.geometry for s in level.spaces.all()]) + geoms.doors = unary_union([d.geometry for d in level.doors.all()]) + walkable_geom = unary_union([s.walkable_geom for s in level.spaces.all()]).union(geoms.doors) + + for altitudearea in level.altitudeareas.all(): + geoms.altitudeareas.append((altitudearea.geometry.intersection(walkable_geom), altitudearea.altitude)) + + geoms.walls = buildings_geom.difference(spaces_geom).difference(geoms.doors) + level.geoms_cache = pickle.dumps(geoms) + level.save() + + with transaction.atomic(): + for level in levels: + level.save() + + +def get_render_level_data(level): + levels = Level.objects.filter(Q(on_top_of=level.pk) | Q(base_altitude__lte=level.base_altitude)) + levels = levels.values_list('geoms_cache', 'default_height') + return levels diff --git a/src/c3nav/mapdata/render/svg.py b/src/c3nav/mapdata/render/svg.py index 078b6f25..ce55eca8 100644 --- a/src/c3nav/mapdata/render/svg.py +++ b/src/c3nav/mapdata/render/svg.py @@ -1,8 +1,8 @@ -from django.db.models import Prefetch, Q -from shapely.geometry import box -from shapely.ops import unary_union +import pickle -from c3nav.mapdata.models import AltitudeArea, Building, Door, Level, Space +from shapely.geometry import box + +from c3nav.mapdata.render.base import get_render_level_data from c3nav.mapdata.utils.svg import SVGImage @@ -12,41 +12,14 @@ def render_svg(level, miny, minx, maxy, maxx, scale=1): within_coords = (minx-2, miny-2, maxx+2, maxy+2) bbox = box(*within_coords) - levels = Level.objects.filter(Q(on_top_of=level.pk) | Q(base_altitude__lte=level.base_altitude)) - levels = levels.prefetch_related(Prefetch('altitudeareas', AltitudeArea.objects.within(*within_coords)), - Prefetch('buildings', Building.objects.within(*within_coords)), - Prefetch('doors', Door.objects.within(*within_coords)), - Prefetch('spaces', Space.objects.within(*within_coords).prefetch_related( - 'holes', 'columns' - ))) + for geoms_cache, default_height in get_render_level_data(level): + geoms = pickle.loads(geoms_cache) + for altitudearea_geom, altitude in geoms.altitudeareas: + svg.add_geometry(bbox.intersection(altitudearea_geom), fill_color='#eeeeee', altitude=altitude) - for level in levels: - buildings_geom = bbox.intersection(unary_union([b.geometry for b in level.buildings.all()])) - # svg.add_geometry(buildings_geom, fill_color='#aaaaaa') + svg.add_geometry(bbox.intersection(geoms.walls), + fill_color='#aaaaaa', stroke_px=0.5, stroke_color='#aaaaaa', elevation=default_height) - for space in level.spaces.all(): - if space.outside: - space.geometry = space.geometry.difference(buildings_geom) - space.geometry = space.geometry.difference(unary_union([c.geometry for c in space.columns.all()])) - space.holes_geom = unary_union([h.geometry for h in space.holes.all()]) - space.walkable_geom = space.geometry.difference(space.holes_geom) - - spaces_geom = bbox.intersection(unary_union([s.geometry for s in level.spaces.all()])) - doors_geom = bbox.intersection(unary_union([d.geometry for d in level.doors.all()])) - walkable_geom = unary_union([w.walkable_geom for w in level.spaces.all()]).union(doors_geom) - - for altitudearea in level.altitudeareas.all(): - svg.add_geometry(bbox.intersection(altitudearea.geometry).intersection(walkable_geom), - fill_color='#eeeeee', altitude=altitudearea.altitude) - - spaces_geom = bbox.intersection(unary_union([s.geometry for s in level.spaces.all()])) - doors_geom = bbox.intersection(unary_union([d.geometry for d in level.doors.all()])) - - walls_geom = buildings_geom.difference(spaces_geom).difference(doors_geom) - - svg.add_geometry(walls_geom, fill_color='#aaaaaa', stroke_px=0.5, stroke_color='#aaaaaa', - elevation=level.default_height) - - svg.add_geometry(doors_geom, fill_color='#ffffff', elevation=0) + svg.add_geometry(bbox.intersection(geoms.doors), fill_color='#ffffff', elevation=0) return svg