From f2563db8bfb85f0a5cc63fb7f2b6b627d6aa5315 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Sat, 10 Dec 2016 14:58:53 +0100 Subject: [PATCH] buildgraph: improve point placement and use isolated areas for faster building --- src/c3nav/routing/area.py | 50 ++++++++++++++++++++++ src/c3nav/routing/level.py | 5 ++- src/c3nav/routing/room.py | 76 +++++++++++++++++----------------- src/c3nav/routing/utils/mpl.py | 25 +++++++---- 4 files changed, 109 insertions(+), 47 deletions(-) create mode 100644 src/c3nav/routing/area.py diff --git a/src/c3nav/routing/area.py b/src/c3nav/routing/area.py new file mode 100644 index 00000000..1d5c4b00 --- /dev/null +++ b/src/c3nav/routing/area.py @@ -0,0 +1,50 @@ +from itertools import combinations + +import numpy as np +from matplotlib.path import Path + +from c3nav.routing.utils.coords import coord_angle + + +class GraphArea(): + def __init__(self, room, mpl_clear, mpl_stairs): + self.room = room + self.graph = room.graph + + self.mpl_clear = mpl_clear + self.mpl_stairs = mpl_stairs + + self.points = [] + + def build_connections(self): + for point1, point2 in combinations(self.points, 2): + path = Path(np.vstack((point1.xy, point2.xy))) + + # lies within room + if self.mpl_clear.intersects_path(path): + continue + + # stair checker + angle = coord_angle(point1.xy, point2.xy) + valid = True + for stair_path, stair_angle in self.mpl_stairs: + if not path.intersects_path(stair_path): + continue + + angle_diff = ((stair_angle - angle + 180) % 360) - 180 + up = angle_diff < 0 # noqa + if not (50 < abs(angle_diff) < 130): + valid = False + break + + if not valid: + continue + + self.graph.add_connection(point1, point2) + self.graph.add_connection(point2, point1) + + def add_point(self, point): + if not self.mpl_clear.contains_point(point.xy): + return False + self.points.append(point) + return True diff --git a/src/c3nav/routing/level.py b/src/c3nav/routing/level.py index 50a80311..388307fd 100644 --- a/src/c3nav/routing/level.py +++ b/src/c3nav/routing/level.py @@ -28,6 +28,7 @@ class GraphLevel(): print('%d rooms' % len(self.rooms)) for room in self.rooms: + room.build_areas() room.build_points() self.create_doors() @@ -67,7 +68,7 @@ class GraphLevel(): for subpolygon in assert_multipolygon(polygon.intersection(room.geometry)): connected_rooms.add(room) nearest_point = get_nearest_point(room.clear_geometry, subpolygon.centroid) - point = GraphPoint(nearest_point.x, nearest_point.y, room) + point, = room.add_point(nearest_point.coords[0]) room.points.append(point) points.append(point) @@ -96,7 +97,7 @@ class GraphLevel(): point = subpolygon.centroid if not point.within(room.clear_geometry): point = get_nearest_point(room.clear_geometry, point) - point = GraphPoint(point.x, point.y, room) + point, = room.add_point(point.coords[0]) room.points.append(point) self.graph.add_levelconnector_point(levelconnector, point) diff --git a/src/c3nav/routing/room.py b/src/c3nav/routing/room.py index c2767152..a48a1a53 100644 --- a/src/c3nav/routing/room.py +++ b/src/c3nav/routing/room.py @@ -1,10 +1,9 @@ -from itertools import combinations - import numpy as np from matplotlib.path import Path from shapely.geometry import CAP_STYLE, JOIN_STYLE, LineString from c3nav.mapdata.utils.geometry import assert_multilinestring, assert_multipolygon +from c3nav.routing.area import GraphArea from c3nav.routing.point import GraphPoint from c3nav.routing.router import Router from c3nav.routing.utils.coords import coord_angle, get_coords_angles @@ -19,6 +18,7 @@ class GraphRoom(): self.geometry = geometry self.mpl_clear = mpl_clear + self.areas = [] self.points = [] # Building the Graph @@ -33,8 +33,25 @@ class GraphRoom(): for stair_line in assert_multilinestring(self.level.level.geometries.stairs): coords = tuple(stair_line.coords) self.mpl_stairs += tuple((Path(part), coord_angle(*part)) for part in zip(coords[:-1], coords[1:])) + + self.isolated_areas = [] return True + def build_areas(self): + 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 = stairs_areas.intersection(self.geometry) + self.stairs_areas = assert_multipolygon(stairs_areas) + + isolated_areas = tuple(assert_multipolygon(stairs_areas.intersection(self.clear_geometry))) + isolated_areas += tuple(assert_multipolygon(self.clear_geometry.difference(stairs_areas))) + + for isolated_area in isolated_areas: + mpl_clear = shapely_to_mpl(isolated_area.buffer(0.01, join_style=JOIN_STYLE.mitre)) + mpl_stairs = tuple((stair, angle) for stair, angle in self.mpl_stairs + if mpl_clear.intersects_path(stair, filled=True)) + self.areas.append(GraphArea(self, mpl_clear, mpl_stairs)) + def build_points(self): original_geometry = self.geometry geometry = original_geometry.buffer(-0.6, join_style=JOIN_STYLE.mitre) @@ -72,20 +89,29 @@ class GraphRoom(): points += self._add_ring(interior, want_left=True) # 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: + for polygon in self.stairs_areas: for ring in (polygon.exterior, )+tuple(polygon.interiors): for linestring in assert_multilinestring(ring.intersection(self.clear_geometry)): coords = tuple(linestring.coords) - start = 1 + if len(coords) == 2: + path = Path(coords) + length = abs(np.linalg.norm(path.vertices[0] - path.vertices[1])) + for coord in tuple(path.interpolated(int(length / 1.0 + 1)).vertices): + self.add_point(coord) + continue + + start = 0 for segment in zip(coords[:-1], coords[1:]): path = Path(segment) length = abs(np.linalg.norm(path.vertices[0] - path.vertices[1])) - for coord in tuple(path.interpolated(max(int(length / 1.0), 2)).vertices)[start:-1]: + if length < 1.0: + coords = (path.vertices[1 if start == 0 else 0], ) + else: + coords = tuple(path.interpolated(int(length / 1.0 + 0.5)).vertices)[start:] + for coord in coords: self.add_point(coord) - start = 0 + start = 1 + # break def _add_ring(self, geom, want_left): """ @@ -126,37 +152,13 @@ class GraphRoom(): return [] point = GraphPoint(coord[0], coord[1], self) self.points.append(point) + for area in self.areas: + area.add_point(point) return [point] def build_connections(self): - i = 0 - own_points = [point for point in self.points if point not in self.level.room_transfer_points] - for point1, point2 in combinations(own_points, 2): - path = Path(np.vstack((point1.xy, point2.xy))) - - # lies within room - if self.mpl_clear.intersects_path(path): - continue - - # stair checker - angle = coord_angle(point1.xy, point2.xy) - valid = True - for stair_path, stair_angle in self.mpl_stairs: - if not path.intersects_path(stair_path): - continue - - angle_diff = ((stair_angle - angle + 180) % 360) - 180 - up = angle_diff < 0 # noqa - if not (70 < abs(angle_diff) < 110): - valid = False - break - - if not valid: - continue - - self.graph.add_connection(point1, point2) - self.graph.add_connection(point2, point1) - i += 1 + for area in self.areas: + area.build_connections() # Routing def build_router(self): diff --git a/src/c3nav/routing/utils/mpl.py b/src/c3nav/routing/utils/mpl.py index b986647c..f88379a0 100644 --- a/src/c3nav/routing/utils/mpl.py +++ b/src/c3nav/routing/utils/mpl.py @@ -20,9 +20,9 @@ class MplMultipolygonPath(MplPathProxy): def __init__(self, polygon): self.polygons = [MplPolygonPath(polygon) for polygon in assert_multipolygon(polygon)] - def intersects_path(self, path): + def intersects_path(self, path, filled=False): for polygon in self.polygons: - if polygon.intersects_path(path): + if polygon.intersects_path(path, filled=filled): return True return False @@ -38,14 +38,23 @@ class MplPolygonPath(MplPathProxy): 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 + def intersects_path(self, path, filled=False): + if filled: + if not self.exterior.intersects_path(path, filled=True): + return False - for interior in self.interiors: - if interior.intersects_path(path, filled=False): + for interior in self.interiors: + if interior.contains_path(path): + return False + return True + else: + if self.exterior.intersects_path(path, filled=False): return True - return False + + 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):