introduce intermediate level setting

This commit is contained in:
Laura Klünder 2024-12-19 00:05:37 +01:00
parent a4a7f02d0b
commit 9446c34e8a
8 changed files with 39 additions and 15 deletions

View file

@ -72,11 +72,16 @@ class LevelsForLevel:
# noinspection PyPep8Naming # noinspection PyPep8Naming
levels_under = () levels_under = ()
levels_on_top = () levels_on_top = ()
lower_level = level.lower(Level).first() lower_levels = []
primary_levels = (level,) + ((lower_level,) if lower_level else ()) 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') secondary_levels = Level.objects.filter(on_top_of__in=primary_levels).values_list('pk', 'on_top_of')
if lower_level: lower_level_pks = set(l.pk for l in lower_levels)
levels_under = tuple(pk for pk, on_top_of in secondary_levels if on_top_of == lower_level.pk) 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: if True:
levels_on_top = tuple(pk for pk, on_top_of in secondary_levels if on_top_of == level.pk) levels_on_top = tuple(pk for pk, on_top_of in secondary_levels if on_top_of == level.pk)

View file

@ -401,7 +401,8 @@ def create_editor_form(editor_model):
'slug', 'name', 'title', 'title_plural', 'help_text', 'position_secret', 'icon', 'join_edges', 'slug', 'name', 'title', 'title_plural', 'help_text', 'position_secret', 'icon', 'join_edges',
'up_separate', 'bssid', 'main_point', 'external_url', 'hub_import_type', 'walk', 'ordering', 'up_separate', 'bssid', 'main_point', 'external_url', 'hub_import_type', 'walk', 'ordering',
'category', 'width', 'groups', 'height', 'color', 'in_legend', 'priority', 'hierarchy', 'icon_name', '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', 'can_describe', 'geometry', 'single', 'altitude', 'short_label', 'origin_space', 'target_space', 'data',
'comment', 'slow_down_factor', 'groundaltitude', 'node_number', 'wifi_bssid', 'bluetooth_address', "group", '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', 'ibeacon_uuid', 'ibeacon_major', 'ibeacon_minor', 'uwb_address', 'extra_seconds', 'speed', 'can_report_missing',

View file

@ -25,6 +25,7 @@ class Level(SpecificLocation, models.Model):
validators=[MinValueValidator(Decimal('0'))]) validators=[MinValueValidator(Decimal('0'))])
on_top_of = models.ForeignKey('mapdata.Level', null=True, on_delete=models.CASCADE, on_top_of = models.ForeignKey('mapdata.Level', null=True, on_delete=models.CASCADE,
related_name='levels_on_top', verbose_name=_('on top of')) 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) short_label = models.SlugField(max_length=20, verbose_name=_('short label'), unique=True)
class Meta: class Meta:

View file

@ -64,7 +64,7 @@ class RenderEngine(ABC):
def add_group(self, group): def add_group(self, group):
pass pass
def darken(self, area): def darken(self, area, much=False):
pass pass
def add_geometry(self, geometry, fill: Optional[FillAttribs] = None, stroke: Optional[StrokeAttribs] = None, def add_geometry(self, geometry, fill: Optional[FillAttribs] = None, stroke: Optional[StrokeAttribs] = None,

View file

@ -232,9 +232,9 @@ class SVGEngine(RenderEngine):
else: else:
self.altitudes[new_altitude] = new_geometry self.altitudes[new_altitude] = new_geometry
def darken(self, area): def darken(self, area, much=False):
if area: 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], def _add_geometry(self, geometry, fill: Optional[FillAttribs], stroke: Optional[StrokeAttribs],
altitude=None, height=None, shadow_color=None, shape_cache_key=None, **kwargs): altitude=None, height=None, shadow_color=None, shape_cache_key=None, **kwargs):

View file

@ -6,11 +6,11 @@ from functools import reduce
from itertools import chain from itertools import chain
import numpy as np import numpy as np
from shapely import prepared from shapely import prepared, box
from shapely.geometry import GeometryCollection, Polygon, MultiPolygon from shapely.geometry import GeometryCollection, Polygon, MultiPolygon
from shapely.ops import unary_union 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.altitudearea import AltitudeAreaGeometries
from c3nav.mapdata.render.geometry.hybrid import HybridGeometry from c3nav.mapdata.render.geometry.hybrid import HybridGeometry
from c3nav.mapdata.render.geometry.mesh import Mesh 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))) walls_geom = buildings_geom.difference(unary_union((spaces_geom, doors_geom)))
if level.on_top_of_id is None: if level.on_top_of_id is None:
holes_geom = unary_union([s.holes_geom for s in spaces]) 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: else:
holes_geom = None holes_geom = None

View file

@ -53,6 +53,7 @@ class LevelRenderData:
lowest_important_level: int lowest_important_level: int
levels: list[CompositeLevelGeometries] = field(default_factory=list) levels: list[CompositeLevelGeometries] = field(default_factory=list)
darken_area: MultiPolygon | None = None darken_area: MultiPolygon | None = None
darken_much: bool = False
@staticmethod @staticmethod
def rebuild(update_cache_key): def rebuild(update_cache_key):
@ -125,10 +126,11 @@ class LevelRenderData:
map_history = MapHistory.open_level(render_level.pk, 'base') map_history = MapHistory.open_level(render_level.pk, 'base')
# collect potentially relevant levels for rendering this level # 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( relevant_levels = tuple(
sublevel for sublevel in levels 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 upper_bounds[geoms.pk] = last_lower_bound
last_lower_bound = geoms.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) 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 if geoms.holes is not None: # there area holes on this area
@ -180,6 +182,10 @@ class LevelRenderData:
if crop_to.is_empty: if crop_to.is_empty:
break 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( render_data = LevelRenderData(
base_altitude=render_level.base_altitude, base_altitude=render_level.base_altitude,
lowest_important_level=lowest_important_level.pk, 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: if single_geoms.holes and render_data.darken_area is None and lowest_important_level_passed:
render_data.darken_area = single_geoms.holes render_data.darken_area = single_geoms.holes
if render_level.intermediate:
render_data.darken_much = True
if crop_to.geometry is not None: if crop_to.geometry is not None:
map_history.composite(MapHistory.open_level(level.pk, 'base'), crop_to.geometry) map_history.composite(MapHistory.open_level(level.pk, 'base'), crop_to.geometry)

View file

@ -1,8 +1,10 @@
from itertools import chain
from django.utils.functional import cached_property from django.utils.functional import cached_property
from shapely import prepared from shapely import prepared
from shapely.geometry import box 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.engines.base import FillAttribs, StrokeAttribs
from c3nav.mapdata.render.geometry import hybrid_union from c3nav.mapdata.render.geometry import hybrid_union
from c3nav.mapdata.render.renderdata import LevelRenderData from c3nav.mapdata.render.renderdata import LevelRenderData
@ -61,7 +63,7 @@ class MapRenderer:
engine.add_group('level_%s' % geoms.short_label) engine.add_group('level_%s' % geoms.short_label)
if geoms.pk == level_render_data.lowest_important_level: 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): if not bbox.intersects(geoms.affected_area):
continue continue