buildgraph: add doors and connections
This commit is contained in:
parent
ab70ef7678
commit
68773adcc2
2 changed files with 115 additions and 6 deletions
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
from itertools import permutations
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.functional import cached_property
|
||||
|
@ -6,7 +7,7 @@ from PIL import Image, ImageDraw
|
|||
from shapely.geometry import JOIN_STYLE, LineString, Polygon
|
||||
|
||||
from c3nav.mapdata.models import Level
|
||||
from c3nav.routing.utils import get_coords_angles, polygon_to_mpl_path
|
||||
from c3nav.routing.utils import assert_multipolygon, get_coords_angles, get_nearest_point, polygon_to_mpl_path
|
||||
|
||||
|
||||
class GraphLevel():
|
||||
|
@ -29,12 +30,35 @@ class GraphLevel():
|
|||
for room in self.rooms:
|
||||
room.create_points()
|
||||
|
||||
doors = self.level.geometries.doors
|
||||
doors = assert_multipolygon(doors)
|
||||
for door in doors:
|
||||
polygon = door.buffer(0.01, join_style=JOIN_STYLE.mitre)
|
||||
center = door.centroid
|
||||
points = []
|
||||
for room in self.rooms:
|
||||
if polygon.intersects(room.geometry):
|
||||
nearest_point = get_nearest_point(room.clear_geometry, center)
|
||||
point = GraphPoint(room, *nearest_point.coords[0])
|
||||
points.append(point)
|
||||
room.points.append(point)
|
||||
|
||||
if len(points) < 2:
|
||||
print('waaaah')
|
||||
|
||||
for from_point, to_point in permutations(points, 2):
|
||||
from_point.connect_to(to_point)
|
||||
|
||||
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 _line_coords(self, from_point, to_point, height):
|
||||
return (from_point.x * settings.RENDER_SCALE, height - (from_point.y * settings.RENDER_SCALE),
|
||||
to_point.x * settings.RENDER_SCALE, height - (to_point.y * settings.RENDER_SCALE))
|
||||
|
||||
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)
|
||||
|
@ -47,6 +71,8 @@ class GraphLevel():
|
|||
for point in room.points:
|
||||
i += 1
|
||||
draw.ellipse(self._ellipse_bbox(point.x, point.y, height), (255, 0, 0))
|
||||
for otherpoint, connection in point.connections.items():
|
||||
draw.line(self._line_coords(point, otherpoint, height), fill=(255, 0, 0))
|
||||
print(i, 'points')
|
||||
|
||||
im.save(graph_filename)
|
||||
|
@ -69,17 +95,30 @@ class GraphRoom():
|
|||
if geometry.is_empty:
|
||||
return
|
||||
|
||||
if isinstance(geometry, Polygon):
|
||||
polygons = [geometry]
|
||||
else:
|
||||
polygons = geometry.geoms
|
||||
|
||||
# points with 60cm distance to borders
|
||||
polygons = assert_multipolygon(geometry)
|
||||
for polygon in polygons:
|
||||
self._add_ring(polygon.exterior, want_left=False)
|
||||
|
||||
for interior in polygon.interiors:
|
||||
self._add_ring(interior, want_left=True)
|
||||
|
||||
# now fill in missing doorways or similar
|
||||
missing_geometry = self.clear_geometry.difference(geometry.buffer(0.61, join_style=JOIN_STYLE.mitre))
|
||||
polygons = assert_multipolygon(missing_geometry)
|
||||
for polygon in polygons:
|
||||
overlaps = polygon.buffer(0.62).intersection(geometry)
|
||||
if overlaps.is_empty:
|
||||
continue
|
||||
overlaps = assert_multipolygon(overlaps)
|
||||
for overlap in overlaps:
|
||||
self.points.append(GraphPoint(self, *overlap.centroid.coords[0]))
|
||||
|
||||
self._add_ring(polygon.exterior, want_left=False)
|
||||
|
||||
for interior in polygon.interiors:
|
||||
self._add_ring(interior, want_left=True)
|
||||
|
||||
def _add_ring(self, geom, want_left):
|
||||
"""
|
||||
add the points of a ring, but only those that have a specific direction change.
|
||||
|
@ -117,6 +156,8 @@ class GraphPoint():
|
|||
self.room = room
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.connections = {}
|
||||
self.connections_in = {}
|
||||
|
||||
@cached_property
|
||||
def ellipse_bbox(self):
|
||||
|
@ -124,10 +165,24 @@ class GraphPoint():
|
|||
y = self.y * settings.RENDER_SCALE
|
||||
return ((x-5, y-5), (x+5, y+5))
|
||||
|
||||
def connect_to(self, to_point):
|
||||
self.room.level.graph.add_connection(self, to_point)
|
||||
|
||||
|
||||
class GraphConnection():
|
||||
def __init__(self, graph, from_point, to_point):
|
||||
if to_point in from_point.connections:
|
||||
raise ValueError('already connected')
|
||||
|
||||
self.graph = graph
|
||||
from_point.connections[to_point] = self
|
||||
to_point.connections_in[from_point] = self
|
||||
|
||||
|
||||
class Graph():
|
||||
def __init__(self):
|
||||
self.levels = {}
|
||||
self.connections = []
|
||||
|
||||
def build(self):
|
||||
for level in Level.objects.all():
|
||||
|
@ -136,3 +191,6 @@ class Graph():
|
|||
for level in self.levels.values():
|
||||
level.build()
|
||||
level.draw_png()
|
||||
|
||||
def add_connection(self, from_point, to_point):
|
||||
self.connections.append(GraphConnection(self, from_point, to_point))
|
||||
|
|
|
@ -2,6 +2,7 @@ from math import atan2, degrees
|
|||
|
||||
import numpy as np
|
||||
from matplotlib.path import Path
|
||||
from shapely.geometry import Polygon
|
||||
|
||||
|
||||
def cleanup_coords(coords):
|
||||
|
@ -72,3 +73,53 @@ def _mpl_add_linearring(linearring, vertices, codes):
|
|||
vertices.append(coords[0])
|
||||
codes.append(Path.MOVETO)
|
||||
codes.extend([Path.LINETO] * len(coords))
|
||||
|
||||
|
||||
def assert_multipolygon(geometry):
|
||||
"""
|
||||
given a Polygon or a MultiPolygon, return a list of Polygons
|
||||
:param geometry: a Polygon or a MultiPolygon
|
||||
:return: a list of Polygons
|
||||
"""
|
||||
if isinstance(geometry, Polygon):
|
||||
polygons = [geometry]
|
||||
else:
|
||||
polygons = geometry.geoms
|
||||
return polygons
|
||||
|
||||
|
||||
def get_nearest_point(polygon, point):
|
||||
"""
|
||||
calculate the nearest point on of a polygon to another point that lies outside
|
||||
:param polygon: a Polygon or a MultiPolygon
|
||||
:param point: something that shapely understands as a point
|
||||
:return: a Point
|
||||
"""
|
||||
polygons = assert_multipolygon(polygon)
|
||||
nearest_distance = float('inf')
|
||||
nearest_point = None
|
||||
for polygon in polygons:
|
||||
if point.within(Polygon(polygon.exterior.coords)):
|
||||
for interior in polygon.interiors:
|
||||
if point.within(Polygon(interior.coords)):
|
||||
point_ = _nearest_point_ring(interior, point)
|
||||
distance = point_.distance(point)
|
||||
if distance and distance < nearest_distance:
|
||||
nearest_distance = distance
|
||||
nearest_point = point_
|
||||
break # in a valid polygon a point can not be within multiple interiors
|
||||
break # in a valid multipolygon a point can not be within multiple polygons
|
||||
else:
|
||||
point_ = _nearest_point_ring(polygon.exterior, point)
|
||||
distance = point_.distance(point)
|
||||
if distance and distance < nearest_distance:
|
||||
nearest_distance = distance
|
||||
nearest_point = point_
|
||||
|
||||
if nearest_point is None:
|
||||
raise ValueError('Point inside polygon.')
|
||||
return nearest_point
|
||||
|
||||
|
||||
def _nearest_point_ring(ring, point):
|
||||
return ring.interpolate(ring.project(point))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue