HybridGeometry.difference() aka access restrictions in map rendering
This commit is contained in:
parent
b8709b3a98
commit
c365debfba
2 changed files with 72 additions and 30 deletions
|
@ -2,11 +2,13 @@ import operator
|
||||||
import pickle
|
import pickle
|
||||||
import threading
|
import threading
|
||||||
from collections import Counter, deque
|
from collections import Counter, deque
|
||||||
|
from functools import reduce
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from scipy.interpolate import NearestNDInterpolator
|
from scipy.interpolate import NearestNDInterpolator
|
||||||
|
from shapely import prepared
|
||||||
from shapely.geometry import GeometryCollection, LineString, MultiLineString
|
from shapely.geometry import GeometryCollection, LineString, MultiLineString
|
||||||
from shapely.ops import unary_union
|
from shapely.ops import unary_union
|
||||||
|
|
||||||
|
@ -22,16 +24,24 @@ def hybrid_union(geoms):
|
||||||
return HybridGeometry(GeometryCollection(), ())
|
return HybridGeometry(GeometryCollection(), ())
|
||||||
if len(geoms) == 1:
|
if len(geoms) == 1:
|
||||||
return geoms[0]
|
return geoms[0]
|
||||||
return HybridGeometry(unary_union(tuple(geom.geom for geom in geoms)),
|
add_faces = {}
|
||||||
tuple(chain(*(geom.faces for geom in geoms))))
|
for other in geoms:
|
||||||
|
for crop_id, faces in other.add_faces.items():
|
||||||
|
add_faces[crop_id] = add_faces.get(crop_id, ()) + faces
|
||||||
|
return HybridGeometry(geom=unary_union(tuple(geom.geom for geom in geoms)),
|
||||||
|
faces=tuple(chain(*(geom.faces for geom in geoms))),
|
||||||
|
add_faces=add_faces,
|
||||||
|
crop_ids=reduce(operator.or_, (other.crop_ids for other in geoms), set()))
|
||||||
|
|
||||||
|
|
||||||
class HybridGeometry:
|
class HybridGeometry:
|
||||||
__slots__ = ('geom', 'faces')
|
__slots__ = ('geom', 'faces', 'crop_ids', 'add_faces')
|
||||||
|
|
||||||
def __init__(self, geom, faces):
|
def __init__(self, geom, faces, crop_ids=frozenset(), add_faces=None):
|
||||||
self.geom = geom
|
self.geom = geom
|
||||||
self.faces = faces
|
self.faces = faces
|
||||||
|
self.add_faces = add_faces or {}
|
||||||
|
self.crop_ids = crop_ids
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, geom, face_centers):
|
def create(cls, geom, face_centers):
|
||||||
|
@ -43,15 +53,33 @@ class HybridGeometry:
|
||||||
)
|
)
|
||||||
return HybridGeometry(geom, tuple(f for f in faces if f))
|
return HybridGeometry(geom, tuple(f for f in faces if f))
|
||||||
|
|
||||||
def union(self, geom):
|
def union(self, other):
|
||||||
return HybridGeometry(self.geom.union(geom.geom), self.faces+geom.faces)
|
add_faces = self.add_faces
|
||||||
|
for crop_id, faces in other.add_faces.items():
|
||||||
|
add_faces[crop_id] = add_faces.get(crop_id, ())+faces
|
||||||
|
return HybridGeometry(geom=self.geom.union(other.geom), faces=self.faces+other.faces, add_faces=add_faces,
|
||||||
|
crop_ids=self.crop_ids | other.crop_ids)
|
||||||
|
|
||||||
def difference(self, geom):
|
def difference(self, other):
|
||||||
return HybridGeometry(self.geom.difference(geom.geom), self.faces)
|
return HybridGeometry(geom=self.geom.difference(other.geom), faces=self.faces,
|
||||||
|
add_faces={crop_id: faces for crop_id, faces in self.add_faces.items()
|
||||||
|
if crop_id not in other.crop_ids},
|
||||||
|
crop_ids=self.crop_ids - other.crop_ids)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_empty(self):
|
def is_empty(self):
|
||||||
return not self.faces
|
return not self.faces and not any(self.add_faces.values())
|
||||||
|
|
||||||
|
def build_polyhedron(self, create_polyhedron, bottom, top, crops=None):
|
||||||
|
remaining_faces = self.faces
|
||||||
|
for crop, prep in crops or ():
|
||||||
|
if prep.intersects(self.geom):
|
||||||
|
crop_faces = set(chain(*crop.faces))
|
||||||
|
crop_id = tuple(crop.crop_ids)[0]
|
||||||
|
self.add_faces[crop_id] = create_polyhedron(tuple((faces & crop_faces) for faces in self.faces),
|
||||||
|
bottom=bottom, top=top)
|
||||||
|
remaining_faces = tuple((faces - crop_faces) for faces in self.faces)
|
||||||
|
self.faces = create_polyhedron(remaining_faces, bottom=bottom, top=top)
|
||||||
|
|
||||||
|
|
||||||
class AltitudeAreaGeometries:
|
class AltitudeAreaGeometries:
|
||||||
|
@ -72,11 +100,11 @@ class AltitudeAreaGeometries:
|
||||||
self.colors = {color: {key: HybridGeometry.create(geom, face_centers) for key, geom in areas.items()}
|
self.colors = {color: {key: HybridGeometry.create(geom, face_centers) for key, geom in areas.items()}
|
||||||
for color, areas in self.colors.items()}
|
for color, areas in self.colors.items()}
|
||||||
|
|
||||||
def create_polyhedrons(self, create_polyhedron):
|
def create_polyhedrons(self, create_polyhedron, crops):
|
||||||
altitude = float(self.altitude)
|
altitude = float(self.altitude)
|
||||||
self.geometry.faces = create_polyhedron(self.geometry, bottom=altitude-0.7, top=altitude)
|
self.geometry.build_polyhedron(create_polyhedron, bottom=altitude-0.7, top=altitude, crops=crops)
|
||||||
for geometry in chain(*(areas.values() for areas in self.colors.values())):
|
for geometry in chain(*(areas.values() for areas in self.colors.values())):
|
||||||
geometry.faces = create_polyhedron(geometry, bottom=altitude-0.1, top=0.001)
|
geometry.build_polyhedron(create_polyhedron, bottom=altitude-0.1, top=0.001, crops=crops)
|
||||||
|
|
||||||
|
|
||||||
class FakeCropper:
|
class FakeCropper:
|
||||||
|
@ -373,13 +401,15 @@ class LevelGeometries:
|
||||||
|
|
||||||
return vertex_values
|
return vertex_values
|
||||||
|
|
||||||
def _create_polyhedron(self, geometry, bottom=None, top=None):
|
def _create_polyhedron(self, faces, bottom=None, top=None, crops=None):
|
||||||
if geometry.is_empty:
|
if not any(faces):
|
||||||
return
|
return
|
||||||
|
|
||||||
# collect rings/boundaries
|
# collect rings/boundaries
|
||||||
boundaries = deque()
|
boundaries = deque()
|
||||||
for subfaces in geometry.faces:
|
for subfaces in faces:
|
||||||
|
if not subfaces:
|
||||||
|
continue
|
||||||
subfaces = self.faces[np.array(tuple(subfaces))]
|
subfaces = self.faces[np.array(tuple(subfaces))]
|
||||||
segments = subfaces[:, (0, 1, 1, 2, 2, 0)].reshape((-1, 2))
|
segments = subfaces[:, (0, 1, 1, 2, 2, 0)].reshape((-1, 2))
|
||||||
edges = set(edge for edge, num in Counter(tuple(a) for a in np.sort(segments, axis=1)).items() if num == 1)
|
edges = set(edge for edge, num in Counter(tuple(a) for a in np.sort(segments, axis=1)).items() if num == 1)
|
||||||
|
@ -396,8 +426,8 @@ class LevelGeometries:
|
||||||
boundaries.append(tuple(zip(chain((new_ring[-1], ), new_ring), new_ring)))
|
boundaries.append(tuple(zip(chain((new_ring[-1], ), new_ring), new_ring)))
|
||||||
boundaries = np.vstack(boundaries)
|
boundaries = np.vstack(boundaries)
|
||||||
|
|
||||||
faces = deque()
|
new_faces = deque()
|
||||||
geom_faces = self.faces[np.array(tuple(chain(*geometry.faces)))]
|
geom_faces = self.faces[np.array(tuple(chain(*faces)))]
|
||||||
|
|
||||||
if not isinstance(top, np.ndarray):
|
if not isinstance(top, np.ndarray):
|
||||||
top = np.full(self.vertices.shape[0], fill_value=top)
|
top = np.full(self.vertices.shape[0], fill_value=top)
|
||||||
|
@ -406,20 +436,20 @@ class LevelGeometries:
|
||||||
bottom = np.full(self.vertices.shape[0], fill_value=bottom)
|
bottom = np.full(self.vertices.shape[0], fill_value=bottom)
|
||||||
|
|
||||||
# upper faces
|
# upper faces
|
||||||
faces.append(np.dstack((self.vertices[geom_faces], top[geom_faces])))
|
new_faces.append(np.dstack((self.vertices[geom_faces], top[geom_faces])))
|
||||||
|
|
||||||
# side faces (upper)
|
# side faces (upper)
|
||||||
faces.append(np.dstack((self.vertices[boundaries[:, (1, 0, 0)]],
|
new_faces.append(np.dstack((self.vertices[boundaries[:, (1, 0, 0)]],
|
||||||
np.hstack((top[boundaries[:, (1, 0)]], bottom[boundaries[:, (0,)]])))))
|
np.hstack((top[boundaries[:, (1, 0)]], bottom[boundaries[:, (0,)]])))))
|
||||||
|
|
||||||
# side faces (lower)
|
# side faces (lower)
|
||||||
faces.append(np.dstack((self.vertices[boundaries[:, (0, 1, 1)]],
|
new_faces.append(np.dstack((self.vertices[boundaries[:, (0, 1, 1)]],
|
||||||
np.hstack((bottom[boundaries[:, (0, 1)]], top[boundaries[:, (1,)]])))))
|
np.hstack((bottom[boundaries[:, (0, 1)]], top[boundaries[:, (1,)]])))))
|
||||||
|
|
||||||
# lower faces
|
# lower faces
|
||||||
faces.append(np.dstack((self.vertices[np.flip(geom_faces, axis=1)], bottom[geom_faces])))
|
new_faces.append(np.dstack((self.vertices[np.flip(geom_faces, axis=1)], bottom[geom_faces])))
|
||||||
|
|
||||||
return (np.vstack(faces), )
|
return (np.vstack(new_faces), )
|
||||||
|
|
||||||
def build_mesh(self):
|
def build_mesh(self):
|
||||||
rings = tuple(chain(*(get_rings(geom) for geom in self.get_geometries())))
|
rings = tuple(chain(*(get_rings(geom) for geom in self.get_geometries())))
|
||||||
|
@ -434,15 +464,25 @@ class LevelGeometries:
|
||||||
vertex_wall_heights = vertex_altitudes + vertex_heights
|
vertex_wall_heights = vertex_altitudes + vertex_heights
|
||||||
|
|
||||||
# create polyhedrons
|
# create polyhedrons
|
||||||
self.walls.faces = self._create_polyhedron(self.walls, bottom=vertex_altitudes, top=vertex_wall_heights)
|
self.walls.build_polyhedron(self._create_polyhedron, bottom=vertex_altitudes, top=vertex_wall_heights)
|
||||||
self.doors.faces = self._create_polyhedron(self.doors, bottom=vertex_wall_heights-1, top=vertex_wall_heights)
|
|
||||||
for key, geometry in self.restricted_spaces_indoors.items():
|
for key, geometry in self.restricted_spaces_indoors.items():
|
||||||
geometry.faces = self._create_polyhedron(geometry, bottom=vertex_altitudes, top=vertex_wall_heights)
|
geometry.crop_ids = frozenset(('in:%s' % key, ))
|
||||||
for key, geometry in self.restricted_spaces_outdoors.items():
|
for key, geometry in self.restricted_spaces_outdoors.items():
|
||||||
geometry.faces = None
|
geometry.crop_ids = frozenset(('out:%s' % key, ))
|
||||||
|
crops = tuple((crop, prepared.prep(crop.geom)) for crop in chain(self.restricted_spaces_indoors.values(),
|
||||||
|
self.restricted_spaces_outdoors.values()))
|
||||||
|
|
||||||
|
self.doors.build_polyhedron(self._create_polyhedron, crops=crops,
|
||||||
|
bottom=vertex_wall_heights-1, top=vertex_wall_heights)
|
||||||
|
|
||||||
for area in self.altitudeareas:
|
for area in self.altitudeareas:
|
||||||
area.create_polyhedrons(self._create_polyhedron)
|
area.create_polyhedrons(self._create_polyhedron, crops=crops)
|
||||||
|
|
||||||
|
for key, geometry in self.restricted_spaces_indoors.items():
|
||||||
|
geometry.build_polyhedron(self._create_polyhedron, bottom=vertex_altitudes, top=vertex_wall_heights)
|
||||||
|
for key, geometry in self.restricted_spaces_outdoors.items():
|
||||||
|
geometry.faces = None
|
||||||
|
|
||||||
"""
|
"""
|
||||||
for area in self.altitudeareas:
|
for area in self.altitudeareas:
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from c3nav.mapdata.render.data import HybridGeometry
|
from c3nav.mapdata.render.data import HybridGeometry
|
||||||
|
@ -27,6 +29,6 @@ class Base3DEngine(RenderEngine):
|
||||||
return vertices
|
return vertices
|
||||||
|
|
||||||
def _place_geometry(self, geometry: HybridGeometry, append=None):
|
def _place_geometry(self, geometry: HybridGeometry, append=None):
|
||||||
faces = geometry.faces[0] if len(geometry.faces) == 1 else np.vstack(geometry.faces)
|
faces = np.vstack(tuple(chain(geometry.faces, *geometry.add_faces.values())))
|
||||||
vertices = faces.reshape(-1, 3) * self.np_scale + self.np_offset
|
vertices = faces.reshape(-1, 3) * self.np_scale + self.np_offset
|
||||||
return self._append_to_vertices(vertices, append).flatten()
|
return self._append_to_vertices(vertices, append).flatten()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue