diff --git a/src/c3nav/mapdata/grid.py b/src/c3nav/mapdata/grid.py new file mode 100644 index 00000000..12f1817e --- /dev/null +++ b/src/c3nav/mapdata/grid.py @@ -0,0 +1,61 @@ +import bisect +import string +from abc import ABC, abstractmethod +from typing import Optional + +from django.conf import settings + + +class AbstractGrid(ABC): + @abstractmethod + def get_cell_for_point(self, x, y) -> Optional[str]: + pass + + +class Grid(AbstractGrid): + def __init__(self, rows, cols): + rows = tuple(float(y) for y in rows) + cols = tuple(float(x) for x in cols) + self.rows = tuple(sorted(rows)) + self.cols = tuple(sorted(cols)) + + if self.rows == rows: + self.invert_y = False + elif self.rows == tuple(reversed(rows)): + self.invert_y = True + else: + raise ValueError('row coordinates are not ordered') + + if self.cols == cols: + self.invert_x = False + elif self.cols == tuple(reversed(cols)): + self.invert_x = True + else: + raise ValueError('column coordinates are not ordered') + + def get_cell_for_point(self, x, y): + x = bisect.bisect(self.cols, x) + if x <= 0 or x >= len(self.cols): + return None + + y = bisect.bisect(self.rows, y) + if y <= 0 or y >= len(self.rows): + return None + + if self.invert_x: + x = len(self.cols) - x + if self.invert_y: + y = len(self.rows) - y + + return '%s%d' % (string.ascii_uppercase[x-1], y) + + +class DummyGrid(AbstractGrid): + def get_cell_for_point(self, x, y): + return None + + +if settings.GRID_COLS and settings.GRID_ROWS: + grid = Grid(settings.GRID_ROWS.split(','), settings.GRID_COLS.split(',')) +else: + grid = DummyGrid() diff --git a/src/c3nav/mapdata/utils/locations.py b/src/c3nav/mapdata/utils/locations.py index 957bf9f2..f6d96783 100644 --- a/src/c3nav/mapdata/utils/locations.py +++ b/src/c3nav/mapdata/utils/locations.py @@ -13,6 +13,7 @@ from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ from shapely.ops import cascaded_union +from c3nav.mapdata.grid import grid from c3nav.mapdata.models import Level, Location, LocationGroup, MapUpdate from c3nav.mapdata.models.access import AccessPermission from c3nav.mapdata.models.geometry.base import GeometryMixin @@ -367,9 +368,12 @@ class CustomLocation: @cached_property def title_subtitle(self): + grid_cell = grid.get_cell_for_point(self.x, self.y) + level_subtitle = self.level.title if grid_cell is None else ','.join((grid_cell, str(self.level.title))) + title = _('In %(level)s') % {'level': self.level.title} if not self.space: - return title, self.level.title, + return title, level_subtitle subtitle = () if self.near_poi: @@ -385,9 +389,10 @@ class CustomLocation: elif self.near_area: title = _('Near %(area)s') % {'area': self.near_area.title} else: - return _('In %(space)s') % {'space': self.space.title}, self.level.title + return _('In %(space)s') % {'space': self.space.title}, level_subtitle - subtitle = ', '.join(str(title) for title in chain(subtitle, (self.space.title, self.level.title))) + subtitle_segments = chain((grid_cell, ), subtitle, (self.space.title, self.level.title)) + subtitle = ', '.join(str(title) for title in subtitle_segments if title is not None) return title, subtitle @cached_property diff --git a/src/c3nav/settings.py b/src/c3nav/settings.py index 66dec62c..8493eb11 100644 --- a/src/c3nav/settings.py +++ b/src/c3nav/settings.py @@ -90,6 +90,9 @@ CACHE_RESOLUTION = config.get('c3nav', 'cache_resolution', fallback=4) INITIAL_LEVEL = config.get('c3nav', 'initial_level', fallback=None) INITIAL_BOUNDS = config.get('c3nav', 'initial_bounds', fallback='').split(' ') +GRID_ROWS = config.get('c3nav', 'grid_rows', fallback=None) +GRID_COLS = config.get('c3nav', 'grid_cols', fallback=None) + if len(INITIAL_BOUNDS) == 4: try: INITIAL_BOUNDS = tuple(float(i) for i in INITIAL_BOUNDS)