From 7640e77c14a0c80140bf208d6205c7c53d385690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Tue, 24 Oct 2017 15:29:43 +0200 Subject: [PATCH] build map history with holes --- src/c3nav/mapdata/cache.py | 95 +++++++++++++++++++++++++------- src/c3nav/mapdata/render/base.py | 8 +++ 2 files changed, 82 insertions(+), 21 deletions(-) diff --git a/src/c3nav/mapdata/cache.py b/src/c3nav/mapdata/cache.py index a6c01ef4..dae32adb 100644 --- a/src/c3nav/mapdata/cache.py +++ b/src/c3nav/mapdata/cache.py @@ -76,7 +76,7 @@ class MapHistory: f.write(struct.pack('<'+'II'*len(self.updates), *chain(*self.updates))) f.write(self.data.tobytes('C')) - def add_new(self, geometry): + def add_new(self, geometry, data=None): prep = prepared.prep(geometry) minx, miny, maxx, maxy = geometry.bounds res = self.resolution @@ -85,34 +85,44 @@ class MapHistory: maxx = int(math.ceil(maxx/res)) maxy = int(math.ceil(maxy/res)) - data = self.data - if self.resolution != settings.CACHE_RESOLUTION: - data = None - self.updates = self.updates[-1:] + direct = data is None - if not data.size: - data = np.zeros(((maxy-miny), (maxx-minx)), dtype=np.uint16) - self.x, self.y = minx, miny + if direct: + data = self.data + if self.resolution != settings.CACHE_RESOLUTION: + data = None + self.updates = self.updates[-1:] + + if not data.size: + data = np.zeros(((maxy-miny), (maxx-minx)), dtype=np.uint16) + self.x, self.y = minx, miny + else: + orig_height, orig_width = data.shape + if minx < self.x or miny < self.y or maxx > self.x+orig_width or maxy > self.y+orig_height: + new_x, new_y = min(minx, self.x), min(miny, self.y) + new_width = max(maxx, self.x+orig_width)-new_x + new_height = max(maxy, self.y+orig_height)-new_y + new_data = np.zeros((new_height, new_width), dtype=np.uint16) + dx, dy = self.x-new_x, self.y-new_y + new_data[dy:(dy+orig_height), dx:(dx+orig_width)] = data + data = new_data + self.x, self.y = new_x, new_y else: - orig_height, orig_width = data.shape - if minx < self.x or miny < self.y or maxx > self.x+orig_width or maxy > self.y+orig_height: - new_x, new_y = min(minx, self.x), min(miny, self.y) - new_width = max(maxx, self.x+orig_width)-new_x - new_height = max(maxy, self.y+orig_height)-new_y - new_data = np.zeros((new_height, new_width), dtype=np.uint16) - dx, dy = self.x-new_x, self.y-new_y - new_data[dy:(dy+orig_height), dx:(dx+orig_width)] = data - data = new_data - self.x, self.y = new_x, new_y + height, width = data.shape + minx, miny = max(minx, self.x), max(miny, self.y) + maxx, maxy = min(maxx, self.x+width), max(maxy, self.y+height) - new_val = len(self.updates) + new_val = len(self.updates) if direct else 1 for iy, y in enumerate(range(miny*res, maxy*res, res), start=miny-self.y): for ix, x in enumerate(range(minx*res, maxx*res, res), start=minx-self.x): if prep.intersects(box(x, y, x+res, y+res)): data[iy, ix] = new_val - self.data = data - self.unfinished = True + if direct: + self.data = data + self.unfinished = True + else: + return data def finish(self, update): self.unfinished = False @@ -140,6 +150,49 @@ class MapHistory: self.y += miny self.data = self.data[miny:maxy+1, minx:maxx+1] + def composite(self, other, mask_geometry): + if other.resolution != other.resolution: + return + + # check overlapping area + self_height, self_width = self.data.shape + other_height, other_width = other.data.shape + minx, miny = max(self.x, other.x), max(self.y, other.y) + maxx = min(self.x+self_width-1, other.x+other_width-1) + maxy = min(self.y+self_height-1, other.y+other_height-1) + if maxx < minx or maxy < miny: + return + + # merge update lists + self_update_i = {update: i for i, update in enumerate(self.updates)} + other_update_i = {update: i for i, update in enumerate(other.updates)} + new_updates = sorted(set(self_update_i.keys()) | set(other_update_i.keys())) + + # create slices + self_slice = slice(miny-self.y, maxy-self.y+1), slice(minx-self.x, maxx-self.x+1) + other_slice = slice(miny-other.y, maxy-other.y+1), slice(minx-other.x, maxx-other.x+1) + + # reindex according to new update list + other_data = np.zeros_like(self.data) + other_data[self_slice] = other.data[other_slice] + for i, update in enumerate(new_updates): + if update in self_update_i: + self.data[self.data == self_update_i[update]] = i + if update in other_update_i: + other_data[other_data == other_update_i[update]] = i + + # calculate maximum + maximum = np.maximum(self.data, other_data) + + # add with mask + mask = self.add_new(mask_geometry.buffer(1), data=np.zeros_like(self.data, dtype=np.bool)) + self.data[mask] = maximum[mask] + + # write new updates + self.updates = new_updates + + self.simplify() + def to_image(self): from c3nav.mapdata.models import Source (miny, minx), (maxy, maxx) = Source.max_bounds() diff --git a/src/c3nav/mapdata/render/base.py b/src/c3nav/mapdata/render/base.py index 7e4044b8..7bdcdcef 100644 --- a/src/c3nav/mapdata/render/base.py +++ b/src/c3nav/mapdata/render/base.py @@ -4,6 +4,7 @@ from django.core.cache import cache from django.db import transaction from shapely.ops import unary_union +from c3nav.mapdata.cache import MapHistory from c3nav.mapdata.models import Level, MapUpdate @@ -40,6 +41,8 @@ class LevelRenderData: if level.on_top_of_id is not None: continue + map_history = MapHistory.open_level(level.pk, 'base') + level_crop_to = {} # choose a crop area for each level. non-intermediate levels (not on_top_of) below the one that we are @@ -69,6 +72,9 @@ class LevelRenderData: old_geoms = single_level_geoms[sublevel.pk] crop_to = level_crop_to[sublevel.pk] + if crop_to is not FakeCropper: + map_history.composite(MapHistory.open_level(sublevel.pk, 'base'), crop_to) + new_geoms = LevelGeometries() new_geoms.doors = crop_to.intersection(old_geoms.doors) new_geoms.walls = crop_to.intersection(old_geoms.walls) @@ -129,6 +135,8 @@ class LevelRenderData: level.render_data = pickle.dumps(render_data) + map_history.save(MapHistory.level_filename(level.pk, 'render')) + with transaction.atomic(): for level in levels: level.save()