HybridGeometry.difference() aka access restrictions in map rendering

This commit is contained in:
Laura Klünder 2017-11-09 17:29:19 +01:00
parent b8709b3a98
commit c365debfba
2 changed files with 72 additions and 30 deletions

View file

@ -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:

View file

@ -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()