From 9446c34e8a37f433096f2c27caee0201d0848cc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Thu, 19 Dec 2024 00:05:37 +0100 Subject: [PATCH] introduce intermediate level setting --- src/c3nav/editor/api/geometries.py | 13 +++++++++---- src/c3nav/editor/forms.py | 3 ++- src/c3nav/mapdata/models/level.py | 1 + src/c3nav/mapdata/render/engines/base.py | 2 +- src/c3nav/mapdata/render/engines/svg.py | 4 ++-- src/c3nav/mapdata/render/geometry/level.py | 11 +++++++++-- src/c3nav/mapdata/render/renderdata.py | 14 +++++++++++--- src/c3nav/mapdata/render/renderer.py | 6 ++++-- 8 files changed, 39 insertions(+), 15 deletions(-) diff --git a/src/c3nav/editor/api/geometries.py b/src/c3nav/editor/api/geometries.py index aac676ca..3f4b39f3 100644 --- a/src/c3nav/editor/api/geometries.py +++ b/src/c3nav/editor/api/geometries.py @@ -72,11 +72,16 @@ class LevelsForLevel: # noinspection PyPep8Naming levels_under = () levels_on_top = () - lower_level = level.lower(Level).first() - primary_levels = (level,) + ((lower_level,) if lower_level else ()) + lower_levels = [] + for sublevel in level.lower(Level): + lower_levels.append(sublevel) + if not sublevel.intermediate: + break + primary_levels = chain((level,), lower_levels) secondary_levels = Level.objects.filter(on_top_of__in=primary_levels).values_list('pk', 'on_top_of') - if lower_level: - levels_under = tuple(pk for pk, on_top_of in secondary_levels if on_top_of == lower_level.pk) + lower_level_pks = set(l.pk for l in lower_levels) + if lower_levels: + levels_under = tuple(pk for pk, on_top_of in secondary_levels if on_top_of in lower_level_pks) if True: levels_on_top = tuple(pk for pk, on_top_of in secondary_levels if on_top_of == level.pk) diff --git a/src/c3nav/editor/forms.py b/src/c3nav/editor/forms.py index ecf7d7c3..dc1a0166 100644 --- a/src/c3nav/editor/forms.py +++ b/src/c3nav/editor/forms.py @@ -401,7 +401,8 @@ def create_editor_form(editor_model): 'slug', 'name', 'title', 'title_plural', 'help_text', 'position_secret', 'icon', 'join_edges', 'up_separate', 'bssid', 'main_point', 'external_url', 'hub_import_type', 'walk', 'ordering', 'category', 'width', 'groups', 'height', 'color', 'in_legend', 'priority', 'hierarchy', 'icon_name', - 'base_altitude', 'waytype', 'access_restriction', 'default_height', 'door_height', 'outside', 'can_search', + 'base_altitude', 'intermediate', + 'waytype', 'access_restriction', 'default_height', 'door_height', 'outside', 'can_search', 'can_describe', 'geometry', 'single', 'altitude', 'short_label', 'origin_space', 'target_space', 'data', 'comment', 'slow_down_factor', 'groundaltitude', 'node_number', 'wifi_bssid', 'bluetooth_address', "group", 'ibeacon_uuid', 'ibeacon_major', 'ibeacon_minor', 'uwb_address', 'extra_seconds', 'speed', 'can_report_missing', diff --git a/src/c3nav/mapdata/models/level.py b/src/c3nav/mapdata/models/level.py index 90ff8cc1..826684bd 100644 --- a/src/c3nav/mapdata/models/level.py +++ b/src/c3nav/mapdata/models/level.py @@ -25,6 +25,7 @@ class Level(SpecificLocation, models.Model): validators=[MinValueValidator(Decimal('0'))]) on_top_of = models.ForeignKey('mapdata.Level', null=True, on_delete=models.CASCADE, related_name='levels_on_top', verbose_name=_('on top of')) + intermediate = models.BooleanField(_("intermediate level"), default=False) short_label = models.SlugField(max_length=20, verbose_name=_('short label'), unique=True) class Meta: diff --git a/src/c3nav/mapdata/render/engines/base.py b/src/c3nav/mapdata/render/engines/base.py index e8df5137..4a5ae859 100644 --- a/src/c3nav/mapdata/render/engines/base.py +++ b/src/c3nav/mapdata/render/engines/base.py @@ -64,7 +64,7 @@ class RenderEngine(ABC): def add_group(self, group): pass - def darken(self, area): + def darken(self, area, much=False): pass def add_geometry(self, geometry, fill: Optional[FillAttribs] = None, stroke: Optional[StrokeAttribs] = None, diff --git a/src/c3nav/mapdata/render/engines/svg.py b/src/c3nav/mapdata/render/engines/svg.py index ea7fb922..f7004241 100644 --- a/src/c3nav/mapdata/render/engines/svg.py +++ b/src/c3nav/mapdata/render/engines/svg.py @@ -232,9 +232,9 @@ class SVGEngine(RenderEngine): else: self.altitudes[new_altitude] = new_geometry - def darken(self, area): + def darken(self, area, much=False): if area: - self.add_geometry(geometry=area, fill=FillAttribs('#000000', 0.1), category='darken') + self.add_geometry(geometry=area, fill=FillAttribs('#000000', 0.5 if much else 0.1), category='darken') def _add_geometry(self, geometry, fill: Optional[FillAttribs], stroke: Optional[StrokeAttribs], altitude=None, height=None, shadow_color=None, shape_cache_key=None, **kwargs): diff --git a/src/c3nav/mapdata/render/geometry/level.py b/src/c3nav/mapdata/render/geometry/level.py index 9b8382bb..b9235ccd 100644 --- a/src/c3nav/mapdata/render/geometry/level.py +++ b/src/c3nav/mapdata/render/geometry/level.py @@ -6,11 +6,11 @@ from functools import reduce from itertools import chain import numpy as np -from shapely import prepared +from shapely import prepared, box from shapely.geometry import GeometryCollection, Polygon, MultiPolygon from shapely.ops import unary_union -from c3nav.mapdata.models import Space, Level, AltitudeArea +from c3nav.mapdata.models import Space, Level, AltitudeArea, Source from c3nav.mapdata.render.geometry.altitudearea import AltitudeAreaGeometries from c3nav.mapdata.render.geometry.hybrid import HybridGeometry from c3nav.mapdata.render.geometry.mesh import Mesh @@ -294,6 +294,13 @@ class SingleLevelGeometries(BaseLevelGeometries): walls_geom = buildings_geom.difference(unary_union((spaces_geom, doors_geom))) if level.on_top_of_id is None: holes_geom = unary_union([s.holes_geom for s in spaces]) + + if level.intermediate: + holes_geom = unary_union([ + holes_geom, + box(*chain(*Source.max_bounds())).difference(buildings_geom).difference(spaces_geom) + ]) + else: holes_geom = None diff --git a/src/c3nav/mapdata/render/renderdata.py b/src/c3nav/mapdata/render/renderdata.py index a8d5e521..f7bd5e7f 100644 --- a/src/c3nav/mapdata/render/renderdata.py +++ b/src/c3nav/mapdata/render/renderdata.py @@ -53,6 +53,7 @@ class LevelRenderData: lowest_important_level: int levels: list[CompositeLevelGeometries] = field(default_factory=list) darken_area: MultiPolygon | None = None + darken_much: bool = False @staticmethod def rebuild(update_cache_key): @@ -125,10 +126,11 @@ class LevelRenderData: map_history = MapHistory.open_level(render_level.pk, 'base') # collect potentially relevant levels for rendering this level - # these are all levels that are on_top_of this level or below this level + # these are all levels that are on_top_of this level or below this level (inless intermediate) 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 + if (sublevel.pk == render_level.pk or sublevel.on_top_of_id == render_level.pk or + (sublevel.base_altitude <= render_level.base_altitude and not sublevel.intermediate)) ) """ @@ -168,7 +170,7 @@ class LevelRenderData: upper_bounds[geoms.pk] = last_lower_bound last_lower_bound = geoms.lower_bound - # set crop area if we area on the second primary layer from top or below + # set crop area if we are on the second primary layer from top or below level_crop_to[level.pk] = Cropper(crop_to if primary_level_count > 1 else None) if geoms.holes is not None: # there area holes on this area @@ -180,6 +182,10 @@ class LevelRenderData: if crop_to.is_empty: break + if render_level.intermediate: + # todo: would be nice to still have the staircases leading to this level i guess? + lowest_important_level = render_level + render_data = LevelRenderData( base_altitude=render_level.base_altitude, lowest_important_level=lowest_important_level.pk, @@ -201,6 +207,8 @@ class LevelRenderData: if single_geoms.holes and render_data.darken_area is None and lowest_important_level_passed: render_data.darken_area = single_geoms.holes + if render_level.intermediate: + render_data.darken_much = True if crop_to.geometry is not None: map_history.composite(MapHistory.open_level(level.pk, 'base'), crop_to.geometry) diff --git a/src/c3nav/mapdata/render/renderer.py b/src/c3nav/mapdata/render/renderer.py index d76d5b04..3a88583b 100644 --- a/src/c3nav/mapdata/render/renderer.py +++ b/src/c3nav/mapdata/render/renderer.py @@ -1,8 +1,10 @@ +from itertools import chain + from django.utils.functional import cached_property from shapely import prepared from shapely.geometry import box -from c3nav.mapdata.models import Level +from c3nav.mapdata.models import Level, Source from c3nav.mapdata.render.engines.base import FillAttribs, StrokeAttribs from c3nav.mapdata.render.geometry import hybrid_union from c3nav.mapdata.render.renderdata import LevelRenderData @@ -61,7 +63,7 @@ class MapRenderer: engine.add_group('level_%s' % geoms.short_label) if geoms.pk == level_render_data.lowest_important_level: - engine.darken(level_render_data.darken_area) + engine.darken(level_render_data.darken_area, much=level_render_data.darken_much) if not bbox.intersects(geoms.affected_area): continue