buildgraph: new internal data handling, new serialization

This commit is contained in:
Laura Klünder 2016-12-13 23:00:38 +01:00
parent 1f1b28dc99
commit 06ad6a6e31
6 changed files with 159 additions and 91 deletions

View file

@ -7,17 +7,27 @@ from c3nav.routing.utils.coords import coord_angle
class GraphArea():
def __init__(self, room, mpl_clear, mpl_stairs):
def __init__(self, room, mpl_clear, mpl_stairs, points=None):
self.room = room
self.graph = room.graph
self.mpl_clear = mpl_clear
self.mpl_stairs = mpl_stairs
self.points = []
self.points = points
def serialize(self):
return (
self.mpl_clear,
self.mpl_stairs,
self.points,
)
def prepare_build(self):
self._built_points = []
def build_connections(self):
for point1, point2 in combinations(self.points, 2):
for point1, point2 in combinations(self._built_points, 2):
path = Path(np.vstack((point1.xy, point2.xy)))
# lies within room
@ -40,11 +50,14 @@ class GraphArea():
if not valid:
continue
self.graph.add_connection(point1, point2)
self.graph.add_connection(point2, point1)
point1.connect_to(point2)
point2.connect_to(point1)
def add_point(self, point):
if not self.mpl_clear.contains_point(point.xy):
return False
self.points.append(point)
self._built_points.append(point)
return True
def finish_build(self):
self.points = np.array(tuple(point.i for point in self._built_points))

View file

@ -2,14 +2,10 @@ import numpy as np
class GraphConnection():
def __init__(self, graph, from_point, to_point, distance=None):
self.graph = graph
def __init__(self, from_point, to_point, distance=None):
self.from_point = from_point
self.to_point = to_point
self.distance = distance if distance is not None else abs(np.linalg.norm(from_point.xy - to_point.xy))
if to_point in from_point.connections:
self.graph.connections.remove(from_point.connections[to_point])
from_point.connections[to_point] = self
to_point.connections_in[from_point] = self
def serialize(self):
return (self.distance, )

View file

@ -2,14 +2,13 @@ import os
import pickle
from collections import OrderedDict
import numpy as np
from django.conf import settings
from c3nav.mapdata.models import Level
from c3nav.mapdata.models.geometry import LevelConnector
from c3nav.routing.connection import GraphConnection
from c3nav.routing.level import GraphLevel
from c3nav.routing.point import GraphPoint
from c3nav.routing.room import GraphRoom
class Graph:
@ -20,60 +19,61 @@ class Graph:
for level in Level.objects.all():
self.levels[level.name] = GraphLevel(self, level)
self.rooms = []
self.points = []
self.connections = []
self.level_transfer_points = []
self.levelconnector_points = {}
self.level_transfer_points = None
# Building the Graph
def build(self):
self._built_level_transfer_points = []
self._built_levelconnector_points = {}
for level in self.levels.values():
level.build()
# collect rooms and points
self.rooms = sum((level.rooms for level in self.levels.values()), [])
self.points = sum((level.points for level in self.levels.values()), [])
rooms = sum((level.rooms for level in self.levels.values()), [])
self.points = sum((level._built_points for level in self.levels.values()), [])
# create connections between levels
print()
self.connect_levelconnectors()
# convert everything to tuples
self.rooms = tuple(self.rooms)
# finishing build: creating numpy arrays and convert everything else to tuples
self.points = tuple(self.points)
self.connections = tuple(self.connections)
# give numbers to rooms and points
for i, room in enumerate(self.rooms):
for i, room in enumerate(rooms):
room.i = i
for i, point in enumerate(self.points):
point.i = i
self.level_transfer_points = np.array(tuple(point.i for point in self._built_level_transfer_points))
for level in self.levels.values():
level.finish_build()
print()
print('Total:')
print('%d points' % len(self.points))
print('%d rooms' % len(self.rooms))
print('%d level transfer points' % len(self.level_transfer_points))
print('%d connections' % len(self.connections))
self.print_stats()
print()
print('Points per room:')
for name, level in self.levels.items():
print(('Level %s:' % name), *(sorted((len(room.points) for room in level.rooms), reverse=True)))
def add_connection(self, from_point, to_point, distance=None):
self.connections.append(GraphConnection(self, from_point, to_point, distance))
def print_stats(self):
print('%d points' % len(self.points))
print('%d rooms' % sum(len(level.rooms) for level in self.levels.values()))
print('%d level transfer points' % len(self.level_transfer_points))
print('%d connections' % sum(len(point.connections) for point in self.points))
def add_levelconnector_point(self, levelconnector, point):
self.levelconnector_points.setdefault(levelconnector.name, []).append(point)
self._built_levelconnector_points.setdefault(levelconnector.name, []).append(point)
def connect_levelconnectors(self):
for levelconnector in LevelConnector.objects.all():
center = levelconnector.geometry.centroid
points = self.levelconnector_points.get(levelconnector.name, [])
points = self._built_levelconnector_points.get(levelconnector.name, [])
rooms = tuple(set(sum((point.rooms for point in points), [])))
if len(rooms) < 2:
@ -84,26 +84,27 @@ class Graph:
center_point = GraphPoint(center.x, center.y, rooms=rooms)
self.points.append(center_point)
self._built_level_transfer_points.append(center_point)
levels = tuple(set(room.level for room in rooms))
for level in levels:
level.room_transfer_points.append(center_point)
level.points.append(center_point)
level._built_room_transfer_points.append(center_point)
level._built_points.append(center_point)
for room in rooms:
room.points.append(center_point)
room._built_points.append(center_point)
for point in points:
self.add_connection(center_point, point)
self.add_connection(point, center_point)
center_point.connect_to(point)
point.connect_to(center_point)
# Loading/Saving the Graph
def serialize(self):
rooms = tuple((room.level.level.name, room.mpl_clear) for room in self.rooms)
points = tuple((point.x, point.y, tuple(room.i for room in point.rooms)) for point in self.points)
connections = tuple((conn.from_point.i, conn.to_point.i, conn.distance) for conn in self.connections)
return rooms, points, connections
return (
{name: level.serialize() for name, level in self.levels.items()},
[point.serialize() for point in self.points],
self.level_transfer_points,
)
def save(self, filename=None):
if filename is None:
@ -113,24 +114,14 @@ class Graph:
@classmethod
def unserialize(cls, data):
levels, points, level_transfer_points = data
graph = cls()
rooms, points, connections = data
graph.rooms = [GraphRoom(graph.levels[room[0]], mpl_clear=room[1]) for room in rooms]
graph.points = [GraphPoint(point[0], point[1], rooms=tuple(graph.rooms[i] for i in point[2]))
for point in points]
for point in graph.points:
for room in point.rooms:
room.points.append(point)
for name, level in graph.levels.items():
level.rooms = [room for room in graph.rooms if room.level == level]
level.points = list(set(sum((room.points for room in level.rooms), [])))
level.room_transfer_points = [point for point in level.points if len(point.rooms) > 1]
level.unserialize(levels[name])
for from_point, to_point, distance in connections:
graph.add_connection(graph.points[from_point], graph.points[to_point], distance)
graph.points = tuple(GraphPoint(*point) for point in points)
graph.level_transfer_points = level_transfer_points
return graph
@ -140,6 +131,7 @@ class Graph:
filename = cls.default_filename
with open(filename, 'rb') as f:
graph = cls.unserialize(pickle.load(f))
graph.print_stats()
return graph
# Drawing

View file

@ -1,5 +1,6 @@
import os
import numpy as np
from django.conf import settings
from PIL import Image, ImageDraw
from shapely.geometry import JOIN_STYLE
@ -15,15 +16,32 @@ class GraphLevel():
def __init__(self, graph, level):
self.graph = graph
self.level = level
self.rooms = []
self.points = []
self.room_transfer_points = []
self.rooms = []
self.room_transfer_points = None
self.level_transfer_points = None
def serialize(self):
return (
[room.serialize() for room in self.rooms],
self.points,
self.room_transfer_points,
self.level_transfer_points,
)
def unserialize(self, data):
rooms, self.points, self.room_transfer_points, self.level_transfer_points = data
self.rooms = tuple(GraphRoom.unserialize(self, room) for room in rooms)
# Building the Graph
def build(self):
print()
print('Level %s:' % self.level.name)
self._built_points = []
self._built_room_transfer_points = []
self.collect_rooms()
print('%d rooms' % len(self.rooms))
@ -34,21 +52,21 @@ class GraphLevel():
self.create_doors()
self.create_levelconnectors()
self.points = sum((room.points for room in self.rooms), [])
self.points.extend(self.room_transfer_points)
self._built_points = sum((room._built_points for room in self.rooms), [])
self._built_points.extend(self._built_room_transfer_points)
for room in self.rooms:
room.build_connections()
print('%d points' % len(self.points))
print('%d room transfer points' % len(self.room_transfer_points))
print('%d points' % len(self._built_points))
print('%d room transfer points' % len(self._built_room_transfer_points))
def collect_rooms(self):
accessibles = self.level.geometries.accessible
accessibles = assert_multipolygon(accessibles)
for geometry in accessibles:
room = GraphRoom(self, geometry)
if room.prepare_build():
room = GraphRoom(self)
if room.prepare_build(geometry):
self.rooms.append(room)
def create_doors(self):
@ -62,14 +80,13 @@ class GraphLevel():
connected_rooms = set()
points = []
for room in self.rooms:
if not polygon.intersects(room.geometry):
if not polygon.intersects(room._built_geometry):
continue
for subpolygon in assert_multipolygon(polygon.intersection(room.geometry)):
for subpolygon in assert_multipolygon(polygon.intersection(room._built_geometry)):
connected_rooms.add(room)
nearest_point = get_nearest_point(room.clear_geometry, subpolygon.centroid)
point, = room.add_point(nearest_point.coords[0])
room.points.append(point)
points.append(point)
if len(points) < 2:
@ -77,30 +94,39 @@ class GraphLevel():
continue
center_point = GraphPoint(center.x, center.y, rooms=tuple(connected_rooms))
self.room_transfer_points.append(center_point)
self._built_room_transfer_points.append(center_point)
for room in connected_rooms:
room.points.append(center_point)
room._built_points.append(center_point)
for point in points:
self.graph.add_connection(center_point, point)
self.graph.add_connection(point, center_point)
center_point.connect_to(point)
point.connect_to(center_point)
def create_levelconnectors(self):
for levelconnector in self.level.levelconnectors.all():
polygon = levelconnector.geometry
for room in self.rooms:
if not polygon.intersects(room.geometry):
if not polygon.intersects(room._built_geometry):
continue
for subpolygon in assert_multipolygon(polygon.intersection(room.geometry)):
for subpolygon in assert_multipolygon(polygon.intersection(room._built_geometry)):
point = subpolygon.centroid
if not point.within(room.clear_geometry):
point = get_nearest_point(room.clear_geometry, point)
point, = room.add_point(point.coords[0])
room.points.append(point)
room._built_points.append(point)
self.graph.add_levelconnector_point(levelconnector, point)
def finish_build(self):
self.rooms = tuple(self.rooms)
self.points = np.array(tuple(point.i for point in self._built_points))
self.room_transfer_points = np.array(tuple(point.i for point in self._built_room_transfer_points))
self.level_transfer_points = np.array(tuple(i for i in self.points if i in self.graph.level_transfer_points))
for room in self.rooms:
room.finish_build()
# Drawing
def draw_png(self, points=True, lines=True):
filename = os.path.join(settings.RENDER_ROOT, 'level-%s.base.png' % self.level.name)
@ -118,10 +144,10 @@ class GraphLevel():
for point in self.points:
draw.ellipse(_ellipse_bbox(point.x, point.y, height), (200, 0, 0))
for point in self.room_transfer_points:
for point in self._built_room_transfer_points:
draw.ellipse(_ellipse_bbox(point.x, point.y, height), (0, 0, 255))
for point in self.room_transfer_points:
for point in self._built_room_transfer_points:
for otherpoint, connection in point.connections.items():
draw.line(_line_coords(point, otherpoint, height), fill=(0, 255, 255))

View file

@ -2,6 +2,8 @@ import numpy as np
from django.conf import settings
from django.utils.functional import cached_property
from c3nav.routing.connection import GraphConnection
class GraphPoint():
def __init__(self, x, y, room=None, rooms=None):
@ -13,8 +15,19 @@ class GraphPoint():
self.connections = {}
self.connections_in = {}
def serialize(self):
return (
self.x,
self.y,
)
@cached_property
def ellipse_bbox(self):
x = self.x * settings.RENDER_SCALE
y = self.y * settings.RENDER_SCALE
return ((x-5, y-5), (x+5, y+5))
def connect_to(self, other_point):
connection = GraphConnection(self, other_point)
self.connections[other_point] = connection
other_point.connections_in[self] = connection

View file

@ -11,23 +11,41 @@ from c3nav.routing.utils.mpl import shapely_to_mpl
class GraphRoom():
def __init__(self, level, geometry=None, mpl_clear=None):
def __init__(self, level):
self.level = level
self.graph = level.graph
self.geometry = geometry
self.mpl_clear = mpl_clear
self.mpl_clear = None
self.areas = []
self.points = []
self.points = None
self.room_transfer_points = None
def serialize(self):
return (
self.mpl_clear,
[area.serialize() for area in self.areas],
self.points,
self.room_transfer_points,
)
@classmethod
def unserialize(cls, level, data):
room = cls(level)
room.mpl_clear, areas, room.points, room.room_transfer_points = data
room.areas = tuple(GraphArea(room, *area) for area in areas)
return room
# Building the Graph
def prepare_build(self):
self.clear_geometry = self.geometry.buffer(-0.3, join_style=JOIN_STYLE.mitre)
def prepare_build(self, geometry):
self._built_geometry = geometry
self.clear_geometry = self._built_geometry.buffer(-0.3, join_style=JOIN_STYLE.mitre)
if self.clear_geometry.is_empty:
return False
self._built_points = []
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):
@ -40,7 +58,7 @@ class GraphRoom():
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)
stairs_areas = stairs_areas.intersection(self._built_geometry)
self.stairs_areas = assert_multipolygon(stairs_areas)
isolated_areas = tuple(assert_multipolygon(stairs_areas.intersection(self.clear_geometry)))
@ -50,10 +68,12 @@ class GraphRoom():
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))
area = GraphArea(self, mpl_clear, mpl_stairs)
area.prepare_build()
self.areas.append(area)
def build_points(self):
narrowed_geometry = self.geometry.buffer(-0.6, join_style=JOIN_STYLE.mitre)
narrowed_geometry = self._built_geometry.buffer(-0.6, join_style=JOIN_STYLE.mitre)
geometry = narrowed_geometry.buffer(0.31, join_style=JOIN_STYLE.mitre).intersection(self.clear_geometry)
if geometry.is_empty:
@ -150,7 +170,7 @@ class GraphRoom():
if not self.mpl_clear.contains_point(coord):
return []
point = GraphPoint(coord[0], coord[1], self)
self.points.append(point)
self._built_points.append(point)
for area in self.areas:
area.add_point(point)
return [point]
@ -159,7 +179,15 @@ class GraphRoom():
for area in self.areas:
area.build_connections()
def finish_build(self):
self.areas = tuple(self.areas)
self.points = np.array(tuple(point.i for point in self._built_points))
self.room_transfer_points = np.array(tuple(i for i in self.points if i in self.level.room_transfer_points))
for area in self.areas:
area.finish_build()
# Routing
def build_router(self):
self.router = Router()
self.router.build(self.points)
self.router.build(self._built_points)