From 211ef767db37f6f5a520375da5f58dc7925c8c1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Fri, 2 Dec 2016 23:32:19 +0100 Subject: [PATCH] =?UTF-8?q?first=20stuff=20for=20graph=20building=20?= =?UTF-8?q?=E2=80=93=20collecting=20points?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/c3nav/mapdata/models/level.py | 4 + src/c3nav/routing/__init__.py | 0 src/c3nav/routing/graph.py | 147 ++++++++++++++++++ src/c3nav/routing/management/__init__.py | 0 .../routing/management/commands/__init__.py | 0 .../routing/management/commands/buildgraph.py | 12 ++ src/c3nav/settings.py | 1 + 7 files changed, 164 insertions(+) create mode 100644 src/c3nav/routing/__init__.py create mode 100644 src/c3nav/routing/graph.py create mode 100644 src/c3nav/routing/management/__init__.py create mode 100644 src/c3nav/routing/management/commands/__init__.py create mode 100644 src/c3nav/routing/management/commands/buildgraph.py diff --git a/src/c3nav/mapdata/models/level.py b/src/c3nav/mapdata/models/level.py index 2724c1e3..8fd2206c 100644 --- a/src/c3nav/mapdata/models/level.py +++ b/src/c3nav/mapdata/models/level.py @@ -90,6 +90,10 @@ class LevelGeometries(): def holes(self): return cascaded_union([holes.geometry for holes in self.level.holes.all()]).intersection(self.areas) + @cached_property + def accessible(self): + return self.areas.difference(self.holes).difference(self.obstacles) + @cached_property def buildings_with_holes(self): return self.buildings.difference(self.holes) diff --git a/src/c3nav/routing/__init__.py b/src/c3nav/routing/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/c3nav/routing/graph.py b/src/c3nav/routing/graph.py new file mode 100644 index 00000000..e030f349 --- /dev/null +++ b/src/c3nav/routing/graph.py @@ -0,0 +1,147 @@ +import os +from math import atan2, pi, degrees + +from PIL import Image +from PIL import ImageDraw +from django.conf import settings +from kombu.utils import cached_property +from shapely.geometry import JOIN_STYLE +from shapely.geometry import LineString +from shapely.geometry import MultiPolygon +from shapely.geometry import Point +from shapely.geometry import Polygon + +from c3nav.mapdata.models import Level + + +class GraphLevel(): + def __init__(self, graph, level): + self.graph = graph + self.level = level + self.rooms = [] + + def build(self): + self.collect_rooms() + self.create_points() + + def collect_rooms(self): + accessibles = self.level.geometries.accessible + accessibles = [accessibles] if isinstance(accessibles, Polygon) else accessibles.geoms + for geometry in accessibles: + self.rooms.append(GraphRoom(self, geometry)) + + def create_points(self): + for room in self.rooms: + room.create_points() + + def _ellipse_bbox(self, x, y, height): + x *= settings.RENDER_SCALE + y *= settings.RENDER_SCALE + y = height-y + return ((x - 2, y - 2), (x + 2, y + 2)) + + def draw_png(self): + filename = os.path.join(settings.RENDER_ROOT, 'level-%s.png' % self.level.name) + graph_filename = os.path.join(settings.RENDER_ROOT, 'level-%s-graph.png' % self.level.name) + + im = Image.open(filename) + height = im.size[1] + draw = ImageDraw.Draw(im) + i = 0 + for room in self.rooms: + for point in room.points: + i += 1 + draw.ellipse(self._ellipse_bbox(point.x, point.y, height), (255, 0, 0)) + print(i, 'points') + + im.save(graph_filename) + + +class GraphRoom(): + def __init__(self, level, geometry): + self.level = level + self.geometry = geometry + self.points = [] + + def cleanup_coords(self, coords): + result = [] + last_coord = coords[-1] + for coord in coords: + if ((coord[0] - last_coord[0]) ** 2 + (coord[1] - last_coord[1]) ** 2) ** 0.5 >= 0.01: + result.append(coord) + last_coord = coord + return result + + def coord_angle(self, coord1, coord2): + return degrees(atan2(-(coord2[1] - coord1[1]), coord2[0] - coord1[0])) % 360 + + def split_coords_by_angle(self, geom): + coords = list(self.cleanup_coords(geom.coords)) + last_coords = coords[-2:] + last_angle = self.coord_angle(last_coords[-2], last_coords[-1]) + left = [] + right = [] + for coord in coords: + angle = self.coord_angle(last_coords[-1], coord) + angle_diff = (last_angle-angle) % 360 + if angle_diff < 180: + left.append(last_coords[-1]) + else: + right.append(last_coords[-1]) + last_coords.append(coord) + last_angle = angle + + if not geom.is_ccw: + left, right = right, left + + return left, right + + def create_points(self): + original_geometry = self.geometry + geometry = original_geometry.buffer(-0.6, join_style=JOIN_STYLE.mitre) + + if geometry.is_empty: + return + + if isinstance(geometry, Polygon): + polygons = [geometry] + else: + polygons = geometry.geoms + + for polygon in polygons: + left, right = self.split_coords_by_angle(polygon.exterior) + for x, y in right: + self.points.append(GraphPoint(self, x, y)) + + for interior in polygon.interiors: + left, right = self.split_coords_by_angle(interior) + for x, y in left: + self.points.append(GraphPoint(self, x, y)) + + +class GraphPoint(): + def __init__(self, room, x, y): + self.room = room + self.x = x + self.y = 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)) + + +class Graph(): + def __init__(self): + self.levels = {} + + def build(self): + for level in Level.objects.all(): + self.levels[level.name] = GraphLevel(self, level) + + for level in self.levels.values(): + level.build() + level.draw_png() + + diff --git a/src/c3nav/routing/management/__init__.py b/src/c3nav/routing/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/c3nav/routing/management/commands/__init__.py b/src/c3nav/routing/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/c3nav/routing/management/commands/buildgraph.py b/src/c3nav/routing/management/commands/buildgraph.py new file mode 100644 index 00000000..120589a7 --- /dev/null +++ b/src/c3nav/routing/management/commands/buildgraph.py @@ -0,0 +1,12 @@ +from django.core.management.base import BaseCommand + +from c3nav.mapdata.render import render_all_levels +from c3nav.routing.graph import Graph + + +class Command(BaseCommand): + help = 'build the routing graph' + + def handle(self, *args, **options): + graphbuilder = Graph() + graphbuilder.build() diff --git a/src/c3nav/settings.py b/src/c3nav/settings.py index 01115198..133d300f 100644 --- a/src/c3nav/settings.py +++ b/src/c3nav/settings.py @@ -145,6 +145,7 @@ INSTALLED_APPS = [ 'c3nav.api', 'rest_framework', 'c3nav.mapdata', + 'c3nav.routing', 'c3nav.editor', 'c3nav.control', ]