From 405c688eace37a30833b16949f2665216af1b106 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Sun, 18 Dec 2016 13:28:02 +0100 Subject: [PATCH] buildgraph: add support for escalator_up and escalator_down --- src/c3nav/mapdata/models/level.py | 4 +++ src/c3nav/mapdata/utils/geometry.py | 4 +++ src/c3nav/routing/area.py | 47 ++++++++++++++++++++++++----- src/c3nav/routing/level.py | 34 ++++++++++++++++++++- src/c3nav/routing/room.py | 29 ++++++++++++------ 5 files changed, 100 insertions(+), 18 deletions(-) diff --git a/src/c3nav/mapdata/models/level.py b/src/c3nav/mapdata/models/level.py index 65ce3f8a..39f91943 100644 --- a/src/c3nav/mapdata/models/level.py +++ b/src/c3nav/mapdata/models/level.py @@ -237,6 +237,10 @@ class LevelGeometries(): def stairs(self): return cascaded_union([stair.geometry for stair in self.query('stairs')]).intersection(self.accessible) + @cached_property + def escalatorslopes(self): + return cascaded_union([s.geometry for s in self.query('escalatorslopes')]).intersection(self.accessible) + @cached_property def stair_areas(self): left = [] diff --git a/src/c3nav/mapdata/utils/geometry.py b/src/c3nav/mapdata/utils/geometry.py index bc0d92a6..a9a3ac19 100644 --- a/src/c3nav/mapdata/utils/geometry.py +++ b/src/c3nav/mapdata/utils/geometry.py @@ -25,6 +25,8 @@ def assert_multipolygon(geometry): :param geometry: a Polygon or a MultiPolygon :return: a list of Polygons """ + if geometry.is_empty: + return [] if isinstance(geometry, Polygon): return [geometry] return geometry.geoms @@ -36,6 +38,8 @@ def assert_multilinestring(geometry): :param geometry: a Geometry or a GeometryCollection :return: a list of Geometries """ + if geometry.is_empty: + return [] if isinstance(geometry, LineString): return [geometry] return geometry.geoms diff --git a/src/c3nav/routing/area.py b/src/c3nav/routing/area.py index 00c73302..5245fa04 100644 --- a/src/c3nav/routing/area.py +++ b/src/c3nav/routing/area.py @@ -7,12 +7,13 @@ from c3nav.routing.utils.coords import coord_angle class GraphArea(): - def __init__(self, room, mpl_clear, mpl_stairs, points=None): + def __init__(self, room, mpl_clear, mpl_stairs, escalators, points=None): self.room = room self.graph = room.graph self.mpl_clear = mpl_clear self.mpl_stairs = mpl_stairs + self.escalators = escalators self.points = points @@ -20,6 +21,7 @@ class GraphArea(): return ( self.mpl_clear, self.mpl_stairs, + self.escalators, self.points, ) @@ -37,7 +39,7 @@ class GraphArea(): # stair checker angle = coord_angle(point1.xy, point2.xy) valid = True - direction_up = None + stair_direction_up = None for stair_path, stair_angle in self.mpl_stairs: if not path.intersects_path(stair_path): continue @@ -45,9 +47,9 @@ class GraphArea(): angle_diff = ((stair_angle - angle + 180) % 360) - 180 new_direction_up = (angle_diff > 0) - if direction_up is None: - direction_up = new_direction_up - elif direction_up != new_direction_up: + if stair_direction_up is None: + stair_direction_up = new_direction_up + elif stair_direction_up != new_direction_up: valid = False break @@ -55,11 +57,42 @@ class GraphArea(): valid = False break + if not valid: + break + + # escalator checker + angle = coord_angle(point1.xy, point2.xy) + valid = True + escalator_direction_up = None + escalator_swap_direction = False + for escalator in self.escalators: + if not escalator.mpl_geom.intersects_path(path, filled=True): + continue + + if escalator_direction_up is not None: + # only one escalator per connection + valid = False + break + + angle_diff = ((escalator.angle - angle + 180) % 360) - 180 + + escalator_direction_up = (angle_diff > 0) + escalator_swap_direction = (escalator_direction_up != escalator.direction_up) + if not valid: continue - point1.connect_to(point2, ctype={True: 'steps_up', False: 'steps_down'}.get(direction_up, '')) - point2.connect_to(point1, ctype={True: 'steps_down', False: 'steps_up'}.get(direction_up, '')) + if stair_direction_up is not None: + point1.connect_to(point2, ctype=('steps_up' if stair_direction_up else 'steps_down')) + point2.connect_to(point1, ctype=('steps_down' if stair_direction_up else 'steps_up')) + elif escalator_direction_up is not None: + if not escalator_swap_direction: + point1.connect_to(point2, ctype=('escalator_up' if escalator_direction_up else 'escalator_down')) + else: + point2.connect_to(point1, ctype=('escalator_down' if escalator_direction_up else 'escalator_up')) + else: + point1.connect_to(point2) + point2.connect_to(point1) def add_point(self, point): if not self.mpl_clear.contains_point(point.xy): diff --git a/src/c3nav/routing/level.py b/src/c3nav/routing/level.py index 4175658e..6402063f 100644 --- a/src/c3nav/routing/level.py +++ b/src/c3nav/routing/level.py @@ -3,15 +3,17 @@ from collections import namedtuple import numpy as np from django.conf import settings +from matplotlib.path import Path from PIL import Image, ImageDraw from scipy.sparse.csgraph._shortest_path import shortest_path from scipy.sparse.csgraph._tools import csgraph_from_dense from shapely.geometry import JOIN_STYLE -from c3nav.mapdata.utils.geometry import assert_multipolygon +from c3nav.mapdata.utils.geometry import assert_multilinestring, assert_multipolygon from c3nav.routing.point import GraphPoint from c3nav.routing.room import GraphRoom from c3nav.routing.utils.base import get_nearest_point +from c3nav.routing.utils.coords import coord_angle from c3nav.routing.utils.draw import _ellipse_bbox, _line_coords from c3nav.routing.utils.mpl import shapely_to_mpl @@ -48,6 +50,9 @@ class GraphLevel(): self._built_points = [] self._built_room_transfer_points = [] + self.collect_stairs() + self.collect_escalators() + self.collect_rooms() print('%d rooms' % len(self.rooms)) @@ -74,6 +79,30 @@ class GraphLevel(): def connection_count(self): return sum(room.connection_count() for room in self.rooms) + def collect_stairs(self): + self.mpl_stairs = () + for stair_line in assert_multilinestring(self.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:])) + + def collect_escalators(self): + self.mpl_escalatorslopes = () + for escalatorslope_line in assert_multilinestring(self.level.geometries.escalatorslopes): + coords = tuple(escalatorslope_line.coords) + self.mpl_escalatorslopes += tuple((Path(part), coord_angle(*part)) + for part in zip(coords[:-1], coords[1:])) + + self._built_escalators = [] + for escalator in self.level.escalators.all(): + mpl_escalator = shapely_to_mpl(escalator.geometry) + for slope_line, angle in self.mpl_escalatorslopes: + if mpl_escalator.intersects_path(slope_line, filled=True): + self._built_escalators.append(EscalatorData(mpl_escalator, escalator.direction, slope_line, angle)) + break + else: + print('Escalator %s has no slope line!' % escalator.name) + continue + def collect_rooms(self): accessibles = self.level.geometries.accessible accessibles = assert_multipolygon(accessibles) @@ -183,6 +212,8 @@ class GraphLevel(): '': (50, 200, 0), 'steps_up': (255, 50, 50), 'steps_down': (255, 50, 50), + 'escalator_up': (255, 150, 0), + 'escalator_down': (200, 100, 0), 'elevator_up': (200, 0, 200), 'elevator_down': (200, 0, 200), } @@ -260,3 +291,4 @@ class GraphLevel(): LevelRouter = namedtuple('LevelRouter', ('shortest_paths', 'predecessors', 'room_transfers', )) +EscalatorData = namedtuple('EscalatorData', ('mpl_geom', 'direction_up', 'slope', 'angle')) diff --git a/src/c3nav/routing/room.py b/src/c3nav/routing/room.py index 84ad9c09..5f8d237a 100644 --- a/src/c3nav/routing/room.py +++ b/src/c3nav/routing/room.py @@ -6,12 +6,13 @@ from matplotlib.path import Path from scipy.sparse.csgraph._shortest_path import shortest_path from scipy.sparse.csgraph._tools import csgraph_from_dense from shapely.geometry import CAP_STYLE, JOIN_STYLE, LineString +from shapely.ops import cascaded_union from c3nav.mapdata.utils.geometry import assert_multilinestring, assert_multipolygon from c3nav.routing.area import GraphArea from c3nav.routing.connection import GraphConnection from c3nav.routing.point import GraphPoint -from c3nav.routing.utils.coords import coord_angle, get_coords_angles +from c3nav.routing.utils.coords import get_coords_angles from c3nav.routing.utils.mpl import shapely_to_mpl @@ -58,10 +59,10 @@ class GraphRoom(): self._built_is_elevatorlevel = False self.mpl_clear = shapely_to_mpl(self.clear_geometry.buffer(0.01, join_style=JOIN_STYLE.mitre)) - self.mpl_stairs = () - 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.mpl_stairs = tuple((stair, angle) for stair, angle in self.level.mpl_stairs + if self.mpl_clear.intersects_path(stair, filled=True)) + self._built_escalators = tuple(escalator for escalator in self.level._built_escalators + if self.mpl_clear.intersects_path(escalator.mpl_geom.exterior, filled=True)) self.isolated_areas = [] return True @@ -70,16 +71,25 @@ class GraphRoom(): 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._built_geometry) - self.stairs_areas = assert_multipolygon(stairs_areas) + self._built_isolated_areas = tuple(assert_multipolygon(stairs_areas)) + + escalators_areas = self.level.level.geometries.escalators + escalators_areas = escalators_areas.intersection(self._built_geometry) + self._built_isolated_areas += tuple(assert_multipolygon(escalators_areas)) + + escalators_and_stairs = cascaded_union((stairs_areas, escalators_areas)) isolated_areas = tuple(assert_multipolygon(stairs_areas.intersection(self.clear_geometry))) - isolated_areas += tuple(assert_multipolygon(self.clear_geometry.difference(stairs_areas))) + isolated_areas += tuple(assert_multipolygon(escalators_areas.intersection(self.clear_geometry))) + isolated_areas += tuple(assert_multipolygon(self.clear_geometry.difference(escalators_and_stairs))) 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)) - area = GraphArea(self, mpl_clear, mpl_stairs) + escalators = tuple(escalator for escalator in self._built_escalators + if escalator.mpl_geom.intersects_path(mpl_clear.exterior, filled=True)) + area = GraphArea(self, mpl_clear, mpl_stairs, escalators) area.prepare_build() self.areas.append(area) @@ -120,7 +130,7 @@ class GraphRoom(): points += self._add_ring(interior, want_left=True) # points around steps - for polygon in self.stairs_areas: + for polygon in self._built_isolated_areas: for ring in (polygon.exterior, )+tuple(polygon.interiors): for linestring in assert_multilinestring(ring.intersection(self.clear_geometry)): coords = tuple(linestring.coords) @@ -225,7 +235,6 @@ class GraphRoom(): # Routing def build_router(self, allowed_ctypes): ctypes = tuple(i for i, ctype in enumerate(self.ctypes) if ctype in allowed_ctypes) - print(self.ctypes) cache_key = ('c3nav__graph__roomrouter__%s__%s__%s' % (self.graph.mtime, self.i, ','.join(str(i) for i in ctypes))) roomrouter = cache.get(cache_key)