buildgraph: improve point placement and use isolated areas for faster building
This commit is contained in:
parent
61f7ad0ee8
commit
f2563db8bf
4 changed files with 109 additions and 47 deletions
50
src/c3nav/routing/area.py
Normal file
50
src/c3nav/routing/area.py
Normal file
|
@ -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
|
|
@ -28,6 +28,7 @@ class GraphLevel():
|
||||||
print('%d rooms' % len(self.rooms))
|
print('%d rooms' % len(self.rooms))
|
||||||
|
|
||||||
for room in self.rooms:
|
for room in self.rooms:
|
||||||
|
room.build_areas()
|
||||||
room.build_points()
|
room.build_points()
|
||||||
|
|
||||||
self.create_doors()
|
self.create_doors()
|
||||||
|
@ -67,7 +68,7 @@ class GraphLevel():
|
||||||
for subpolygon in assert_multipolygon(polygon.intersection(room.geometry)):
|
for subpolygon in assert_multipolygon(polygon.intersection(room.geometry)):
|
||||||
connected_rooms.add(room)
|
connected_rooms.add(room)
|
||||||
nearest_point = get_nearest_point(room.clear_geometry, subpolygon.centroid)
|
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)
|
room.points.append(point)
|
||||||
points.append(point)
|
points.append(point)
|
||||||
|
|
||||||
|
@ -96,7 +97,7 @@ class GraphLevel():
|
||||||
point = subpolygon.centroid
|
point = subpolygon.centroid
|
||||||
if not point.within(room.clear_geometry):
|
if not point.within(room.clear_geometry):
|
||||||
point = get_nearest_point(room.clear_geometry, point)
|
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)
|
room.points.append(point)
|
||||||
self.graph.add_levelconnector_point(levelconnector, point)
|
self.graph.add_levelconnector_point(levelconnector, point)
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
from itertools import combinations
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from matplotlib.path import Path
|
from matplotlib.path import Path
|
||||||
from shapely.geometry import CAP_STYLE, JOIN_STYLE, LineString
|
from shapely.geometry import CAP_STYLE, JOIN_STYLE, LineString
|
||||||
|
|
||||||
from c3nav.mapdata.utils.geometry import assert_multilinestring, assert_multipolygon
|
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.point import GraphPoint
|
||||||
from c3nav.routing.router import Router
|
from c3nav.routing.router import Router
|
||||||
from c3nav.routing.utils.coords import coord_angle, get_coords_angles
|
from c3nav.routing.utils.coords import coord_angle, get_coords_angles
|
||||||
|
@ -19,6 +18,7 @@ class GraphRoom():
|
||||||
self.geometry = geometry
|
self.geometry = geometry
|
||||||
self.mpl_clear = mpl_clear
|
self.mpl_clear = mpl_clear
|
||||||
|
|
||||||
|
self.areas = []
|
||||||
self.points = []
|
self.points = []
|
||||||
|
|
||||||
# Building the Graph
|
# Building the Graph
|
||||||
|
@ -33,8 +33,25 @@ class GraphRoom():
|
||||||
for stair_line in assert_multilinestring(self.level.level.geometries.stairs):
|
for stair_line in assert_multilinestring(self.level.level.geometries.stairs):
|
||||||
coords = tuple(stair_line.coords)
|
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((Path(part), coord_angle(*part)) for part in zip(coords[:-1], coords[1:]))
|
||||||
|
|
||||||
|
self.isolated_areas = []
|
||||||
return True
|
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):
|
def build_points(self):
|
||||||
original_geometry = self.geometry
|
original_geometry = self.geometry
|
||||||
geometry = original_geometry.buffer(-0.6, join_style=JOIN_STYLE.mitre)
|
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 += self._add_ring(interior, want_left=True)
|
||||||
|
|
||||||
# points around steps
|
# points around steps
|
||||||
stairs_areas = self.level.level.geometries.stairs
|
for polygon in self.stairs_areas:
|
||||||
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 ring in (polygon.exterior, )+tuple(polygon.interiors):
|
for ring in (polygon.exterior, )+tuple(polygon.interiors):
|
||||||
for linestring in assert_multilinestring(ring.intersection(self.clear_geometry)):
|
for linestring in assert_multilinestring(ring.intersection(self.clear_geometry)):
|
||||||
coords = tuple(linestring.coords)
|
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:]):
|
for segment in zip(coords[:-1], coords[1:]):
|
||||||
path = Path(segment)
|
path = Path(segment)
|
||||||
length = abs(np.linalg.norm(path.vertices[0] - path.vertices[1]))
|
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)
|
self.add_point(coord)
|
||||||
start = 0
|
start = 1
|
||||||
|
# break
|
||||||
|
|
||||||
def _add_ring(self, geom, want_left):
|
def _add_ring(self, geom, want_left):
|
||||||
"""
|
"""
|
||||||
|
@ -126,37 +152,13 @@ class GraphRoom():
|
||||||
return []
|
return []
|
||||||
point = GraphPoint(coord[0], coord[1], self)
|
point = GraphPoint(coord[0], coord[1], self)
|
||||||
self.points.append(point)
|
self.points.append(point)
|
||||||
|
for area in self.areas:
|
||||||
|
area.add_point(point)
|
||||||
return [point]
|
return [point]
|
||||||
|
|
||||||
def build_connections(self):
|
def build_connections(self):
|
||||||
i = 0
|
for area in self.areas:
|
||||||
own_points = [point for point in self.points if point not in self.level.room_transfer_points]
|
area.build_connections()
|
||||||
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
|
|
||||||
|
|
||||||
# Routing
|
# Routing
|
||||||
def build_router(self):
|
def build_router(self):
|
||||||
|
|
|
@ -20,9 +20,9 @@ class MplMultipolygonPath(MplPathProxy):
|
||||||
def __init__(self, polygon):
|
def __init__(self, polygon):
|
||||||
self.polygons = [MplPolygonPath(polygon) for polygon in assert_multipolygon(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:
|
for polygon in self.polygons:
|
||||||
if polygon.intersects_path(path):
|
if polygon.intersects_path(path, filled=filled):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -38,14 +38,23 @@ class MplPolygonPath(MplPathProxy):
|
||||||
self.exterior = linearring_to_mpl_path(polygon.exterior)
|
self.exterior = linearring_to_mpl_path(polygon.exterior)
|
||||||
self.interiors = [linearring_to_mpl_path(interior) for interior in polygon.interiors]
|
self.interiors = [linearring_to_mpl_path(interior) for interior in polygon.interiors]
|
||||||
|
|
||||||
def intersects_path(self, path):
|
def intersects_path(self, path, filled=False):
|
||||||
if self.exterior.intersects_path(path, filled=False):
|
if filled:
|
||||||
return True
|
if not self.exterior.intersects_path(path, filled=True):
|
||||||
|
return False
|
||||||
|
|
||||||
for interior in self.interiors:
|
for interior in self.interiors:
|
||||||
if interior.intersects_path(path, filled=False):
|
if interior.contains_path(path):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
if self.exterior.intersects_path(path, filled=False):
|
||||||
return True
|
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):
|
def contains_point(self, point):
|
||||||
if not self.exterior.contains_point(point):
|
if not self.exterior.contains_point(point):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue