fix more bugs caused by updates, especially using unwrap_geom

This commit is contained in:
Laura Klünder 2023-07-24 11:51:25 +02:00
parent 1837c49ab8
commit 9618d7304f
9 changed files with 59 additions and 38 deletions

View file

@ -22,6 +22,7 @@ from c3nav.editor.views.base import etag_func
from c3nav.mapdata.api import api_etag
from c3nav.mapdata.models import Area, MapUpdate, Source
from c3nav.mapdata.models.geometry.space import POI
from c3nav.mapdata.utils.geometry import unwrap_geom
from c3nav.mapdata.utils.user import can_access_editor
@ -69,7 +70,7 @@ class EditorViewSet(EditorViewSetMixin, ViewSet):
@staticmethod
def _get_level_geometries(level):
buildings = level.buildings.all()
buildings_geom = unary_union([building.geometry.wrapped_geom for building in buildings])
buildings_geom = unary_union([unwrap_geom(building.geometry) for building in buildings])
spaces = {space.pk: space for space in level.spaces.all()}
holes_geom = []
for space in spaces.values():
@ -77,9 +78,9 @@ class EditorViewSet(EditorViewSetMixin, ViewSet):
space.geometry = space.geometry.difference(buildings_geom)
columns = [column.geometry for column in space.columns.all()]
if columns:
columns_geom = unary_union([column.geometry.wrapped_geom for column in space.columns.all()])
columns_geom = unary_union([unwrap_geom(column.geometry) for column in space.columns.all()])
space.geometry = space.geometry.difference(columns_geom)
holes = [hole.geometry.wrapped_geom for hole in space.holes.all()]
holes = [unwrap_geom(hole.geometry) for hole in space.holes.all()]
if holes:
space_holes_geom = unary_union(holes)
holes_geom.append(space_holes_geom.intersection(space.geometry))
@ -221,7 +222,10 @@ class EditorViewSet(EditorViewSetMixin, ViewSet):
if request.user_permissions.can_access_base_mapdata:
doors = [door for door in level.doors.filter(Door.q_for_request(request)).all()
if door.geometry.intersects(space.geometry)]
doors_space_geom = unary_union([door.geometry for door in doors]+[space.geometry])
doors_space_geom = unary_union(
[unwrap_geom(door.geometry) for door in doors] +
[unwrap_geom(space.geometry)]
)
levels, levels_on_top, levels_under = self._get_levels_pk(request, level.primary_level)
if level.on_top_of_id is not None:

View file

@ -41,7 +41,7 @@ class EditorFormBase(I18nModelFormMixin, ModelForm):
# hide geometry widget
self.fields['geometry'].widget = HiddenInput()
if not creating:
self.initial['geometry'] = json.dumps(mapping(self.instance.geometry), separators=(',', ':'))
self.initial['geometry'] = mapping(self.instance.geometry)
if self._meta.model.__name__ == 'Source' and self.request.user.is_superuser:
Source = self.request.changeset.wrap_model('Source')

View file

@ -55,6 +55,9 @@ class GeometryField(models.JSONField):
def to_python(self, value):
if value is None or value == '':
return None
if isinstance(value, str):
# todo: this is all too complex, why do we need this?
value = json.loads(value)
try:
geometry = shape(value)
except Exception:

View file

@ -9,7 +9,7 @@ from shapely.geometry.base import BaseGeometry
from shapely.ops import unary_union
from c3nav.mapdata.models.base import SerializableMixin
from c3nav.mapdata.utils.geometry import assert_multipolygon, good_representative_point, smart_mapping
from c3nav.mapdata.utils.geometry import assert_multipolygon, good_representative_point, smart_mapping, unwrap_geom
from c3nav.mapdata.utils.json import format_geojson
geometry_affecting_fields = ('height', 'width', 'access_restriction')
@ -119,11 +119,11 @@ class GeometryMixin(SerializableMixin):
return True
if self.geometry is self.orig_geometry:
return False
if not self.geometry.equals_exact(self.orig_geometry, 0.05):
if not self.geometry.equals_exact(unwrap_geom(self.orig_geometry), 0.05):
return True
field = self._meta.get_field('geometry')
rounded = field.to_python(field.get_prep_value(self.geometry))
if not rounded.equals_exact(self.orig_geometry, 0.005):
if not rounded.equals_exact(unwrap_geom(self.orig_geometry), 0.005):
return True
return False
@ -132,7 +132,7 @@ class GeometryMixin(SerializableMixin):
new_geometry = field.get_final_value(self.geometry)
if self.orig_geometry is None:
return new_geometry
difference = new_geometry.symmetric_difference(self.orig_geometry)
difference = new_geometry.symmetric_difference(unwrap_geom(self.orig_geometry))
if self._meta.get_field('geometry').geomtype in ('polygon', 'multipolygon'):
difference = unary_union(assert_multipolygon(difference))
return difference

View file

@ -26,7 +26,7 @@ from c3nav.mapdata.models.geometry.base import GeometryMixin
from c3nav.mapdata.models.locations import SpecificLocation
from c3nav.mapdata.utils.cache.changes import changed_geometries
from c3nav.mapdata.utils.geometry import (assert_multilinestring, assert_multipolygon, clean_cut_polygon,
cut_polygon_with_line)
cut_polygon_with_line, unwrap_geom)
class LevelGeometryMixin(GeometryMixin):
@ -222,7 +222,7 @@ class AltitudeArea(LevelGeometryMixin, models.Model):
stairs = []
# collect all accessible areas on this level
buildings_geom = unary_union(tuple(building.geometry.wrapped_geom for building in level.buildings.all()))
buildings_geom = unary_union(tuple(unwrap_geom(building.geometry) for building in level.buildings.all()))
for space in level.spaces.all():
spaces[space.pk] = space
space.orig_geometry = space.geometry
@ -271,9 +271,9 @@ class AltitudeArea(LevelGeometryMixin, models.Model):
space_areas.update({space.pk: [] for space in level.spaces.all()})
for area in areas:
area.spaces = set()
area.geometry_prep = prepared.prep(area.geometry)
area.geometry_prep = prepared.prep(unwrap_geom(area.geometry))
for space in level.spaces.all():
if area.geometry_prep.intersects(space.geometry):
if area.geometry_prep.intersects(unwrap_geom(space.geometry)):
area.spaces.add(space.pk)
space_areas[space.pk].append(area)
@ -470,9 +470,11 @@ class AltitudeArea(LevelGeometryMixin, models.Model):
for space in level.spaces.all():
space.geometry = space.orig_geometry
buildings_geom = unary_union(tuple(b.geometry.wrapped_geom for b in level.buildings.all()))
buildings_geom = unary_union(tuple(unwrap_geom(b.geometry) for b in level.buildings.all()))
doors_geom = unary_union(tuple(d.geometry for d in level.doors.all()))
space_geom = unary_union(tuple((s.geometry if not s.outside else s.geometry.difference(buildings_geom))
space_geom = unary_union(tuple((unwrap_geom(s.geometry)
if not s.outside
else s.geometry.difference(buildings_geom))
for s in level.spaces.all()))
# accessible area on this level is doors + spaces - holes
@ -494,7 +496,7 @@ class AltitudeArea(LevelGeometryMixin, models.Model):
space_geom = space.geometry
if space.outside:
space_geom = space_geom.difference(buildings_geom)
space_geom_prep = prepared.prep(space_geom)
space_geom_prep = prepared.prep(unwrap_geom(space_geom))
holes_geom = unary_union(tuple(h.geometry for h in space.holes.all()))
# remaining_space means remaining space (=obstacles) that still needs to be added to altitude areas
@ -572,7 +574,7 @@ class AltitudeArea(LevelGeometryMixin, models.Model):
all_candidates = AltitudeArea.objects.select_related('level')
for candidate in all_candidates:
candidate.area = candidate.geometry.area
candidate.geometry_prep = prepared.prep(candidate.geometry)
candidate.geometry_prep = prepared.prep(unwrap_geom(candidate.geometry))
all_candidates = sorted(all_candidates, key=attrgetter('area'), reverse=True)
num_modified = 0
@ -606,7 +608,7 @@ class AltitudeArea(LevelGeometryMixin, models.Model):
num_deleted += 1
continue
if not field.get_final_value(new_area.geometry).equals_exact(candidate.geometry, 0.00001):
if not field.get_final_value(new_area.geometry).equals_exact(unwrap_geom(candidate.geometry), 0.00001):
num_modified += 1
candidate.geometry = new_area.geometry

View file

@ -13,7 +13,7 @@ from c3nav.mapdata.render.geometry.altitudearea import AltitudeAreaGeometries
from c3nav.mapdata.render.geometry.hybrid import HybridGeometry
from c3nav.mapdata.render.geometry.mesh import Mesh
from c3nav.mapdata.utils.cache import AccessRestrictionAffected
from c3nav.mapdata.utils.geometry import get_rings
from c3nav.mapdata.utils.geometry import get_rings, unwrap_geom
from c3nav.mapdata.utils.mesh import triangulate_rings
empty_geometry_collection = GeometryCollection()
@ -65,7 +65,7 @@ class LevelGeometries:
@classmethod
def build_for_level(cls, level, altitudeareas_above):
geoms = LevelGeometries()
buildings_geom = unary_union([b.geometry.wrapped_geom for b in level.buildings.all()])
buildings_geom = unary_union([unwrap_geom(b.geometry) for b in level.buildings.all()])
geoms.buildings = buildings_geom
buildings_geom_prep = prepared.prep(buildings_geom)
@ -89,10 +89,10 @@ class LevelGeometries:
space.holes_geom = empty_geometry_collection
space.walkable_geom = space.geometry
spaces_geom = unary_union([s.geometry for s in level.spaces.all()])
doors_geom = unary_union([d.geometry for d in level.doors.all()])
spaces_geom = unary_union([unwrap_geom(s.geometry) for s in level.spaces.all()])
doors_geom = unary_union([unwrap_geom(d.geometry) for d in level.doors.all()])
doors_geom = doors_geom.intersection(buildings_geom)
walkable_spaces_geom = unary_union([s.walkable_geom for s in level.spaces.all()])
walkable_spaces_geom = unary_union([unwrap_geom(s.walkable_geom) for s in level.spaces.all()])
geoms.doors = doors_geom.difference(walkable_spaces_geom)
if level.on_top_of_id is None:
geoms.holes = unary_union([s.holes_geom for s in level.spaces.all()])
@ -127,7 +127,9 @@ class LevelGeometries:
buffered.difference(buildings_geom)
)
colors.setdefault(space.get_color_sorted(), {}).setdefault(access_restriction, []).append(space.geometry)
colors.setdefault(space.get_color_sorted(), {}).setdefault(access_restriction, []).append(
unwrap_geom(space.geometry)
)
for area in space.areas.all():
access_restriction = area.access_restriction_id or space.access_restriction_id
@ -166,7 +168,9 @@ class LevelGeometries:
geoms.ramps.extend(ramp.geometry for ramp in space.ramps.all())
heightareas.setdefault(int((space.height or level.default_height)*1000), []).append(space.geometry)
heightareas.setdefault(int((space.height or level.default_height)*1000), []).append(
unwrap_geom(space.geometry)
)
colors.pop(None, None)
# merge ground colors
@ -178,8 +182,8 @@ class LevelGeometries:
# add altitudegroup geometries and split ground colors into them
for altitudearea in level.altitudeareas.all():
altitudearea_prep = prepared.prep(altitudearea.geometry)
altitudearea_colors = {color: {access_restriction: area.intersection(altitudearea.geometry)
altitudearea_prep = prepared.prep(unwrap_geom(altitudearea.geometry))
altitudearea_colors = {color: {access_restriction: area.intersection(unwrap_geom(altitudearea.geometry))
for access_restriction, area in areas.items()
if altitudearea_prep.intersects(area)}
for color, areas in colors.items()}

View file

@ -16,7 +16,7 @@ from c3nav.mapdata.models import Level, MapUpdate, Source
from c3nav.mapdata.render.geometry import AltitudeAreaGeometries, LevelGeometries
from c3nav.mapdata.utils.cache import AccessRestrictionAffected, MapHistory
from c3nav.mapdata.utils.cache.package import CachePackage
from c3nav.mapdata.utils.geometry import get_rings
from c3nav.mapdata.utils.geometry import get_rings, unwrap_geom
empty_geometry_collection = GeometryCollection()
@ -24,7 +24,7 @@ empty_geometry_collection = GeometryCollection()
class Cropper:
def __init__(self, geometry=None):
self.geometry = geometry
self.geometry_prep = None if geometry is None else prepared.prep(geometry)
self.geometry_prep = None if geometry is None else prepared.prep(unwrap_geom(geometry))
def intersection(self, other):
if self.geometry is None:
@ -179,7 +179,7 @@ class LevelRenderData:
) if not geom.is_empty)
for altitudearea in old_geoms.altitudeareas:
new_geometry = crop_to.intersection(altitudearea.geometry)
new_geometry = crop_to.intersection(unwrap_geom(altitudearea.geometry))
if new_geometry.is_empty:
continue
new_geometry_prep = prepared.prep(new_geometry)

View file

@ -40,10 +40,18 @@ class WrappedGeometry():
@cached_property
def wrapped_geom(self):
if not self.wrapped_geojson['coordinates']:
if not self.wrapped_geojson or not self.wrapped_geojson['coordinates']:
return GeometryCollection()
return shapely_shape(self.wrapped_geojson)
def __getstate__(self):
self.picklable = True
# make sure geometry is cached
if self.wrapped_geojson:
# noinspection PyStatementEffect
self.wrapped_geom
super().__getstate__()
def __getattr__(self, name):
if name in ('__getstate__'):
self.picklable = True
@ -64,8 +72,8 @@ class WrappedGeometry():
return result
def unwrap_geometry(geometry):
return getattr(geometry, 'geom', geometry)
def unwrap_geom(geometry):
return geometry.wrapped_geom if isinstance(geometry, WrappedGeometry) else geometry
def smart_mapping(geometry):

View file

@ -20,7 +20,7 @@ from shapely.ops import unary_union
from c3nav.mapdata.models import AltitudeArea, Area, GraphEdge, Level, LocationGroup, MapUpdate, Space, WayType
from c3nav.mapdata.models.geometry.space import POI, CrossDescription, LeaveDescription
from c3nav.mapdata.models.locations import CustomLocationProxyMixin
from c3nav.mapdata.utils.geometry import assert_multipolygon, get_rings, good_representative_point
from c3nav.mapdata.utils.geometry import assert_multipolygon, get_rings, good_representative_point, unwrap_geom
from c3nav.mapdata.utils.locations import CustomLocation
from c3nav.routing.exceptions import LocationUnreachable, NoRouteFound, NotYetRoutable
from c3nav.routing.route import Route
@ -63,7 +63,7 @@ class Router:
restrictions = {}
nodes = deque()
for level in levels_query:
buildings_geom = unary_union(tuple(building.geometry.wrapped_geom for building in level.buildings.all()))
buildings_geom = unary_union(tuple(unwrap_geom(building.geometry) for building in level.buildings.all()))
nodes_before_count = len(nodes)
@ -122,9 +122,9 @@ class Router:
space.areas.add(area.pk)
for area in level.altitudeareas.all():
if not space.geometry_prep.intersects(area.geometry):
if not space.geometry_prep.intersects(unwrap_geom(area.geometry)):
continue
for subgeom in assert_multipolygon(accessible_geom.intersection(area.geometry)):
for subgeom in assert_multipolygon(accessible_geom.intersection(unwrap_geom(area.geometry))):
if subgeom.is_empty:
continue
area_clear_geom = unary_union(tuple(get_rings(subgeom.difference(obstacles_geom))))
@ -511,7 +511,7 @@ class BaseRouterProxy:
@cached_property
def geometry_prep(self):
return prepared.prep(self.src.geometry)
return prepared.prep(unwrap_geom(self.src.geometry))
def __getstate__(self):
result = self.__dict__.copy()