split LevelGeometries into its two subtypes

This commit is contained in:
Laura Klünder 2024-08-19 15:49:58 +02:00
parent 9f264d1f59
commit c5b2ed9f4b
3 changed files with 68 additions and 64 deletions

View file

@ -1,3 +1,3 @@
from c3nav.mapdata.render.geometry.hybrid import hybrid_union, HybridGeometry # noqa from c3nav.mapdata.render.geometry.hybrid import hybrid_union, HybridGeometry # noqa
from c3nav.mapdata.render.geometry.level import LevelGeometries # noqa from c3nav.mapdata.render.geometry.level import SingleLevelGeometries, CompositeLevelGeometries # noqa
from c3nav.mapdata.render.geometry.altitudearea import AltitudeAreaGeometries # noqa from c3nav.mapdata.render.geometry.altitudearea import AltitudeAreaGeometries # noqa

View file

@ -28,33 +28,23 @@ ZeroOrMorePolygons: typing.TypeAlias = GeometryCollection | Polygon | MultiPolyg
@dataclass @dataclass
class LevelGeometries: class BaseLevelGeometries:
""" """
Store geometries for a Level. Geometries for a Level.
""" """
# todo: split into the two versions of this # todo: split into the two versions of this
buildings: ZeroOrMorePolygons buildings: ZeroOrMorePolygons
altitudeareas: list[AltitudeAreaGeometries] altitudeareas: list[AltitudeAreaGeometries]
heightareas: typing.Sequence[tuple[ZeroOrMorePolygons, float]] heightareas: typing.Sequence[tuple[ZeroOrMorePolygons, float]]
walls: ZeroOrMorePolygons walls: ZeroOrMorePolygons
walls_extended: None | HybridGeometry
all_walls: ZeroOrMorePolygons all_walls: ZeroOrMorePolygons
short_walls: list[tuple[AltitudeArea, ZeroOrMorePolygons]] | typing.Sequence[ZeroOrMorePolygons] short_walls: list[tuple[AltitudeArea, ZeroOrMorePolygons]] | typing.Sequence[ZeroOrMorePolygons]
doors: ZeroOrMorePolygons | None doors: ZeroOrMorePolygons | None
doors_extended: HybridGeometry | None holes: ZeroOrMorePolygons | None
holes: None
access_restriction_affected: dict[int, ZeroOrMorePolygons] | None
restricted_spaces_indoors: dict[int, ZeroOrMorePolygons] restricted_spaces_indoors: dict[int, ZeroOrMorePolygons]
restricted_spaces_outdoors: dict[int, ZeroOrMorePolygons] restricted_spaces_outdoors: dict[int, ZeroOrMorePolygons]
affected_area: ZeroOrMorePolygons | None
ramps: typing.Sequence[ZeroOrMorePolygons] ramps: typing.Sequence[ZeroOrMorePolygons]
vertices: None | np.ndarray
faces: None | np.ndarray
walls_base: None | HybridGeometry
walls_bottom: None | HybridGeometry
pk: int pk: int
on_top_of_id: int | None on_top_of_id: int | None
short_label: str short_label: str
@ -66,11 +56,18 @@ class LevelGeometries:
max_height: int max_height: int
lower_bound: int lower_bound: int
upper_bound: None
def __repr__(self): def __repr__(self):
return '<LevelGeometries for Level %s (#%d)>' % (self.short_label, self.pk) return '<LevelGeometries for Level %s (#%d)>' % (self.short_label, self.pk)
@dataclass(slots=True)
class SingleLevelGeometries(BaseLevelGeometries):
"""
Geometries for a level, base calculation on the way to LevelRenderData
"""
access_restriction_affected: dict[int, ZeroOrMorePolygons]
@dataclass @dataclass
class SpaceGeometries: class SpaceGeometries:
geometry: ZeroOrMorePolygons geometry: ZeroOrMorePolygons
@ -365,28 +362,35 @@ class LevelGeometries:
max_height=(min(height for area, height in heightareas_geom) max_height=(min(height for area, height in heightareas_geom)
if analysis.heightareas else default_height), if analysis.heightareas else default_height),
lower_bound=min_altitude-700, lower_bound=min_altitude-700,
affected_area=None,
doors_extended=None,
faces=None,
upper_bound=None,
vertices=None,
walls_base=None,
walls_bottom=None,
walls_extended=None,
) )
AccessRestrictionAffected.build(geoms.access_restriction_affected).save_level(level.pk, 'base') AccessRestrictionAffected.build(geoms.access_restriction_affected).save_level(level.pk, 'base')
return geoms return geoms
def get_geometries(self):
@dataclass(slots=True)
class CompositeLevelGeometries(BaseLevelGeometries):
"""
Geometries for a level, as a member of a composite level rendering, the final type in LevelRenderData
"""
affected_area: ZeroOrMorePolygons
doors_extended: HybridGeometry | None
vertices: None | np.ndarray
faces: None | np.ndarray
upper_bound: int
walls_base: None | HybridGeometry
walls_bottom: None | HybridGeometry
walls_extended: None | HybridGeometry
def get_geometries(self): # called on the final thing
# omit heightareas as these are never drawn # omit heightareas as these are never drawn
return chain((area.geometry for area in self.altitudeareas), (self.walls, self.doors,), return chain((area.geometry for area in self.altitudeareas), (self.walls, self.doors,),
self.restricted_spaces_indoors.values(), self.restricted_spaces_outdoors.values(), self.ramps, self.restricted_spaces_indoors.values(), self.restricted_spaces_outdoors.values(), self.ramps,
(geom for altitude, geom in self.short_walls)) (geom for altitude, geom in self.short_walls))
def create_hybrid_geometries(self, face_centers): def create_hybrid_geometries(self, face_centers): # called on the final thing
vertices_offset = self.vertices.shape[0] vertices_offset = self.vertices.shape[0]
faces_offset = self.faces.shape[0] faces_offset = self.faces.shape[0]
new_vertices = deque() new_vertices = deque()

View file

@ -14,7 +14,7 @@ from shapely.prepared import PreparedGeometry
from c3nav.mapdata.models import Level, MapUpdate, Source from c3nav.mapdata.models import Level, MapUpdate, Source
from c3nav.mapdata.models.theme import Theme from c3nav.mapdata.models.theme import Theme
from c3nav.mapdata.render.geometry import AltitudeAreaGeometries, LevelGeometries from c3nav.mapdata.render.geometry import AltitudeAreaGeometries, SingleLevelGeometries, CompositeLevelGeometries
from c3nav.mapdata.utils.cache import AccessRestrictionAffected, MapHistory from c3nav.mapdata.utils.cache import AccessRestrictionAffected, MapHistory
from c3nav.mapdata.utils.cache.package import CachePackage from c3nav.mapdata.utils.cache.package import CachePackage
from c3nav.mapdata.utils.geometry import get_rings, unwrap_geom from c3nav.mapdata.utils.geometry import get_rings, unwrap_geom
@ -51,7 +51,7 @@ class LevelRenderData:
""" """
base_altitude: float base_altitude: float
lowest_important_level: int lowest_important_level: int
levels: list[LevelGeometries] = field(default_factory=list) levels: list[CompositeLevelGeometries] = field(default_factory=list)
darken_area: MultiPolygon | None = None darken_area: MultiPolygon | None = None
@staticmethod @staticmethod
@ -77,15 +77,15 @@ class LevelRenderData:
first pass in reverse to collect some data that we need later first pass in reverse to collect some data that we need later
""" """
# level geometry for every single level # level geometry for every single level
single_level_geoms: dict[int, LevelGeometries] = {} single_level_geoms: dict[int, SingleLevelGeometries] = {}
# interpolator are used to create the 3d mesh # interpolator are used to create the 3d mesh
interpolators = {} interpolators = {}
last_interpolator: NearestNDInterpolator | None = None last_interpolator: NearestNDInterpolator | None = None
# altitudeareas of levels on top are are collected on the way down to supply to the levelgeometries builder # altitudeareas of levels on top are collected on the way down to supply to the levelgeometries builder
altitudeareas_above = [] # todo: typing altitudeareas_above = [] # todo: typing
for render_level in reversed(levels): for render_level in reversed(levels):
# build level geometry for every single level # build level geometry for every single level
single_level_geoms[render_level.pk] = LevelGeometries.build_for_level( single_level_geoms[render_level.pk] = SingleLevelGeometries.build_for_level(
render_level, color_manager, altitudeareas_above render_level, color_manager, altitudeareas_above
) )
@ -116,6 +116,7 @@ class LevelRenderData:
""" """
second pass, forward to create the LevelRenderData for each level second pass, forward to create the LevelRenderData for each level
""" """
upper_bounds: dict[int, int] = {}
for render_level in levels: for render_level in levels:
# we don't create render data for on_top_of levels # we don't create render data for on_top_of levels
if render_level.on_top_of_id is not None: if render_level.on_top_of_id is not None:
@ -162,9 +163,9 @@ class LevelRenderData:
# make upper bounds # make upper bounds
if geoms.on_top_of_id is None: if geoms.on_top_of_id is None:
if last_lower_bound is None: if last_lower_bound is None:
geoms.upper_bound = geoms.max_altitude+geoms.max_height upper_bounds[geoms.pk] = geoms.max_altitude+geoms.max_height
else: else:
geoms.upper_bound = 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 area on the second primary layer from top or below
@ -193,34 +194,34 @@ class LevelRenderData:
except KeyError: except KeyError:
continue continue
old_geoms = single_level_geoms[level.pk] single_geoms = single_level_geoms[level.pk]
if render_data.lowest_important_level == level.pk: if render_data.lowest_important_level == level.pk:
lowest_important_level_passed = True lowest_important_level_passed = True
if old_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 = old_geoms.holes render_data.darken_area = single_geoms.holes
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)
elif render_level.pk != level.pk: elif render_level.pk != level.pk:
map_history.composite(MapHistory.open_level(level.pk, 'base'), None) map_history.composite(MapHistory.open_level(level.pk, 'base'), None)
new_buildings_geoms = crop_to.intersection(old_geoms.buildings) new_buildings_geoms = crop_to.intersection(single_geoms.buildings)
if old_geoms.on_top_of_id is None: if single_geoms.on_top_of_id is None:
new_holes_geoms = crop_to.intersection(old_geoms.holes) new_holes_geoms = crop_to.intersection(single_geoms.holes)
else: else:
new_holes_geoms = None new_holes_geoms = None
new_doors_geoms = crop_to.intersection(old_geoms.doors) new_doors_geoms = crop_to.intersection(single_geoms.doors)
new_walls_geoms = crop_to.intersection(old_geoms.walls) new_walls_geoms = crop_to.intersection(single_geoms.walls)
new_all_walls_geoms = crop_to.intersection(old_geoms.all_walls) new_all_walls_geoms = crop_to.intersection(single_geoms.all_walls)
new_short_walls_geoms = tuple((altitude, geom) for altitude, geom in tuple( new_short_walls_geoms = tuple((altitude, geom) for altitude, geom in tuple(
(altitude, crop_to.intersection(geom)) (altitude, crop_to.intersection(geom))
for altitude, geom in old_geoms.short_walls for altitude, geom in single_geoms.short_walls
) if not geom.is_empty) ) if not geom.is_empty)
new_altitudeareas = [] new_altitudeareas = []
for altitudearea in old_geoms.altitudeareas: for altitudearea in single_geoms.altitudeareas:
new_geometry = crop_to.intersection(unwrap_geom(altitudearea.geometry)) new_geometry = crop_to.intersection(unwrap_geom(altitudearea.geometry))
if new_geometry.is_empty: if new_geometry.is_empty:
continue continue
@ -267,42 +268,42 @@ class LevelRenderData:
new_heightareas = tuple( new_heightareas = tuple(
(area, height) for area, height in ((crop_to.intersection(unwrap_geom(area)), height) (area, height) for area, height in ((crop_to.intersection(unwrap_geom(area)), height)
for area, height in old_geoms.heightareas) for area, height in single_geoms.heightareas)
if not area.is_empty if not area.is_empty
) )
for access_restriction, area in old_geoms.access_restriction_affected.items(): for access_restriction, area in single_geoms.access_restriction_affected.items():
new_area = crop_to.intersection(area) new_area = crop_to.intersection(area)
if not new_area.is_empty: if not new_area.is_empty:
access_restriction_affected.setdefault(access_restriction, []).append(new_area) access_restriction_affected.setdefault(access_restriction, []).append(new_area)
new_restricted_spaces_indoors = {} new_restricted_spaces_indoors = {}
for access_restriction, area in old_geoms.restricted_spaces_indoors.items(): for access_restriction, area in single_geoms.restricted_spaces_indoors.items():
new_area = crop_to.intersection(area) new_area = crop_to.intersection(area)
if not new_area.is_empty: if not new_area.is_empty:
new_restricted_spaces_indoors[access_restriction] = new_area new_restricted_spaces_indoors[access_restriction] = new_area
new_restricted_spaces_outdoors = {} new_restricted_spaces_outdoors = {}
for access_restriction, area in old_geoms.restricted_spaces_outdoors.items(): for access_restriction, area in single_geoms.restricted_spaces_outdoors.items():
new_area = crop_to.intersection(area) new_area = crop_to.intersection(area)
if not new_area.is_empty: if not new_area.is_empty:
new_restricted_spaces_outdoors[access_restriction] = new_area new_restricted_spaces_outdoors[access_restriction] = new_area
new_geoms = LevelGeometries( composite_geoms = CompositeLevelGeometries(
pk=old_geoms.pk, pk=single_geoms.pk,
on_top_of_id=old_geoms.on_top_of_id, on_top_of_id=single_geoms.on_top_of_id,
short_label=old_geoms.short_label, short_label=single_geoms.short_label,
base_altitude=old_geoms.base_altitude, base_altitude=single_geoms.base_altitude,
default_height=old_geoms.default_height, default_height=single_geoms.default_height,
door_height=old_geoms.door_height, door_height=single_geoms.door_height,
min_altitude=(min(area.min_altitude for area in new_altitudeareas) min_altitude=(min(area.min_altitude for area in new_altitudeareas)
if new_altitudeareas else old_geoms.base_altitude), if new_altitudeareas else single_geoms.base_altitude),
max_altitude=(max(area.max_altitude for area in new_altitudeareas) max_altitude=(max(area.max_altitude for area in new_altitudeareas)
if new_altitudeareas else old_geoms.base_altitude), if new_altitudeareas else single_geoms.base_altitude),
max_height=(min(height for area, height in new_heightareas) max_height=(min(height for area, height in new_heightareas)
if new_heightareas else old_geoms.default_height), if new_heightareas else single_geoms.default_height),
lower_bound=old_geoms.lower_bound, lower_bound=single_geoms.lower_bound,
upper_bound=old_geoms.upper_bound, upper_bound=upper_bounds[single_geoms.pk],
heightareas=new_heightareas, heightareas=new_heightareas,
altitudeareas=new_altitudeareas, altitudeareas=new_altitudeareas,
@ -317,7 +318,7 @@ class LevelRenderData:
restricted_spaces_outdoors=new_restricted_spaces_outdoors, restricted_spaces_outdoors=new_restricted_spaces_outdoors,
ramps=tuple( ramps=tuple(
ramp for ramp in (crop_to.intersection(unwrap_geom(ramp)) for ramp in old_geoms.ramps) ramp for ramp in (crop_to.intersection(unwrap_geom(ramp)) for ramp in single_geoms.ramps)
if not ramp.is_empty if not ramp.is_empty
), ),
@ -327,7 +328,6 @@ class LevelRenderData:
*((new_holes_geoms.buffer(1),) if new_holes_geoms else ()), *((new_holes_geoms.buffer(1),) if new_holes_geoms else ()),
)), )),
access_restriction_affected=None,
doors_extended=None, doors_extended=None,
faces=None, faces=None,
vertices=None, vertices=None,
@ -336,9 +336,9 @@ class LevelRenderData:
walls_extended=None, walls_extended=None,
) )
new_geoms.build_mesh(interpolators.get(render_level.pk) if level.pk == render_level.pk else None) composite_geoms.build_mesh(interpolators.get(render_level.pk) if level.pk == render_level.pk else None)
render_data.levels.append(new_geoms) render_data.levels.append(composite_geoms)
access_restriction_affected = { access_restriction_affected = {
access_restriction: unary_union(areas) access_restriction: unary_union(areas)