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,
|
_editing_layer: null,
|
||||||
_get_geometries_next_time: false,
|
_get_geometries_next_time: false,
|
||||||
_geometries: {},
|
_geometries: {},
|
||||||
|
_geometries_shadows: {},
|
||||||
_creating: false,
|
_creating: false,
|
||||||
_editing: null,
|
_editing: null,
|
||||||
_geometry_types: [],
|
_geometry_types: [],
|
||||||
|
@ -232,6 +233,7 @@ editor = {
|
||||||
get_geometries: function () {
|
get_geometries: function () {
|
||||||
// reload geometries of current level
|
// reload geometries of current level
|
||||||
editor._geometries = {};
|
editor._geometries = {};
|
||||||
|
editor._geometries_shadows = {};
|
||||||
if (editor._geometries_layer !== null) {
|
if (editor._geometries_layer !== null) {
|
||||||
editor.map.removeLayer(editor._geometries_layer);
|
editor.map.removeLayer(editor._geometries_layer);
|
||||||
}
|
}
|
||||||
|
@ -263,24 +265,41 @@ editor = {
|
||||||
'door': '#FF00FF',
|
'door': '#FF00FF',
|
||||||
'hole': '#66CC66',
|
'hole': '#66CC66',
|
||||||
'elevatorlevel': '#9EF8FB',
|
'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) {
|
_get_geometry_style: function (feature) {
|
||||||
// style callback for GeoJSON loader
|
// 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_mapitem_type_style: function (mapitem_type) {
|
||||||
// get styles for a specific mapitem
|
// get styles for a specific mapitem
|
||||||
return {
|
return {
|
||||||
|
stroke: false,
|
||||||
fillColor: editor._geometry_colors[mapitem_type],
|
fillColor: editor._geometry_colors[mapitem_type],
|
||||||
weight: 0,
|
|
||||||
fillOpacity: 0.6,
|
fillOpacity: 0.6,
|
||||||
smoothFactor: 0
|
smoothFactor: 0
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
_register_geojson_feature: function (feature, layer) {
|
_register_geojson_feature: function (feature, layer) {
|
||||||
// onEachFeature callback for GeoJSON loader – register all needed events
|
// 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)
|
layer.on('mouseover', editor._hover_geometry_layer)
|
||||||
.on('mouseout', editor._unhighlight_geometry)
|
.on('mouseout', editor._unhighlight_geometry)
|
||||||
.on('click', editor._click_geometry_layer)
|
.on('click', editor._click_geometry_layer)
|
||||||
|
@ -361,6 +380,10 @@ editor = {
|
||||||
var name = form.attr('data-name');
|
var name = form.attr('data-name');
|
||||||
var pk = mapitem_type+'-'+name;
|
var pk = mapitem_type+'-'+name;
|
||||||
editor._geometries_layer.removeLayer(editor._geometries[pk]);
|
editor._geometries_layer.removeLayer(editor._geometries[pk]);
|
||||||
|
var shadow = editor._geometries_shadows[pk];
|
||||||
|
if (shadow) {
|
||||||
|
editor._geometries_layer.removeLayer(shadow);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
editor._editing = L.geoJSON({
|
editor._editing = L.geoJSON({
|
||||||
|
@ -384,6 +407,7 @@ editor = {
|
||||||
if (geomtype == 'polygon') {
|
if (geomtype == 'polygon') {
|
||||||
editor.map.editTools.startPolygon(null, options);
|
editor.map.editTools.startPolygon(null, options);
|
||||||
} else if (geomtype == 'polyline') {
|
} else if (geomtype == 'polyline') {
|
||||||
|
options = editor._line_draw_geometry_style(options);
|
||||||
editor.map.editTools.startPolyline(null, options);
|
editor.map.editTools.startPolyline(null, options);
|
||||||
}
|
}
|
||||||
editor._creating = true;
|
editor._creating = true;
|
||||||
|
|
|
@ -10,6 +10,7 @@ from rest_framework.response import Response
|
||||||
from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet
|
from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet
|
||||||
|
|
||||||
from c3nav.mapdata.models import GEOMETRY_MAPITEM_TYPES, Level, Package, Source
|
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.permissions import filter_queryset_by_package_access, get_unlocked_packages_names
|
||||||
from c3nav.mapdata.serializers.main import LevelSerializer, PackageSerializer, SourceSerializer
|
from c3nav.mapdata.serializers.main import LevelSerializer, PackageSerializer, SourceSerializer
|
||||||
from c3nav.mapdata.utils.cache import (CachedReadOnlyViewSetMixin, cache_mapdata_api_response, get_levels_cached,
|
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):
|
if hasattr(mapitemtype, field_name):
|
||||||
queryset.prefetch_related(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)
|
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 collections import OrderedDict
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from shapely.geometry import CAP_STYLE, JOIN_STYLE
|
||||||
from shapely.geometry.geo import mapping, shape
|
from shapely.geometry.geo import mapping, shape
|
||||||
|
|
||||||
from c3nav.mapdata.fields import GeometryField
|
from c3nav.mapdata.fields import GeometryField
|
||||||
|
@ -26,7 +26,6 @@ class GeometryMapItem(MapItem, metaclass=GeometryMapItemMeta):
|
||||||
A map feature
|
A map feature
|
||||||
"""
|
"""
|
||||||
geometry = GeometryField()
|
geometry = GeometryField()
|
||||||
cached_geojson = {}
|
|
||||||
|
|
||||||
geomtype = None
|
geomtype = None
|
||||||
|
|
||||||
|
@ -50,12 +49,6 @@ class GeometryMapItem(MapItem, metaclass=GeometryMapItemMeta):
|
||||||
|
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_styles(cls):
|
|
||||||
return {
|
|
||||||
cls.__name__.lower(): cls.color
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_geojson_properties(self):
|
def get_geojson_properties(self):
|
||||||
return OrderedDict((
|
return OrderedDict((
|
||||||
('type', self.__class__.__name__.lower()),
|
('type', self.__class__.__name__.lower()),
|
||||||
|
@ -64,25 +57,20 @@ class GeometryMapItem(MapItem, metaclass=GeometryMapItemMeta):
|
||||||
))
|
))
|
||||||
|
|
||||||
def to_geojson(self):
|
def to_geojson(self):
|
||||||
if settings.DIRECT_EDITING:
|
return OrderedDict((
|
||||||
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((
|
|
||||||
('type', 'Feature'),
|
('type', 'Feature'),
|
||||||
('properties', self.get_geojson_properties()),
|
('properties', self.get_geojson_properties()),
|
||||||
('geometry', format_geojson(mapping(self.geometry), round=False)),
|
('geometry', format_geojson(mapping(self.geometry), round=False)),
|
||||||
))]
|
))
|
||||||
|
|
||||||
def tofile(self):
|
def tofile(self):
|
||||||
result = super().tofile()
|
result = super().tofile()
|
||||||
result['geometry'] = format_geojson(mapping(self.geometry))
|
result['geometry'] = format_geojson(mapping(self.geometry))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def get_shadow_geojson(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class GeometryMapItemWithLevel(GeometryMapItem):
|
class GeometryMapItemWithLevel(GeometryMapItem):
|
||||||
"""
|
"""
|
||||||
|
@ -289,3 +277,42 @@ 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'
|
||||||
|
|
|
@ -5,6 +5,7 @@ from shapely.geometry import JOIN_STYLE
|
||||||
from shapely.ops import cascaded_union
|
from shapely.ops import cascaded_union
|
||||||
|
|
||||||
from c3nav.mapdata.models.base import MapItem
|
from c3nav.mapdata.models.base import MapItem
|
||||||
|
from c3nav.mapdata.utils.geometry import assert_multilinestring
|
||||||
|
|
||||||
|
|
||||||
class Level(MapItem):
|
class Level(MapItem):
|
||||||
|
@ -193,3 +194,14 @@ class LevelGeometries():
|
||||||
|
|
||||||
shadows = shadows.difference(connectors.buffer(1.0, join_style=JOIN_STYLE.mitre))
|
shadows = shadows.difference(connectors.buffer(1.0, join_style=JOIN_STYLE.mitre))
|
||||||
return shadows
|
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)
|
return (width * settings.RENDER_SCALE, height * settings.RENDER_SCALE)
|
||||||
|
|
||||||
@staticmethod
|
@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))
|
scaled = scale(geometry, xfact=settings.RENDER_SCALE, yfact=settings.RENDER_SCALE, origin=(0, 0))
|
||||||
element = ET.fromstring(scaled.svg(0, fill_color or '#FFFFFF'))
|
element = ET.fromstring(scaled.svg(0, fill_color or '#FFFFFF'))
|
||||||
if element.tag != 'g':
|
if element.tag != 'g':
|
||||||
|
@ -40,7 +41,11 @@ class LevelRenderer():
|
||||||
new_element.append(element)
|
new_element.append(element)
|
||||||
element = new_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.attrib.pop('opacity')
|
||||||
path.set('stroke-width', str(stroke_width * settings.RENDER_SCALE))
|
path.set('stroke-width', str(stroke_width * settings.RENDER_SCALE))
|
||||||
|
|
||||||
|
@ -56,8 +61,8 @@ class LevelRenderer():
|
||||||
elif 'stroke' in path.attrib:
|
elif 'stroke' in path.attrib:
|
||||||
path.attrib.pop('stroke')
|
path.attrib.pop('stroke')
|
||||||
|
|
||||||
if filter is not None:
|
if stroke_opacity is not None:
|
||||||
path.set('filter', filter)
|
path.set('stroke-opacity', str(stroke_opacity))
|
||||||
|
|
||||||
return element
|
return element
|
||||||
|
|
||||||
|
@ -107,6 +112,16 @@ class LevelRenderer():
|
||||||
contents.append(self.polygon_svg(self.level.geometries.outsides_with_holes,
|
contents.append(self.polygon_svg(self.level.geometries.outsides_with_holes,
|
||||||
fill_color='#DCE6DC'))
|
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,
|
contents.append(self.polygon_svg(self.level.geometries.walls_shadow,
|
||||||
fill_color='#000000',
|
fill_color='#000000',
|
||||||
fill_opacity=0.06))
|
fill_opacity=0.06))
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from shapely.geometry import Polygon
|
from shapely.geometry import LineString, Polygon
|
||||||
|
|
||||||
|
|
||||||
def clean_geometry(geometry):
|
def clean_geometry(geometry):
|
||||||
|
@ -26,7 +26,16 @@ def assert_multipolygon(geometry):
|
||||||
:return: a list of Polygons
|
:return: a list of Polygons
|
||||||
"""
|
"""
|
||||||
if isinstance(geometry, Polygon):
|
if isinstance(geometry, Polygon):
|
||||||
polygons = [geometry]
|
return [geometry]
|
||||||
else:
|
return geometry.geoms
|
||||||
polygons = geometry.geoms
|
|
||||||
return polygons
|
|
||||||
|
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