add stairs
This commit is contained in:
parent
3090d52831
commit
56b083f714
7 changed files with 167 additions and 32 deletions
|
@ -168,6 +168,7 @@ editor = {
|
|||
_editing_layer: null,
|
||||
_get_geometries_next_time: false,
|
||||
_geometries: {},
|
||||
_geometries_shadows: {},
|
||||
_creating: false,
|
||||
_editing: null,
|
||||
_geometry_types: [],
|
||||
|
@ -232,6 +233,7 @@ editor = {
|
|||
get_geometries: function () {
|
||||
// reload geometries of current level
|
||||
editor._geometries = {};
|
||||
editor._geometries_shadows = {};
|
||||
if (editor._geometries_layer !== null) {
|
||||
editor.map.removeLayer(editor._geometries_layer);
|
||||
}
|
||||
|
@ -263,24 +265,41 @@ editor = {
|
|||
'door': '#FF00FF',
|
||||
'hole': '#66CC66',
|
||||
'elevatorlevel': '#9EF8FB',
|
||||
'levelconnector': '#FFFF00'
|
||||
'levelconnector': '#FFFF00',
|
||||
'shadow': '#000000',
|
||||
'stair': '#FF0000'
|
||||
},
|
||||
_line_draw_geometry_style: function(style) {
|
||||
style.stroke = true;
|
||||
style.opacity = 0.6;
|
||||
style.color = style.fillColor;
|
||||
style.weight = 5;
|
||||
return style;
|
||||
},
|
||||
_get_geometry_style: function (feature) {
|
||||
// style callback for GeoJSON loader
|
||||
return editor._get_mapitem_type_style(feature.properties.type);
|
||||
var style = editor._get_mapitem_type_style(feature.properties.type);
|
||||
if (feature.geometry.type == 'LineString') {
|
||||
style = editor._line_draw_geometry_style(style);
|
||||
}
|
||||
return style
|
||||
},
|
||||
_get_mapitem_type_style: function (mapitem_type) {
|
||||
// get styles for a specific mapitem
|
||||
return {
|
||||
stroke: false,
|
||||
fillColor: editor._geometry_colors[mapitem_type],
|
||||
weight: 0,
|
||||
fillOpacity: 0.6,
|
||||
smoothFactor: 0
|
||||
};
|
||||
},
|
||||
_register_geojson_feature: function (feature, layer) {
|
||||
// onEachFeature callback for GeoJSON loader – register all needed events
|
||||
editor._geometries[feature.properties.type+'-'+feature.properties.name] = layer;
|
||||
if (feature.properties.type == 'shadow') {
|
||||
editor._geometries_shadows[feature.properties.original_type+'-'+feature.properties.original_name] = layer;
|
||||
} else {
|
||||
editor._geometries[feature.properties.type+'-'+feature.properties.name] = layer;
|
||||
}
|
||||
layer.on('mouseover', editor._hover_geometry_layer)
|
||||
.on('mouseout', editor._unhighlight_geometry)
|
||||
.on('click', editor._click_geometry_layer)
|
||||
|
@ -361,6 +380,10 @@ editor = {
|
|||
var name = form.attr('data-name');
|
||||
var pk = mapitem_type+'-'+name;
|
||||
editor._geometries_layer.removeLayer(editor._geometries[pk]);
|
||||
var shadow = editor._geometries_shadows[pk];
|
||||
if (shadow) {
|
||||
editor._geometries_layer.removeLayer(shadow);
|
||||
}
|
||||
}
|
||||
|
||||
editor._editing = L.geoJSON({
|
||||
|
@ -384,6 +407,7 @@ editor = {
|
|||
if (geomtype == 'polygon') {
|
||||
editor.map.editTools.startPolygon(null, options);
|
||||
} else if (geomtype == 'polyline') {
|
||||
options = editor._line_draw_geometry_style(options);
|
||||
editor.map.editTools.startPolyline(null, options);
|
||||
}
|
||||
editor._creating = true;
|
||||
|
|
|
@ -10,6 +10,7 @@ from rest_framework.response import Response
|
|||
from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet
|
||||
|
||||
from c3nav.mapdata.models import GEOMETRY_MAPITEM_TYPES, Level, Package, Source
|
||||
from c3nav.mapdata.models.geometry import LineGeometryMapItemWithLevel
|
||||
from c3nav.mapdata.permissions import filter_queryset_by_package_access, get_unlocked_packages_names
|
||||
from c3nav.mapdata.serializers.main import LevelSerializer, PackageSerializer, SourceSerializer
|
||||
from c3nav.mapdata.utils.cache import (CachedReadOnlyViewSetMixin, cache_mapdata_api_response, get_levels_cached,
|
||||
|
@ -92,7 +93,10 @@ class GeometryViewSet(ViewSet):
|
|||
if hasattr(mapitemtype, field_name):
|
||||
queryset.prefetch_related(field_name)
|
||||
|
||||
results.extend(sum((obj.to_geojson() for obj in queryset), []))
|
||||
if issubclass(mapitemtype, LineGeometryMapItemWithLevel):
|
||||
results.extend(obj.to_shadow_geojson() for obj in queryset)
|
||||
|
||||
results.extend(obj.to_geojson() for obj in queryset)
|
||||
|
||||
return Response(results)
|
||||
|
||||
|
|
44
src/c3nav/mapdata/migrations/0013_auto_20161208_0937.py
Normal file
44
src/c3nav/mapdata/migrations/0013_auto_20161208_0937.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.4 on 2016-12-08 09:37
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import c3nav.mapdata.fields
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mapdata', '0012_auto_20161204_1544'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Stair',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.SlugField(unique=True, verbose_name='Name')),
|
||||
('geometry', c3nav.mapdata.fields.GeometryField()),
|
||||
],
|
||||
options={
|
||||
'default_related_name': 'stairs',
|
||||
'verbose_name_plural': 'Stairs',
|
||||
'verbose_name': 'Stair',
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='level',
|
||||
options={'ordering': ['altitude'], 'verbose_name': 'Level', 'verbose_name_plural': 'Levels'},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='stair',
|
||||
name='level',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='stairs', to='mapdata.Level', verbose_name='level'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='stair',
|
||||
name='package',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='stairs', to='mapdata.Package', verbose_name='map package'),
|
||||
),
|
||||
]
|
|
@ -1,8 +1,8 @@
|
|||
from collections import OrderedDict
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from shapely.geometry import CAP_STYLE, JOIN_STYLE
|
||||
from shapely.geometry.geo import mapping, shape
|
||||
|
||||
from c3nav.mapdata.fields import GeometryField
|
||||
|
@ -26,7 +26,6 @@ class GeometryMapItem(MapItem, metaclass=GeometryMapItemMeta):
|
|||
A map feature
|
||||
"""
|
||||
geometry = GeometryField()
|
||||
cached_geojson = {}
|
||||
|
||||
geomtype = None
|
||||
|
||||
|
@ -50,12 +49,6 @@ class GeometryMapItem(MapItem, metaclass=GeometryMapItemMeta):
|
|||
|
||||
return kwargs
|
||||
|
||||
@classmethod
|
||||
def get_styles(cls):
|
||||
return {
|
||||
cls.__name__.lower(): cls.color
|
||||
}
|
||||
|
||||
def get_geojson_properties(self):
|
||||
return OrderedDict((
|
||||
('type', self.__class__.__name__.lower()),
|
||||
|
@ -64,25 +57,20 @@ class GeometryMapItem(MapItem, metaclass=GeometryMapItemMeta):
|
|||
))
|
||||
|
||||
def to_geojson(self):
|
||||
if settings.DIRECT_EDITING:
|
||||
return self._to_geojson()
|
||||
key = (self.__class__, self.name)
|
||||
if key not in self.cached_geojson:
|
||||
self.cached_geojson[key] = self._to_geojson()
|
||||
return self.cached_geojson[key]
|
||||
|
||||
def _to_geojson(self):
|
||||
return [OrderedDict((
|
||||
return OrderedDict((
|
||||
('type', 'Feature'),
|
||||
('properties', self.get_geojson_properties()),
|
||||
('geometry', format_geojson(mapping(self.geometry), round=False)),
|
||||
))]
|
||||
))
|
||||
|
||||
def tofile(self):
|
||||
result = super().tofile()
|
||||
result['geometry'] = format_geojson(mapping(self.geometry))
|
||||
return result
|
||||
|
||||
def get_shadow_geojson(self):
|
||||
return None
|
||||
|
||||
|
||||
class GeometryMapItemWithLevel(GeometryMapItem):
|
||||
"""
|
||||
|
@ -289,3 +277,42 @@ class ElevatorLevel(GeometryMapItemWithLevel):
|
|||
result['elevator'] = self.elevator.name
|
||||
result['button'] = self.button
|
||||
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'
|
||||
|
|
|
@ -5,6 +5,7 @@ from shapely.geometry import JOIN_STYLE
|
|||
from shapely.ops import cascaded_union
|
||||
|
||||
from c3nav.mapdata.models.base import MapItem
|
||||
from c3nav.mapdata.utils.geometry import assert_multilinestring
|
||||
|
||||
|
||||
class Level(MapItem):
|
||||
|
@ -193,3 +194,14 @@ class LevelGeometries():
|
|||
|
||||
shadows = shadows.difference(connectors.buffer(1.0, join_style=JOIN_STYLE.mitre))
|
||||
return shadows
|
||||
|
||||
@cached_property
|
||||
def stairs(self):
|
||||
return cascaded_union([stair.geometry for stair in self.level.stairs.all()])
|
||||
|
||||
@cached_property
|
||||
def stair_shadows(self):
|
||||
shadows = []
|
||||
for stair in assert_multilinestring(self.stairs):
|
||||
shadows.append(stair.parallel_offset(0.1, 'left', join_style=JOIN_STYLE.mitre))
|
||||
return cascaded_union(shadows)
|
||||
|
|
|
@ -32,7 +32,8 @@ class LevelRenderer():
|
|||
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, filter=None):
|
||||
def polygon_svg(geometry, fill_color=None, fill_opacity=None,
|
||||
stroke_width=0.0, stroke_color=None, stroke_opacity=None):
|
||||
scaled = scale(geometry, xfact=settings.RENDER_SCALE, yfact=settings.RENDER_SCALE, origin=(0, 0))
|
||||
element = ET.fromstring(scaled.svg(0, fill_color or '#FFFFFF'))
|
||||
if element.tag != 'g':
|
||||
|
@ -40,7 +41,11 @@ class LevelRenderer():
|
|||
new_element.append(element)
|
||||
element = new_element
|
||||
|
||||
for path in element.findall('path'):
|
||||
paths = element.findall('polyline')
|
||||
if len(paths) == 0:
|
||||
paths = element.findall('path')
|
||||
|
||||
for path in paths:
|
||||
path.attrib.pop('opacity')
|
||||
path.set('stroke-width', str(stroke_width * settings.RENDER_SCALE))
|
||||
|
||||
|
@ -56,8 +61,8 @@ class LevelRenderer():
|
|||
elif 'stroke' in path.attrib:
|
||||
path.attrib.pop('stroke')
|
||||
|
||||
if filter is not None:
|
||||
path.set('filter', filter)
|
||||
if stroke_opacity is not None:
|
||||
path.set('stroke-opacity', str(stroke_opacity))
|
||||
|
||||
return element
|
||||
|
||||
|
@ -107,6 +112,16 @@ class LevelRenderer():
|
|||
contents.append(self.polygon_svg(self.level.geometries.outsides_with_holes,
|
||||
fill_color='#DCE6DC'))
|
||||
|
||||
contents.append(self.polygon_svg(self.level.geometries.stair_shadows,
|
||||
stroke_color='#000000',
|
||||
stroke_width=0.1,
|
||||
stroke_opacity=0.1))
|
||||
|
||||
contents.append(self.polygon_svg(self.level.geometries.stairs,
|
||||
stroke_color='#000000',
|
||||
stroke_width=0.06,
|
||||
stroke_opacity=0.2))
|
||||
|
||||
contents.append(self.polygon_svg(self.level.geometries.walls_shadow,
|
||||
fill_color='#000000',
|
||||
fill_opacity=0.06))
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from shapely.geometry import Polygon
|
||||
from shapely.geometry import LineString, Polygon
|
||||
|
||||
|
||||
def clean_geometry(geometry):
|
||||
|
@ -26,7 +26,16 @@ def assert_multipolygon(geometry):
|
|||
:return: a list of Polygons
|
||||
"""
|
||||
if isinstance(geometry, Polygon):
|
||||
polygons = [geometry]
|
||||
else:
|
||||
polygons = geometry.geoms
|
||||
return polygons
|
||||
return [geometry]
|
||||
return geometry.geoms
|
||||
|
||||
|
||||
def assert_multilinestring(geometry):
|
||||
"""
|
||||
given a Geometry or GeometryCollection, return a list of Geometries
|
||||
:param geometry: a Geometry or a GeometryCollection
|
||||
:return: a list of Geometries
|
||||
"""
|
||||
if isinstance(geometry, LineString):
|
||||
return [geometry]
|
||||
return geometry.geoms
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue