import operator import struct from functools import reduce import numpy as np from c3nav.mapdata.utils.cache.indexed import LevelGeometryIndexed class AccessRestrictionAffected(LevelGeometryIndexed): # metadata format: # 64 times: # 4 bytes (uint32): access restriction id (or 0x00 if empty) # each uint64 cell contains a bitmask of restrictions. # e.g.: 2^n bit set → restriction with index 2^n does apply dtype = np.uint64 variant_id = 2 variant_name = 'restrictions' def __init__(self, restrictions=None, **kwargs): super().__init__(**kwargs) self.restrictions = [] if restrictions is None else restrictions self.restrictions_lookup = {restriction: i for i, restriction in enumerate(self.restrictions)} @classmethod def _read_metadata(cls, f, kwargs): restrictions = list(struct.unpack('<'+'I'*64, f.read(4*64))) while restrictions and restrictions[-1] == 0: restrictions.pop() kwargs['restrictions'] = restrictions def _write_metadata(self, f): f.write(struct.pack('<'+'I'*64, *self.restrictions, *((0, )*(64-len(self.restrictions))))) @classmethod def build(cls, access_restriction_affected): result = cls() for restriction, area in access_restriction_affected.items(): result[area.buffer(1)].add(restriction) return result @classmethod def open(cls, filename): try: instance = super().open(filename) except FileNotFoundError: instance = cls(restrictions=[], filename=filename) return instance def _get_restriction_index(self, restriction, create=False): i = self.restrictions_lookup.get(restriction) if create and i is None: i = len(self.restrictions) self.restrictions_lookup[restriction] = i self.restrictions.append(restriction) return i def __getitem__(self, selector): return AccessRestrictionAffectedCells(self, selector) def __setitem__(self, selector, value): raise TypeError('__setitem__ not supported for AccessRestriction matrix') class AccessRestrictionAffectedCells: def __init__(self, parent: AccessRestrictionAffected, selector): self.parent = parent self.selector = selector self.values = self._get_values() def _get_values(self): return LevelGeometryIndexed.__getitem__(self.parent, self.selector) def _set(self, values): self.values = values LevelGeometryIndexed.__setitem__(self.parent, self.selector, values) def __contains__(self, restriction): i = self.parent._get_restriction_index(restriction) return (self.values & (2**i)).any() def add(self, restriction): from shapely.geometry.base import BaseGeometry if not isinstance(self.selector, BaseGeometry): raise TypeError('Can only add restrictions with Geometry based selectors') # expand array bounds = self.parent._get_geometry_bounds(self.selector) self.parent.fit_bounds(*bounds) self.values = self._get_values() i = self.parent._get_restriction_index(restriction, create=True) self._set(self.values | (2**i)) def discard(self, restriction): from shapely.geometry.base import BaseGeometry if not isinstance(self.selector, BaseGeometry): raise TypeError('Can only discard restrictions with Geometry based selectors') i = self.parent._get_restriction_index(restriction) self._set(self.values & ((2**64-1) ^ (2**i))) def __iter__(self): all = reduce(operator.or_, self.values.tolist(), 0) yield from (restriction for i, restriction in enumerate(self.parent.restrictions) if (all & 2**i))