add level_index so short_label can look nicer
This commit is contained in:
parent
6167c92e98
commit
fe98f5e618
14 changed files with 89 additions and 31 deletions
|
@ -401,9 +401,9 @@ def create_editor_form(editor_model):
|
|||
'slug', 'name', 'title', 'title_plural', 'help_text', 'position_secret', 'icon', 'join_edges',
|
||||
'up_separate', 'bssid', 'main_point', 'external_url', 'hub_import_type', 'walk', 'ordering',
|
||||
'category', 'width', 'groups', 'height', 'color', 'in_legend', 'priority', 'hierarchy', 'icon_name',
|
||||
'base_altitude', 'intermediate',
|
||||
'waytype', 'access_restriction', 'default_height', 'door_height', 'outside', 'can_search',
|
||||
'can_describe', 'geometry', 'single', 'altitude', 'short_label', 'origin_space', 'target_space', 'data',
|
||||
'base_altitude', 'intermediate', 'waytype', 'access_restriction', 'default_height', 'door_height', 'outside',
|
||||
'can_search', 'can_describe', 'geometry', 'single', 'altitude', 'level_index', 'short_label',
|
||||
'origin_space', 'target_space', 'data',
|
||||
'comment', 'slow_down_factor', 'groundaltitude', 'node_number', 'wifi_bssid', 'bluetooth_address', "group",
|
||||
'ibeacon_uuid', 'ibeacon_major', 'ibeacon_minor', 'uwb_address', 'extra_seconds', 'speed', 'can_report_missing',
|
||||
"can_report_mistake", 'description', 'speed_up', 'description_up', 'avoid_by_default', 'report_help_text',
|
||||
|
|
|
@ -20,9 +20,9 @@ class Command(BaseCommand):
|
|||
return Level.objects.filter(on_top_of__isnull=True)
|
||||
|
||||
values = set(v for v in value.split(',') if v)
|
||||
levels = Level.objects.filter(on_top_of__isnull=True, short_label__in=values)
|
||||
levels = Level.objects.filter(on_top_of__isnull=True, level___in=values)
|
||||
|
||||
not_found = values - set(level.short_label for level in levels)
|
||||
not_found = values - set(level.level_index for level in levels)
|
||||
if not_found:
|
||||
raise argparse.ArgumentTypeError(
|
||||
ngettext_lazy('Unknown level: %s', 'Unknown levels: %s', len(not_found)) % ', '.join(not_found)
|
||||
|
@ -122,7 +122,7 @@ class Command(BaseCommand):
|
|||
scale=options['scale'], full_levels=options['full_levels'],
|
||||
min_width=options['min_width'])
|
||||
|
||||
name = options['name'] or ('level_%s' % level.short_label)
|
||||
name = options['name'] or ('level_%s' % level.level_index)
|
||||
filename = settings.RENDER_ROOT / ('%s.%s' % (name, options['filetype']))
|
||||
|
||||
if options['filetype'] == 'svg':
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# Generated by Django 5.0.8 on 2024-12-19 08:18
|
||||
|
||||
import django.core.validators
|
||||
import re
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def set_level_index(apps, schema_editor):
|
||||
Level = apps.get_model('mapdata', 'Level')
|
||||
for level in Level.objects.all():
|
||||
level.level_index = level.short_label
|
||||
level.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mapdata', '0120_level_intermediate'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='level',
|
||||
name='level_index',
|
||||
field=models.CharField(help_text='used for coordinates', max_length=20, null=True, unique=True, validators=[django.core.validators.RegexValidator(re.compile('^[-a-zA-Z0-9._]+\\Z'), 'Enter a valid “level index” consisting of letters, numbers, underscores, dots or hyphens.', 'invalid')], verbose_name='level index')
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='level',
|
||||
name='short_label',
|
||||
field=models.CharField(help_text='used for the level selector', max_length=20, unique=True, verbose_name='short label'),
|
||||
),
|
||||
migrations.RunPython(set_level_index, migrations.RunPython.noop),
|
||||
migrations.AlterField(
|
||||
model_name='level',
|
||||
name='level_index',
|
||||
field=models.CharField(help_text='used for coordinates', max_length=20, unique=True, validators=[django.core.validators.RegexValidator(re.compile('^[-a-zA-Z0-9._]+\\Z'), 'Enter a valid “level index” consisting of letters, numbers, underscores, dots or hyphens.', 'invalid')], verbose_name='level index')
|
||||
),
|
||||
]
|
|
@ -2,16 +2,26 @@ from decimal import Decimal
|
|||
from itertools import chain
|
||||
from operator import attrgetter
|
||||
|
||||
from django.core.validators import MinValueValidator
|
||||
from django.core.validators import MinValueValidator, RegexValidator
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.regex_helper import _lazy_re_compile
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from shapely.ops import unary_union
|
||||
|
||||
from c3nav.mapdata.models.locations import SpecificLocation
|
||||
|
||||
|
||||
level_index_re = _lazy_re_compile(r"^[-a-zA-Z0-9._]+\Z")
|
||||
validate_level_index = RegexValidator(
|
||||
level_index_re,
|
||||
# Translators: "letters" means latin letters: a-z and A-Z.
|
||||
_("Enter a valid “level index” consisting of letters, numbers, underscores, dots or hyphens."),
|
||||
"invalid",
|
||||
)
|
||||
|
||||
|
||||
class Level(SpecificLocation, models.Model):
|
||||
"""
|
||||
A physical level of the map, containing building, spaces, doors…
|
||||
|
@ -26,7 +36,10 @@ class Level(SpecificLocation, models.Model):
|
|||
on_top_of = models.ForeignKey('mapdata.Level', null=True, on_delete=models.CASCADE,
|
||||
related_name='levels_on_top', verbose_name=_('on top of'))
|
||||
intermediate = models.BooleanField(_("intermediate level"), default=False)
|
||||
short_label = models.SlugField(max_length=20, verbose_name=_('short label'), unique=True)
|
||||
short_label = models.CharField(max_length=20, verbose_name=_('short label'), unique=True,
|
||||
help_text=_('used for the level selector'))
|
||||
level_index = models.CharField(max_length=20, verbose_name=_('level index'), unique=True,
|
||||
validators=[validate_level_index], help_text=_('used for coordinates'))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Level')
|
||||
|
|
|
@ -265,7 +265,7 @@ class BlenderEngine(Base3DEngine):
|
|||
holes = geoms.holes.difference(restricted_spaces)
|
||||
buildings = buildings.difference(holes)
|
||||
|
||||
self._add_polygon('Level %s' % geoms.short_label, buildings,
|
||||
self._add_polygon('Level %s' % geoms.level_index, buildings,
|
||||
geoms.lower_bound, geoms.upper_bound)
|
||||
self._set_last_polygon_to_main()
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@ class BaseLevelGeometries:
|
|||
|
||||
pk: int
|
||||
on_top_of_id: int | None
|
||||
level_index: str
|
||||
short_label: str
|
||||
base_altitude: int
|
||||
default_height: int
|
||||
|
@ -360,6 +361,7 @@ class SingleLevelGeometries(BaseLevelGeometries):
|
|||
pk=level.pk,
|
||||
on_top_of_id=level.on_top_of_id,
|
||||
short_label=level.short_label,
|
||||
level_index=level.level_index,
|
||||
base_altitude=base_altitude,
|
||||
default_height=default_height,
|
||||
door_height=door_height,
|
||||
|
|
|
@ -301,6 +301,7 @@ class LevelRenderData:
|
|||
pk=single_geoms.pk,
|
||||
on_top_of_id=single_geoms.on_top_of_id,
|
||||
short_label=single_geoms.short_label,
|
||||
level_index=single_geoms.level_index,
|
||||
base_altitude=single_geoms.base_altitude,
|
||||
default_height=single_geoms.default_height,
|
||||
door_height=single_geoms.door_height,
|
||||
|
|
|
@ -60,7 +60,7 @@ class MapRenderer:
|
|||
not_full_levels = engine.is_3d # always do non-full-levels until after the first primary level
|
||||
full_levels = self.full_levels and engine.is_3d
|
||||
for geoms in levels:
|
||||
engine.add_group('level_%s' % geoms.short_label)
|
||||
engine.add_group('level_%s' % geoms.level_index)
|
||||
|
||||
if geoms.pk == level_render_data.lowest_important_level:
|
||||
engine.darken(level_render_data.darken_area, much=level_render_data.darken_much)
|
||||
|
|
|
@ -32,6 +32,10 @@ class LevelSchema(SpecificLocationSchema, DjangoModelSchema):
|
|||
title="short label (for level selector)",
|
||||
description="unique among levels",
|
||||
)
|
||||
level_index: NonEmptyStr = APIField(
|
||||
title="level index (for coordinates)",
|
||||
description="unique among levels",
|
||||
)
|
||||
on_top_of: Union[
|
||||
Annotated[PositiveInt, APIField(title="level ID", description="level this level is on top of", example=1)],
|
||||
Annotated[None, APIField(title="null", description="this is a main level, not on top of any other")]
|
||||
|
|
12
src/c3nav/mapdata/utils/cache/stats.py
vendored
12
src/c3nav/mapdata/utils/cache/stats.py
vendored
|
@ -91,7 +91,7 @@ def convert_locate(data):
|
|||
pos = CustomLocation(measurement.space.level, measurement.geometry.x, measurement.geometry.y,
|
||||
permissions=set())
|
||||
space_slug = measurement.space.effective_slug
|
||||
level_label = measurement.space.level.short_label
|
||||
level_label = measurement.space.level.level_index
|
||||
grid_square = pos.grid_square if grid.enabled else None
|
||||
measurement_lookup[pos.pk] = (measurement.pk, grid_square, space_slug, level_label)
|
||||
result['by_measurement_id'][measurement.pk] = 0
|
||||
|
@ -138,7 +138,7 @@ def convert_location(data):
|
|||
|
||||
# fill up lists with zeros
|
||||
location_slugs = {}
|
||||
level_labels = {}
|
||||
level_indices = {}
|
||||
for location in LocationSlug.objects.all():
|
||||
location = location.get_child()
|
||||
if isinstance(location, LocationRedirect):
|
||||
|
@ -146,9 +146,9 @@ def convert_location(data):
|
|||
result['locations']['by_type'].setdefault(location.__class__.__name__.lower(), {})[location.effective_slug] = 0
|
||||
location_slugs[location.pk] = location.effective_slug
|
||||
if isinstance(location, Level):
|
||||
result['locations']['by_level'][location.short_label] = 0
|
||||
result['coordinates']['by_level'][location.short_label] = 0
|
||||
level_labels[location.pk] = location.short_label
|
||||
result['locations']['by_level'][location.level_index] = 0
|
||||
result['coordinates']['by_level'][location.level_index] = 0
|
||||
level_indices[location.pk] = location.short_label
|
||||
if isinstance(location, Space):
|
||||
result['locations']['by_space'][location.effective_slug] = 0
|
||||
result['coordinates']['by_space'][location.effective_slug] = 0
|
||||
|
@ -191,7 +191,7 @@ def convert_location(data):
|
|||
if hasattr(location, 'space_id'):
|
||||
result['locations']['by_space'][location_slugs[location.space_id]] += value
|
||||
if hasattr(location, 'level_id'):
|
||||
result['locations']['by_level'][level_labels[location.level_id]] += value
|
||||
result['locations']['by_level'][level_indices[location.level_id]] += value
|
||||
if hasattr(location, 'groups'):
|
||||
for group in location.groups.all():
|
||||
result['locations']['by_group'][location_slugs[group.pk]] += value
|
||||
|
|
|
@ -193,14 +193,14 @@ def locations_by_slug_for_request(request) -> Mapping[str, LocationSlug]:
|
|||
return locations
|
||||
|
||||
|
||||
def levels_by_short_label_for_request(request) -> Mapping[str, Level]:
|
||||
cache_key = 'mapdata:levels:by_short_label:%s' % AccessPermission.cache_key_for_request(request)
|
||||
def levels_by_level_index_for_request(request) -> Mapping[str, Level]:
|
||||
cache_key = 'mapdata:levels:by_level_index:%s' % AccessPermission.cache_key_for_request(request)
|
||||
levels = proxied_cache.get(cache_key, None)
|
||||
if levels is not None:
|
||||
return levels
|
||||
|
||||
levels = OrderedDict(
|
||||
(level.short_label, level)
|
||||
(level.level_index, level)
|
||||
for level in Level.qs_for_request(request).filter(on_top_of_id__isnull=True).order_by('base_altitude')
|
||||
)
|
||||
|
||||
|
@ -263,10 +263,10 @@ def get_location_by_slug_for_request(slug: str, request) -> Optional[Union[Locat
|
|||
|
||||
|
||||
def get_custom_location_for_request(slug: str, request):
|
||||
match = re.match(r'^c:(?P<level>[a-z0-9-_]+):(?P<x>-?\d+(\.\d+)?):(?P<y>-?\d+(\.\d+)?)$', slug)
|
||||
match = re.match(r'^c:(?P<level>[a-z0-9-_.]+):(?P<x>-?\d+(\.\d+)?):(?P<y>-?\d+(\.\d+)?)$', slug)
|
||||
if match is None:
|
||||
return None
|
||||
level = levels_by_short_label_for_request(request).get(match.group('level'))
|
||||
level = levels_by_level_index_for_request(request).get(match.group('level'))
|
||||
if not isinstance(level, Level):
|
||||
return None
|
||||
return CustomLocation(level, float(match.group('x')), float(match.group('y')),
|
||||
|
|
|
@ -12,7 +12,7 @@ class LocationConverter:
|
|||
|
||||
|
||||
class CoordinatesConverter:
|
||||
regex = r'[a-z0-9-_:]+:-?\d+(\.\d+)?:-?\d+(\.\d+)?'
|
||||
regex = r'[a-z0-9-_.]+:-?\d+(\.\d+)?:-?\d+(\.\d+)?'
|
||||
|
||||
def to_python(self, value):
|
||||
return value
|
||||
|
@ -25,7 +25,7 @@ AtPos = namedtuple('AtPos', ('level', 'x', 'y', 'zoom'))
|
|||
|
||||
|
||||
class AtPositionConverter:
|
||||
regex = r'(@[a-z0-9-_:]+,-?\d+(\.\d+)?,-?\d+(\.\d+)?,-?\d+(\.\d+)?)?'
|
||||
regex = r'(@[a-z0-9-_.]+,-?\d+(\.\d+)?,-?\d+(\.\d+)?,-?\d+(\.\d+)?)?'
|
||||
|
||||
def to_python(self, value):
|
||||
if not value:
|
||||
|
|
|
@ -758,7 +758,7 @@ c3nav = {
|
|||
url += 'options/'
|
||||
}
|
||||
if (state.center) {
|
||||
url += '@' + String(c3nav.level_labels_by_id[state.level]) + ',' + String(state.center[0]) + ',' + String(state.center[1]) + ',' + String(state.zoom);
|
||||
url += '@' + String(c3nav.level_indices_by_id[state.level]) + ',' + String(state.center[0]) + ',' + String(state.center[1]) + ',' + String(state.zoom);
|
||||
}
|
||||
return url
|
||||
},
|
||||
|
@ -1414,9 +1414,9 @@ c3nav = {
|
|||
c3nav.initial_level = 0
|
||||
}
|
||||
|
||||
c3nav.level_labels_by_id = {};
|
||||
c3nav.level_indices_by_id = {};
|
||||
for (i = 0; i < c3nav.levels.length; i++) {
|
||||
c3nav.level_labels_by_id[c3nav.levels[i][0]] = c3nav.levels[i][1];
|
||||
c3nav.level_indices_by_id[c3nav.levels[i][0]] = c3nav.levels[i][1];
|
||||
}
|
||||
|
||||
minZoom = Math.log2(Math.max(0.25, Math.min(
|
||||
|
@ -1468,7 +1468,7 @@ c3nav = {
|
|||
c3nav._labelLayer = L.LayerGroup.collision({margin: 5}).addTo(c3nav.map);
|
||||
for (i = c3nav.levels.length - 1; i >= 0; i--) {
|
||||
var level = c3nav.levels[i];
|
||||
var layerGroup = c3nav._levelControl.addLevel(level[0], level[1]);
|
||||
var layerGroup = c3nav._levelControl.addLevel(level[0], level[2]);
|
||||
c3nav._detailLayers[level[0]] = L.layerGroup().addTo(layerGroup);
|
||||
c3nav._locationLayers[level[0]] = L.layerGroup().addTo(layerGroup);
|
||||
c3nav._routeLayers[level[0]] = L.layerGroup().addTo(layerGroup);
|
||||
|
@ -1572,7 +1572,7 @@ c3nav = {
|
|||
},
|
||||
_latlng_to_name: function (latlng) {
|
||||
var level = c3nav.current_level();
|
||||
return 'c:' + String(c3nav.level_labels_by_id[level]) + ':' + Math.round(latlng.lng * 100) / 100 + ':' + Math.round(latlng.lat * 100) / 100;
|
||||
return 'c:' + String(c3nav.level_indices_by_id[level]) + ':' + Math.round(latlng.lng * 100) / 100 + ':' + Math.round(latlng.lat * 100) / 100;
|
||||
},
|
||||
_click_anywhere_load: function (nearby, latlng) {
|
||||
if (!c3nav._click_anywhere_popup && !latlng) return;
|
||||
|
|
|
@ -40,7 +40,7 @@ from c3nav.mapdata.models.locations import (LocationGroup, LocationRedirect, Pos
|
|||
from c3nav.mapdata.models.report import Report, ReportUpdate
|
||||
from c3nav.mapdata.schemas.models import SlimLocationSchema
|
||||
from c3nav.mapdata.utils.locations import (get_location_by_id_for_request, get_location_by_slug_for_request,
|
||||
levels_by_short_label_for_request)
|
||||
levels_by_level_index_for_request)
|
||||
from c3nav.mapdata.utils.user import can_access_editor, get_user_data
|
||||
from c3nav.mapdata.views import set_tile_access_cookie
|
||||
from c3nav.routing.models import RouteOptions
|
||||
|
@ -134,7 +134,7 @@ def map_index(request, mode=None, slug=None, slug2=None, details=None, options=N
|
|||
'nearby': True if nearby else False,
|
||||
}
|
||||
|
||||
levels = levels_by_short_label_for_request(request)
|
||||
levels = levels_by_level_index_for_request(request)
|
||||
|
||||
level = levels.get(pos.level, None) if pos else None
|
||||
if level is not None:
|
||||
|
@ -198,7 +198,7 @@ def map_index(request, mode=None, slug=None, slug2=None, details=None, options=N
|
|||
from c3nav.mapdata.models.theme import Theme
|
||||
ctx = {
|
||||
'bounds': json.dumps(Source.max_bounds(), separators=(',', ':')),
|
||||
'levels': json.dumps(tuple((level.pk, level.short_label) for level in levels.values()), separators=(',', ':')),
|
||||
'levels': json.dumps(tuple((level.pk, level.level_index, level.short_label) for level in levels.values()), separators=(',', ':')),
|
||||
'state': json.dumps(state, separators=(',', ':'), cls=DjangoJSONEncoder),
|
||||
'tile_cache_server': settings.TILE_CACHE_SERVER,
|
||||
'initial_level': settings.INITIAL_LEVEL,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue