def geometries(self, request, update_cache_key, update_cache_key_match, *args, **kwargs):
        Level = request.changeset.wrap_model('Level')
        Space = request.changeset.wrap_model('Space')
        Column = request.changeset.wrap_model('Column')
        Hole = request.changeset.wrap_model('Hole')
        AltitudeMarker = request.changeset.wrap_model('AltitudeMarker')
        Building = request.changeset.wrap_model('Building')
        Door = request.changeset.wrap_model('Door')
        LocationGroup = request.changeset.wrap_model('LocationGroup')
        WifiMeasurement = request.changeset.wrap_model('WifiMeasurement')

        level = request.GET.get('level')
        space = request.GET.get('space')
        if level is not None:
            if space is not None:
                raise ValidationError('Only level or space can be specified.')

            level = get_object_or_404(Level.objects.filter(Level.q_for_request(request)), pk=level)

            edit_utils = LevelChildEditUtils(level, request)
            if not edit_utils.can_access_child_base_mapdata:
                raise PermissionDenied

            levels, levels_on_top, levels_under = self._get_levels_pk(request, level)
            # don't prefetch groups for now as changesets do not yet work with m2m-prefetches
            levels = Level.objects.filter(pk__in=levels).filter(Level.q_for_request(request))
            # graphnodes_qs = request.changeset.wrap_model('GraphNode').objects.all()
            levels = levels.prefetch_related(
                Prefetch('spaces', Space.objects.filter(Space.q_for_request(request)).only(
                    'geometry', 'level', 'outside'
                )),
                Prefetch('doors', Door.objects.filter(Door.q_for_request(request)).only('geometry', 'level')),
                Prefetch('spaces__columns', Column.objects.filter(
                    Q(access_restriction__isnull=True) | ~Column.q_for_request(request)
                ).only('geometry', 'space')),
                Prefetch('spaces__groups', LocationGroup.objects.only(
                    'color', 'category', 'priority', 'hierarchy', 'category__priority', 'category__allow_spaces'
                )),
                Prefetch('buildings', Building.objects.only('geometry', 'level')),
                Prefetch('spaces__holes', Hole.objects.only('geometry', 'space')),
                Prefetch('spaces__altitudemarkers', AltitudeMarker.objects.only('geometry', 'space')),
                Prefetch('spaces__wifi_measurements', WifiMeasurement.objects.only('geometry', 'space')),
                # Prefetch('spaces__graphnodes', graphnodes_qs)
            )

            levels = {s.pk: s for s in levels}

            level = levels[level.pk]
            levels_under = [levels[pk] for pk in levels_under]
            levels_on_top = [levels[pk] for pk in levels_on_top]

            # todo: permissions
            # graphnodes = tuple(chain(*(space.graphnodes.all()
            #                            for space in chain(*(level.spaces.all() for level in levels.values())))))
            # graphnodes_lookup = {node.pk: node for node in graphnodes}

            # graphedges = request.changeset.wrap_model('GraphEdge').objects.all()
            # graphedges = graphedges.filter(Q(from_node__in=graphnodes) | Q(to_node__in=graphnodes))
            # graphedges = graphedges.select_related('waytype')

            # this is faster because we only deserialize graphnode geometries once
            # missing_graphnodes = graphnodes_qs.filter(pk__in=set(chain(*((edge.from_node_id, edge.to_node_id)
            #                                                              for edge in graphedges))))
            # graphnodes_lookup.update({node.pk: node for node in missing_graphnodes})
            # for edge in graphedges:
            #     edge._from_node_cache = graphnodes_lookup[edge.from_node_id]
            #     edge._to_node_cache = graphnodes_lookup[edge.to_node_id]

            # graphedges = [edge for edge in graphedges if edge.from_node.space_id != edge.to_node.space_id]

            results = chain(
                *(self._get_level_geometries(l) for l in levels_under),
                self._get_level_geometries(level),
                *(self._get_level_geometries(l) for l in levels_on_top),
                *(space.altitudemarkers.all() for space in level.spaces.all()),
                *(space.wifi_measurements.all() for space in level.spaces.all())
                # graphedges,
                # graphnodes,
            )
        elif space is not None:
            space_q_for_request = Space.q_for_request(request)
            qs = Space.objects.filter(space_q_for_request)
            space = get_object_or_404(qs.select_related('level', 'level__on_top_of'), pk=space)
            level = space.level

            edit_utils = SpaceChildEditUtils(space, request)
            if not edit_utils.can_access_child_base_mapdata:
                raise PermissionDenied

            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 = cascaded_union([door.geometry for door in doors]+[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:
                    levels = chain([level.pk], levels_on_top)
                other_spaces = Space.objects.filter(space_q_for_request, level__pk__in=levels).only(
                    'geometry', 'level'
                ).prefetch_related(
                    Prefetch('groups', LocationGroup.objects.only(
                        'color', 'category', 'priority', 'hierarchy', 'category__priority', 'category__allow_spaces'
                    ).filter(color__isnull=False))
                )

                space = next(s for s in other_spaces if s.pk == space.pk)
                other_spaces = [s for s in other_spaces
                                if s.geometry.intersects(doors_space_geom) and s.pk != space.pk]
                all_other_spaces = other_spaces

                if level.on_top_of_id is None:
                    other_spaces_lower = [s for s in other_spaces if s.level_id in levels_under]
                    other_spaces_upper = [s for s in other_spaces if s.level_id in levels_on_top]
                else:
                    other_spaces_lower = [s for s in other_spaces if s.level_id == level.on_top_of_id]
                    other_spaces_upper = []
                other_spaces = [s for s in other_spaces if s.level_id == level.pk]

                space.bounds = True

                # deactivated for performance reasons
                buildings = level.buildings.all()
                # buildings_geom = cascaded_union([building.geometry for building in buildings])
                # for other_space in other_spaces:
                #     if other_space.outside:
                #         other_space.geometry = other_space.geometry.difference(buildings_geom)
                for other_space in chain(other_spaces, other_spaces_lower, other_spaces_upper):
                    other_space.opacity = 0.4
                    other_space.color = '#ffffff'
                for building in buildings:
                    building.opacity = 0.5
            else:
                buildings = []
                doors = []
                other_spaces = []
                other_spaces_lower = []
                other_spaces_upper = []
                all_other_spaces = []

            # todo: permissions
            if request.user_permissions.can_access_base_mapdata:
                graphnodes = request.changeset.wrap_model('GraphNode').objects.all()
                graphnodes = graphnodes.filter((Q(space__in=all_other_spaces)) | Q(space__pk=space.pk))

                space_graphnodes = tuple(node for node in graphnodes if node.space_id == space.pk)

                graphedges = request.changeset.wrap_model('GraphEdge').objects.all()
                space_graphnodes_ids = tuple(node.pk for node in space_graphnodes)
                graphedges = graphedges.filter(Q(from_node__pk__in=space_graphnodes_ids) |
                                               Q(to_node__pk__in=space_graphnodes_ids))
                graphedges = graphedges.select_related('from_node', 'to_node', 'waytype').only(
                    'from_node__geometry', 'to_node__geometry', 'waytype__color'
                )
            else:
                graphnodes = []
                graphedges = []

            areas = space.areas.filter(Area.q_for_request(request)).only(
                'geometry', 'space'
            ).prefetch_related(
                Prefetch('groups', LocationGroup.objects.order_by(
                    '-category__priority', '-hierarchy', '-priority'
                ).only(
                    'color', 'category', 'priority', 'hierarchy', 'category__priority', 'category__allow_areas'
                ))
            )
            for area in areas:
                area.opacity = 0.5
            areas = sorted(areas, key=self.area_sorting_func)

            results = chain(
                buildings,
                other_spaces_lower,
                doors,
                other_spaces,
                [space],
                areas,
                space.holes.all().only('geometry', 'space'),
                space.stairs.all().only('geometry', 'space'),
                space.ramps.all().only('geometry', 'space'),
                space.obstacles.all().only('geometry', 'space', 'color'),
                space.lineobstacles.all().only('geometry', 'width', 'space', 'color'),
                space.columns.all().only('geometry', 'space'),
                space.altitudemarkers.all().only('geometry', 'space'),
                space.wifi_measurements.all().only('geometry', 'space'),
                space.pois.filter(POI.q_for_request(request)).only('geometry', 'space').prefetch_related(
                    Prefetch('groups', LocationGroup.objects.only(
                        'color', 'category', 'priority', 'hierarchy', 'category__priority', 'category__allow_pois'
                    ).filter(color__isnull=False))
                ),
                other_spaces_upper,
                graphedges,
                graphnodes
            )
        else:
            raise ValidationError('No level or space specified.')

        return Response(list(chain(
            [('update_cache_key', update_cache_key)],
            (self.conditional_geojson(obj, update_cache_key_match) for obj in results)
        )))