2017-05-12 23:37:03 +02:00
|
|
|
from django.conf import settings
|
2016-08-29 18:49:24 +02:00
|
|
|
from django.db import models
|
|
|
|
from django.utils.translation import ugettext_lazy as _
|
2017-05-13 14:59:59 +02:00
|
|
|
from shapely.geometry import JOIN_STYLE
|
2016-11-26 13:48:44 +01:00
|
|
|
from shapely.ops import cascaded_union
|
2016-08-29 18:49:24 +02:00
|
|
|
|
2017-05-08 16:40:22 +02:00
|
|
|
from c3nav.mapdata.models.base import EditorFormMixin
|
2017-05-10 18:03:57 +02:00
|
|
|
from c3nav.mapdata.models.locations import SpecificLocation
|
2017-05-21 23:41:42 +02:00
|
|
|
from c3nav.mapdata.utils.svg import SVGImage
|
2016-08-29 18:49:24 +02:00
|
|
|
|
2016-10-12 15:25:00 +02:00
|
|
|
|
2017-05-10 18:03:57 +02:00
|
|
|
class Section(SpecificLocation, EditorFormMixin, models.Model):
|
2016-08-29 18:49:24 +02:00
|
|
|
"""
|
2017-05-07 12:06:13 +02:00
|
|
|
A map section like a level
|
2016-08-29 18:49:24 +02:00
|
|
|
"""
|
2017-05-07 12:06:13 +02:00
|
|
|
altitude = models.DecimalField(_('section altitude'), null=False, unique=True, max_digits=6, decimal_places=2)
|
2016-08-29 18:49:24 +02:00
|
|
|
|
2016-10-12 15:25:00 +02:00
|
|
|
class Meta:
|
2017-05-07 12:06:13 +02:00
|
|
|
verbose_name = _('Section')
|
|
|
|
verbose_name_plural = _('Sections')
|
|
|
|
default_related_name = 'sections'
|
2016-12-05 12:14:07 +01:00
|
|
|
ordering = ['altitude']
|
2016-09-24 14:09:52 +02:00
|
|
|
|
2016-11-26 13:48:44 +01:00
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
super().__init__(*args, **kwargs)
|
2016-12-04 01:49:49 +01:00
|
|
|
|
|
|
|
def lower(self):
|
2017-05-07 12:06:13 +02:00
|
|
|
return Section.objects.filter(altitude__lt=self.altitude).order_by('altitude')
|
2016-12-04 01:49:49 +01:00
|
|
|
|
|
|
|
def higher(self):
|
2017-05-07 12:06:13 +02:00
|
|
|
return Section.objects.filter(altitude__gt=self.altitude).order_by('altitude')
|
2016-12-04 01:49:49 +01:00
|
|
|
|
2017-05-11 19:36:49 +02:00
|
|
|
def _serialize(self, section=True, **kwargs):
|
|
|
|
result = super()._serialize(**kwargs)
|
|
|
|
result['altitude'] = float(str(self.altitude))
|
|
|
|
return result
|
2016-12-01 12:25:02 +01:00
|
|
|
|
2017-05-13 21:25:57 +02:00
|
|
|
def _render_space_ground(self, svg, space):
|
|
|
|
areas_by_color = {}
|
|
|
|
for area in space.areas.all():
|
|
|
|
areas_by_color.setdefault(area.get_color(), []).append(area)
|
|
|
|
areas_by_color.pop(None, None)
|
|
|
|
areas_by_color.pop('', None)
|
|
|
|
for i, (color, color_areas) in enumerate(areas_by_color.items()):
|
|
|
|
geometries = cascaded_union(tuple(area.geometry for area in color_areas)).intersection(space.geometry)
|
|
|
|
svg.add_geometry(geometries, fill_color=color)
|
|
|
|
|
|
|
|
stair_geometries = tuple(stair.geometry for stair in space.stairs.all())
|
|
|
|
svg.add_geometry(cascaded_union(stair_geometries).intersection(space.geometry),
|
|
|
|
stroke_width=0.06, stroke_color='#000000', opacity=0.15)
|
|
|
|
for i in range(2):
|
|
|
|
svg.add_geometry(cascaded_union(tuple(g.parallel_offset(0.06+0.04*i, 'right', join_style=JOIN_STYLE.mitre)
|
|
|
|
for g in stair_geometries)).intersection(space.geometry),
|
|
|
|
stroke_width=0.04, stroke_color='#000000', opacity=0.07-0.05*i)
|
|
|
|
|
|
|
|
def _render_space_inventory(self, svg, space):
|
|
|
|
obstacle_geometries = cascaded_union(
|
|
|
|
tuple(obstacle.geometry for obstacle in space.obstacles.all()) +
|
|
|
|
tuple(obstacle.buffered_geometry for obstacle in space.lineobstacles.all())
|
|
|
|
).intersection(space.geometry)
|
|
|
|
svg.add_geometry(obstacle_geometries, fill_color='#999999')
|
|
|
|
|
2017-05-21 23:39:26 +02:00
|
|
|
def render_svg(self, effects=True, draw_spaces=None):
|
2017-05-26 22:38:46 +02:00
|
|
|
from c3nav.mapdata.models import Source
|
|
|
|
bounds = Source.max_bounds()
|
|
|
|
svg = SVGImage(bounds=bounds, scale=settings.RENDER_SCALE)
|
2017-05-12 23:37:03 +02:00
|
|
|
|
|
|
|
building_geometries = cascaded_union(tuple(b.geometry for b in self.buildings.all()))
|
|
|
|
|
2017-06-08 16:30:58 +02:00
|
|
|
spaces = self.spaces.all().prefetch_related('groups', 'holes', 'areas', 'areas__groups',
|
|
|
|
'stairs', 'obstacles', 'lineobstacles')
|
2017-05-12 23:37:03 +02:00
|
|
|
space_levels = {
|
|
|
|
'upper': [],
|
|
|
|
'lower': [],
|
2017-05-26 20:25:24 +02:00
|
|
|
'normal': [],
|
2017-05-12 23:37:03 +02:00
|
|
|
}
|
|
|
|
for space in spaces:
|
|
|
|
space_levels[space.level].append(space)
|
2017-06-08 16:21:32 +02:00
|
|
|
for space in space_levels['normal']:
|
|
|
|
if space.outside:
|
|
|
|
space.geometry = space.geometry.difference(building_geometries)
|
|
|
|
else:
|
|
|
|
space.geometry = space.geometry.intersection(building_geometries)
|
|
|
|
space_geometries = {level: cascaded_union(tuple(s.geometry for s in level_spaces))
|
|
|
|
for level, level_spaces in space_levels.items()}
|
|
|
|
|
|
|
|
for space in spaces:
|
|
|
|
space_holes = cascaded_union(tuple(hole.geometry for hole in space.holes.all()))
|
|
|
|
space.hole_geometries = space_holes.intersection(space.geometry)
|
2017-05-12 23:37:03 +02:00
|
|
|
|
2017-06-08 16:21:32 +02:00
|
|
|
hole_geometries = cascaded_union(tuple(space.hole_geometries for space in space_levels['normal']))
|
2017-05-12 23:37:03 +02:00
|
|
|
|
2017-05-13 16:39:01 +02:00
|
|
|
lower_spaces_by_color = {}
|
|
|
|
for space in space_levels['lower']:
|
|
|
|
lower_spaces_by_color.setdefault(space.get_color(), []).append(space)
|
|
|
|
for i, (color, color_spaces) in enumerate(lower_spaces_by_color.items()):
|
|
|
|
geometries = cascaded_union(tuple(space.geometry for space in color_spaces))
|
2017-05-13 20:10:12 +02:00
|
|
|
svg.add_geometry(geometries, fill_color=color or '#d1d1d1')
|
2017-05-12 23:37:03 +02:00
|
|
|
|
2017-05-13 21:25:57 +02:00
|
|
|
for space in space_levels['lower']:
|
|
|
|
self._render_space_ground(svg, space)
|
|
|
|
self._render_space_inventory(svg, space)
|
|
|
|
|
2017-05-13 16:39:01 +02:00
|
|
|
# draw space background
|
2017-06-08 16:30:58 +02:00
|
|
|
doors = self.doors.all()
|
|
|
|
door_geometries = cascaded_union(tuple(d.geometry for d in doors))
|
2017-05-26 20:25:24 +02:00
|
|
|
section_geometry = cascaded_union((space_geometries['normal'], building_geometries, door_geometries))
|
2017-05-13 19:19:28 +02:00
|
|
|
section_geometry = section_geometry.difference(hole_geometries)
|
2017-05-13 20:10:12 +02:00
|
|
|
section_clip = svg.register_geometry(section_geometry, defid='section', as_clip_path=True)
|
|
|
|
svg.add_geometry(fill_color='#d1d1d1', clip_path=section_clip)
|
2017-05-13 14:59:59 +02:00
|
|
|
|
2017-05-13 16:39:01 +02:00
|
|
|
# color in spaces
|
|
|
|
spaces_by_color = {}
|
2017-05-26 20:25:24 +02:00
|
|
|
for space in space_levels['normal']:
|
2017-05-13 16:39:01 +02:00
|
|
|
spaces_by_color.setdefault(space.get_color(), []).append(space)
|
|
|
|
spaces_by_color.pop(None, None)
|
2017-05-13 20:48:48 +02:00
|
|
|
spaces_by_color.pop('', None)
|
2017-05-13 16:39:01 +02:00
|
|
|
for i, (color, color_spaces) in enumerate(spaces_by_color.items()):
|
|
|
|
geometries = cascaded_union(tuple(space.geometry for space in color_spaces))
|
2017-05-13 20:48:48 +02:00
|
|
|
svg.add_geometry(geometries, fill_color=color)
|
2017-05-13 16:39:01 +02:00
|
|
|
|
2017-05-26 20:25:24 +02:00
|
|
|
for space in space_levels['normal']:
|
2017-05-13 21:25:57 +02:00
|
|
|
self._render_space_ground(svg, space)
|
|
|
|
|
2017-05-13 16:39:01 +02:00
|
|
|
# calculate walls
|
2017-05-26 20:25:24 +02:00
|
|
|
wall_geometry = building_geometries.difference(space_geometries['normal']).difference(door_geometries)
|
2017-05-12 23:37:03 +02:00
|
|
|
|
2017-05-13 16:39:01 +02:00
|
|
|
# draw wall shadow
|
2017-05-21 23:39:26 +02:00
|
|
|
if effects:
|
|
|
|
wall_dilated_geometry = wall_geometry.buffer(0.7, join_style=JOIN_STYLE.mitre)
|
|
|
|
svg.add_geometry(wall_dilated_geometry, fill_color='#000000', opacity=0.1, filter='wallblur',
|
|
|
|
clip_path=section_clip)
|
2017-05-13 14:59:59 +02:00
|
|
|
|
2017-05-26 20:25:24 +02:00
|
|
|
for space in space_levels['normal']:
|
2017-05-13 21:25:57 +02:00
|
|
|
self._render_space_inventory(svg, space)
|
|
|
|
|
2017-05-13 16:39:01 +02:00
|
|
|
# draw walls
|
2017-05-13 20:10:12 +02:00
|
|
|
svg.add_geometry(wall_geometry, fill_color='#929292', stroke_color='#333333', stroke_width=0.07)
|
2017-05-12 23:37:03 +02:00
|
|
|
|
2017-05-13 16:39:01 +02:00
|
|
|
# draw doors
|
2017-06-08 16:30:58 +02:00
|
|
|
door_geometries = cascaded_union(tuple(d.geometry for d in doors))
|
2017-05-26 20:25:24 +02:00
|
|
|
door_geometries = door_geometries.difference(space_geometries['normal'])
|
2017-05-13 20:10:12 +02:00
|
|
|
svg.add_geometry(door_geometries, fill_color='#ffffff', stroke_color='#929292', stroke_width=0.07)
|
2017-05-12 23:37:03 +02:00
|
|
|
|
2017-05-13 16:39:01 +02:00
|
|
|
# draw upper spaces
|
|
|
|
upper_spaces_by_color = {}
|
|
|
|
for space in space_levels['upper']:
|
|
|
|
upper_spaces_by_color.setdefault(space.get_color(), []).append(space)
|
|
|
|
for i, (color, color_spaces) in enumerate(upper_spaces_by_color.items()):
|
|
|
|
geometries = cascaded_union(tuple(space.geometry for space in color_spaces))
|
2017-05-13 20:10:12 +02:00
|
|
|
svg.add_geometry(geometries, fill_color=color or '#d1d1d1')
|
2017-05-13 14:59:59 +02:00
|
|
|
|
2017-05-13 21:25:57 +02:00
|
|
|
for space in space_levels['upper']:
|
|
|
|
self._render_space_ground(svg, space)
|
|
|
|
self._render_space_inventory(svg, space)
|
|
|
|
|
2017-05-12 23:37:03 +02:00
|
|
|
return svg.get_xml()
|