implement label settings
This commit is contained in:
parent
3ce5e663c5
commit
ecd7cc5637
8 changed files with 180 additions and 33 deletions
|
@ -3,6 +3,7 @@ import operator
|
||||||
import os
|
import os
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
from operator import attrgetter
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
@ -124,8 +125,18 @@ class EditorFormBase(I18nModelFormMixin, ModelForm):
|
||||||
help_text=category.help_text)
|
help_text=category.help_text)
|
||||||
self.fields[name] = field
|
self.fields[name] = field
|
||||||
|
|
||||||
|
if 'label_settings' in self.fields:
|
||||||
|
self.fields.move_to_end('label_settings')
|
||||||
|
|
||||||
|
for field in tuple(self.fields.keys()):
|
||||||
|
if field.startswith('label_override'):
|
||||||
|
self.fields.move_to_end(field)
|
||||||
|
|
||||||
if 'category' in self.fields:
|
if 'category' in self.fields:
|
||||||
self.fields['category'].label_from_instance = lambda obj: obj.title
|
self.fields['category'].label_from_instance = attrgetter('title')
|
||||||
|
|
||||||
|
if 'label_settings' in self.fields:
|
||||||
|
self.fields['label_settings'].label_from_instance = attrgetter('title')
|
||||||
|
|
||||||
if 'access_restriction' in self.fields:
|
if 'access_restriction' in self.fields:
|
||||||
AccessRestriction = self.request.changeset.wrap_model('AccessRestriction')
|
AccessRestriction = self.request.changeset.wrap_model('AccessRestriction')
|
||||||
|
@ -268,12 +279,12 @@ class EditorFormBase(I18nModelFormMixin, ModelForm):
|
||||||
def create_editor_form(editor_model):
|
def create_editor_form(editor_model):
|
||||||
possible_fields = ['slug', 'name', 'title', 'title_plural', 'help_text', 'icon', 'join_edges', 'up_separate',
|
possible_fields = ['slug', 'name', 'title', 'title_plural', 'help_text', 'icon', 'join_edges', 'up_separate',
|
||||||
'walk', 'ordering', 'category', 'width', 'groups', 'color', 'priority', 'hierarchy', 'icon_name',
|
'walk', 'ordering', 'category', 'width', 'groups', 'color', 'priority', 'hierarchy', 'icon_name',
|
||||||
'show_labels', 'show_label',
|
|
||||||
'base_altitude', 'waytype', 'access_restriction', 'height', 'default_height', 'door_height',
|
'base_altitude', 'waytype', 'access_restriction', 'height', 'default_height', 'door_height',
|
||||||
'outside', 'can_search', 'can_describe', 'geometry', 'single', 'altitude', 'short_label',
|
'outside', 'can_search', 'can_describe', 'geometry', 'single', 'altitude', 'short_label',
|
||||||
'origin_space', 'target_space', 'data', 'comment', 'slow_down_factor',
|
'origin_space', 'target_space', 'data', 'comment', 'slow_down_factor',
|
||||||
'extra_seconds', 'speed', 'description', 'speed_up', 'description_up', 'enter_description',
|
'extra_seconds', 'speed', 'description', 'speed_up', 'description_up', 'enter_description',
|
||||||
'level_change_description', 'base_mapdata_accessible',
|
'level_change_description', 'base_mapdata_accessible',
|
||||||
|
'label_settings', 'label_override', 'min_zoom', 'max_zoom', 'font_size',
|
||||||
'allow_levels', 'allow_spaces', 'allow_areas', 'allow_pois', 'left', 'top', 'right', 'bottom']
|
'allow_levels', 'allow_spaces', 'allow_areas', 'allow_pois', 'left', 'top', 'right', 'bottom']
|
||||||
field_names = [field.name for field in editor_model._meta.get_fields() if not field.one_to_many]
|
field_names = [field.name for field in editor_model._meta.get_fields() if not field.one_to_many]
|
||||||
existing_fields = [name for name in possible_fields if name in field_names]
|
existing_fields = [name for name in possible_fields if name in field_names]
|
||||||
|
|
|
@ -57,6 +57,7 @@ urlpatterns.extend(add_editor_urls('WayType'))
|
||||||
urlpatterns.extend(add_editor_urls('AccessRestriction'))
|
urlpatterns.extend(add_editor_urls('AccessRestriction'))
|
||||||
urlpatterns.extend(add_editor_urls('AccessRestrictionGroup'))
|
urlpatterns.extend(add_editor_urls('AccessRestrictionGroup'))
|
||||||
urlpatterns.extend(add_editor_urls('Source'))
|
urlpatterns.extend(add_editor_urls('Source'))
|
||||||
|
urlpatterns.extend(add_editor_urls('LabelSettings'))
|
||||||
urlpatterns.extend(add_editor_urls('Building', 'Level'))
|
urlpatterns.extend(add_editor_urls('Building', 'Level'))
|
||||||
urlpatterns.extend(add_editor_urls('Space', 'Level', explicit_edit=True))
|
urlpatterns.extend(add_editor_urls('Space', 'Level', explicit_edit=True))
|
||||||
urlpatterns.extend(add_editor_urls('Door', 'Level'))
|
urlpatterns.extend(add_editor_urls('Door', 'Level'))
|
||||||
|
|
|
@ -55,6 +55,7 @@ def main_index(request):
|
||||||
child_model(request, 'WayType'),
|
child_model(request, 'WayType'),
|
||||||
child_model(request, 'AccessRestriction'),
|
child_model(request, 'AccessRestriction'),
|
||||||
child_model(request, 'AccessRestrictionGroup'),
|
child_model(request, 'AccessRestrictionGroup'),
|
||||||
|
child_model(request, 'LabelSettings'),
|
||||||
child_model(request, 'Source'),
|
child_model(request, 'Source'),
|
||||||
],
|
],
|
||||||
}, fields=('can_create_level', 'child_models'))
|
}, fields=('can_create_level', 'child_models'))
|
||||||
|
|
97
src/c3nav/mapdata/migrations/0075_label_settings.py
Normal file
97
src/c3nav/mapdata/migrations/0075_label_settings.py
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
# Generated by Django 2.2.8 on 2019-12-21 23:27
|
||||||
|
|
||||||
|
import c3nav.mapdata.fields
|
||||||
|
from decimal import Decimal
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('mapdata', '0074_show_labels'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='LabelSettings',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('title', c3nav.mapdata.fields.I18nField(fallback_any=True, plural_name='titles', verbose_name='Title')),
|
||||||
|
('min_zoom', models.DecimalField(decimal_places=1, default=-10, max_digits=3, validators=[django.core.validators.MinValueValidator(Decimal('-10')), django.core.validators.MaxValueValidator(Decimal('10'))], verbose_name='min zoom')),
|
||||||
|
('max_zoom', models.DecimalField(decimal_places=1, default=10, max_digits=3, validators=[django.core.validators.MinValueValidator(Decimal('-10')), django.core.validators.MaxValueValidator(Decimal('10'))], verbose_name='max zoom')),
|
||||||
|
('font_size', models.IntegerField(default=12, validators=[django.core.validators.MinValueValidator(12), django.core.validators.MaxValueValidator(30)], verbose_name='font size')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Label Settings',
|
||||||
|
'verbose_name_plural': 'Label Settings',
|
||||||
|
'default_related_name': 'labelsettings',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='area',
|
||||||
|
name='show_label',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='level',
|
||||||
|
name='show_label',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='locationgroup',
|
||||||
|
name='show_labels',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='poi',
|
||||||
|
name='show_label',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='space',
|
||||||
|
name='show_label',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='area',
|
||||||
|
name='label_override',
|
||||||
|
field=c3nav.mapdata.fields.I18nField(blank=True, fallback_any=True, plural_name='label_overrides', verbose_name='Label override'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='level',
|
||||||
|
name='label_override',
|
||||||
|
field=c3nav.mapdata.fields.I18nField(blank=True, fallback_any=True, plural_name='label_overrides', verbose_name='Label override'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='poi',
|
||||||
|
name='label_override',
|
||||||
|
field=c3nav.mapdata.fields.I18nField(blank=True, fallback_any=True, plural_name='label_overrides', verbose_name='Label override'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='space',
|
||||||
|
name='label_override',
|
||||||
|
field=c3nav.mapdata.fields.I18nField(blank=True, fallback_any=True, plural_name='label_overrides', verbose_name='Label override'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='area',
|
||||||
|
name='label_settings',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='areas', to='mapdata.LabelSettings', verbose_name='label settings'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='level',
|
||||||
|
name='label_settings',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='levels', to='mapdata.LabelSettings', verbose_name='label settings'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='locationgroup',
|
||||||
|
name='label_settings',
|
||||||
|
field=models.ForeignKey(help_text='unless location specifies otherwise', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='locationgroups', to='mapdata.LabelSettings', verbose_name='label settings'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='poi',
|
||||||
|
name='label_settings',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='pois', to='mapdata.LabelSettings', verbose_name='label settings'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='space',
|
||||||
|
name='label_settings',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='spaces', to='mapdata.LabelSettings', verbose_name='label settings'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,7 +1,8 @@
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
|
from decimal import Decimal
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
|
|
||||||
from django.core.validators import RegexValidator
|
from django.core.validators import RegexValidator, MinValueValidator, MaxValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import FieldDoesNotExist, Prefetch
|
from django.db.models import FieldDoesNotExist, Prefetch
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
@ -103,7 +104,7 @@ class Location(LocationSlug, AccessRestrictionMixin, TitledMixin, models.Model):
|
||||||
result = super().serialize(detailed=detailed, **kwargs)
|
result = super().serialize(detailed=detailed, **kwargs)
|
||||||
if not detailed:
|
if not detailed:
|
||||||
fields = ('id', 'type', 'slug', 'title', 'subtitle', 'icon', 'point', 'bounds', 'grid_square',
|
fields = ('id', 'type', 'slug', 'title', 'subtitle', 'icon', 'point', 'bounds', 'grid_square',
|
||||||
'locations', 'on_top_of', 'show_label')
|
'locations', 'on_top_of', 'label_settings', 'label_override')
|
||||||
result = {name: result[name] for name in fields if name in result}
|
result = {name: result[name] for name in fields if name in result}
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -158,14 +159,10 @@ class Location(LocationSlug, AccessRestrictionMixin, TitledMixin, models.Model):
|
||||||
|
|
||||||
|
|
||||||
class SpecificLocation(Location, models.Model):
|
class SpecificLocation(Location, models.Model):
|
||||||
SHOW_LABEL_OPTIONS = (
|
|
||||||
('inherit', _('inherit from groups (default)')),
|
|
||||||
('show_text', _('yes, show the title')),
|
|
||||||
('no', _('don\'t show')),
|
|
||||||
)
|
|
||||||
|
|
||||||
groups = models.ManyToManyField('mapdata.LocationGroup', verbose_name=_('Location Groups'), blank=True)
|
groups = models.ManyToManyField('mapdata.LocationGroup', verbose_name=_('Location Groups'), blank=True)
|
||||||
show_label = models.CharField(_('show label'), max_length=16, default='inherit', choices=SHOW_LABEL_OPTIONS)
|
label_settings = models.ForeignKey('mapdata.LabelSettings', null=True, on_delete=models.PROTECT,
|
||||||
|
verbose_name=_('label settings'))
|
||||||
|
label_override = I18nField(_('Label override'), plural_name='label_overrides', blank=True, fallback_any=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
@ -184,18 +181,22 @@ class SpecificLocation(Location, models.Model):
|
||||||
for category, items in groups.items()
|
for category, items in groups.items()
|
||||||
if getattr(category, 'allow_'+self.__class__._meta.default_related_name)}
|
if getattr(category, 'allow_'+self.__class__._meta.default_related_name)}
|
||||||
result['groups'] = groups
|
result['groups'] = groups
|
||||||
result['show_label'] = self.get_show_label()
|
|
||||||
|
label_settings = self.get_label_settings()
|
||||||
|
if label_settings:
|
||||||
|
result['label_settings'] = label_settings.serialize(detailed=False)
|
||||||
|
if self.label_overrides:
|
||||||
|
# todo: what if only one language is set?
|
||||||
|
result['label_override'] = self.label_override
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_show_label(self):
|
def get_label_settings(self):
|
||||||
if self.show_label == 'inherit':
|
if self.label_settings:
|
||||||
for group in self.groups.all():
|
return self.label_settings
|
||||||
if group.show_labels != 'no':
|
for group in self.groups.all():
|
||||||
return group.show_labels
|
if group.label_settings:
|
||||||
return None
|
return group.label_settings
|
||||||
if self.show_label == 'no':
|
return None
|
||||||
return None
|
|
||||||
return self.show_label
|
|
||||||
|
|
||||||
def details_display(self, **kwargs):
|
def details_display(self, **kwargs):
|
||||||
result = super().details_display(**kwargs)
|
result = super().details_display(**kwargs)
|
||||||
|
@ -304,17 +305,13 @@ class LocationGroupManager(models.Manager):
|
||||||
|
|
||||||
|
|
||||||
class LocationGroup(Location, models.Model):
|
class LocationGroup(Location, models.Model):
|
||||||
SHOW_LABELS_OPTIONS = (
|
|
||||||
('no', _('no (default)')),
|
|
||||||
('show_text', _('yes, show the title')),
|
|
||||||
)
|
|
||||||
|
|
||||||
category = models.ForeignKey(LocationGroupCategory, related_name='groups', on_delete=models.PROTECT,
|
category = models.ForeignKey(LocationGroupCategory, related_name='groups', on_delete=models.PROTECT,
|
||||||
verbose_name=_('Category'))
|
verbose_name=_('Category'))
|
||||||
priority = models.IntegerField(default=0, db_index=True)
|
priority = models.IntegerField(default=0, db_index=True)
|
||||||
hierarchy = models.IntegerField(default=0, db_index=True, verbose_name=_('hierarchy'))
|
hierarchy = models.IntegerField(default=0, db_index=True, verbose_name=_('hierarchy'))
|
||||||
show_labels = models.CharField(_('show labels'), max_length=16, default='no', choices=SHOW_LABELS_OPTIONS,
|
label_settings = models.ForeignKey('mapdata.LabelSettings', null=True, on_delete=models.PROTECT,
|
||||||
help_text=_('unless location specifies otherwise'))
|
verbose_name=_('label settings'),
|
||||||
|
help_text=_('unless location specifies otherwise'))
|
||||||
color = models.CharField(null=True, blank=True, max_length=32, verbose_name=_('background color'))
|
color = models.CharField(null=True, blank=True, max_length=32, verbose_name=_('background color'))
|
||||||
|
|
||||||
objects = LocationGroupManager()
|
objects = LocationGroupManager()
|
||||||
|
@ -418,3 +415,32 @@ class LocationRedirect(LocationSlug):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
default_related_name = 'redirect'
|
default_related_name = 'redirect'
|
||||||
|
|
||||||
|
|
||||||
|
class LabelSettings(SerializableMixin, models.Model):
|
||||||
|
title = I18nField(_('Title'), plural_name='titles', fallback_any=True)
|
||||||
|
min_zoom = models.DecimalField(_('min zoom'), max_digits=3, decimal_places=1, default=-10,
|
||||||
|
validators=[MinValueValidator(Decimal('-10')),
|
||||||
|
MaxValueValidator(Decimal('10'))])
|
||||||
|
max_zoom = models.DecimalField(_('max zoom'), max_digits=3, decimal_places=1, default=10,
|
||||||
|
validators=[MinValueValidator(Decimal('-10')),
|
||||||
|
MaxValueValidator(Decimal('10'))])
|
||||||
|
font_size = models.IntegerField(_('font size'), default=12,
|
||||||
|
validators=[MinValueValidator(12),
|
||||||
|
MaxValueValidator(30)])
|
||||||
|
|
||||||
|
def _serialize(self, detailed=True, **kwargs):
|
||||||
|
result = super()._serialize(detailed=detailed, **kwargs)
|
||||||
|
if detailed:
|
||||||
|
result['titles'] = self.titles
|
||||||
|
if self.min_zoom > -10:
|
||||||
|
result['min_zoom'] = self.min_zoom
|
||||||
|
if self.max_zoom < 10:
|
||||||
|
result['max_zoom'] = self.max_zoom
|
||||||
|
result['font_size'] = self.font_size
|
||||||
|
return result
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('Label Settings')
|
||||||
|
verbose_name_plural = _('Label Settings')
|
||||||
|
default_related_name = 'labelsettings'
|
||||||
|
|
|
@ -78,6 +78,8 @@ def round_polygon(coordinates):
|
||||||
# round each ring on it's own and remove rings that are invalid
|
# round each ring on it's own and remove rings that are invalid
|
||||||
# if the exterior ring is invalid, return and empty polygon
|
# if the exterior ring is invalid, return and empty polygon
|
||||||
coordinates = tuple(round_coordinates(ring) for ring in coordinates)
|
coordinates = tuple(round_coordinates(ring) for ring in coordinates)
|
||||||
|
if not coordinates:
|
||||||
|
return coordinates
|
||||||
exterior, *interiors = coordinates
|
exterior, *interiors = coordinates
|
||||||
if not check_ring(exterior):
|
if not check_ring(exterior):
|
||||||
return ()
|
return ()
|
||||||
|
|
|
@ -579,6 +579,10 @@ main.show-options #resultswrapper #route-options {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.location-label-text {
|
.location-label-text {
|
||||||
|
background-color: rgba(255, 255, 255, 0.6);
|
||||||
|
line-height: 100%;
|
||||||
|
padding: 2px 3px;
|
||||||
|
border-radius: 2px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
transform: translateX(-50%) translateY(-50%);
|
transform: translateX(-50%) translateY(-50%);
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,7 +108,7 @@ c3nav = {
|
||||||
location.match = ' ' + location.title_words.join(' ') + ' ' + location.subtitle_words.join(' ') + ' ' + location.slug;
|
location.match = ' ' + location.title_words.join(' ') + ' ' + location.subtitle_words.join(' ') + ' ' + location.slug;
|
||||||
locations.push(location);
|
locations.push(location);
|
||||||
locations_by_id[location.id] = location;
|
locations_by_id[location.id] = location;
|
||||||
if (location.point && location.show_label) {
|
if (location.point && location.label_settings) {
|
||||||
location.label = c3nav._build_location_label(location);
|
location.label = c3nav._build_location_label(location);
|
||||||
if (!(location.point[0] in labels)) labels[location.point[0]] = [];
|
if (!(location.point[0] in labels)) labels[location.point[0]] = [];
|
||||||
labels[location.point[0]].push(location);
|
labels[location.point[0]].push(location);
|
||||||
|
@ -303,12 +303,15 @@ c3nav = {
|
||||||
update_location_labels: function() {
|
update_location_labels: function() {
|
||||||
c3nav._labelLayer.clearLayers();
|
c3nav._labelLayer.clearLayers();
|
||||||
var labels = c3nav.labels[c3nav._levelControl.currentLevel],
|
var labels = c3nav.labels[c3nav._levelControl.currentLevel],
|
||||||
bounds = c3nav.map.getBounds();
|
bounds = c3nav.map.getBounds(),
|
||||||
|
zoom = c3nav.map.getZoom();
|
||||||
if (!labels) return;
|
if (!labels) return;
|
||||||
|
|
||||||
for (var location of labels) {
|
for (var location of labels) {
|
||||||
if (bounds.contains(location.label.getLatLng())) {
|
if (bounds.contains(location.label.getLatLng()) &&
|
||||||
c3nav._labelLayer._maybeAddLayerToRBush(location.label);
|
(location.label_settings.min_zoom || -10) < zoom &&
|
||||||
|
(location.label_settings.max_zoom || 10) > zoom) {
|
||||||
|
c3nav._labelLayer._maybeAddLayerToRBush(location.label);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -801,9 +804,11 @@ c3nav = {
|
||||||
return html[0].outerHTML;
|
return html[0].outerHTML;
|
||||||
},
|
},
|
||||||
_build_location_label: function(location) {
|
_build_location_label: function(location) {
|
||||||
|
var html = $('<div class="location-label-text">').text(location.label_override || location.title);
|
||||||
|
html.css('font-size', location.label_settings.font_size+'px');
|
||||||
return L.marker(L.GeoJSON.coordsToLatLng(location.point.slice(1)), {
|
return L.marker(L.GeoJSON.coordsToLatLng(location.point.slice(1)), {
|
||||||
icon: L.divIcon({
|
icon: L.divIcon({
|
||||||
html: $('<div class="location-label-text">').text(location.title)[0].outerHTML,
|
html: html[0].outerHTML,
|
||||||
iconSize: null,
|
iconSize: null,
|
||||||
className: 'location-label'
|
className: 'location-label'
|
||||||
}),
|
}),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue