From 2863b9da8e8bd261a9e2f6da402cd0eb8a3cb69f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Thu, 8 Dec 2016 16:58:08 +0100 Subject: [PATCH] refactor graph building --- src/c3nav/routing/graph.py | 2 +- src/c3nav/routing/level.py | 5 ++- src/c3nav/routing/point.py | 4 ++ src/c3nav/routing/room.py | 56 +++++++++++++++------------ src/c3nav/routing/utils/mpl.py | 69 ++++++++++++++++++++++++++++++---- 5 files changed, 100 insertions(+), 36 deletions(-) diff --git a/src/c3nav/routing/graph.py b/src/c3nav/routing/graph.py index ca00d89b..9eddc79e 100644 --- a/src/c3nav/routing/graph.py +++ b/src/c3nav/routing/graph.py @@ -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) diff --git a/src/c3nav/routing/level.py b/src/c3nav/routing/level.py index 451683b3..730c4764 100644 --- a/src/c3nav/routing/level.py +++ b/src/c3nav/routing/level.py @@ -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)) diff --git a/src/c3nav/routing/point.py b/src/c3nav/routing/point.py index 60c7d833..4d7580aa 100644 --- a/src/c3nav/routing/point.py +++ b/src/c3nav/routing/point.py @@ -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) diff --git a/src/c3nav/routing/room.py b/src/c3nav/routing/room.py index 69f7c135..fd4c9a1e 100644 --- a/src/c3nav/routing/room.py +++ b/src/c3nav/routing/room.py @@ -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) diff --git a/src/c3nav/routing/utils/mpl.py b/src/c3nav/routing/utils/mpl.py index 6d31e836..b986647c 100644 --- a/src/c3nav/routing/utils/mpl.py +++ b/src/c3nav/routing/utils/mpl.py @@ -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):