first stuff for showing the route on the site

This commit is contained in:
Laura Klünder 2016-12-17 19:25:27 +01:00
parent ad58ddbbcb
commit f6129d621c
14 changed files with 366 additions and 154 deletions

View file

@ -1,11 +1,40 @@
import os
from django.http import HttpResponse
from PIL import Image
from c3nav.mapdata.render.utils import get_render_path
from c3nav.mapdata.utils.misc import get_render_path
def get_level_image(request, level):
im = Image.open(get_render_path('png', level.name, 'full', True))
response = HttpResponse(content_type="image/png")
im.save(response, 'PNG')
return response
class LevelComposer:
images = {}
images_mtimes = {}
@classmethod
def _get_image(cls, filename, cached=True):
mtime = None
if cached:
mtime = os.path.getmtime(filename)
if filename in cls.images:
if cls.images_mtimes[filename] == mtime:
return cls.images[filename]
img = Image.open(filename)
if cached:
cls.images[filename] = img
cls.images_mtimes[filename] = mtime
return img
def _get_public_level_image(self, level):
return self._get_image(get_render_path('png', level.name, 'full', True))
def get_level_image(self, request, level):
img = self._get_public_level_image(level)
response = HttpResponse(content_type="image/png")
img.save(response, 'PNG')
return response
composer = LevelComposer()

View file

@ -5,7 +5,7 @@ from django.conf import settings
from shapely.affinity import scale
from shapely.geometry import JOIN_STYLE, box
from c3nav.mapdata.render.utils import get_dimensions, get_render_path
from c3nav.mapdata.utils.misc import get_dimensions, get_render_dimensions, get_render_path
class LevelRenderer():
@ -21,11 +21,6 @@ class LevelRenderer():
def get_filename(self, mode, filetype, level=None):
return get_render_path(filetype, self.level.name if level is None else level, mode, self.only_public)
@staticmethod
def get_dimensions():
width, height = get_dimensions()
return (width * settings.RENDER_SCALE, height * settings.RENDER_SCALE)
@staticmethod
def polygon_svg(geometry, fill_color=None, fill_opacity=None,
stroke_width=0.0, stroke_color=None, stroke_opacity=None):
@ -62,7 +57,7 @@ class LevelRenderer():
return element
def create_svg(self):
width, height = self.get_dimensions()
width, height = get_render_dimensions()
svg = ET.Element('svg', {
'width': str(width),
'height': str(height),
@ -73,7 +68,7 @@ class LevelRenderer():
return svg
def add_svg_content(self, svg):
width, height = self.get_dimensions()
width, height = get_render_dimensions()
contents = ET.Element('g', {
'transform': 'scale(1 -1) translate(0 -%d)' % (height),
})
@ -81,7 +76,7 @@ class LevelRenderer():
return contents
def add_svg_image(self, svg, image):
width, height = self.get_dimensions()
width, height = get_render_dimensions()
contents = ET.Element('image', {
'x': '0',
'y': '0',

View file

@ -4,8 +4,10 @@ from django.conf import settings
from django.db.models import Max, Min
from c3nav.mapdata.models import Package
from c3nav.mapdata.utils.cache import cache_result
@cache_result('c3nav__mapdata__dimensions')
def get_dimensions():
aggregate = Package.objects.all().aggregate(Max('right'), Min('left'), Max('top'), Min('bottom'))
return (
@ -14,6 +16,12 @@ def get_dimensions():
)
@cache_result('c3nav__mapdata__render_dimensions')
def get_render_dimensions():
width, height = get_dimensions()
return (width * settings.RENDER_SCALE, height * settings.RENDER_SCALE)
def get_render_path(filetype, level, mode, public):
return os.path.join(settings.RENDER_ROOT,
'%s%s-level-%s.%s' % (('public-' if public else ''), mode, level, filetype))

View file

@ -6,3 +6,6 @@ class GraphConnection():
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))
def __repr__(self):
return '<GraphConnection %r %r distance=%f>' % (self.from_point, self.to_point, self.distance)

View file

@ -12,7 +12,8 @@ from c3nav.mapdata.models.geometry import LevelConnector
from c3nav.mapdata.models.locations import AreaLocation, Location, LocationGroup, PointLocation
from c3nav.routing.level import GraphLevel
from c3nav.routing.point import GraphPoint
from c3nav.routing.route import GraphRouteSegment, LevelRouteSegment, NoRoute, RoomRouteSegment, SegmentRoute
from c3nav.routing.route import NoRoute
from c3nav.routing.routesegments import GraphRouteSegment, LevelRouteSegment, RoomRouteSegment, SegmentRoute
class Graph:

View file

@ -15,6 +15,10 @@ class GraphPoint():
self.connections = {}
self.connections_in = {}
@cached_property
def level(self):
return self.room and self.room.level
def serialize(self):
return (
self.x,
@ -32,3 +36,6 @@ class GraphPoint():
connection = GraphConnection(self, other_point)
self.connections[other_point] = connection
other_point.connections_in[self] = connection
def __repr__(self):
return '<GraphPoint x=%f y=%f room=%s>' % (self.x, self.y, (id(self.room) if self.room else None))

View file

@ -1,139 +1,7 @@
from abc import ABC, abstractmethod
import numpy as np
from django.utils.functional import cached_property
class RouteSegment(ABC):
def __init__(self, routers, router, from_point, to_point):
"""
:param router: a Router (RoomRouter, GraphRouter, )
:param from_point: in-router index of first point
:param to_point: in-router index of last point
"""
self.routers = routers
self.router = router
self.from_point = int(from_point)
self.to_point = int(to_point)
def as_route(self):
return SegmentRoute([self])
def _get_points(self):
points = [self.to_point]
first = self.from_point
current = self.to_point
while current != first:
current = self.router.predecessors[first, current]
points.append(current)
return tuple(reversed(points))
@abstractmethod
def get_connections(self):
pass
@cached_property
def distance(self):
return self.router.shortest_paths[self.from_point, self.to_point]
class RoomRouteSegment(RouteSegment):
def __init__(self, room, routers, from_point, to_point):
"""
Route segment within a Room
:param room: GraphRoom
"""
super().__init__(routers, routers[room], from_point, to_point)
self.room = room
self.global_from_point = room.points[from_point]
self.global_to_point = room.points[to_point]
def get_connections(self):
points = self._get_points()
return tuple(self.room.get_connection(from_point, to_point)
for from_point, to_point in zip(points[:-1], points[1:]))
def __repr__(self):
return ('<RoomRouteSegment in %r from points %d to %d with distance %f>' %
(self.room, self.from_point, self.to_point, self.distance))
class LevelRouteSegment(RouteSegment):
def __init__(self, level, routers, from_point, to_point):
"""
Route segment within a Level (from room transfer point to room transfer point)
:param level: GraphLevel
"""
super().__init__(routers, routers[level], from_point, to_point)
self.level = level
self.global_from_point = level.room_transfer_points[from_point]
self.global_to_point = level.room_transfer_points[to_point]
def split(self):
segments = []
points = self._get_points()
for from_point, to_point in zip(points[:-1], points[1:]):
room = self.level.rooms[self.router.room_transfers[from_point, to_point]]
global_from_point = self.level.room_transfer_points[from_point]
global_to_point = self.level.room_transfer_points[to_point]
segments.append(RoomRouteSegment(room, self.routers,
from_point=room.points.index(global_from_point),
to_point=room.points.index(global_to_point)))
return tuple(segments)
def get_connections(self):
return sum((segment.get_connections() for segment in self.split()), ())
def __repr__(self):
return ('<LevelRouteSegment in %r from points %d to %d with distance %f>' %
(self.level, self.from_point, self.to_point, self.distance))
class GraphRouteSegment(RouteSegment):
def __init__(self, graph, routers, from_point, to_point):
"""
Route segment within a Graph (from level transfer point to level transfer point)
:param graph: Graph
"""
super().__init__(routers, routers[graph], from_point, to_point)
self.graph = graph
self.global_from_point = graph.level_transfer_points[from_point]
self.global_to_point = graph.level_transfer_points[to_point]
def split(self):
segments = []
points = self._get_points()
for from_point, to_point in zip(points[:-1], points[1:]):
level = self.graph.levels[self.router.level_transfers[from_point, to_point]]
global_from_point = self.graph.level_transfer_points[from_point]
global_to_point = self.graph.level_transfer_points[to_point]
segments.append(LevelRouteSegment(level, self.routers,
from_point=level.room_transfer_points.index(global_from_point),
to_point=level.room_transfer_points.index(global_to_point)))
return tuple(segments)
def get_connections(self):
return sum((segment.get_connections() for segment in self.split()), ())
def __repr__(self):
return ('<GraphRouteSegment in %r from points %d to %d with distance %f>' %
(self.graph, self.from_point, self.to_point, self.distance))
class SegmentRoute:
def __init__(self, segments, distance=None):
self.segments = sum(((item.segments if isinstance(item, SegmentRoute) else (item,))
for item in segments if item.from_point != item.to_point), ())
self.distance = sum(segment.distance for segment in self.segments)
self.from_point = segments[0].global_from_point
self.to_point = segments[-1].global_to_point
def __repr__(self):
return ('<SegmentedRoute (\n %s\n) distance=%f>' %
('\n '.join(repr(segment) for segment in self.segments), self.distance))
def split(self):
return Route(sum((segment.get_connections() for segment in self.segments), ()))
from c3nav.mapdata.utils.misc import get_dimensions
class Route:
@ -147,6 +15,73 @@ class Route:
return ('<Route (\n %s\n) distance=%f>' %
('\n '.join(repr(connection) for connection in self.connections), self.distance))
@cached_property
def routeparts(self):
routeparts = []
connections = []
level = self.connections[0].from_point.level
for connection in self.connections:
connections.append(connection)
point = connection.to_point
if point.level and point.level != level:
routeparts.append(RoutePart(level, connections))
level = point.level
connections = []
if connections:
routeparts.append(RoutePart(level, connections))
print(routeparts)
return tuple(routeparts)
class RoutePart:
def __init__(self, level, connections):
self.level = level
self.level_name = level.level.name
self.connections = connections
width, height = get_dimensions()
points = (connections[0].from_point, ) + tuple(connection.to_point for connection in connections)
for point in points:
point.svg_x = point.x * 6
point.svg_y = (height - point.y) * 6
x, y = zip(*((point.svg_x, point.svg_y) for point in points if point.level == level))
self.distance = sum(connection.distance for connection in connections)
# bounds for rendering
self.min_x = min(x) - 20
self.max_x = max(x) + 20
self.min_y = min(y) - 20
self.max_y = max(y) + 20
width = self.max_x - self.min_x
height = self.max_y - self.min_y
if width < 150:
self.min_x -= (10 - width) / 2
self.max_x += (10 - width) / 2
if height < 150:
self.min_y -= (10 - height) / 2
self.max_y += (10 - height) / 2
self.width = self.max_x - self.min_x
self.height = self.max_y - self.min_y
def __str__(self):
return repr(self.__dict__)
class RouteLine:
def __init__(self, from_point, to_point, distance):
self.from_point = from_point
self.to_point = to_point
self.distance = distance
class NoRoute:
distance = np.inf

View file

@ -0,0 +1,137 @@
from abc import ABC, abstractmethod
from django.utils.functional import cached_property
from c3nav.routing.route import Route
class RouteSegment(ABC):
def __init__(self, routers, router, from_point, to_point):
"""
:param router: a Router (RoomRouter, GraphRouter, )
:param from_point: in-router index of first point
:param to_point: in-router index of last point
"""
self.routers = routers
self.router = router
self.from_point = int(from_point)
self.to_point = int(to_point)
def as_route(self):
return SegmentRoute([self])
def _get_points(self):
points = [self.to_point]
first = self.from_point
current = self.to_point
while current != first:
current = self.router.predecessors[first, current]
points.append(current)
return tuple(reversed(points))
@abstractmethod
def get_connections(self):
pass
@cached_property
def distance(self):
return self.router.shortest_paths[self.from_point, self.to_point]
class RoomRouteSegment(RouteSegment):
def __init__(self, room, routers, from_point, to_point):
"""
Route segment within a Room
:param room: GraphRoom
"""
super().__init__(routers, routers[room], from_point, to_point)
self.room = room
self.global_from_point = room.points[from_point]
self.global_to_point = room.points[to_point]
def get_connections(self):
points = self._get_points()
return tuple(self.room.get_connection(from_point, to_point)
for from_point, to_point in zip(points[:-1], points[1:]))
def __repr__(self):
return ('<RoomRouteSegment in %r from points %d to %d with distance %f>' %
(self.room, self.from_point, self.to_point, self.distance))
class LevelRouteSegment(RouteSegment):
def __init__(self, level, routers, from_point, to_point):
"""
Route segment within a Level (from room transfer point to room transfer point)
:param level: GraphLevel
"""
super().__init__(routers, routers[level], from_point, to_point)
self.level = level
self.global_from_point = level.room_transfer_points[from_point]
self.global_to_point = level.room_transfer_points[to_point]
def split(self):
segments = []
points = self._get_points()
for from_point, to_point in zip(points[:-1], points[1:]):
room = self.level.rooms[self.router.room_transfers[from_point, to_point]]
global_from_point = self.level.room_transfer_points[from_point]
global_to_point = self.level.room_transfer_points[to_point]
segments.append(RoomRouteSegment(room, self.routers,
from_point=room.points.index(global_from_point),
to_point=room.points.index(global_to_point)))
return tuple(segments)
def get_connections(self):
return sum((segment.get_connections() for segment in self.split()), ())
def __repr__(self):
return ('<LevelRouteSegment in %r from points %d to %d with distance %f>' %
(self.level, self.from_point, self.to_point, self.distance))
class GraphRouteSegment(RouteSegment):
def __init__(self, graph, routers, from_point, to_point):
"""
Route segment within a Graph (from level transfer point to level transfer point)
:param graph: Graph
"""
super().__init__(routers, routers[graph], from_point, to_point)
self.graph = graph
self.global_from_point = graph.level_transfer_points[from_point]
self.global_to_point = graph.level_transfer_points[to_point]
def split(self):
segments = []
points = self._get_points()
for from_point, to_point in zip(points[:-1], points[1:]):
level = self.graph.levels[self.router.level_transfers[from_point, to_point]]
global_from_point = self.graph.level_transfer_points[from_point]
global_to_point = self.graph.level_transfer_points[to_point]
segments.append(LevelRouteSegment(level, self.routers,
from_point=level.room_transfer_points.index(global_from_point),
to_point=level.room_transfer_points.index(global_to_point)))
return tuple(segments)
def get_connections(self):
return sum((segment.get_connections() for segment in self.split()), ())
def __repr__(self):
return ('<GraphRouteSegment in %r from points %d to %d with distance %f>' %
(self.graph, self.from_point, self.to_point, self.distance))
class SegmentRoute:
def __init__(self, segments, distance=None):
self.segments = sum(((item.segments if isinstance(item, SegmentRoute) else (item,))
for item in segments if item.from_point != item.to_point), ())
self.distance = sum(segment.distance for segment in self.segments)
self.from_point = segments[0].global_from_point
self.to_point = segments[-1].global_to_point
def __repr__(self):
return ('<SegmentedRoute (\n %s\n) distance=%f>' %
('\n '.join(repr(segment) for segment in self.segments), self.distance))
def split(self):
return Route(sum((segment.get_connections() for segment in self.segments), ()))

View file

@ -90,3 +90,22 @@ body {
.tt-suggestion p {
margin: 0;
}
svg {
width:100%;
height:auto;
vertical-align:bottom;
}
line {
stroke:#FF0000;
stroke-width:2.5px;
}
marker path {
fill: #FF0000;
stroke: 0;
}
circle.pos {
fill:#3399FF;
stroke-width:10px;
stroke:rgba(51, 153, 255, 0.2);
}

View file

@ -2,6 +2,7 @@
{% load static %}
{% load i18n %}
{% load route_render %}
{% block content %}
<form>
@ -12,6 +13,34 @@
{% trans "Destination" as heading %}
{% include 'site/fragment_location.html' with name='destination' location=destination heading=heading %}
</div>
</form>
{% if route %}
<h2>Your Route</h2>
<div class="routeparts">
{% for routepart in route.routeparts %}
<svg xmlns="http://www.w3.org/2000/svg" class="map" data-level="{{ routepart.level_name }}"
viewBox="0 0 {{ routepart.width }} {{ routepart.height }}"
style="max-height:{% if routepart.height > 300 %}300{% else %}{{ routepart.height }}{% endif %}px">
<defs>
<marker id="arrow-{{ forloop.counter0 }}" markerWidth="4" markerHeight="4" refX="2.5" refY="2" orient="auto">
<path d="M0,0 L3,2 L0,4 L0,0" style="fill: #FF0000; stroke: 0;"></path>
</marker>
</defs>
<image width="{{ svg_width }}" height="{{ svg_height }}" x="{{ routepart.min_x | negate }}" y="{{ routepart.min_y | negate }}" xlink:href="/map/{{ routepart.level_name }}.png"></image>
<g class="connections">
{% for c in routepart.connections %}
<line x1="{{ c.from_point.svg_x | subtract:routepart.min_x }}"
y1="{{ c.from_point.svg_y | subtract:routepart.min_y }}"
x2="{{ c.to_point.svg_x | subtract:routepart.min_x }}"
y2="{{ c.to_point.svg_y | subtract:routepart.min_y }}"
marker-end="url(#arrow-{{ forloop.parentloop.counter0 }})"></line>
{% endfor %}
</g>
</svg>
{% endfor %}
</div>
{% endif %}
{% endblock %}

View file

View file

@ -0,0 +1,13 @@
from django import template
register = template.Library()
@register.filter
def negate(value):
return -value
@register.filter
def subtract(value, arg):
return value - arg

View file

@ -1,8 +1,9 @@
from django.conf.urls import url
from c3nav.site.views import main
from c3nav.site.views import level_image, main
urlpatterns = [
url(r'^map/(?P<level>[a-z0-9-_:]+).png$', level_image, name='site.level_image'),
url(r'^(?P<origin>[a-z0-9-_:]+)/$', main, name='site.main'),
url(r'^_/(?P<destination>[a-z0-9-_:]+)/$', main, name='site.main'),
url(r'^(?P<origin>[a-z0-9-_:]+)/(?P<destination>[a-z0-9-_:]+)/$', main, name='site.main'),

View file

@ -1,7 +1,16 @@
from django.shortcuts import redirect, render
import os
from django.conf import settings
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, redirect, render
from PIL import Image, ImageDraw
from c3nav.mapdata.models import Level
from c3nav.mapdata.models.locations import get_location
from c3nav.mapdata.render.compose import composer
from c3nav.mapdata.utils.misc import get_dimensions
from c3nav.routing.graph import Graph
from c3nav.routing.utils.draw import _line_coords
def main(request, origin=None, destination=None):
@ -30,13 +39,39 @@ def main(request, origin=None, destination=None):
redirect(new_url)
route = None
if origin and destination:
graph = Graph.load()
route = graph.get_route(origin, destination)
route = route.split()
print(route)
print(route)
if False:
filename = os.path.join(settings.RENDER_ROOT, 'base-level-0.png')
im = Image.open(filename)
height = im.size[1]
draw = ImageDraw.Draw(im)
for connection in route.connections:
draw.line(_line_coords(connection.from_point, connection.to_point, height), fill=(255, 100, 100))
response = HttpResponse(content_type="image/png")
im.save(response, "PNG")
return response
width, height = get_dimensions()
return render(request, 'site/main.html', {
'origin': origin,
'destination': destination
'destination': destination,
'route': route,
'width': width,
'height': height,
'svg_width': width*6,
'svg_height': height*6,
})
def level_image(request, level):
level = get_object_or_404(Level, name=level, intermediate=False)
return composer.get_level_image(request, level)