first stuff for graph building – collecting points
This commit is contained in:
parent
ffd5c3fa70
commit
211ef767db
7 changed files with 164 additions and 0 deletions
|
@ -90,6 +90,10 @@ class LevelGeometries():
|
||||||
def holes(self):
|
def holes(self):
|
||||||
return cascaded_union([holes.geometry for holes in self.level.holes.all()]).intersection(self.areas)
|
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
|
@cached_property
|
||||||
def buildings_with_holes(self):
|
def buildings_with_holes(self):
|
||||||
return self.buildings.difference(self.holes)
|
return self.buildings.difference(self.holes)
|
||||||
|
|
0
src/c3nav/routing/__init__.py
Normal file
0
src/c3nav/routing/__init__.py
Normal file
147
src/c3nav/routing/graph.py
Normal file
147
src/c3nav/routing/graph.py
Normal file
|
@ -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()
|
||||||
|
|
||||||
|
|
0
src/c3nav/routing/management/__init__.py
Normal file
0
src/c3nav/routing/management/__init__.py
Normal file
0
src/c3nav/routing/management/commands/__init__.py
Normal file
0
src/c3nav/routing/management/commands/__init__.py
Normal file
12
src/c3nav/routing/management/commands/buildgraph.py
Normal file
12
src/c3nav/routing/management/commands/buildgraph.py
Normal file
|
@ -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()
|
|
@ -145,6 +145,7 @@ INSTALLED_APPS = [
|
||||||
'c3nav.api',
|
'c3nav.api',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'c3nav.mapdata',
|
'c3nav.mapdata',
|
||||||
|
'c3nav.routing',
|
||||||
'c3nav.editor',
|
'c3nav.editor',
|
||||||
'c3nav.control',
|
'c3nav.control',
|
||||||
]
|
]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue