Ejemplo n.º 1
0
    def collect_arealocations(self):
        public_packages = get_public_packages()

        self._built_arealocations = {}
        self._built_excludables = {}
        for excludable in self.level.arealocations.all():
            self._built_arealocations[excludable.name] = excludable.geometry
            if excludable.routing_inclusion != 'default' or excludable.package not in public_packages:
                self._built_excludables[excludable.name] = excludable.geometry

        public_area, private_area = get_public_private_area(self.level)

        self._built_arealocations[':public'] = public_area
        self._built_excludables[':public'] = public_area

        self._built_arealocations[':nonpublic'] = private_area
        self._built_excludables[':nonpublic'] = private_area

        # add points inside arealocations to be able to route to its borders
        for excludable in self._built_arealocations.values():
            smaller = excludable.buffer(-0.05, join_style=JOIN_STYLE.mitre)
            for room in self.rooms:
                room.add_points_on_rings(assert_multipolygon(smaller))

        # add points outside excludables so if excluded you can walk around them
        for excludable in self._built_excludables.values():
            for polygon in assert_multipolygon(excludable.buffer(0.28, join_style=JOIN_STYLE.mitre)):
                for room in self.rooms:
                    room._add_ring(polygon.exterior, want_left=True)

                    for interior in polygon.interiors:
                        room._add_ring(interior, want_left=False)
Ejemplo n.º 2
0
    def create_doors(self):
        doors = self.level.geometries.doors
        doors = assert_multipolygon(doors)
        for door in doors:
            polygon = door.buffer(0.01, join_style=JOIN_STYLE.mitre)
            center = door.centroid

            num_points = 0
            connected_rooms = set()
            points = []
            for room in self.rooms:
                if not polygon.intersects(room._built_geometry):
                    continue

                for subpolygon in assert_multipolygon(polygon.intersection(room._built_geometry)):
                    connected_rooms.add(room)
                    nearest_point = get_nearest_point(room.clear_geometry, subpolygon.centroid)
                    point, = room.add_point(nearest_point.coords[0])
                    points.append(point)

            if len(points) < 2:
                print('door with <2 points (%d) detected at (%.2f, %.2f)' % (num_points, center.x, center.y))
                continue

            center_point = GraphPoint(center.x, center.y, None)
            self._built_room_transfer_points.append(center_point)
            for room in connected_rooms:
                room._built_points.append(center_point)

            for point in points:
                center_point.connect_to(point)
                point.connect_to(center_point)
Ejemplo n.º 3
0
def get_nearest_point(polygon, point):
    """
    calculate the nearest point on of a polygon to another point that lies outside
    :param polygon: a Polygon or a MultiPolygon
    :param point: something that shapely understands as a point
    :return: a Point
    """
    polygons = assert_multipolygon(polygon)
    nearest_distance = float('inf')
    nearest_point = None
    for polygon in polygons:
        if point.within(Polygon(polygon.exterior.coords)):
            for interior in polygon.interiors:
                if point.within(Polygon(interior.coords)):
                    point_ = _nearest_point_ring(interior, point)
                    distance = point_.distance(point)
                    if distance and distance < nearest_distance:
                        nearest_distance = distance
                        nearest_point = point_
                    break  # in a valid polygon a point can not be within multiple interiors
            break  # in a valid multipolygon a point can not be within multiple polygons
        else:
            point_ = _nearest_point_ring(polygon.exterior, point)
            distance = point_.distance(point)
            if distance and distance < nearest_distance:
                nearest_distance = distance
                nearest_point = point_

    if nearest_point is None:
        raise ValueError('Point inside polygon.')
    return nearest_point
Ejemplo n.º 4
0
    def _add_polygon(self, name, geometry, minz, maxz):
        geometry = geometry.buffer(0)
        polygons = []
        for polygon in assert_multipolygon(geometry):
            points = []
            points_lookup = {}
            output_rings = []
            for ring in [polygon.exterior]+list(polygon.interiors):
                output_ring = []
                for coords in ring.coords:
                    try:
                        i = points_lookup[coords]
                    except KeyError:
                        points_lookup[coords] = len(points)
                        i = len(points)
                        points.append(list(coords))
                    output_ring.append(i)
                if output_ring[0] == output_ring[-1]:
                    output_ring = output_ring[:-1]
                output_rings.append(output_ring)
            polygons.append(OpenScadCommand('polygon(%(points)r, %(rings)r, 10);' % {
                'points': points,
                'rings': output_rings,
            }))

        if not polygons:
            return None

        extrude_cmd = 'linear_extrude(height=%f, convexity=10)' % (abs(maxz-minz)/1000)
        translate_cmd = 'translate([0, 0, %f])' % (min(maxz, minz)/1000)
        return OpenScadBlock(translate_cmd, children=[OpenScadBlock(extrude_cmd, comment=name, children=polygons)])
Ejemplo n.º 5
0
 def collect_rooms(self):
     accessibles = self.level.geometries.accessible_without_oneways
     accessibles = assert_multipolygon(accessibles)
     for geometry in accessibles:
         room = GraphRoom(self)
         if room.prepare_build(geometry):
             self.rooms.append(room)
Ejemplo n.º 6
0
    def create_full(cls, geom, vertices_offset, faces_offset):
        """
        Create by triangulating a polygon and adding the resulting facets to the total list.
        """
        if isinstance(geom, (LineString, MultiLineString, Point)):
            return HybridGeometry(geom, set()), np.empty((0, 2), dtype=np.int32), np.empty((0, 3), dtype=np.uint32)

        vertices = deque()
        faces = deque()
        faces_i = deque()
        for subgeom in assert_multipolygon(geom):
            new_vertices, new_faces = triangulate_polygon(subgeom)
            new_faces += vertices_offset
            vertices.append(new_vertices)
            faces.append(new_faces)
            faces_i.append(set(range(faces_offset, faces_offset+new_faces.shape[0])))
            vertices_offset += new_vertices.shape[0]
            faces_offset += new_faces.shape[0]

        if not vertices:
            return HybridGeometry(geom, set()), np.empty((0, 2), dtype=np.int32), np.empty((0, 3), dtype=np.uint32)

        vertices = np.vstack(vertices)
        faces = np.vstack(faces)

        return HybridGeometry(geom, tuple(faces_i)), vertices, faces
Ejemplo n.º 7
0
    def collect_stuffedareas(self):
        self._built_stuffedareas = self.level.geometries.stuffedareas
        for polygon in assert_multipolygon(self._built_stuffedareas.buffer(0.05, join_style=JOIN_STYLE.mitre)):
            for room in self.rooms:
                room._add_ring(polygon.exterior, want_left=True)

                for interior in polygon.interiors:
                    room._add_ring(interior, want_left=False)
Ejemplo n.º 8
0
 def get_changed_geometry(self):
     field = self._meta.get_field('geometry')
     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)
     if self._meta.get_field('geometry').geomtype in ('polygon', 'multipolygon'):
         difference = unary_union(assert_multipolygon(difference))
     return difference
Ejemplo n.º 9
0
    def build_points(self):
        narrowed_geometry = self._built_geometry.buffer(
            -0.6, join_style=JOIN_STYLE.mitre)
        geometry = narrowed_geometry.buffer(
            0.31,
            join_style=JOIN_STYLE.mitre).intersection(self.clear_geometry)

        if geometry.is_empty:
            return

        # points with 60cm distance to borders
        polygons = assert_multipolygon(geometry)
        for polygon in polygons:
            self._add_ring(polygon.exterior, want_left=False)

            for interior in polygon.interiors:
                self._add_ring(interior, want_left=True)

        # now fill in missing doorways or similar
        accessible_clear_geometry = geometry.buffer(
            0.31, join_style=JOIN_STYLE.mitre)
        missing_geometry = self.clear_geometry.difference(
            accessible_clear_geometry)
        polygons = assert_multipolygon(missing_geometry)
        for polygon in polygons:
            overlaps = polygon.buffer(0.02).intersection(
                accessible_clear_geometry)
            if overlaps.is_empty:
                continue

            points = []

            # overlaps to non-missing areas
            overlaps = assert_multipolygon(overlaps)
            for overlap in overlaps:
                points += self.add_point(overlap.centroid.coords[0])

            points += self._add_ring(polygon.exterior, want_left=False)

            for interior in polygon.interiors:
                points += self._add_ring(interior, want_left=True)

        # points around steps
        self.add_points_on_rings(self._built_isolated_areas)
Ejemplo n.º 10
0
 def create(cls, geom, face_centers):
     """
     Create from existing facets and just select the ones that lie inside this polygon.
     """
     if isinstance(geom, (LineString, MultiLineString)):
         return HybridGeometry(geom, set())
     faces = tuple(
         set(np.argwhere(shapely_to_mpl(subgeom).contains_points(face_centers)).flatten())
         for subgeom in assert_multipolygon(geom)
     )
     return HybridGeometry(geom, tuple(f for f in faces if f))
Ejemplo n.º 11
0
    def build_areas(self):
        stairs_areas = self.level.level.geometries.stairs
        stairs_areas = stairs_areas.buffer(0.3,
                                           join_style=JOIN_STYLE.mitre,
                                           cap_style=CAP_STYLE.flat)
        stairs_areas = stairs_areas.intersection(self._built_geometry)
        self._built_isolated_areas = tuple(assert_multipolygon(stairs_areas))

        escalators_areas = self.level.level.geometries.escalators
        escalators_areas = escalators_areas.intersection(self._built_geometry)
        self._built_isolated_areas += tuple(
            assert_multipolygon(escalators_areas))

        escalators_and_stairs = cascaded_union(
            (stairs_areas, escalators_areas))

        isolated_areas = tuple(
            assert_multipolygon(stairs_areas.intersection(
                self.clear_geometry)))
        isolated_areas += tuple(
            assert_multipolygon(
                escalators_areas.intersection(self.clear_geometry)))
        isolated_areas += tuple(
            assert_multipolygon(
                self.clear_geometry.difference(escalators_and_stairs)))

        for isolated_area in isolated_areas:
            mpl_clear = shapely_to_mpl(
                isolated_area.buffer(0.01, join_style=JOIN_STYLE.mitre))
            mpl_stairs = tuple(
                (stair, angle) for stair, angle in self.mpl_stairs
                if mpl_clear.intersects_path(stair, filled=True))
            escalators = tuple(escalator
                               for escalator in self._built_escalators
                               if escalator.mpl_geom.intersects_path(
                                   mpl_clear.exterior, filled=True))
            area = GraphArea(self, mpl_clear, mpl_stairs, escalators)
            area.prepare_build()
            self.areas.append(area)
Ejemplo n.º 12
0
    def create_levelconnectors(self):
        for levelconnector in self.level.levelconnectors.all():
            polygon = levelconnector.geometry

            for room in self.rooms:
                if not polygon.intersects(room._built_geometry):
                    continue

                for subpolygon in assert_multipolygon(polygon.intersection(room._built_geometry)):
                    point = subpolygon.centroid
                    if not point.within(room.clear_geometry):
                        point = get_nearest_point(room.clear_geometry, point)
                    point, = room.add_point(point.coords[0])
                    self.graph.add_levelconnector_point(levelconnector, point)
Ejemplo n.º 13
0
 def _add_polygon(self, name, geometry, minz, maxz):
     geometry = geometry.buffer(0)
     self._add_python('sub_polygons = []')
     for polygon in assert_multipolygon(geometry):
         self._add_python(
             'sub_polygons.append(add_polygon(name=%(name)r, exterior=%(exterior)r, interiors=%(interiors)r, '
             'minz=%(minz)f, maxz=%(maxz)f))' % {
                 'name': name,
                 'exterior': tuple(polygon.exterior.coords),
                 'interiors': tuple(tuple(interior.coords) for interior in polygon.interiors),
                 'minz': minz/1000,
                 'maxz': maxz/1000,
             }
         )
     self._add_python('last_polygon = sub_polygons[0]')
     self._add_python('join_objects(sub_polygons)')
Ejemplo n.º 14
0
    def create_oneways(self):
        oneways = self.level.geometries.oneways
        oneways = assert_multilinestring(oneways)

        segments = ()
        for oneway in oneways:
            coords = tuple(oneway.coords)
            segments += tuple((Path(part), coord_angle(*part))
                              for part in zip(coords[:-1], coords[1:]))

        for oneway, oneway_angle in segments:
            line_string = LineString(tuple(oneway.vertices))
            polygon = line_string.buffer(0.10, join_style=JOIN_STYLE.mitre, cap_style=CAP_STYLE.flat)
            center = polygon.centroid

            num_points = 0
            connected_rooms = set()
            points = []
            for room in self.rooms:
                if not polygon.intersects(room._built_geometry):
                    continue

                for subpolygon in assert_multipolygon(polygon.intersection(room._built_geometry)):
                    connected_rooms.add(room)
                    nearest_point = get_nearest_point(room.clear_geometry, subpolygon.centroid)
                    point, = room.add_point(nearest_point.coords[0])
                    points.append(point)

            if len(points) < 2:
                print('oneway with <2 points (%d) detected at (%.2f, %.2f)' % (num_points, center.x, center.y))
                continue

            center_point = GraphPoint(center.x, center.y, None)
            self._built_room_transfer_points.append(center_point)
            for room in connected_rooms:
                room._built_points.append(center_point)

            for point in points:
                angle = coord_angle(point.xy, center_point.xy)
                angle_diff = ((oneway_angle - angle + 180) % 360) - 180
                direction_up = (angle_diff > 0)
                if direction_up:
                    point.connect_to(center_point)
                else:
                    center_point.connect_to(point)
Ejemplo n.º 15
0
    def cropped_obstacles(self):
        levels_by_name = {}
        obstacles_by_crop_to_level = {}
        for obstacle in self.query('obstacles').filter(
                crop_to_level__isnull=False):
            level_name = obstacle.crop_to_level.name
            levels_by_name.setdefault(level_name, obstacle.crop_to_level)
            obstacles_by_crop_to_level.setdefault(level_name,
                                                  []).append(obstacle.geometry)

        all_obstacles = []
        for level_name, obstacles in obstacles_by_crop_to_level.items():
            obstacles = cascaded_union(obstacles).intersection(
                levels_by_name[level_name].geometries.mapped)
            all_obstacles.append(obstacles)
        all_obstacles.extend(assert_multipolygon(self.lineobstacles))

        return cascaded_union(all_obstacles).intersection(self.mapped)
def assign_locations_area(apps, schema_editor):
    Space = apps.get_model('mapdata', 'Space')
    Area = apps.get_model('mapdata', 'Area')
    AreaLocation = apps.get_model('mapdata', 'AreaLocation')
    LocationSlug = apps.get_model('mapdata', 'LocationSlug')
    LocationGroup = apps.get_model('mapdata', 'LocationGroup')
    for obj in AreaLocation.objects.filter(location_type='area').order_by('slug'):
        spaces = [s for s in Space.objects.filter(section=obj.section, level='')
                  if s.geometry.intersection(obj.geometry).area / s.geometry.area > 0.10]

        if not spaces:
            obj.delete()
            continue

        total_spaces = [s for s in spaces
                        if s.geometry.intersection(obj.geometry).area / s.geometry.area > 0.95]
        partial_spaces = [s for s in spaces if s not in total_spaces]

        to_obj = LocationGroup()
        to_obj.locationslug_ptr = LocationSlug.objects.create(slug=obj.slug)
        to_obj.titles = obj.titles
        to_obj.can_search = obj.can_search
        to_obj.can_describe = obj.can_describe
        to_obj.color = obj.color
        to_obj.compiled_area = True
        to_obj.save()

        for space in total_spaces:
            space.groups.add(to_obj)

        for space in partial_spaces:
            for polygon in assert_multipolygon(space.geometry.intersection(obj.geometry)):
                area = Area()
                area.locationslug_ptr = LocationSlug.objects.create()
                area.geometry = polygon
                area.space = space
                area.can_search = False
                area.can_describe = False
                area.save()
                area.groups.add(to_obj)

        obj.delete()
Ejemplo n.º 17
0
    def _add_polygon(self, name, geometry, minz, maxz):
        geometry = geometry.buffer(0)
        polygons = []
        for polygon in assert_multipolygon(geometry):
            points = []
            points_lookup = {}
            output_rings = []
            for ring in [polygon.exterior] + list(polygon.interiors):
                output_ring = []
                for coords in ring.coords:
                    try:
                        i = points_lookup[coords]
                    except KeyError:
                        points_lookup[coords] = len(points)
                        i = len(points)
                        points.append(list(coords))
                    output_ring.append(i)
                if output_ring[0] == output_ring[-1]:
                    output_ring = output_ring[:-1]
                output_rings.append(output_ring)
            polygons.append(
                OpenScadCommand('polygon(%(points)r, %(rings)r, 10);' % {
                    'points': points,
                    'rings': output_rings,
                }))

        if not polygons:
            return None

        extrude_cmd = 'linear_extrude(height=%f, convexity=10)' % (
            abs(maxz - minz) / 1000)
        translate_cmd = 'translate([0, 0, %f])' % (min(maxz, minz) / 1000)
        return OpenScadBlock(translate_cmd,
                             children=[
                                 OpenScadBlock(extrude_cmd,
                                               comment=name,
                                               children=polygons)
                             ])
Ejemplo n.º 18
0
Archivo: level.py Proyecto: bate/c3nav
    def recalculate(cls):
        # collect location areas
        all_areas = []
        all_ramps = []
        space_areas = {}
        spaces = {}
        levels = Level.objects.prefetch_related('buildings', 'doors', 'spaces', 'spaces__columns',
                                                'spaces__obstacles', 'spaces__lineobstacles', 'spaces__holes',
                                                'spaces__stairs', 'spaces__ramps', 'spaces__altitudemarkers')
        logger = logging.getLogger('c3nav')

        for level in levels:
            areas = []
            ramps = []
            stairs = []

            # collect all accessible areas on this level
            buildings_geom = unary_union(tuple(building.geometry for building in level.buildings.all()))
            for space in level.spaces.all():
                spaces[space.pk] = space
                space.orig_geometry = space.geometry
                if space.outside:
                    space.geometry = space.geometry.difference(buildings_geom)
                space_accessible = space.geometry.difference(
                    unary_union(tuple(c.geometry for c in space.columns.all()) +
                                tuple(o.geometry for o in space.obstacles.all()) +
                                tuple(o.buffered_geometry for o in space.lineobstacles.all()) +
                                tuple(h.geometry for h in space.holes.all()))
                )

                space_ramps = unary_union(tuple(r.geometry for r in space.ramps.all()))
                areas.append(space_accessible.difference(space_ramps))
                for geometry in assert_multipolygon(space_accessible.intersection(space_ramps)):
                    ramp = AltitudeArea(geometry=geometry, level=level)
                    ramp.geometry_prep = prepared.prep(geometry)
                    ramp.space = space.pk
                    ramps.append(ramp)

            areas = tuple(orient(polygon) for polygon in assert_multipolygon(
                unary_union(areas+list(door.geometry for door in level.doors.all()))
            ))

            # collect all stairs on this level
            for space in level.spaces.all():
                space_buffer = space.geometry.buffer(0.001, join_style=JOIN_STYLE.mitre)
                for stair in space.stairs.all():
                    stairs.extend(assert_multilinestring(
                        stair.geometry.intersection(space_buffer)
                    ))

            # divide areas using stairs
            for stair in stairs:
                areas = cut_polygon_with_line(areas, stair)

            # create altitudearea objects
            areas = [AltitudeArea(geometry=clean_cut_polygon(area), level=level)
                     for area in areas]

            # prepare area geometries
            for area in areas:
                area.geometry_prep = prepared.prep(area.geometry)

            # assign spaces to areas
            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)
                for space in level.spaces.all():
                    if area.geometry_prep.intersects(space.geometry):
                        area.spaces.add(space.pk)
                        space_areas[space.pk].append(area)

            # give altitudes to areas
            for space in level.spaces.all():
                for altitudemarker in space.altitudemarkers.all():
                    for area in space_areas[space.pk]:
                        if area.geometry_prep.contains(altitudemarker.geometry):
                            area.altitude = altitudemarker.altitude
                            break
                    else:
                        logger.error(
                            _('AltitudeMarker #%(marker_id)d in Space #%(space_id)d on Level %(level_label)s '
                              'is not placed in an accessible area') % {'marker_id': altitudemarker.pk,
                                                                        'space_id': space.pk,
                                                                        'level_label': level.short_label})

            # determine altitude area connections
            for area in areas:
                area.connected_to = []
            for area, other_area in combinations(areas, 2):
                if area.geometry_prep.intersects(other_area.geometry):
                    area.connected_to.append(other_area)
                    other_area.connected_to.append(area)

            # determine ramp connections
            for ramp in ramps:
                ramp.connected_to = []
                buffered = ramp.geometry.buffer(0.001)
                for area in areas:
                    if area.geometry_prep.intersects(buffered):
                        intersection = area.geometry.intersection(buffered)
                        ramp.connected_to.append((area, intersection))
                if len(ramp.connected_to) != 2:
                    if len(ramp.connected_to) == 0:
                        logger.warning('Ramp with no connections!')
                    elif len(ramp.connected_to) == 1:
                        logger.warning('Ramp with only one connection!')
                    else:
                        logger.warning('Ramp with more than one connections!')

            # add areas to global areas
            all_areas.extend(areas)
            all_ramps.extend(ramps)

        # give temporary ids to all areas
        areas = all_areas
        ramps = all_ramps
        for i, area in enumerate(areas):
            area.tmpid = i
        for area in areas:
            area.connected_to = set(area.tmpid for area in area.connected_to)
        for space in space_areas.keys():
            space_areas[space] = set(area.tmpid for area in space_areas[space])
        areas_without_altitude = set(area.tmpid for area in areas if area.altitude is None)

        # interpolate altitudes
        areas_with_altitude = [i for i in range(len(areas)) if i not in areas_without_altitude]
        for i, tmpid in enumerate(areas_with_altitude):
            areas[tmpid].i = i

        csgraph = np.zeros((len(areas), len(areas)), dtype=bool)
        for area in areas:
            for connected_tmpid in area.connected_to:
                csgraph[area.tmpid, connected_tmpid] = True

        repeat = True
        while repeat:
            repeat = False
            distances, predecessors = dijkstra(csgraph, directed=False, return_predecessors=True, unweighted=True)
            np_areas_with_altitude = np.array(areas_with_altitude, dtype=np.uint32)
            relevant_distances = distances[np_areas_with_altitude[:, None], np_areas_with_altitude]
            # noinspection PyTypeChecker
            for from_i, to_i in np.argwhere(np.logical_and(relevant_distances < np.inf, relevant_distances > 1)):
                from_area = areas[areas_with_altitude[from_i]]
                to_area = areas[areas_with_altitude[to_i]]
                if from_area.altitude == to_area.altitude:
                    continue

                path = [to_area.tmpid]
                while path[-1] != from_area.tmpid:
                    path.append(predecessors[from_area.tmpid, path[-1]])

                from_altitude = from_area.altitude
                delta_altitude = (to_area.altitude-from_altitude)/(len(path)-1)

                if set(path[1:-1]).difference(areas_without_altitude):
                    continue

                for i, tmpid in enumerate(reversed(path[1:-1]), start=1):
                    area = areas[tmpid]
                    area.altitude = Decimal(from_altitude+delta_altitude*i).quantize(Decimal('1.00'))
                    areas_without_altitude.discard(tmpid)
                    area.i = len(areas_with_altitude)
                    areas_with_altitude.append(tmpid)

                for from_tmpid, to_tmpid in zip(path[:-1], path[1:]):
                    csgraph[from_tmpid, to_tmpid] = False
                    csgraph[to_tmpid, from_tmpid] = False

                repeat = True

        # remaining areas: copy altitude from connected areas if any
        repeat = True
        while repeat:
            repeat = False
            for tmpid in tuple(areas_without_altitude):
                area = areas[tmpid]
                connected_with_altitude = area.connected_to-areas_without_altitude
                if connected_with_altitude:
                    area.altitude = areas[next(iter(connected_with_altitude))].altitude
                    areas_without_altitude.discard(tmpid)
                    repeat = True

        # remaining areas which belong to a room that has an altitude somewhere
        for contained_areas in space_areas.values():
            contained_areas_with_altitude = contained_areas - areas_without_altitude
            contained_areas_without_altitude = contained_areas - contained_areas_with_altitude
            if contained_areas_with_altitude and contained_areas_without_altitude:
                altitude_areas = {}
                for tmpid in contained_areas_with_altitude:
                    area = areas[tmpid]
                    altitude_areas.setdefault(area.altitude, []).append(area.geometry)

                for altitude in altitude_areas.keys():
                    altitude_areas[altitude] = unary_union(altitude_areas[altitude])
                for tmpid in contained_areas_without_altitude:
                    area = areas[tmpid]
                    area.altitude = min(altitude_areas.items(), key=lambda aa: aa[1].distance(area.geometry))[0]
                areas_without_altitude.difference_update(contained_areas_without_altitude)

        # last fallback: level base_altitude
        for tmpid in areas_without_altitude:
            area = areas[tmpid]
            area.altitude = area.level.base_altitude

        # prepare per-level operations
        level_areas = {}
        for area in areas:
            level_areas.setdefault(area.level, set()).add(area.tmpid)

        # make sure there is only one altitude area per altitude per level
        for level in levels:
            areas_by_altitude = {}
            for tmpid in level_areas.get(level, []):
                area = areas[tmpid]
                areas_by_altitude.setdefault(area.altitude, []).append(area.geometry)

            level_areas[level] = [AltitudeArea(level=level, geometry=unary_union(geometries), altitude=altitude)
                                  for altitude, geometries in areas_by_altitude.items()]

        # renumber joined areas
        areas = list(chain(*(a for a in level_areas.values())))
        for i, area in enumerate(areas):
            area.tmpid = i

        # finalize ramps
        for ramp in ramps:
            if not ramp.connected_to:
                for area in space_areas[ramp.space]:
                    ramp.altitude = areas[area].altitude
                    break
                else:
                    ramp.altitude = ramp.level.base_altitude
                continue

            if len(ramp.connected_to) == 1:
                ramp.altitude = ramp.connected_to[0][0].altitude
                continue

            if len(ramp.connected_to) > 2:
                ramp.connected_to = sorted(ramp.connected_to, key=lambda item: item[1].area)[-2:]

            ramp.point1 = ramp.connected_to[0][1].centroid
            ramp.point2 = ramp.connected_to[1][1].centroid
            ramp.altitude = ramp.connected_to[0][0].altitude
            ramp.altitude2 = ramp.connected_to[1][0].altitude

            ramp.tmpid = len(areas)
            areas.append(ramp)
            level_areas[ramp.level].append(ramp)

        #
        # now fill in the obstacles and so on
        #
        for level in levels:
            for space in level.spaces.all():
                space.geometry = space.orig_geometry

            buildings_geom = unary_union(tuple(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))
                                           for s in level.spaces.all()))
            accessible_area = unary_union((doors_geom, space_geom))
            for space in level.spaces.all():
                accessible_area = accessible_area.difference(space.geometry.intersection(
                    unary_union(tuple(h.geometry for h in space.holes.all()))
                ))

            our_areas = level_areas.get(level, [])
            for area in our_areas:
                area.orig_geometry = area.geometry
                area.orig_geometry_prep = prepared.prep(area.geometry)

            stairs = []
            for space in level.spaces.all():
                space_geom = space.geometry
                if space.outside:
                    space_geom = space_geom.difference(buildings_geom)
                space_geom_prep = prepared.prep(space_geom)
                holes_geom = unary_union(tuple(h.geometry for h in space.holes.all()))
                remaining_space = (
                    tuple(o.geometry for o in space.obstacles.all()) +
                    tuple(o.buffered_geometry for o in space.lineobstacles.all())
                )
                remaining_space = tuple(g.intersection(space_geom).difference(holes_geom)
                                        for g in remaining_space
                                        if space_geom_prep.intersects(g))
                remaining_space = tuple(chain(*(
                    assert_multipolygon(g) for g in remaining_space if not g.is_empty
                )))
                if not remaining_space:
                    continue

                cuts = []
                for cut in chain(*(assert_multilinestring(stair.geometry) for stair in space.stairs.all()),
                                 (ramp.geometry.exterior for ramp in space.ramps.all())):
                    for coord1, coord2 in zip(tuple(cut.coords)[:-1], tuple(cut.coords)[1:]):
                        line = space_geom.intersection(LineString([coord1, coord2]))
                        if line.is_empty:
                            continue
                        factor = (line.length + 2) / line.length
                        line = scale(line, xfact=factor, yfact=factor)
                        centroid = line.centroid
                        line = min(assert_multilinestring(space_geom.intersection(line)),
                                   key=lambda l: l.centroid.distance(centroid), default=None)
                        cuts.append(scale(line, xfact=1.01, yfact=1.01))

                remaining_space = tuple(
                    orient(polygon) for polygon in remaining_space
                )

                for cut in cuts:
                    remaining_space = tuple(chain(*(cut_polygon_with_line(geom, cut)
                                                    for geom in remaining_space)))
                remaining_space = MultiPolygon(remaining_space)

                for polygon in assert_multipolygon(remaining_space):
                    polygon = clean_cut_polygon(polygon).buffer(0)
                    buffered = polygon.buffer(0.001)

                    center = polygon.centroid
                    touches = tuple((area, buffered.intersection(area.orig_geometry).area)
                                    for area in our_areas
                                    if area.orig_geometry_prep.intersects(buffered))
                    if touches:
                        min_touches = sum((t[1] for t in touches), 0)/4
                        area = max(touches, key=lambda item: (item[1] > min_touches,
                                                              item[0].altitude2 is not None,
                                                              item[0].altitude,
                                                              item[1]))[0]
                    else:
                        area = min(our_areas,
                                   key=lambda a: a.orig_geometry.distance(center)-(0 if a.altitude2 is None else 0.6))
                    area.geometry = area.geometry.buffer(0).union(polygon)

        for level in levels:
            level_areas[level] = set(area.tmpid for area in level_areas.get(level, []))

        # save to database
        areas_to_save = set(range(len(areas)))

        all_candidates = AltitudeArea.objects.select_related('level')
        for candidate in all_candidates:
            candidate.area = candidate.geometry.area
            candidate.geometry_prep = prepared.prep(candidate.geometry)
        all_candidates = sorted(all_candidates, key=attrgetter('area'), reverse=True)

        num_modified = 0
        num_deleted = 0
        num_created = 0

        field = AltitudeArea._meta.get_field('geometry')

        for candidate in all_candidates:
            new_area = None

            if candidate.altitude2 is None:
                for tmpid in level_areas.get(candidate.level, set()):
                    area = areas[tmpid]
                    if area.altitude2 is None and area.altitude == candidate.altitude:
                        new_area = area
                        break
            else:
                potential_areas = [areas[tmpid] for tmpid in level_areas.get(candidate.level, set())]
                potential_areas = [area for area in potential_areas
                                   if (candidate.altitude, candidate.altitude2) in ((area.altitude, area.altitude2),
                                                                                    (area.altitude2, area.altitude))]
                potential_areas = [(area, area.geometry.intersection(candidate.geometry).area)
                                   for area in potential_areas
                                   if candidate.geometry_prep.intersects(area.geometry)]
                if potential_areas:
                    new_area = max(potential_areas, key=itemgetter(1))[0]

            if new_area is None:
                candidate.delete()
                num_deleted += 1
                continue

            if not field.get_final_value(new_area.geometry).almost_equals(candidate.geometry):
                num_modified += 1

            candidate.geometry = new_area.geometry
            candidate.altitude = new_area.altitude
            candidate.altitude2 = new_area.altitude2
            candidate.point1 = new_area.point1
            candidate.point2 = new_area.point2
            candidate.save()
            areas_to_save.discard(new_area.tmpid)
            level_areas[new_area.level].discard(new_area.tmpid)

        for tmpid in areas_to_save:
            num_created += 1
            areas[tmpid].save()

        logger = logging.getLogger('c3nav')
        logger.info(_('%d altitude areas built.') % len(areas))
        logger.info(_('%(num_modified)d modified, %(num_deleted)d deleted, %(num_created)d created.') %
                    {'num_modified': num_modified, 'num_deleted': num_deleted, 'num_created': num_created})
Ejemplo n.º 19
0
    def rebuild(cls, update):
        levels_query = Level.objects.prefetch_related('buildings', 'spaces', 'altitudeareas', 'groups',
                                                      'spaces__holes', 'spaces__columns', 'spaces__groups',
                                                      'spaces__obstacles', 'spaces__lineobstacles',
                                                      'spaces__graphnodes', 'spaces__areas', 'spaces__areas__groups',
                                                      'spaces__pois',  'spaces__pois__groups')

        levels = {}
        spaces = {}
        areas = {}
        pois = {}
        groups = {}
        restrictions = {}
        nodes = deque()
        for level in levels_query:
            buildings_geom = unary_union(tuple(building.geometry for building in level.buildings.all()))

            nodes_before_count = len(nodes)

            for group in level.groups.all():
                groups.setdefault(group.pk, {}).setdefault('levels', set()).add(level.pk)

            if level.access_restriction_id:
                restrictions.setdefault(level.access_restriction_id, RouterRestriction()).spaces.update(
                    space.pk for space in level.spaces.all()
                )

            for space in level.spaces.all():
                # create space geometries
                accessible_geom = space.geometry.difference(unary_union(
                    tuple(column.geometry for column in space.columns.all() if column.access_restriction_id is None) +
                    tuple(hole.geometry for hole in space.holes.all()) +
                    ((buildings_geom, ) if space.outside else ())
                ))
                obstacles_geom = unary_union(
                    tuple(obstacle.geometry for obstacle in space.obstacles.all()) +
                    tuple(lineobstacle.buffered_geometry for lineobstacle in space.lineobstacles.all())
                )
                clear_geom = unary_union(tuple(get_rings(accessible_geom.difference(obstacles_geom))))
                clear_geom_prep = prepared.prep(clear_geom)

                for group in space.groups.all():
                    groups.setdefault(group.pk, {}).setdefault('spaces', set()).add(space.pk)

                if space.access_restriction_id:
                    restrictions.setdefault(space.access_restriction_id, RouterRestriction()).spaces.add(space.pk)

                space_nodes = tuple(RouterNode.from_graph_node(node, i)
                                    for i, node in enumerate(space.graphnodes.all()))
                for i, node in enumerate(space_nodes, start=len(nodes)):
                    node.i = i
                nodes.extend(space_nodes)

                space_obj = space
                space = RouterSpace(space)
                space.nodes = set(node.i for node in space_nodes)

                for area in space_obj.areas.all():
                    for group in area.groups.all():
                        groups.setdefault(group.pk, {}).setdefault('areas', set()).add(area.pk)
                    area._prefetched_objects_cache = {}

                    area = RouterArea(area)
                    area_nodes = tuple(node for node in space_nodes if area.geometry_prep.intersects(node.point))
                    area.nodes = set(node.i for node in area_nodes)
                    for node in area_nodes:
                        node.areas.add(area.pk)
                    if not area.nodes and space_nodes:
                        nearest_node = min(space_nodes, key=lambda node: area.geometry.distance(node.point))
                        area.nodes.add(nearest_node.i)
                    areas[area.pk] = area
                    space.areas.add(area.pk)

                for area in level.altitudeareas.all():
                    if not space.geometry_prep.intersects(area.geometry):
                        continue
                    for subgeom in assert_multipolygon(accessible_geom.intersection(area.geometry)):
                        if subgeom.is_empty:
                            continue
                        area_clear_geom = unary_union(tuple(get_rings(subgeom.difference(obstacles_geom))))
                        if area_clear_geom.is_empty:
                            continue
                        area = RouterAltitudeArea(subgeom, area_clear_geom,
                                                  area.altitude, area.altitude2, area.point1, area.point2)
                        area_nodes = tuple(node for node in space_nodes if area.geometry_prep.intersects(node.point))
                        area.nodes = set(node.i for node in area_nodes)
                        for node in area_nodes:
                            altitude = area.get_altitude(node)
                            if node.altitude is None or node.altitude < altitude:
                                node.altitude = altitude

                        space.altitudeareas.append(area)

                for node in space_nodes:
                    if node.altitude is not None:
                        continue
                    logger.warning('Node %d in space %d is not inside an altitude area' % (node.pk, space.pk))
                    node_altitudearea = min(space.altitudeareas,
                                            key=lambda a: a.geometry.distance(node.point), default=None)
                    if node_altitudearea:
                        node.altitude = node_altitudearea.get_altitude(node)
                    else:
                        node.altitude = float(level.base_altitude)
                        logger.info('Space %d has no altitude areas' % space.pk)

                for area in space.altitudeareas:
                    # create fallback nodes
                    if not area.nodes and space_nodes:
                        fallback_point = good_representative_point(area.clear_geometry)
                        fallback_node = RouterNode(None, None, fallback_point.x, fallback_point.y,
                                                   space.pk, area.get_altitude(fallback_point))
                        # todo: check waytypes here
                        for node in space_nodes:
                            line = LineString([(node.x, node.y), (fallback_node.x, fallback_node.y)])
                            if line.length < 5 and not clear_geom_prep.intersects(line):
                                area.fallback_nodes[node.i] = (
                                    fallback_node,
                                    RouterEdge(fallback_node, node, 0)
                                )
                        if not area.fallback_nodes:
                            nearest_node = min(space_nodes, key=lambda node: fallback_point.distance(node.point))
                            area.fallback_nodes[nearest_node.i] = (
                                fallback_node,
                                RouterEdge(fallback_node, nearest_node, 0)
                            )

                for poi in space_obj.pois.all():
                    for group in poi.groups.all():
                        groups.setdefault(group.pk, {}).setdefault('pois', set()).add(poi.pk)
                    poi._prefetched_objects_cache = {}

                    poi = RouterPoint(poi)
                    try:
                        altitudearea = space.altitudearea_for_point(poi.geometry)
                        poi.altitude = altitudearea.get_altitude(poi.geometry)
                        poi_nodes = altitudearea.nodes_for_point(poi.geometry, all_nodes=nodes)
                    except LocationUnreachable:
                        poi_nodes = {}
                    poi.nodes = set(i for i in poi_nodes.keys())
                    poi.nodes_addition = poi_nodes
                    pois[poi.pk] = poi
                    space.pois.add(poi.pk)

                for column in space_obj.columns.all():
                    if column.access_restriction_id is None:
                        continue
                    column.geometry_prep = prepared.prep(column.geometry)
                    column_nodes = tuple(node for node in space_nodes if column.geometry_prep.intersects(node.point))
                    column_nodes = set(node.i for node in column_nodes)
                    restrictions.setdefault(column.access_restriction_id,
                                            RouterRestriction()).additional_nodes.update(column_nodes)

                space_obj._prefetched_objects_cache = {}

                space.src.geometry = accessible_geom

                spaces[space.pk] = space

            level_spaces = set(space.pk for space in level.spaces.all())
            level._prefetched_objects_cache = {}

            level = RouterLevel(level, spaces=level_spaces)
            level.nodes = set(range(nodes_before_count, len(nodes)))
            levels[level.pk] = level

        # add graph descriptions
        for description in LeaveDescription.objects.all():
            spaces[description.space_id].leave_descriptions[description.target_space_id] = description.description

        for description in CrossDescription.objects.all():
            spaces[description.space_id].cross_descriptions[(description.origin_space_id,
                                                             description.target_space_id)] = description.description

        # waytypes
        waytypes = deque([RouterWayType(None)])
        waytypes_lookup = {None: 0}
        for i, waytype in enumerate(WayType.objects.all(), start=1):
            waytypes.append(RouterWayType(waytype))
            waytypes_lookup[waytype.pk] = i
        waytypes = tuple(waytypes)

        # collect nodes
        nodes = tuple(nodes)
        nodes_lookup = {node.pk: node.i for node in nodes}

        # collect edges
        edges = tuple(RouterEdge(from_node=nodes[nodes_lookup[edge.from_node_id]],
                                 to_node=nodes[nodes_lookup[edge.to_node_id]],
                                 waytype=waytypes_lookup[edge.waytype_id],
                                 access_restriction=edge.access_restriction_id) for edge in GraphEdge.objects.all())
        edges = {(edge.from_node, edge.to_node): edge for edge in edges}

        # build graph matrix
        graph = np.full(shape=(len(nodes), len(nodes)), fill_value=np.inf, dtype=np.float32)
        for edge in edges.values():
            index = (edge.from_node, edge.to_node)
            graph[index] = edge.distance
            waytype = waytypes[edge.waytype]
            (waytype.upwards_indices if edge.rise > 0 else waytype.nonupwards_indices).append(index)
            if edge.access_restriction:
                restrictions.setdefault(edge.access_restriction, RouterRestriction()).edges.append(index)

        # respect slow_down_factor
        for area in areas.values():
            if area.slow_down_factor != 1:
                area_nodes = np.array(tuple(area.nodes), dtype=np.uint32)
                graph[area_nodes.reshape((-1, 1)), area_nodes] *= float(area.slow_down_factor)

        # finalize waytype matrixes
        for waytype in waytypes:
            waytype.upwards_indices = np.array(waytype.upwards_indices, dtype=np.uint32).reshape((-1, 2))
            waytype.nonupwards_indices = np.array(waytype.nonupwards_indices, dtype=np.uint32).reshape((-1, 2))

        # finalize restriction edge matrixes
        for restriction in restrictions.values():
            restriction.edges = np.array(restriction.edges, dtype=np.uint32).reshape((-1, 2))

        router = cls(levels, spaces, areas, pois, groups, restrictions, nodes, edges, waytypes, graph)
        pickle.dump(router, open(cls.build_filename(update), 'wb'))
        return router
Ejemplo n.º 20
0
 def __init__(self, polygon):
     self.polygons = tuple(MplPolygonPath(polygon) for polygon in assert_multipolygon(polygon))
Ejemplo n.º 21
0
    def recalculate(cls):
        # collect location areas
        all_areas = []
        all_ramps = []
        space_areas = {}
        spaces = {}
        levels = Level.objects.prefetch_related('buildings', 'doors', 'spaces', 'spaces__columns',
                                                'spaces__obstacles', 'spaces__lineobstacles', 'spaces__holes',
                                                'spaces__stairs', 'spaces__ramps', 'spaces__altitudemarkers')
        logger = logging.getLogger('c3nav')

        for level in levels:
            areas = []
            ramps = []
            stairs = []

            # collect all accessible areas on this level
            buildings_geom = unary_union(tuple(building.geometry for building in level.buildings.all()))
            for space in level.spaces.all():
                spaces[space.pk] = space
                space.orig_geometry = space.geometry
                if space.outside:
                    space.geometry = space.geometry.difference(buildings_geom)
                space_accessible = space.geometry.difference(
                    unary_union(tuple(c.geometry for c in space.columns.all() if c.access_restriction_id is None) +
                                tuple(o.geometry for o in space.obstacles.all()) +
                                tuple(o.buffered_geometry for o in space.lineobstacles.all()) +
                                tuple(h.geometry for h in space.holes.all()))
                )

                space_ramps = unary_union(tuple(r.geometry for r in space.ramps.all()))
                areas.append(space_accessible.difference(space_ramps))
                for geometry in assert_multipolygon(space_accessible.intersection(space_ramps)):
                    ramp = AltitudeArea(geometry=geometry, level=level)
                    ramp.geometry_prep = prepared.prep(geometry)
                    ramp.space = space.pk
                    ramps.append(ramp)

            areas = tuple(orient(polygon) for polygon in assert_multipolygon(
                unary_union(areas+list(door.geometry for door in level.doors.all()))
            ))

            # collect all stairs on this level
            for space in level.spaces.all():
                space_buffer = space.geometry.buffer(0.001, join_style=JOIN_STYLE.mitre)
                for stair in space.stairs.all():
                    stairs.extend(assert_multilinestring(
                        stair.geometry.intersection(space_buffer)
                    ))

            # divide areas using stairs
            for stair in stairs:
                areas = cut_polygon_with_line(areas, stair)

            # create altitudearea objects
            areas = [AltitudeArea(geometry=clean_cut_polygon(area), level=level)
                     for area in areas]

            # prepare area geometries
            for area in areas:
                area.geometry_prep = prepared.prep(area.geometry)

            # assign spaces to areas
            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)
                for space in level.spaces.all():
                    if area.geometry_prep.intersects(space.geometry):
                        area.spaces.add(space.pk)
                        space_areas[space.pk].append(area)

            # give altitudes to areas
            for space in level.spaces.all():
                for altitudemarker in space.altitudemarkers.all():
                    for area in space_areas[space.pk]:
                        if area.geometry_prep.contains(altitudemarker.geometry):
                            area.altitude = altitudemarker.altitude
                            break
                    else:
                        logger.error(
                            _('AltitudeMarker #%(marker_id)d in Space #%(space_id)d on Level %(level_label)s '
                              'is not placed in an accessible area') % {'marker_id': altitudemarker.pk,
                                                                        'space_id': space.pk,
                                                                        'level_label': level.short_label})

            # determine altitude area connections
            for area in areas:
                area.connected_to = []
            for area, other_area in combinations(areas, 2):
                if area.geometry_prep.intersects(other_area.geometry):
                    area.connected_to.append(other_area)
                    other_area.connected_to.append(area)

            # determine ramp connections
            for ramp in ramps:
                ramp.connected_to = []
                buffered = ramp.geometry.buffer(0.001)
                for area in areas:
                    if area.geometry_prep.intersects(buffered):
                        intersection = area.geometry.intersection(buffered)
                        ramp.connected_to.append((area, intersection))
                if len(ramp.connected_to) != 2:
                    if len(ramp.connected_to) == 0:
                        logger.warning('A ramp in space #%d has no connections!' % ramp.space)
                    elif len(ramp.connected_to) == 1:
                        logger.warning('A ramp in space #%d has only one connection!' % ramp.space)
                    else:
                        logger.warning('A ramp in space #%d has more than two connections!' % ramp.space)

            # add areas to global areas
            all_areas.extend(areas)
            all_ramps.extend(ramps)

        # give temporary ids to all areas
        areas = all_areas
        ramps = all_ramps
        for i, area in enumerate(areas):
            area.tmpid = i
        for area in areas:
            area.connected_to = set(area.tmpid for area in area.connected_to)
        for space in space_areas.keys():
            space_areas[space] = set(area.tmpid for area in space_areas[space])
        areas_without_altitude = set(area.tmpid for area in areas if area.altitude is None)

        # interpolate altitudes
        areas_with_altitude = [i for i in range(len(areas)) if i not in areas_without_altitude]
        for i, tmpid in enumerate(areas_with_altitude):
            areas[tmpid].i = i

        csgraph = np.zeros((len(areas), len(areas)), dtype=bool)
        for area in areas:
            for connected_tmpid in area.connected_to:
                csgraph[area.tmpid, connected_tmpid] = True

        repeat = True
        while repeat:
            repeat = False
            distances, predecessors = dijkstra(csgraph, directed=False, return_predecessors=True, unweighted=True)
            np_areas_with_altitude = np.array(areas_with_altitude, dtype=np.uint32)
            relevant_distances = distances[np_areas_with_altitude[:, None], np_areas_with_altitude]
            # noinspection PyTypeChecker
            for from_i, to_i in np.argwhere(np.logical_and(relevant_distances < np.inf, relevant_distances > 1)):
                from_area = areas[areas_with_altitude[from_i]]
                to_area = areas[areas_with_altitude[to_i]]
                if from_area.altitude == to_area.altitude:
                    continue

                path = [to_area.tmpid]
                while path[-1] != from_area.tmpid:
                    path.append(predecessors[from_area.tmpid, path[-1]])

                from_altitude = from_area.altitude
                delta_altitude = (to_area.altitude-from_altitude)/(len(path)-1)

                if set(path[1:-1]).difference(areas_without_altitude):
                    continue

                for i, tmpid in enumerate(reversed(path[1:-1]), start=1):
                    area = areas[tmpid]
                    area.altitude = Decimal(from_altitude+delta_altitude*i).quantize(Decimal('1.00'))
                    areas_without_altitude.discard(tmpid)
                    area.i = len(areas_with_altitude)
                    areas_with_altitude.append(tmpid)

                for from_tmpid, to_tmpid in zip(path[:-1], path[1:]):
                    csgraph[from_tmpid, to_tmpid] = False
                    csgraph[to_tmpid, from_tmpid] = False

                repeat = True

        # remaining areas: copy altitude from connected areas if any
        repeat = True
        while repeat:
            repeat = False
            for tmpid in tuple(areas_without_altitude):
                area = areas[tmpid]
                connected_with_altitude = area.connected_to-areas_without_altitude
                if connected_with_altitude:
                    area.altitude = areas[next(iter(connected_with_altitude))].altitude
                    areas_without_altitude.discard(tmpid)
                    repeat = True

        # remaining areas which belong to a room that has an altitude somewhere
        for contained_areas in space_areas.values():
            contained_areas_with_altitude = contained_areas - areas_without_altitude
            contained_areas_without_altitude = contained_areas - contained_areas_with_altitude
            if contained_areas_with_altitude and contained_areas_without_altitude:
                altitude_areas = {}
                for tmpid in contained_areas_with_altitude:
                    area = areas[tmpid]
                    altitude_areas.setdefault(area.altitude, []).append(area.geometry)

                for altitude in altitude_areas.keys():
                    altitude_areas[altitude] = unary_union(altitude_areas[altitude])
                for tmpid in contained_areas_without_altitude:
                    area = areas[tmpid]
                    area.altitude = min(altitude_areas.items(), key=lambda aa: aa[1].distance(area.geometry))[0]
                areas_without_altitude.difference_update(contained_areas_without_altitude)

        # last fallback: level base_altitude
        for tmpid in areas_without_altitude:
            area = areas[tmpid]
            area.altitude = area.level.base_altitude

        # prepare per-level operations
        level_areas = {}
        for area in areas:
            level_areas.setdefault(area.level, set()).add(area.tmpid)

        # make sure there is only one altitude area per altitude per level
        for level in levels:
            areas_by_altitude = {}
            for tmpid in level_areas.get(level, []):
                area = areas[tmpid]
                areas_by_altitude.setdefault(area.altitude, []).append(area.geometry)

            level_areas[level] = [AltitudeArea(level=level, geometry=unary_union(geometries), altitude=altitude)
                                  for altitude, geometries in areas_by_altitude.items()]

        # renumber joined areas
        areas = list(chain(*(a for a in level_areas.values())))
        for i, area in enumerate(areas):
            area.tmpid = i

        # finalize ramps
        for ramp in ramps:
            if not ramp.connected_to:
                for area in space_areas[ramp.space]:
                    ramp.altitude = areas[area].altitude
                    break
                else:
                    ramp.altitude = ramp.level.base_altitude
                continue

            if len(ramp.connected_to) == 1:
                ramp.altitude = ramp.connected_to[0][0].altitude
                continue

            if len(ramp.connected_to) > 2:
                ramp.connected_to = sorted(ramp.connected_to, key=lambda item: item[1].area)[-2:]

            ramp.point1 = ramp.connected_to[0][1].centroid
            ramp.point2 = ramp.connected_to[1][1].centroid
            ramp.altitude = ramp.connected_to[0][0].altitude
            ramp.altitude2 = ramp.connected_to[1][0].altitude

            ramp.tmpid = len(areas)
            areas.append(ramp)
            level_areas[ramp.level].append(ramp)

        #
        # now fill in the obstacles and so on
        #
        for level in levels:
            for space in level.spaces.all():
                space.geometry = space.orig_geometry

            buildings_geom = unary_union(tuple(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))
                                           for s in level.spaces.all()))
            accessible_area = unary_union((doors_geom, space_geom))
            for space in level.spaces.all():
                accessible_area = accessible_area.difference(space.geometry.intersection(
                    unary_union(tuple(h.geometry for h in space.holes.all()))
                ))

            our_areas = level_areas.get(level, [])
            for area in our_areas:
                area.orig_geometry = area.geometry
                area.orig_geometry_prep = prepared.prep(area.geometry)

            stairs = []
            for space in level.spaces.all():
                space_geom = space.geometry
                if space.outside:
                    space_geom = space_geom.difference(buildings_geom)
                space_geom_prep = prepared.prep(space_geom)
                holes_geom = unary_union(tuple(h.geometry for h in space.holes.all()))
                remaining_space = (
                    tuple(o.geometry for o in space.obstacles.all()) +
                    tuple(o.buffered_geometry for o in space.lineobstacles.all())
                )
                remaining_space = tuple(g.intersection(space_geom).difference(holes_geom)
                                        for g in remaining_space
                                        if space_geom_prep.intersects(g))
                remaining_space = tuple(chain(*(
                    assert_multipolygon(g) for g in remaining_space if not g.is_empty
                )))
                if not remaining_space:
                    continue

                cuts = []
                for cut in chain(*(assert_multilinestring(stair.geometry) for stair in space.stairs.all()),
                                 (ramp.geometry.exterior for ramp in space.ramps.all())):
                    for coord1, coord2 in zip(tuple(cut.coords)[:-1], tuple(cut.coords)[1:]):
                        line = space_geom.intersection(LineString([coord1, coord2]))
                        if line.is_empty:
                            continue
                        factor = (line.length + 2) / line.length
                        line = scale(line, xfact=factor, yfact=factor)
                        centroid = line.centroid
                        line = min(assert_multilinestring(space_geom.intersection(line)),
                                   key=lambda l: l.centroid.distance(centroid), default=None)
                        cuts.append(scale(line, xfact=1.01, yfact=1.01))

                remaining_space = tuple(
                    orient(polygon) for polygon in remaining_space
                )

                for cut in cuts:
                    remaining_space = tuple(chain(*(cut_polygon_with_line(geom, cut)
                                                    for geom in remaining_space)))
                remaining_space = MultiPolygon(remaining_space)

                for polygon in assert_multipolygon(remaining_space):
                    polygon = clean_cut_polygon(polygon).buffer(0)
                    buffered = polygon.buffer(0.001)

                    center = polygon.centroid
                    touches = tuple((area, buffered.intersection(area.orig_geometry).area)
                                    for area in our_areas
                                    if area.orig_geometry_prep.intersects(buffered))
                    if touches:
                        min_touches = sum((t[1] for t in touches), 0)/4
                        area = max(touches, key=lambda item: (item[1] > min_touches,
                                                              item[0].altitude2 is not None,
                                                              item[0].altitude,
                                                              item[1]))[0]
                    else:
                        area = min(our_areas,
                                   key=lambda a: a.orig_geometry.distance(center)-(0 if a.altitude2 is None else 0.6))
                    area.geometry = area.geometry.buffer(0).union(polygon)

        for level in levels:
            level_areas[level] = set(area.tmpid for area in level_areas.get(level, []))

        # save to database
        areas_to_save = set(range(len(areas)))

        all_candidates = AltitudeArea.objects.select_related('level')
        for candidate in all_candidates:
            candidate.area = candidate.geometry.area
            candidate.geometry_prep = prepared.prep(candidate.geometry)
        all_candidates = sorted(all_candidates, key=attrgetter('area'), reverse=True)

        num_modified = 0
        num_deleted = 0
        num_created = 0

        field = AltitudeArea._meta.get_field('geometry')

        for candidate in all_candidates:
            new_area = None

            if candidate.altitude2 is None:
                for tmpid in level_areas.get(candidate.level, set()):
                    area = areas[tmpid]
                    if area.altitude2 is None and area.altitude == candidate.altitude:
                        new_area = area
                        break
            else:
                potential_areas = [areas[tmpid] for tmpid in level_areas.get(candidate.level, set())]
                potential_areas = [area for area in potential_areas
                                   if (candidate.altitude, candidate.altitude2) in ((area.altitude, area.altitude2),
                                                                                    (area.altitude2, area.altitude))]
                potential_areas = [(area, area.geometry.intersection(candidate.geometry).area)
                                   for area in potential_areas
                                   if candidate.geometry_prep.intersects(area.geometry)]
                if potential_areas:
                    new_area = max(potential_areas, key=itemgetter(1))[0]

            if new_area is None:
                candidate.delete()
                num_deleted += 1
                continue

            if not field.get_final_value(new_area.geometry).almost_equals(candidate.geometry):
                num_modified += 1

            candidate.geometry = new_area.geometry
            candidate.altitude = new_area.altitude
            candidate.altitude2 = new_area.altitude2
            candidate.point1 = new_area.point1
            candidate.point2 = new_area.point2
            candidate.save()
            areas_to_save.discard(new_area.tmpid)
            level_areas[new_area.level].discard(new_area.tmpid)

        for tmpid in areas_to_save:
            num_created += 1
            areas[tmpid].save()

        logger = logging.getLogger('c3nav')
        logger.info(_('%d altitude areas built.') % len(areas))
        logger.info(_('%(num_modified)d modified, %(num_deleted)d deleted, %(num_created)d created.') %
                    {'num_modified': num_modified, 'num_deleted': num_deleted, 'num_created': num_created})
Ejemplo n.º 22
0
    def rebuild(cls, update):
        levels_query = Level.objects.prefetch_related('buildings', 'spaces', 'altitudeareas', 'groups',
                                                      'spaces__holes', 'spaces__columns', 'spaces__groups',
                                                      'spaces__obstacles', 'spaces__lineobstacles',
                                                      'spaces__graphnodes', 'spaces__areas', 'spaces__areas__groups',
                                                      'spaces__pois',  'spaces__pois__groups')

        levels = {}
        spaces = {}
        areas = {}
        pois = {}
        groups = {}
        restrictions = {}
        nodes = deque()
        for level in levels_query:
            buildings_geom = unary_union(tuple(building.geometry for building in level.buildings.all()))

            nodes_before_count = len(nodes)

            for group in level.groups.all():
                groups.setdefault(group.pk, {}).setdefault('levels', set()).add(level.pk)

            if level.access_restriction_id:
                restrictions.setdefault(level.access_restriction_id, RouterRestriction()).spaces.update(
                    space.pk for space in level.spaces.all()
                )

            for space in level.spaces.all():
                # create space geometries
                accessible_geom = space.geometry.difference(unary_union(
                    tuple(column.geometry for column in space.columns.all() if column.access_restriction_id is None) +
                    tuple(hole.geometry for hole in space.holes.all()) +
                    ((buildings_geom, ) if space.outside else ())
                ))
                obstacles_geom = unary_union(
                    tuple(obstacle.geometry for obstacle in space.obstacles.all()) +
                    tuple(lineobstacle.buffered_geometry for lineobstacle in space.lineobstacles.all())
                )
                clear_geom = unary_union(tuple(get_rings(accessible_geom.difference(obstacles_geom))))
                clear_geom_prep = prepared.prep(clear_geom)

                for group in space.groups.all():
                    groups.setdefault(group.pk, {}).setdefault('spaces', set()).add(space.pk)

                if space.access_restriction_id:
                    restrictions.setdefault(space.access_restriction_id, RouterRestriction()).spaces.add(space.pk)

                space_nodes = tuple(RouterNode.from_graph_node(node, i)
                                    for i, node in enumerate(space.graphnodes.all()))
                for i, node in enumerate(space_nodes, start=len(nodes)):
                    node.i = i
                nodes.extend(space_nodes)

                space_obj = space
                space = RouterSpace(space)
                space.nodes = set(node.i for node in space_nodes)

                for area in space_obj.areas.all():
                    for group in area.groups.all():
                        groups.setdefault(group.pk, {}).setdefault('areas', set()).add(area.pk)
                    area._prefetched_objects_cache = {}

                    area = RouterArea(area)
                    area_nodes = tuple(node for node in space_nodes if area.geometry_prep.intersects(node.point))
                    area.nodes = set(node.i for node in area_nodes)
                    for node in area_nodes:
                        node.areas.add(area.pk)
                    if not area.nodes and space_nodes:
                        nearest_node = min(space_nodes, key=lambda node: area.geometry.distance(node.point))
                        area.nodes.add(nearest_node.i)
                    areas[area.pk] = area
                    space.areas.add(area.pk)

                for area in level.altitudeareas.all():
                    if not space.geometry_prep.intersects(area.geometry):
                        continue
                    for subgeom in assert_multipolygon(accessible_geom.intersection(area.geometry)):
                        if subgeom.is_empty:
                            continue
                        area_clear_geom = unary_union(tuple(get_rings(subgeom.difference(obstacles_geom))))
                        if area_clear_geom.is_empty:
                            continue
                        area = RouterAltitudeArea(subgeom, area_clear_geom,
                                                  area.altitude, area.altitude2, area.point1, area.point2)
                        area_nodes = tuple(node for node in space_nodes if area.geometry_prep.intersects(node.point))
                        area.nodes = set(node.i for node in area_nodes)
                        for node in area_nodes:
                            altitude = area.get_altitude(node)
                            if node.altitude is None or node.altitude < altitude:
                                node.altitude = altitude

                        space.altitudeareas.append(area)

                for node in space_nodes:
                    if node.altitude is not None:
                        continue
                    logger.warning('Node %d in space %d is not inside an altitude area' % (node.pk, space.pk))
                    node_altitudearea = min(space.altitudeareas, key=lambda a: a.distance(node.point), default=None)
                    if node_altitudearea:
                        node.altitude = node_altitudearea.get_altitude(node)
                    else:
                        node.altitude = float(level.base_altitude)
                        logger.info('Space %d has no altitude areas' % space.pk)

                for area in space.altitudeareas:
                    # create fallback nodes
                    if not area.nodes and space_nodes:
                        fallback_point = good_representative_point(area.clear_geometry)
                        fallback_node = RouterNode(None, None, fallback_point.x, fallback_point.y,
                                                   space.pk, area.get_altitude(fallback_point))
                        # todo: check waytypes here
                        for node in space_nodes:
                            line = LineString([(node.x, node.y), (fallback_node.x, fallback_node.y)])
                            if line.length < 5 and not clear_geom_prep.intersects(line):
                                area.fallback_nodes[node.i] = (
                                    fallback_node,
                                    RouterEdge(fallback_node, node, 0)
                                )
                        if not area.fallback_nodes:
                            nearest_node = min(space_nodes, key=lambda node: fallback_point.distance(node.point))
                            area.fallback_nodes[nearest_node.i] = (
                                fallback_node,
                                RouterEdge(fallback_node, nearest_node, 0)
                            )

                for poi in space_obj.pois.all():
                    for group in poi.groups.all():
                        groups.setdefault(group.pk, {}).setdefault('pois', set()).add(poi.pk)
                    poi._prefetched_objects_cache = {}

                    poi = RouterPoint(poi)
                    try:
                        altitudearea = space.altitudearea_for_point(poi.geometry)
                        poi.altitude = altitudearea.get_altitude(poi.geometry)
                        poi_nodes = altitudearea.nodes_for_point(poi.geometry, all_nodes=nodes)
                    except LocationUnreachable:
                        poi_nodes = {}
                    poi.nodes = set(i for i in poi_nodes.keys())
                    poi.nodes_addition = poi_nodes
                    pois[poi.pk] = poi
                    space.pois.add(poi.pk)

                for column in space_obj.columns.all():
                    if column.access_restriction_id is None:
                        continue
                    column.geometry_prep = prepared.prep(column.geometry)
                    column_nodes = tuple(node for node in space_nodes if column.geometry_prep.intersects(node.point))
                    column_nodes = set(node.i for node in column_nodes)
                    restrictions.setdefault(column.access_restriction_id,
                                            RouterRestriction()).additional_nodes.update(column_nodes)

                space_obj._prefetched_objects_cache = {}

                space.src.geometry = accessible_geom

                spaces[space.pk] = space

            level_spaces = set(space.pk for space in level.spaces.all())
            level._prefetched_objects_cache = {}

            level = RouterLevel(level, spaces=level_spaces)
            level.nodes = set(range(nodes_before_count, len(nodes)))
            levels[level.pk] = level

        # add graph descriptions
        for description in LeaveDescription.objects.all():
            spaces[description.space_id].leave_descriptions[description.target_space_id] = description.description

        for description in CrossDescription.objects.all():
            spaces[description.space_id].cross_descriptions[(description.origin_space_id,
                                                             description.target_space_id)] = description.description

        # waytypes
        waytypes = deque([RouterWayType(None)])
        waytypes_lookup = {None: 0}
        for i, waytype in enumerate(WayType.objects.all(), start=1):
            waytypes.append(RouterWayType(waytype))
            waytypes_lookup[waytype.pk] = i
        waytypes = tuple(waytypes)

        # collect nodes
        nodes = tuple(nodes)
        nodes_lookup = {node.pk: node.i for node in nodes}

        # collect edges
        edges = tuple(RouterEdge(from_node=nodes[nodes_lookup[edge.from_node_id]],
                                 to_node=nodes[nodes_lookup[edge.to_node_id]],
                                 waytype=waytypes_lookup[edge.waytype_id],
                                 access_restriction=edge.access_restriction_id) for edge in GraphEdge.objects.all())
        edges = {(edge.from_node, edge.to_node): edge for edge in edges}

        # build graph matrix
        graph = np.full(shape=(len(nodes), len(nodes)), fill_value=np.inf, dtype=np.float32)
        for edge in edges.values():
            index = (edge.from_node, edge.to_node)
            graph[index] = edge.distance
            waytype = waytypes[edge.waytype]
            (waytype.upwards_indices if edge.rise > 0 else waytype.nonupwards_indices).append(index)
            if edge.access_restriction:
                restrictions.setdefault(edge.access_restriction, RouterRestriction()).edges.append(index)

        # respect slow_down_factor
        for area in areas.values():
            if area.slow_down_factor != 1:
                area_nodes = np.array(tuple(area.nodes), dtype=np.uint32)
                graph[area_nodes.reshape((-1, 1)), area_nodes] *= float(area.slow_down_factor)

        # finalize waytype matrixes
        for waytype in waytypes:
            waytype.upwards_indices = np.array(waytype.upwards_indices, dtype=np.uint32).reshape((-1, 2))
            waytype.nonupwards_indices = np.array(waytype.nonupwards_indices, dtype=np.uint32).reshape((-1, 2))

        # finalize restriction edge matrixes
        for restriction in restrictions.values():
            restriction.edges = np.array(restriction.edges, dtype=np.uint32).reshape((-1, 2))

        router = cls(levels, spaces, areas, pois, groups, restrictions, nodes, edges, waytypes, graph)
        pickle.dump(router, open(cls.build_filename(update), 'wb'))
        return router