diff --git a/src/c3nav/mapdata/api/map.py b/src/c3nav/mapdata/api/map.py index c7683b83..0dbe6988 100644 --- a/src/c3nav/mapdata/api/map.py +++ b/src/c3nav/mapdata/api/map.py @@ -2,6 +2,7 @@ import json from typing import Annotated, Union from django.core.serializers.json import DjangoJSONEncoder +from django.db.models import Prefetch from django.shortcuts import redirect from django.utils import timezone from ninja import Query @@ -15,14 +16,16 @@ from c3nav.api.exceptions import API404, APIPermissionDenied, APIRequestValidati from c3nav.api.schema import BaseSchema from c3nav.api.utils import NonEmptyStr from c3nav.mapdata.api.base import api_etag, api_stats, can_access_geometry -from c3nav.mapdata.models import Source -from c3nav.mapdata.models.locations import DynamicLocation, LocationRedirect, Position +from c3nav.mapdata.models import Source, Theme, Area, Space +from c3nav.mapdata.models.geometry.space import ObstacleGroup, Obstacle +from c3nav.mapdata.models.locations import DynamicLocation, LocationRedirect, Position, LocationGroup +from c3nav.mapdata.render.theme import ColorManager from c3nav.mapdata.schemas.filters import BySearchableFilter, RemoveGeometryFilter from c3nav.mapdata.schemas.model_base import AnyLocationID, AnyPositionID, CustomLocationID from c3nav.mapdata.schemas.models import (AnyPositionStatusSchema, FullListableLocationSchema, FullLocationSchema, LocationDisplay, ProjectionPipelineSchema, ProjectionSchema, SlimListableLocationSchema, SlimLocationSchema, all_location_definitions, - listable_location_definitions) + listable_location_definitions, LegendSchema, LegendItemSchema) from c3nav.mapdata.schemas.responses import LocationGeometry, WithBoundsSchema from c3nav.mapdata.utils.locations import (get_location_by_id_for_request, get_location_by_slug_for_request, searchable_locations_for_request, visible_locations_for_request) @@ -329,3 +332,40 @@ def get_projection(request): 'rotation_matrix': settings.PROJECTION_ROTATION_MATRIX, }) return obj + + +""" +Legend +""" + + +@map_api_router.get('/legend/{theme_id}/', summary="get legend", + description="Get legend / color key fo theme", + response={200: LegendSchema, **API404.dict(), **auth_responses}) +@api_etag(permissions=True) +def legend_for_theme(request, theme_id: int): + try: + manager = ColorManager.for_theme(theme_id or None) + except Theme.DoesNotExist: + raise API404() + locationgroups = LocationGroup.qs_for_request(request).filter(in_legend=True).prefetch_related( + Prefetch('areas', Area.qs_for_request(request)) + ).prefetch_related( + Prefetch('spaces', Space.qs_for_request(request)) + ) + obstaclegroups = ObstacleGroup.objects.filter( + pk__in=set(Obstacle.qs_for_request(request).filter(group__isnull=False).values_list('group', flat=True)), + ) + return LegendSchema( + base=[], + groups=[item for item in (LegendItemSchema(title=group.title, + fill=manager.locationgroup_fill_color(group), + border=manager.locationgroup_border_color(group)) + for group in locationgroups if group.areas.all() or group.spaces.all()) + if item.fill or item.border], + obstacles=[item for item in (LegendItemSchema(title=group.title, + fill=manager.obstaclegroup_fill_color(group), + border=manager.obstaclegroup_border_color(group)) + for group in obstaclegroups) + if item.fill or item.border], + ) diff --git a/src/c3nav/mapdata/migrations/0108_locationgroup_in_legend_alter_beaconmeasurement_data.py b/src/c3nav/mapdata/migrations/0108_in_legend.py similarity index 73% rename from src/c3nav/mapdata/migrations/0108_locationgroup_in_legend_alter_beaconmeasurement_data.py rename to src/c3nav/mapdata/migrations/0108_in_legend.py index ae045562..823cb188 100644 --- a/src/c3nav/mapdata/migrations/0108_locationgroup_in_legend_alter_beaconmeasurement_data.py +++ b/src/c3nav/mapdata/migrations/0108_in_legend.py @@ -8,6 +8,8 @@ def forwards_func(apps, schema_editor): for group in LocationGroup.objects.all(): group.in_legend = bool(group.color and (group.can_describe or group.can_search)) group.save() + ObstacleGroup = apps.get_model('mapdata', 'ObstacleGroup') + ObstacleGroup.objects.filter(color__isnull=False).update(in_legend=True) class Migration(migrations.Migration): @@ -27,5 +29,10 @@ class Migration(migrations.Migration): name='data', field=models.JSONField(default=dict, verbose_name='Measurement list'), ), + migrations.AddField( + model_name='obstaclegroup', + name='in_legend', + field=models.BooleanField(default=False, verbose_name='show in legend (if color set)'), + ), migrations.RunPython(forwards_func, migrations.RunPython.noop), ] diff --git a/src/c3nav/mapdata/models/geometry/space.py b/src/c3nav/mapdata/models/geometry/space.py index 798a1329..cb6828c8 100644 --- a/src/c3nav/mapdata/models/geometry/space.py +++ b/src/c3nav/mapdata/models/geometry/space.py @@ -13,7 +13,7 @@ from shapely.geometry import CAP_STYLE, JOIN_STYLE, mapping from c3nav.mapdata.fields import GeometryField, I18nField from c3nav.mapdata.grid import grid -from c3nav.mapdata.models import Space +from c3nav.mapdata.models import Space, Level from c3nav.mapdata.models.access import AccessRestrictionMixin from c3nav.mapdata.models.base import SerializableMixin, TitledMixin from c3nav.mapdata.models.geometry.base import GeometryMixin @@ -182,6 +182,7 @@ class Ramp(SpaceGeometryMixin, models.Model): class ObstacleGroup(TitledMixin, models.Model): color = models.CharField(max_length=32, null=True, blank=True) + in_legend = models.BooleanField(default=False, verbose_name=_('show in legend (if color set)')) class Meta: verbose_name = _('Obstacle Group') diff --git a/src/c3nav/mapdata/schemas/models.py b/src/c3nav/mapdata/schemas/models.py index 957dc4c5..2f061f64 100644 --- a/src/c3nav/mapdata/schemas/models.py +++ b/src/c3nav/mapdata/schemas/models.py @@ -805,6 +805,7 @@ class ProjectionPipelineSchema(BaseSchema): example='+proj=utm +zone=33 +ellps=GRS80 +units=m +no_defs' ) + class ProjectionSchema(ProjectionPipelineSchema): proj4: NonEmptyStr = APIField( title='proj4 string', @@ -836,3 +837,15 @@ class ProjectionSchema(ProjectionPipelineSchema): 0, 0, 0, 1 ] ) + + +class LegendItemSchema(BaseSchema): + title: NonEmptyStr = APIField() + fill: str | None + border: str | None + + +class LegendSchema(BaseSchema): + base: list[LegendItemSchema] + groups: list[LegendItemSchema] + obstacles: list[LegendItemSchema] \ No newline at end of file