diff --git a/src/c3nav/editor/forms.py b/src/c3nav/editor/forms.py
index bd401582..9e264b17 100644
--- a/src/c3nav/editor/forms.py
+++ b/src/c3nav/editor/forms.py
@@ -61,6 +61,10 @@ class MapitemFormMixin(ModelForm):
# set field_name
self.fields['levels'].to_field_name = 'name'
+ if 'groups' in self.fields:
+ # set field_name
+ self.fields['groups'].to_field_name = 'name'
+
if 'geometry' in self.fields:
# hide geometry widget
self.fields['geometry'].widget = HiddenInput()
@@ -94,12 +98,17 @@ class MapitemFormMixin(ModelForm):
if 'geometry' in self.fields:
if not self.cleaned_data.get('geometry'):
raise ValidationError('Missing geometry.')
+
+ if hasattr(self.instance, 'titles') and not any(self.titles.values()):
+ raise ValidationError(
+ _('You have to select a title in at least one language.')
+ )
super().clean()
def create_editor_form(mapitemtype):
possible_fields = ['name', 'package', 'altitude', 'level', 'intermediate', 'levels', 'geometry',
- 'elevator', 'button', 'crop_to_level', 'width']
+ 'elevator', 'button', 'crop_to_level', 'width', 'groups']
existing_fields = [field for field in possible_fields if hasattr(mapitemtype, field)]
class EditorForm(MapitemFormMixin, ModelForm):
diff --git a/src/c3nav/editor/static/editor/css/editor.css b/src/c3nav/editor/static/editor/css/editor.css
index b55d3c83..47f4fba9 100644
--- a/src/c3nav/editor/static/editor/css/editor.css
+++ b/src/c3nav/editor/static/editor/css/editor.css
@@ -29,7 +29,7 @@ form button.invisiblesubmit {
display: block;
}
-.leaflet-control-layers-toggle {
+.leaflet-container .leaflet-control-layers-toggle {
color:#000000 !important;
font-size:14px;
text-align:center;
@@ -38,7 +38,7 @@ form button.invisiblesubmit {
background-image:none;
padding:0 8px;
}
-.leaflet-control-layers-expanded {
+.leaflet-container .leaflet-control-layers-expanded {
min-width:75px;
}
.leaflet-levels {
diff --git a/src/c3nav/editor/static/editor/js/editor.js b/src/c3nav/editor/static/editor/js/editor.js
index 6e680dfb..808b0299 100644
--- a/src/c3nav/editor/static/editor/js/editor.js
+++ b/src/c3nav/editor/static/editor/js/editor.js
@@ -281,7 +281,8 @@ editor = {
'elevatorlevel': '#9EF8FB',
'levelconnector': '#FFFF00',
'shadow': '#000000',
- 'stair': '#FF0000'
+ 'stair': '#FF0000',
+ 'areaofinterest': '#0099FF'
},
_line_draw_geometry_style: function(style) {
style.stroke = true;
@@ -300,12 +301,19 @@ editor = {
},
_get_mapitem_type_style: function (mapitem_type) {
// get styles for a specific mapitem
- return {
+ var result = {
stroke: false,
fillColor: editor._geometry_colors[mapitem_type],
- fillOpacity: 0.6,
+ fillOpacity: (mapitem_type == 'areaofinterest') ? 0.2 : 0.6,
smoothFactor: 0
};
+ if (mapitem_type == 'areaofinterest') {
+ result.fillOpacity = 0.02;
+ result.color = result.fillColor;
+ result.stroke = true;
+ result.weight = 1;
+ }
+ return result;
},
_register_geojson_feature: function (feature, layer) {
// onEachFeature callback for GeoJSON loader – register all needed events
diff --git a/src/c3nav/editor/templates/editor/mapitems.html b/src/c3nav/editor/templates/editor/mapitems.html
index eea13548..999e0ffa 100644
--- a/src/c3nav/editor/templates/editor/mapitems.html
+++ b/src/c3nav/editor/templates/editor/mapitems.html
@@ -13,7 +13,8 @@
{% endif %}
- {{ item.name }} |
+
+ {% if item.title != item.name %}{{ item.title }} {{ item.name }}{% else %}{{ item.name }}{% endif %} |
{% if has_elevator %}
{{ item.elevator }} |
{% endif %}
diff --git a/src/c3nav/mapdata/migrations/0018_auto_20161212_1205.py b/src/c3nav/mapdata/migrations/0018_auto_20161212_1205.py
new file mode 100644
index 00000000..6343d1f6
--- /dev/null
+++ b/src/c3nav/mapdata/migrations/0018_auto_20161212_1205.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.4 on 2016-12-12 12:05
+from __future__ import unicode_literals
+
+import c3nav.mapdata.fields
+import c3nav.mapdata.models.interest
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('mapdata', '0017_auto_20161208_2039'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='AreaOfInterest',
+ 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()),
+ ('titles', c3nav.mapdata.fields.JSONField()),
+ ],
+ options={
+ 'default_related_name': 'areasofinterest',
+ 'verbose_name_plural': 'Areas of Interest',
+ 'verbose_name': 'Area of Interest',
+ },
+ bases=(models.Model, c3nav.mapdata.models.interest.MapItemOfInterestMixin),
+ ),
+ migrations.CreateModel(
+ name='GroupOfInterest',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.SlugField(unique=True, verbose_name='Name')),
+ ('titles', c3nav.mapdata.fields.JSONField()),
+ ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='groupsofinterest', to='mapdata.Package', verbose_name='map package')),
+ ],
+ options={
+ 'default_related_name': 'groupsofinterest',
+ 'verbose_name_plural': 'Groups of Interest',
+ 'verbose_name': 'Group of Interest',
+ },
+ bases=(models.Model, c3nav.mapdata.models.interest.MapItemOfInterestMixin),
+ ),
+ migrations.AddField(
+ model_name='areaofinterest',
+ name='groups',
+ field=models.ManyToManyField(blank=True, related_name='areasofinterest', to='mapdata.GroupOfInterest', verbose_name='Groups of Interest'),
+ ),
+ migrations.AddField(
+ model_name='areaofinterest',
+ name='level',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='areasofinterest', to='mapdata.Level', verbose_name='level'),
+ ),
+ migrations.AddField(
+ model_name='areaofinterest',
+ name='package',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='areasofinterest', to='mapdata.Package', verbose_name='map package'),
+ ),
+ ]
diff --git a/src/c3nav/mapdata/models/__init__.py b/src/c3nav/mapdata/models/__init__.py
index 0b53072e..ad8be331 100644
--- a/src/c3nav/mapdata/models/__init__.py
+++ b/src/c3nav/mapdata/models/__init__.py
@@ -3,3 +3,4 @@ from .package import Package # noqa
from .source import Source # noqa
from .collections import Elevator # noqa
from .geometry import GeometryMapItemWithLevel, GEOMETRY_MAPITEM_TYPES # noqa
+from .interest import AreaOfInterest, GroupOfInterest # noqa
diff --git a/src/c3nav/mapdata/models/base.py b/src/c3nav/mapdata/models/base.py
index 7dcc7edd..c6551b33 100644
--- a/src/c3nav/mapdata/models/base.py
+++ b/src/c3nav/mapdata/models/base.py
@@ -3,6 +3,7 @@ from collections import OrderedDict
from django.db import models
from django.db.models.base import ModelBase
from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import get_language
from c3nav.mapdata.lastupdate import set_last_mapdata_update
@@ -23,6 +24,15 @@ class MapItem(models.Model, metaclass=MapItemMeta):
EditorForm = None
+ @property
+ def title(self):
+ if not hasattr(self, 'titles'):
+ return self.name
+ lang = get_language()
+ if lang in self.titles:
+ return self.titles[lang]
+ return next(iter(self.titles.values())) if self.titles else self.name
+
@classmethod
def get_path_prefix(cls):
return cls._meta.default_related_name + '/'
diff --git a/src/c3nav/mapdata/models/geometry.py b/src/c3nav/mapdata/models/geometry.py
index 1cfce748..70c49c5f 100644
--- a/src/c3nav/mapdata/models/geometry.py
+++ b/src/c3nav/mapdata/models/geometry.py
@@ -32,10 +32,6 @@ class GeometryMapItem(MapItem, metaclass=GeometryMapItemMeta):
class Meta:
abstract = True
- @property
- def title(self):
- return self.name
-
@classmethod
def fromfile(cls, data, file_path):
kwargs = super().fromfile(data, file_path)
diff --git a/src/c3nav/mapdata/models/interest.py b/src/c3nav/mapdata/models/interest.py
new file mode 100644
index 00000000..5a4418c4
--- /dev/null
+++ b/src/c3nav/mapdata/models/interest.py
@@ -0,0 +1,82 @@
+from collections import OrderedDict
+
+from django.db import models
+from django.utils.translation import ugettext_lazy as _
+
+from c3nav.mapdata.fields import JSONField
+from c3nav.mapdata.models.base import MapItem
+from c3nav.mapdata.models.geometry import GeometryMapItemWithLevel
+
+
+# noinspection PyUnresolvedReferences
+class MapItemOfInterestMixin:
+ def get_geojson_properties(self):
+ result = super().get_geojson_properties()
+ result['titles'] = OrderedDict(sorted(self.titles.items()))
+ return result
+
+ @classmethod
+ def fromfile(cls, data, file_path):
+ kwargs = super().fromfile(data, file_path)
+
+ if 'titles' not in data:
+ raise ValueError('missing titles.')
+ titles = data['titles']
+ if not isinstance(titles, dict):
+ raise ValueError('Invalid titles format.')
+ if any(not isinstance(lang, str) for lang in titles.keys()):
+ raise ValueError('titles: All languages have to be strings.')
+ if any(not isinstance(title, str) for title in titles.values()):
+ raise ValueError('titles: All titles have to be strings.')
+ if any(not title for title in titles.values()):
+ raise ValueError('titles: Titles must not be empty strings.')
+ kwargs['titles'] = titles
+ return kwargs
+
+ def tofile(self):
+ result = super().tofile()
+ result['titles'] = OrderedDict(sorted(self.titles.items()))
+ return result
+
+
+class GroupOfInterest(MapItem, MapItemOfInterestMixin):
+ titles = JSONField()
+
+ class Meta:
+ verbose_name = _('Group of Interest')
+ verbose_name_plural = _('Groups of Interest')
+ default_related_name = 'groupsofinterest'
+
+
+class AreaOfInterest(GeometryMapItemWithLevel, MapItemOfInterestMixin):
+ titles = JSONField()
+ groups = models.ManyToManyField(GroupOfInterest, verbose_name=_('Groups of Interest'), blank=True)
+
+ geomtype = 'polygon'
+
+ class Meta:
+ verbose_name = _('Area of Interest')
+ verbose_name_plural = _('Areas of Interest')
+ default_related_name = 'areasofinterest'
+
+ @classmethod
+ def fromfile(cls, data, file_path):
+ kwargs = super().fromfile(data, file_path)
+
+ groups = data.get('groups', [])
+ if not isinstance(groups, list):
+ raise TypeError('groups has to be a list')
+ kwargs['groups'] = groups
+
+ return kwargs
+
+ def get_geojson_properties(self):
+ result = super().get_geojson_properties()
+ result['groups'] = tuple(self.groups.all().order_by('name').values_list('name', flat=True))
+ return result
+
+ def tofile(self):
+ result = super().tofile()
+ result['groups'] = sorted(self.groups.all().order_by('name').values_list('name', flat=True))
+ result.move_to_end('geometry')
+ return result
diff --git a/src/c3nav/mapdata/packageio/const.py b/src/c3nav/mapdata/packageio/const.py
index 144a88da..4e68f9c2 100644
--- a/src/c3nav/mapdata/packageio/const.py
+++ b/src/c3nav/mapdata/packageio/const.py
@@ -1,7 +1,8 @@
-from c3nav.mapdata.models import Level, Package, Source
+from c3nav.mapdata.models import AreaOfInterest, GroupOfInterest, Level, Package, Source
from c3nav.mapdata.models.collections import Elevator
from c3nav.mapdata.models.geometry import (Building, Door, ElevatorLevel, Hole, LevelConnector, LineObstacle, Obstacle,
Outside, Room, Stair)
ordered_models = (Package, Level, LevelConnector, Source, Building, Room, Outside, Door, Obstacle, Hole)
ordered_models += (Elevator, ElevatorLevel, LineObstacle, Stair)
+ordered_models += (GroupOfInterest, AreaOfInterest)
diff --git a/src/c3nav/mapdata/packageio/read.py b/src/c3nav/mapdata/packageio/read.py
index fe6fb5c3..78b6dfbe 100644
--- a/src/c3nav/mapdata/packageio/read.py
+++ b/src/c3nav/mapdata/packageio/read.py
@@ -7,7 +7,7 @@ from collections import OrderedDict
from django.conf import settings
from django.core.management import CommandError
-from c3nav.mapdata.models import Elevator, Level, Package
+from c3nav.mapdata.models import AreaOfInterest, Elevator, GroupOfInterest, Level, Package
from c3nav.mapdata.models.geometry import LevelConnector
from c3nav.mapdata.packageio.const import ordered_models
@@ -165,6 +165,11 @@ class ReaderItem:
levels = [self.reader.saved_items[Level][name].obj.pk for name in self.data['levels']]
self.data.pop('levels')
+ groups = []
+ if self.model == AreaOfInterest:
+ groups = [self.reader.saved_items[GroupOfInterest][name].obj.pk for name in self.data['groups']]
+ self.data.pop('groups')
+
# Change name references to the referenced object
for name, model in self.relations.items():
if name in self.data:
@@ -186,3 +191,8 @@ class ReaderItem:
self.obj.levels.clear()
for level in levels:
self.obj.levels.add(level)
+
+ if groups:
+ self.obj.groups.clear()
+ for group in groups:
+ self.obj.groups.add(group)