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(): class GraphArea():
def __init__(self, room, mpl_clear, mpl_stairs): def __init__(self, room, mpl_clear, mpl_stairs, points=None):
self.room = room self.room = room
self.graph = room.graph self.graph = room.graph
self.mpl_clear = mpl_clear self.mpl_clear = mpl_clear
self.mpl_stairs = mpl_stairs 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): 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))) path = Path(np.vstack((point1.xy, point2.xy)))
# lies within room # lies within room
@ -40,11 +50,14 @@ class GraphArea():
if not valid: if not valid:
continue continue
self.graph.add_connection(point1, point2) point1.connect_to(point2)
self.graph.add_connection(point2, point1) point2.connect_to(point1)
def add_point(self, point): def add_point(self, point):
if not self.mpl_clear.contains_point(point.xy): if not self.mpl_clear.contains_point(point.xy):
return False return False
self.points.append(point) self._built_points.append(point)
return True 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(): class GraphConnection():
def __init__(self, graph, from_point, to_point, distance=None): def __init__(self, from_point, to_point, distance=None):
self.graph = graph
self.from_point = from_point self.from_point = from_point
self.to_point = to_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)) 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: def serialize(self):
self.graph.connections.remove(from_point.connections[to_point]) return (self.distance, )
from_point.connections[to_point] = self
to_point.connections_in[from_point] = self

View file

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

View file

@ -1,5 +1,6 @@
import os import os
import numpy as np
from django.conf import settings from django.conf import settings
from PIL import Image, ImageDraw from PIL import Image, ImageDraw
from shapely.geometry import JOIN_STYLE from shapely.geometry import JOIN_STYLE
@ -15,15 +16,32 @@ class GraphLevel():
def __init__(self, graph, level): def __init__(self, graph, level):
self.graph = graph self.graph = graph
self.level = level self.level = level
self.rooms = []
self.points = [] self.points = []
self.room_transfer_points = [] self.room_transfer_points = None
self.rooms = [] 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 # Building the Graph
def build(self): def build(self):
print() print()
print('Level %s:' % self.level.name) print('Level %s:' % self.level.name)
self._built_points = []
self._built_room_transfer_points = []
self.collect_rooms() self.collect_rooms()
print('%d rooms' % len(self.rooms)) print('%d rooms' % len(self.rooms))
@ -34,21 +52,21 @@ class GraphLevel():
self.create_doors() self.create_doors()
self.create_levelconnectors() self.create_levelconnectors()
self.points = sum((room.points for room in self.rooms), []) self._built_points = sum((room._built_points for room in self.rooms), [])
self.points.extend(self.room_transfer_points) self._built_points.extend(self._built_room_transfer_points)
for room in self.rooms: for room in self.rooms:
room.build_connections() room.build_connections()
print('%d points' % len(self.points)) print('%d points' % len(self._built_points))
print('%d room transfer points' % len(self.room_transfer_points)) print('%d room transfer points' % len(self._built_room_transfer_points))
def collect_rooms(self): def collect_rooms(self):
accessibles = self.level.geometries.accessible accessibles = self.level.geometries.accessible
accessibles = assert_multipolygon(accessibles) accessibles = assert_multipolygon(accessibles)
for geometry in accessibles: for geometry in accessibles:
room = GraphRoom(self, geometry) room = GraphRoom(self)
if room.prepare_build(): if room.prepare_build(geometry):
self.rooms.append(room) self.rooms.append(room)
def create_doors(self): def create_doors(self):
@ -62,14 +80,13 @@ class GraphLevel():
connected_rooms = set() connected_rooms = set()
points = [] points = []
for room in self.rooms: for room in self.rooms:
if not polygon.intersects(room.geometry): if not polygon.intersects(room._built_geometry):
continue continue
for subpolygon in assert_multipolygon(polygon.intersection(room.geometry)): for subpolygon in assert_multipolygon(polygon.intersection(room._built_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, = room.add_point(nearest_point.coords[0]) point, = room.add_point(nearest_point.coords[0])
room.points.append(point)
points.append(point) points.append(point)
if len(points) < 2: if len(points) < 2:
@ -77,30 +94,39 @@ class GraphLevel():
continue continue
center_point = GraphPoint(center.x, center.y, rooms=tuple(connected_rooms)) 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: for room in connected_rooms:
room.points.append(center_point) room._built_points.append(center_point)
for point in points: for point in points:
self.graph.add_connection(center_point, point) center_point.connect_to(point)
self.graph.add_connection(point, center_point) point.connect_to(center_point)
def create_levelconnectors(self): def create_levelconnectors(self):
for levelconnector in self.level.levelconnectors.all(): for levelconnector in self.level.levelconnectors.all():
polygon = levelconnector.geometry polygon = levelconnector.geometry
for room in self.rooms: for room in self.rooms:
if not polygon.intersects(room.geometry): if not polygon.intersects(room._built_geometry):
continue continue
for subpolygon in assert_multipolygon(polygon.intersection(room.geometry)): for subpolygon in assert_multipolygon(polygon.intersection(room._built_geometry)):
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, = room.add_point(point.coords[0]) point, = room.add_point(point.coords[0])
room.points.append(point) room._built_points.append(point)
self.graph.add_levelconnector_point(levelconnector, 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 # Drawing
def draw_png(self, points=True, lines=True): def draw_png(self, points=True, lines=True):
filename = os.path.join(settings.RENDER_ROOT, 'level-%s.base.png' % self.level.name) 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: for point in self.points:
draw.ellipse(_ellipse_bbox(point.x, point.y, height), (200, 0, 0)) 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)) 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(): for otherpoint, connection in point.connections.items():
draw.line(_line_coords(point, otherpoint, height), fill=(0, 255, 255)) 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.conf import settings
from django.utils.functional import cached_property from django.utils.functional import cached_property
from c3nav.routing.connection import GraphConnection
class GraphPoint(): class GraphPoint():
def __init__(self, x, y, room=None, rooms=None): def __init__(self, x, y, room=None, rooms=None):
@ -13,8 +15,19 @@ class GraphPoint():
self.connections = {} self.connections = {}
self.connections_in = {} self.connections_in = {}
def serialize(self):
return (
self.x,
self.y,
)
@cached_property @cached_property
def ellipse_bbox(self): def ellipse_bbox(self):
x = self.x * settings.RENDER_SCALE x = self.x * settings.RENDER_SCALE
y = self.y * settings.RENDER_SCALE y = self.y * settings.RENDER_SCALE
return ((x-5, y-5), (x+5, y+5)) 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(): class GraphRoom():
def __init__(self, level, geometry=None, mpl_clear=None): def __init__(self, level):
self.level = level self.level = level
self.graph = level.graph self.graph = level.graph
self.geometry = geometry self.mpl_clear = None
self.mpl_clear = mpl_clear
self.areas = [] 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 # Building the Graph
def prepare_build(self): def prepare_build(self, geometry):
self.clear_geometry = self.geometry.buffer(-0.3, join_style=JOIN_STYLE.mitre) self._built_geometry = geometry
self.clear_geometry = self._built_geometry.buffer(-0.3, join_style=JOIN_STYLE.mitre)
if self.clear_geometry.is_empty: if self.clear_geometry.is_empty:
return False return False
self._built_points = []
self.mpl_clear = shapely_to_mpl(self.clear_geometry.buffer(0.01, join_style=JOIN_STYLE.mitre)) self.mpl_clear = shapely_to_mpl(self.clear_geometry.buffer(0.01, join_style=JOIN_STYLE.mitre))
self.mpl_stairs = () self.mpl_stairs = ()
for stair_line in assert_multilinestring(self.level.level.geometries.stairs): for stair_line in assert_multilinestring(self.level.level.geometries.stairs):
@ -40,7 +58,7 @@ class GraphRoom():
def build_areas(self): def build_areas(self):
stairs_areas = self.level.level.geometries.stairs 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.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) self.stairs_areas = assert_multipolygon(stairs_areas)
isolated_areas = tuple(assert_multipolygon(stairs_areas.intersection(self.clear_geometry))) 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_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 mpl_stairs = tuple((stair, angle) for stair, angle in self.mpl_stairs
if mpl_clear.intersects_path(stair, filled=True)) 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): 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) geometry = narrowed_geometry.buffer(0.31, join_style=JOIN_STYLE.mitre).intersection(self.clear_geometry)
if geometry.is_empty: if geometry.is_empty:
@ -150,7 +170,7 @@ class GraphRoom():
if not self.mpl_clear.contains_point(coord): if not self.mpl_clear.contains_point(coord):
return [] return []
point = GraphPoint(coord[0], coord[1], self) point = GraphPoint(coord[0], coord[1], self)
self.points.append(point) self._built_points.append(point)
for area in self.areas: for area in self.areas:
area.add_point(point) area.add_point(point)
return [point] return [point]
@ -159,7 +179,15 @@ class GraphRoom():
for area in self.areas: for area in self.areas:
area.build_connections() 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 # Routing
def build_router(self): def build_router(self):
self.router = Router() self.router = Router()
self.router.build(self.points) self.router.build(self._built_points)