refactor graph building
This commit is contained in:
parent
6cea5949b3
commit
2863b9da8e
5 changed files with 100 additions and 36 deletions
|
@ -59,7 +59,7 @@ class Graph():
|
|||
def name_or_none(obj):
|
||||
return None if obj is None else obj.level.name
|
||||
|
||||
rooms = tuple((room.level.level.name, room.geometry, room.mpl_paths) for room in self.rooms)
|
||||
rooms = tuple((room.level.level.name, room.geometry, room.mpl_clear) for room in self.rooms)
|
||||
points = tuple((point.x, point.y, i_or_none(point.room), name_or_none(point.level)) for point in self.points)
|
||||
connections = tuple((conn.from_point.i, conn.to_point.i, conn.distance) for conn in self.connections)
|
||||
|
||||
|
|
|
@ -25,13 +25,14 @@ class GraphLevel():
|
|||
print('%d rooms' % len(self.rooms))
|
||||
|
||||
for room in self.rooms:
|
||||
room.create_points()
|
||||
room.prepare_build()
|
||||
room.build_points()
|
||||
|
||||
self.create_doors()
|
||||
self.create_levelconnectors()
|
||||
|
||||
for room in self.rooms:
|
||||
room.connect_points()
|
||||
room.build_connections()
|
||||
|
||||
print('%d points' % len(self.points))
|
||||
print('%d room transfer points' % len(self.no_room_points))
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import numpy as np
|
||||
from django.conf import settings
|
||||
from django.utils.functional import cached_property
|
||||
from matplotlib.path import Path
|
||||
|
||||
|
||||
class GraphPoint():
|
||||
|
@ -33,5 +34,8 @@ class GraphPoint():
|
|||
y = self.y * settings.RENDER_SCALE
|
||||
return ((x-5, y-5), (x+5, y+5))
|
||||
|
||||
def path_to(self, to_point):
|
||||
return Path(np.vstack((self.xy, to_point.xy)))
|
||||
|
||||
def connect_to(self, to_point):
|
||||
self.graph.add_connection(self, to_point)
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
from itertools import combinations, permutations
|
||||
|
||||
import numpy as np
|
||||
from matplotlib.path import Path
|
||||
from shapely.geometry import JOIN_STYLE, LineString
|
||||
from shapely.geometry import CAP_STYLE, JOIN_STYLE, LineString
|
||||
|
||||
from c3nav.mapdata.utils.geometry import assert_multipolygon
|
||||
from c3nav.routing.point import GraphPoint
|
||||
from c3nav.routing.router import Router
|
||||
from c3nav.routing.utils.coords import get_coords_angles
|
||||
from c3nav.routing.utils.mpl import polygon_to_mpl_paths
|
||||
from c3nav.routing.utils.mpl import shapely_to_mpl
|
||||
|
||||
|
||||
class GraphRoom():
|
||||
def __init__(self, level, geometry, mpl_paths=None):
|
||||
def __init__(self, level, geometry, mpl_clear=None):
|
||||
self.level = level
|
||||
self.graph = level.graph
|
||||
|
||||
|
@ -28,12 +26,12 @@ class GraphRoom():
|
|||
self.level.rooms.append(self)
|
||||
self.graph.rooms.append(self)
|
||||
|
||||
if mpl_paths is not None:
|
||||
self.mpl_paths = mpl_paths
|
||||
elif not self.empty:
|
||||
self.mpl_paths = polygon_to_mpl_paths(self.clear_geometry.buffer(0.01, join_style=JOIN_STYLE.mitre))
|
||||
self.mpl_clear = mpl_clear
|
||||
|
||||
def create_points(self):
|
||||
def prepare_build(self):
|
||||
self.mpl_clear = shapely_to_mpl(self.clear_geometry.buffer(0.01, join_style=JOIN_STYLE.mitre))
|
||||
|
||||
def build_points(self):
|
||||
original_geometry = self.geometry
|
||||
geometry = original_geometry.buffer(-0.6, join_style=JOIN_STYLE.mitre)
|
||||
|
||||
|
@ -62,7 +60,7 @@ class GraphRoom():
|
|||
# overlaps to non-missing areas
|
||||
overlaps = assert_multipolygon(overlaps)
|
||||
for overlap in overlaps:
|
||||
points.append(self.add_point(overlap.centroid.coords[0]))
|
||||
points += self.add_point(overlap.centroid.coords[0])
|
||||
|
||||
points += self._add_ring(polygon.exterior, want_left=False)
|
||||
|
||||
|
@ -72,9 +70,15 @@ class GraphRoom():
|
|||
for from_point, to_point in permutations(points, 2):
|
||||
from_point.connect_to(to_point)
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
def build_router(self):
|
||||
self.router.build(self.points)
|
||||
# points around steps
|
||||
stairs_areas = self.level.level.geometries.stairs
|
||||
stairs_areas = stairs_areas.buffer(0.3, join_style=JOIN_STYLE.mitre, cap_style=CAP_STYLE.flat)
|
||||
stairs_areas = assert_multipolygon(stairs_areas.intersection(self.geometry))
|
||||
for polygon in stairs_areas:
|
||||
self._add_ring(polygon.exterior, want_left=True)
|
||||
|
||||
for interior in polygon.interiors:
|
||||
self._add_ring(interior, want_left=False)
|
||||
|
||||
def _add_ring(self, geom, want_left):
|
||||
"""
|
||||
|
@ -106,21 +110,23 @@ class GraphRoom():
|
|||
|
||||
points = []
|
||||
for coord in coords:
|
||||
points.append(self.add_point(coord))
|
||||
points += self.add_point(coord)
|
||||
|
||||
return points
|
||||
|
||||
def add_point(self, coord):
|
||||
if not self.mpl_clear.contains_point(coord):
|
||||
return []
|
||||
point = GraphPoint(coord[0], coord[1], self)
|
||||
return point
|
||||
return [point]
|
||||
|
||||
def connect_points(self):
|
||||
room_paths = self.mpl_paths
|
||||
def build_connections(self):
|
||||
for point1, point2 in combinations(self.points, 2):
|
||||
path = Path(np.vstack((point1.xy, point2.xy)))
|
||||
for room_path in room_paths:
|
||||
if room_path.intersects_path(path, False):
|
||||
break
|
||||
else:
|
||||
point1.connect_to(point2)
|
||||
point2.connect_to(point1)
|
||||
path = point1.path_to(point2)
|
||||
if self.mpl_clear.intersects_path(path):
|
||||
continue
|
||||
point1.connect_to(point2)
|
||||
point2.connect_to(point1)
|
||||
|
||||
def build_router(self):
|
||||
self.router.build(self.points)
|
||||
|
|
|
@ -1,20 +1,73 @@
|
|||
from abc import ABC, abstractmethod
|
||||
|
||||
from matplotlib.path import Path
|
||||
from shapely.geometry import MultiPolygon, Polygon
|
||||
|
||||
from c3nav.mapdata.utils.geometry import assert_multipolygon
|
||||
|
||||
|
||||
def polygon_to_mpl_paths(polygon):
|
||||
class MplPathProxy(ABC):
|
||||
@abstractmethod
|
||||
def intersects_path(self, path):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def contains_point(self, point):
|
||||
pass
|
||||
|
||||
|
||||
class MplMultipolygonPath(MplPathProxy):
|
||||
def __init__(self, polygon):
|
||||
self.polygons = [MplPolygonPath(polygon) for polygon in assert_multipolygon(polygon)]
|
||||
|
||||
def intersects_path(self, path):
|
||||
for polygon in self.polygons:
|
||||
if polygon.intersects_path(path):
|
||||
return True
|
||||
return False
|
||||
|
||||
def contains_point(self, point):
|
||||
for polygon in self.polygons:
|
||||
if polygon.contains_point(point):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class MplPolygonPath(MplPathProxy):
|
||||
def __init__(self, polygon):
|
||||
self.exterior = linearring_to_mpl_path(polygon.exterior)
|
||||
self.interiors = [linearring_to_mpl_path(interior) for interior in polygon.interiors]
|
||||
|
||||
def intersects_path(self, path):
|
||||
if self.exterior.intersects_path(path, filled=False):
|
||||
return True
|
||||
|
||||
for interior in self.interiors:
|
||||
if interior.intersects_path(path, filled=False):
|
||||
return True
|
||||
return False
|
||||
|
||||
def contains_point(self, point):
|
||||
if not self.exterior.contains_point(point):
|
||||
return False
|
||||
|
||||
for interior in self.interiors:
|
||||
if interior.contains_point(point):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def shapely_to_mpl(geometry):
|
||||
"""
|
||||
convert a shapely Polygon or Multipolygon to a matplotlib Path
|
||||
:param polygon: shapely Polygon or Multipolygon
|
||||
:return: matplotlib Path
|
||||
:return: MplPathProxy
|
||||
"""
|
||||
paths = []
|
||||
for polygon in assert_multipolygon(polygon):
|
||||
paths.append(linearring_to_mpl_path(polygon.exterior))
|
||||
for interior in polygon.interiors:
|
||||
paths.append(linearring_to_mpl_path(interior))
|
||||
return paths
|
||||
if isinstance(geometry, Polygon):
|
||||
return MplPolygonPath(geometry)
|
||||
elif isinstance(geometry, MultiPolygon):
|
||||
return MplMultipolygonPath(geometry)
|
||||
raise TypeError
|
||||
|
||||
|
||||
def linearring_to_mpl_path(linearring):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue