buildgraph: add points for steps
This commit is contained in:
parent
2863b9da8e
commit
6906e1540c
4 changed files with 83 additions and 51 deletions
|
@ -103,6 +103,35 @@ class GeometryMapItemWithLevel(GeometryMapItem):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class LineGeometryMapItemWithLevel(GeometryMapItemWithLevel):
|
||||||
|
geomtype = 'polyline'
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
def to_geojson(self):
|
||||||
|
result = super().to_geojson()
|
||||||
|
original_geometry = result['geometry']
|
||||||
|
draw = self.geometry.buffer(0.05, join_style=JOIN_STYLE.mitre, cap_style=CAP_STYLE.flat)
|
||||||
|
result['geometry'] = format_geojson(mapping(draw))
|
||||||
|
result['original_geometry'] = original_geometry
|
||||||
|
return result
|
||||||
|
|
||||||
|
def to_shadow_geojson(self):
|
||||||
|
shadow = self.geometry.parallel_offset(0.03, 'left', join_style=JOIN_STYLE.mitre)
|
||||||
|
shadow = shadow.buffer(0.019, join_style=JOIN_STYLE.mitre, cap_style=CAP_STYLE.flat)
|
||||||
|
return OrderedDict((
|
||||||
|
('type', 'Feature'),
|
||||||
|
('properties', OrderedDict((
|
||||||
|
('type', 'shadow'),
|
||||||
|
('original_type', self.__class__.__name__.lower()),
|
||||||
|
('original_name', self.name),
|
||||||
|
('level', self.level.name),
|
||||||
|
))),
|
||||||
|
('geometry', format_geojson(mapping(shadow), round=False)),
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
class Building(GeometryMapItemWithLevel):
|
class Building(GeometryMapItemWithLevel):
|
||||||
"""
|
"""
|
||||||
The outline of a building on a specific level
|
The outline of a building on a specific level
|
||||||
|
@ -139,6 +168,16 @@ class Outside(GeometryMapItemWithLevel):
|
||||||
default_related_name = 'outsides'
|
default_related_name = 'outsides'
|
||||||
|
|
||||||
|
|
||||||
|
class Stair(LineGeometryMapItemWithLevel):
|
||||||
|
"""
|
||||||
|
A stair
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('Stair')
|
||||||
|
verbose_name_plural = _('Stairs')
|
||||||
|
default_related_name = 'stairs'
|
||||||
|
|
||||||
|
|
||||||
class Obstacle(GeometryMapItemWithLevel):
|
class Obstacle(GeometryMapItemWithLevel):
|
||||||
"""
|
"""
|
||||||
An obstacle
|
An obstacle
|
||||||
|
@ -277,42 +316,3 @@ class ElevatorLevel(GeometryMapItemWithLevel):
|
||||||
result['elevator'] = self.elevator.name
|
result['elevator'] = self.elevator.name
|
||||||
result['button'] = self.button
|
result['button'] = self.button
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
class LineGeometryMapItemWithLevel(GeometryMapItemWithLevel):
|
|
||||||
geomtype = 'polyline'
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
def to_geojson(self):
|
|
||||||
result = super().to_geojson()
|
|
||||||
original_geometry = result['geometry']
|
|
||||||
draw = self.geometry.buffer(0.05, join_style=JOIN_STYLE.mitre, cap_style=CAP_STYLE.flat)
|
|
||||||
result['geometry'] = format_geojson(mapping(draw))
|
|
||||||
result['original_geometry'] = original_geometry
|
|
||||||
return result
|
|
||||||
|
|
||||||
def to_shadow_geojson(self):
|
|
||||||
shadow = self.geometry.parallel_offset(0.03, 'left', join_style=JOIN_STYLE.mitre)
|
|
||||||
shadow = shadow.buffer(0.019, join_style=JOIN_STYLE.mitre, cap_style=CAP_STYLE.flat)
|
|
||||||
return OrderedDict((
|
|
||||||
('type', 'Feature'),
|
|
||||||
('properties', OrderedDict((
|
|
||||||
('type', 'shadow'),
|
|
||||||
('original_type', self.__class__.__name__.lower()),
|
|
||||||
('original_name', self.name),
|
|
||||||
('level', self.level.name),
|
|
||||||
))),
|
|
||||||
('geometry', format_geojson(mapping(shadow), round=False)),
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
class Stair(LineGeometryMapItemWithLevel):
|
|
||||||
"""
|
|
||||||
A stair
|
|
||||||
"""
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('Stair')
|
|
||||||
verbose_name_plural = _('Stairs')
|
|
||||||
default_related_name = 'stairs'
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ class GraphConnection():
|
||||||
self.graph = graph
|
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 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:
|
if to_point in from_point.connections:
|
||||||
self.graph.connections.remove(from_point.connections[to_point])
|
self.graph.connections.remove(from_point.connections[to_point])
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import numpy as np
|
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 matplotlib.path import Path
|
|
||||||
|
|
||||||
|
|
||||||
class GraphPoint():
|
class GraphPoint():
|
||||||
|
@ -34,8 +33,5 @@ class GraphPoint():
|
||||||
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 path_to(self, to_point):
|
|
||||||
return Path(np.vstack((self.xy, to_point.xy)))
|
|
||||||
|
|
||||||
def connect_to(self, to_point):
|
def connect_to(self, to_point):
|
||||||
self.graph.add_connection(self, to_point)
|
self.graph.add_connection(self, to_point)
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
from itertools import combinations, permutations
|
from itertools import combinations, permutations
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from matplotlib.path import Path
|
||||||
from shapely.geometry import CAP_STYLE, JOIN_STYLE, LineString
|
from shapely.geometry import CAP_STYLE, JOIN_STYLE, LineString
|
||||||
|
|
||||||
from c3nav.mapdata.utils.geometry import assert_multipolygon
|
from c3nav.mapdata.utils.geometry import assert_multilinestring, assert_multipolygon
|
||||||
from c3nav.routing.point import GraphPoint
|
from c3nav.routing.point import GraphPoint
|
||||||
from c3nav.routing.router import Router
|
from c3nav.routing.router import Router
|
||||||
from c3nav.routing.utils.coords import get_coords_angles
|
from c3nav.routing.utils.coords import coord_angle, get_coords_angles
|
||||||
from c3nav.routing.utils.mpl import shapely_to_mpl
|
from c3nav.routing.utils.mpl import shapely_to_mpl
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,6 +32,10 @@ class GraphRoom():
|
||||||
|
|
||||||
def prepare_build(self):
|
def prepare_build(self):
|
||||||
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 = ()
|
||||||
|
for stair_line in self.level.level.geometries.stairs:
|
||||||
|
coords = tuple(stair_line.coords)
|
||||||
|
self.mpl_stairs += tuple((Path(part), coord_angle(*part)) for part in zip(coords[:-1], coords[1:]))
|
||||||
|
|
||||||
def build_points(self):
|
def build_points(self):
|
||||||
original_geometry = self.geometry
|
original_geometry = self.geometry
|
||||||
|
@ -75,10 +81,19 @@ class GraphRoom():
|
||||||
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 = assert_multipolygon(stairs_areas.intersection(self.geometry))
|
stairs_areas = assert_multipolygon(stairs_areas.intersection(self.geometry))
|
||||||
for polygon in stairs_areas:
|
for polygon in stairs_areas:
|
||||||
self._add_ring(polygon.exterior, want_left=True)
|
for ring in (polygon.exterior, )+tuple(polygon.interiors):
|
||||||
|
print('#')
|
||||||
for interior in polygon.interiors:
|
for linestring in assert_multilinestring(ring.intersection(self.clear_geometry)):
|
||||||
self._add_ring(interior, want_left=False)
|
print(' -')
|
||||||
|
coords = tuple(linestring.coords)
|
||||||
|
start = 1
|
||||||
|
for segment in zip(coords[:-1], coords[1:]):
|
||||||
|
print(' .')
|
||||||
|
path = Path(segment)
|
||||||
|
length = abs(np.linalg.norm(path.vertices[0] - path.vertices[1]))
|
||||||
|
for coord in tuple(path.interpolated(max(int(length / 1.0), 1)).vertices)[start:-1]:
|
||||||
|
self.add_point(coord)
|
||||||
|
start = 0
|
||||||
|
|
||||||
def _add_ring(self, geom, want_left):
|
def _add_ring(self, geom, want_left):
|
||||||
"""
|
"""
|
||||||
|
@ -121,12 +136,33 @@ class GraphRoom():
|
||||||
return [point]
|
return [point]
|
||||||
|
|
||||||
def build_connections(self):
|
def build_connections(self):
|
||||||
|
i = 0
|
||||||
for point1, point2 in combinations(self.points, 2):
|
for point1, point2 in combinations(self.points, 2):
|
||||||
path = point1.path_to(point2)
|
path = Path(np.vstack((point1.xy, point2.xy)))
|
||||||
|
|
||||||
|
# lies within room
|
||||||
if self.mpl_clear.intersects_path(path):
|
if self.mpl_clear.intersects_path(path):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# stair checker
|
||||||
|
angle = coord_angle(point1.xy, point2.xy)
|
||||||
|
valid = True
|
||||||
|
for stair_path, stair_angle in self.mpl_stairs:
|
||||||
|
if not path.intersects_path(stair_path):
|
||||||
|
continue
|
||||||
|
|
||||||
|
angle_diff = ((stair_angle - angle + 180) % 360) - 180
|
||||||
|
up = angle_diff < 0 # noqa
|
||||||
|
if not (70 < abs(angle_diff) < 110):
|
||||||
|
valid = False
|
||||||
|
break
|
||||||
|
|
||||||
|
if not valid:
|
||||||
|
continue
|
||||||
|
|
||||||
point1.connect_to(point2)
|
point1.connect_to(point2)
|
||||||
point2.connect_to(point1)
|
point2.connect_to(point1)
|
||||||
|
i += 1
|
||||||
|
|
||||||
def build_router(self):
|
def build_router(self):
|
||||||
self.router.build(self.points)
|
self.router.build(self.points)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue