split LevelGeometries into its two subtypes
This commit is contained in:
parent
9f264d1f59
commit
c5b2ed9f4b
3 changed files with 68 additions and 64 deletions
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue