From 99c2c4e0f061e1f9677a066f6caed701ada0a019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Thu, 7 Dec 2023 20:11:55 +0100 Subject: [PATCH] some renderdata code modernization --- src/c3nav/mapdata/render/renderdata.py | 98 +++++++++++++++----------- 1 file changed, 58 insertions(+), 40 deletions(-) diff --git a/src/c3nav/mapdata/render/renderdata.py b/src/c3nav/mapdata/render/renderdata.py index ca5383eb..512d31c8 100644 --- a/src/c3nav/mapdata/render/renderdata.py +++ b/src/c3nav/mapdata/render/renderdata.py @@ -64,28 +64,36 @@ class LevelRenderData: package = CachePackage(bounds=tuple(chain(*Source.max_bounds()))) - # first pass in reverse to collect some data that we need later + # todo: we should check that levels on top come before their levels as they should + + """ + first pass in reverse to collect some data that we need later + """ + # level geometry for every single level single_level_geoms: dict[int, LevelGeometries] = {} + # interpolator are used to create the 3d mesh interpolators = {} last_interpolator: NearestNDInterpolator | None = None + # altitudeareas of levels on top are are collected on the way down to supply to the levelgeometries builder altitudeareas_above = [] # todo: typing - for level in reversed(levels): + for render_level in reversed(levels): # build level geometry for every single level - single_level_geoms[level.pk] = LevelGeometries.build_for_level(level, altitudeareas_above) + single_level_geoms[render_level.pk] = LevelGeometries.build_for_level(render_level, altitudeareas_above) # ignore intermediate levels in this pass - if level.on_top_of_id is not None: - altitudeareas_above.extend(single_level_geoms[level.pk].altitudeareas) + if render_level.on_top_of_id is not None: + # todo: shouldn't this be cleared or something? + altitudeareas_above.extend(single_level_geoms[render_level.pk].altitudeareas) altitudeareas_above.sort(key=operator.attrgetter('altitude')) continue - # create interpolator to create the pieces that fit multiple layers together + # create interpolator to create the pieces that fit multiple 3d layers together if last_interpolator is not None: - interpolators[level.pk] = last_interpolator + interpolators[render_level.pk] = last_interpolator coords = deque() values = deque() - for area in single_level_geoms[level.pk].altitudeareas: + for area in single_level_geoms[render_level.pk].altitudeareas: new_coords = np.vstack(tuple(np.array(ring.coords) for ring in get_rings(area.geometry))) coords.append(new_coords) values.append(np.full((new_coords.shape[0], 1), fill_value=area.altitude)) @@ -93,43 +101,53 @@ class LevelRenderData: if coords: last_interpolator = NearestNDInterpolator(np.vstack(coords), np.vstack(values)) else: - last_interpolator = NearestNDInterpolator(np.array([[0, 0]]), np.array([float(level.base_altitude)])) + last_interpolator = NearestNDInterpolator(np.array([[0, 0]]), np.array([float(render_level.base_altitude)])) - for i, level in enumerate(levels): - if level.on_top_of_id is not None: + """ + second pass, forward to create the LevelRenderData for each level + """ + for render_level in levels: + # we don't create render data for on_top_of levels + if render_level.on_top_of_id is not None: continue - map_history = MapHistory.open_level(level.pk, 'base') + map_history = MapHistory.open_level(render_level.pk, 'base') - sublevels = tuple(sublevel for sublevel in levels - if sublevel.on_top_of_id == level.pk or sublevel.base_altitude <= level.base_altitude) + # collect potentially relevant levels for rendering this level + # these are all levels that are on_top_of this level or below this level + relevant_levels = tuple( + sublevel for sublevel in levels + if sublevel.on_top_of_id == render_level.pk or sublevel.base_altitude <= render_level.base_altitude + ) - level_crop_to = {} - - # choose a crop area for each level. non-intermediate levels (not on_top_of) below the one that we are - # currently rendering will be cropped to only render content that is visible through holes indoors in the - # levels above them. + """ + choose a crop area for each level. non-intermediate levels (not on_top_of) below the one that we are + currently rendering will be cropped to only render content that is visible through holes indoors in the + levels above them. + """ + # area to crop each level to, by id + level_crop_to: dict[int, Cropper] = {} + # current remaining area that we're cropping to – None means no cropping crop_to = None primary_level_count = 0 main_level_passed = 0 lowest_important_level = None last_lower_bound = None - for sublevel in reversed(sublevels): - geoms = single_level_geoms[sublevel.pk] + for level in reversed(relevant_levels): # reversed means we are going down + geoms = single_level_geoms[level.pk] if geoms.holes is not None: primary_level_count += 1 # get lowest intermediate level directly below main level - if not main_level_passed: - if geoms.pk == level.pk: + if geoms.pk == render_level.pk: main_level_passed = 1 else: - if not sublevel.on_top_of_id: + if not level.on_top_of_id: main_level_passed += 1 if main_level_passed < 2: - lowest_important_level = sublevel + lowest_important_level = level # make upper bounds if geoms.on_top_of_id is None: @@ -140,9 +158,9 @@ class LevelRenderData: last_lower_bound = geoms.lower_bound # set crop area if we area on the second primary layer from top or below - level_crop_to[sublevel.pk] = Cropper(crop_to if primary_level_count > 1 else None) + level_crop_to[level.pk] = Cropper(crop_to if primary_level_count > 1 else None) - if geoms.holes is not None: + if geoms.holes is not None: # there area holes on this area if crop_to is None: crop_to = geoms.holes else: @@ -152,31 +170,31 @@ class LevelRenderData: break render_data = LevelRenderData( - base_altitude=level.base_altitude, + base_altitude=render_level.base_altitude, lowest_important_level=lowest_important_level.pk, ) access_restriction_affected = {} # go through sublevels, get their level geometries and crop them lowest_important_level_passed = False - for sublevel in sublevels: + for level in relevant_levels: try: - crop_to = level_crop_to[sublevel.pk] + crop_to = level_crop_to[level.pk] except KeyError: break - old_geoms = single_level_geoms[sublevel.pk] + old_geoms = single_level_geoms[level.pk] - if render_data.lowest_important_level == sublevel.pk: + if render_data.lowest_important_level == level.pk: lowest_important_level_passed = True if old_geoms.holes and render_data.darken_area is None and lowest_important_level_passed: render_data.darken_area = old_geoms.holes if crop_to.geometry is not None: - map_history.composite(MapHistory.open_level(sublevel.pk, 'base'), crop_to.geometry) - elif level.pk != sublevel.pk: - map_history.composite(MapHistory.open_level(sublevel.pk, 'base'), None) + map_history.composite(MapHistory.open_level(level.pk, 'base'), crop_to.geometry) + elif render_level.pk != level.pk: + map_history.composite(MapHistory.open_level(level.pk, 'base'), None) new_geoms = LevelGeometries() new_geoms.buildings = crop_to.intersection(old_geoms.buildings) @@ -285,7 +303,7 @@ class LevelRenderData: new_geoms.lower_bound = old_geoms.lower_bound new_geoms.upper_bound = old_geoms.upper_bound - new_geoms.build_mesh(interpolators.get(level.pk) if sublevel.pk == level.pk else None) + new_geoms.build_mesh(interpolators.get(render_level.pk) if level.pk == render_level.pk else None) render_data.levels.append(new_geoms) @@ -295,13 +313,13 @@ class LevelRenderData: } access_restriction_affected = AccessRestrictionAffected.build(access_restriction_affected) - access_restriction_affected.save_level(level.pk, 'composite') + access_restriction_affected.save_level(render_level.pk, 'composite') - map_history.save_level(level.pk, 'composite') + map_history.save_level(render_level.pk, 'composite') - package.add_level(level.pk, map_history, access_restriction_affected) + package.add_level(render_level.pk, map_history, access_restriction_affected) - render_data.save(level.pk) + render_data.save(render_level.pk) package.save_all()