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.level import LevelGeometries # noqa
from c3nav.mapdata.render.geometry.level import SingleLevelGeometries, CompositeLevelGeometries # noqa
from c3nav.mapdata.render.geometry.altitudearea import AltitudeAreaGeometries # noqa

View file

@ -28,33 +28,23 @@ ZeroOrMorePolygons: typing.TypeAlias = GeometryCollection | Polygon | MultiPolyg
@dataclass
class LevelGeometries:
class BaseLevelGeometries:
"""
Store geometries for a Level.
Geometries for a Level.
"""
# todo: split into the two versions of this
buildings: ZeroOrMorePolygons
altitudeareas: list[AltitudeAreaGeometries]
heightareas: typing.Sequence[tuple[ZeroOrMorePolygons, float]]
walls: ZeroOrMorePolygons
walls_extended: None | HybridGeometry
all_walls: ZeroOrMorePolygons
short_walls: list[tuple[AltitudeArea, ZeroOrMorePolygons]] | typing.Sequence[ZeroOrMorePolygons]
doors: ZeroOrMorePolygons | None
doors_extended: HybridGeometry | None
holes: None
access_restriction_affected: dict[int, ZeroOrMorePolygons] | None
holes: ZeroOrMorePolygons | None
restricted_spaces_indoors: dict[int, ZeroOrMorePolygons]
restricted_spaces_outdoors: dict[int, ZeroOrMorePolygons]
affected_area: ZeroOrMorePolygons | None
ramps: typing.Sequence[ZeroOrMorePolygons]
vertices: None | np.ndarray
faces: None | np.ndarray
walls_base: None | HybridGeometry
walls_bottom: None | HybridGeometry
pk: int
on_top_of_id: int | None
short_label: str
@ -66,11 +56,18 @@ class LevelGeometries:
max_height: int
lower_bound: int
upper_bound: None
def __repr__(self):
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
class SpaceGeometries:
geometry: ZeroOrMorePolygons
@ -365,28 +362,35 @@ class LevelGeometries:
max_height=(min(height for area, height in heightareas_geom)
if analysis.heightareas else default_height),
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')
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
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,
(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]
faces_offset = self.faces.shape[0]
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.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.package import CachePackage
from c3nav.mapdata.utils.geometry import get_rings, unwrap_geom
@ -51,7 +51,7 @@ class LevelRenderData:
"""
base_altitude: float
lowest_important_level: int
levels: list[LevelGeometries] = field(default_factory=list)
levels: list[CompositeLevelGeometries] = field(default_factory=list)
darken_area: MultiPolygon | None = None
@staticmethod
@ -77,15 +77,15 @@ class LevelRenderData:
first pass in reverse to collect some data that we need later
"""
# 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
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 of levels on top are collected on the way down to supply to the levelgeometries builder
altitudeareas_above = [] # todo: typing
for render_level in reversed(levels):
# 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
)
@ -116,6 +116,7 @@ class LevelRenderData:
"""
second pass, forward to create the LevelRenderData for each level
"""
upper_bounds: dict[int, int] = {}
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:
@ -162,9 +163,9 @@ class LevelRenderData:
# make upper bounds
if geoms.on_top_of_id 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:
geoms.upper_bound = last_lower_bound
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
@ -193,34 +194,34 @@ class LevelRenderData:
except KeyError:
continue
old_geoms = single_level_geoms[level.pk]
single_geoms = single_level_geoms[level.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 single_geoms.holes and render_data.darken_area is None and lowest_important_level_passed:
render_data.darken_area = single_geoms.holes
if crop_to.geometry is not 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_buildings_geoms = crop_to.intersection(old_geoms.buildings)
if old_geoms.on_top_of_id is None:
new_holes_geoms = crop_to.intersection(old_geoms.holes)
new_buildings_geoms = crop_to.intersection(single_geoms.buildings)
if single_geoms.on_top_of_id is None:
new_holes_geoms = crop_to.intersection(single_geoms.holes)
else:
new_holes_geoms = None
new_doors_geoms = crop_to.intersection(old_geoms.doors)
new_walls_geoms = crop_to.intersection(old_geoms.walls)
new_all_walls_geoms = crop_to.intersection(old_geoms.all_walls)
new_doors_geoms = crop_to.intersection(single_geoms.doors)
new_walls_geoms = crop_to.intersection(single_geoms.walls)
new_all_walls_geoms = crop_to.intersection(single_geoms.all_walls)
new_short_walls_geoms = tuple((altitude, geom) for altitude, geom in tuple(
(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)
new_altitudeareas = []
for altitudearea in old_geoms.altitudeareas:
for altitudearea in single_geoms.altitudeareas:
new_geometry = crop_to.intersection(unwrap_geom(altitudearea.geometry))
if new_geometry.is_empty:
continue
@ -267,42 +268,42 @@ class LevelRenderData:
new_heightareas = tuple(
(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
)
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)
if not new_area.is_empty:
access_restriction_affected.setdefault(access_restriction, []).append(new_area)
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)
if not new_area.is_empty:
new_restricted_spaces_indoors[access_restriction] = new_area
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)
if not new_area.is_empty:
new_restricted_spaces_outdoors[access_restriction] = new_area
new_geoms = LevelGeometries(
pk=old_geoms.pk,
on_top_of_id=old_geoms.on_top_of_id,
short_label=old_geoms.short_label,
base_altitude=old_geoms.base_altitude,
default_height=old_geoms.default_height,
door_height=old_geoms.door_height,
composite_geoms = CompositeLevelGeometries(
pk=single_geoms.pk,
on_top_of_id=single_geoms.on_top_of_id,
short_label=single_geoms.short_label,
base_altitude=single_geoms.base_altitude,
default_height=single_geoms.default_height,
door_height=single_geoms.door_height,
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)
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)
if new_heightareas else old_geoms.default_height),
lower_bound=old_geoms.lower_bound,
upper_bound=old_geoms.upper_bound,
if new_heightareas else single_geoms.default_height),
lower_bound=single_geoms.lower_bound,
upper_bound=upper_bounds[single_geoms.pk],
heightareas=new_heightareas,
altitudeareas=new_altitudeareas,
@ -317,7 +318,7 @@ class LevelRenderData:
restricted_spaces_outdoors=new_restricted_spaces_outdoors,
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
),
@ -327,7 +328,6 @@ class LevelRenderData:
*((new_holes_geoms.buffer(1),) if new_holes_geoms else ()),
)),
access_restriction_affected=None,
doors_extended=None,
faces=None,
vertices=None,
@ -336,9 +336,9 @@ class LevelRenderData:
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: unary_union(areas)