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)
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)
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
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)])
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)
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
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)
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
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)
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))
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)
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)
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)')
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)
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()
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) ])
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})
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
def __init__(self, polygon): self.polygons = tuple(MplPolygonPath(polygon) for polygon in assert_multipolygon(polygon))
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})
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