def get_intersection_point_with(self, point: LidarPoint,
                                    angle: float) -> LidarPoint:
        """
        Calculates the intersection between two this line and the line formed by a point and angle
        :param point: LidarPoint
        :param angle: float in radians
        :return: LidarPoint
        """
        point2 = LidarPoint(x=point.x + math.cos(angle),
                            y=point.y + math.sin(angle))
        vector = LidarVector(point, point2)

        a1, b1, c1 = vector.get_abc()
        a2, b2, c2 = self.get_abc()

        if a1 == 0 and a2 == 0:
            return None
        elif b1 == 0 and b2 == 0:
            return None
        else:
            a = np.array([[a1, b1], [a2, b2]])
            b = np.array([c1, c2])
            intersection = np.linalg.solve(a, b)

        return LidarPoint(x=intersection[0], y=intersection[1])
    def get_mid_points(self, min_wall_distance=300):
        x_s = (self.wall1.start.x + self.wall2.start.x) / 2
        y_s = (self.wall1.start.y + self.wall2.start.y) / 2
        x_t = (self.wall1.end.x + self.wall2.end.x) / 2
        y_t = (self.wall1.end.y + self.wall2.end.y) / 2

        if self.wall_type == WallType.HORIZONTAL:
            x_s -= min_wall_distance
            x_t += min_wall_distance
        else:
            y_s -= min_wall_distance
            y_t += min_wall_distance

        return LidarVector(start=LidarPoint(x_s, y_s),
                           end=LidarPoint(x_t, y_t))
def get_distance_from_nearest_wall(wall_candidates: pd.DataFrame,
                                   pt: LidarPoint, theta: float) -> float:
    min_distance = None
    if math.cos(math.radians(theta)) > 0:
        wall_candidates = wall_candidates[(wall_candidates.xstart > pt.x) |
                                          (wall_candidates.xend > pt.x)]
    else:
        wall_candidates = wall_candidates[(wall_candidates.xstart <= pt.x) |
                                          (wall_candidates.xend <= pt.x)]

    if math.sin(math.radians(theta)) > 0:
        wall_candidates = wall_candidates[(wall_candidates.ystart > pt.y) |
                                          (wall_candidates.yend > pt.y)]
    else:
        wall_candidates = wall_candidates[(wall_candidates.ystart <= pt.y) |
                                          (wall_candidates.yend <= pt.y)]

    for _, wall in wall_candidates.iterrows():
        wall_start = LidarPoint(wall.xstart, wall.ystart)
        wall_end = LidarPoint(wall.xend, wall.yend)
        theta1 = math.degrees(LidarVector(pt, wall_start).direction)
        theta2 = math.degrees(LidarVector(pt, wall_end).direction)

        if theta1 < 0:
            theta1 += 360
        if theta2 < 0:
            theta2 += 360

        if theta1 > theta2:
            theta1, theta2 = theta2, theta1
            wall_start, wall_end = wall_end, wall_start

        if theta2 - theta1 > 180:
            theta1, theta2 = theta2 - 360, theta1
            wall_start, wall_end = wall_end, wall_start

        if theta1 <= theta <= theta2 or theta1 <= theta - 360 <= theta2:
            wall_v = LidarVector(wall_start, wall_end)
            poi = wall_v.get_intersection_point_with(pt, math.radians(theta))

            distance = LidarVector(pt, poi).distance
            if min_distance is None or distance < min_distance:
                min_distance = distance

    return min_distance
    def get_wall_openings(self, min_wall_length=500) -> List[WallOpening]:
        if self.wall_openings is None:
            # Get short walls, walls with length < min_wall_length
            walls = self.get_walls().copy()
            walls['length'] = ((walls.xstart - walls.xend)**2 +
                               (walls.ystart - walls.yend)**2)**0.5
            walls = walls[walls.length < min_wall_length]
            walls['angle'] = walls.apply(lambda row: math.degrees(
                math.atan2(row.yend - row.ystart, row.xend - row.xstart)),
                                         axis=1)
            self.wall_openings = []
            walls_seen = set()
            for _, wall1 in walls.iterrows():
                w1 = LidarVector(start=LidarPoint(wall1.xstart, wall1.ystart),
                                 end=LidarPoint(wall1.xend, wall1.yend))

                if w1.__str__() in walls_seen:
                    continue
                else:
                    walls_seen.add(w1.__str__())
                temp_walls = walls.copy()
                temp_walls['distance'] = temp_walls.apply(lambda row: (
                    (row.xstart - wall1.xstart)**2 +
                    (row.ystart - wall1.ystart)**2)**0.5,
                                                          axis=1)
                temp_walls = temp_walls[temp_walls.distance > 0].sort_values(
                    by='distance')
                wall2 = temp_walls.iloc[0]

                w2 = LidarVector(start=LidarPoint(wall2.xstart, wall2.ystart),
                                 end=LidarPoint(wall2.xend, wall2.yend))
                if w2.__str__() in walls_seen:
                    continue
                else:
                    walls_seen.add(w2.__str__())
                self.wall_openings.append(WallOpening(wall1=w1, wall2=w2))

        return self.wall_openings
def generate_lidar_points(m_csv: str, lp_csv: str, fp_csv: str, num_points=500, verbose=False) -> pd.DataFrame:
    if not (os.path.exists(m_csv) and os.path.exists(fp_csv)):
        return pd.DataFrame(data=[])

    angles, distances = [], []
    walls = pd.read_csv(m_csv, names=['xstart', 'ystart', 'xend', 'yend'])

    pts = DroneMap(
        flight_path_csv=fp_csv,
        lidar_points_csv=lp_csv
    ).get_drone_positions()

    for i in range(len(pts)):
        pt = pts.iloc[i]
        if verbose:
            print("generating lidar points around ({:.4f}, {:.4f})".format(pt.x, pt.y))
        angles.append(i)
        distances.append(num_points)
        for theta in np.linspace(start=0, stop=360, num=num_points, endpoint=False):
            min_distance = get_distance_from_nearest_wall(
                wall_candidates=walls,
                pt=LidarPoint(pt.x, pt.y),
                theta=theta
            )

            angles.append(round(360 - theta, 4))
            distances.append(min_distance)

    lp = pd.DataFrame(
        data=dict(
            angle=np.array(angles),
            distance=np.array(distances).astype(int)
        )
    )

    if os.path.exists(os.path.dirname(lp_csv)):
        lp.to_csv(lp_csv, index=False, header=False)
    if verbose:
        print("Written the csv file: {}".format(os.path.realpath(lp_csv)))

    return lp
    def get_optimum_flight_path(self,
                                start: LidarPoint,
                                end: LidarPoint = None,
                                is_visit_all_rooms=False,
                                plot=False,
                                fp_csv=None,
                                verbose=False,
                                min_wall_distance=300) -> pd.DataFrame:
        connections = []
        points = self.get_all_points()
        if verbose:
            print("generating nodes")
        if is_visit_all_rooms:
            nodes = self.get_nodes(start=start)
        else:
            nodes = self.get_nodes(start=start, end=end)

        for node in nodes:
            for neighbor in node.neighbors:
                vector = LidarVector(node, neighbor)

                if vector.start.x < vector.end.x:
                    start_x, end_x = vector.start.x, vector.end.x
                else:
                    end_x, start_x = vector.start.x, vector.end.x

                if vector.start.y < vector.end.y:
                    start_y, end_y = vector.start.y, vector.end.y
                else:
                    end_y, start_y = vector.start.y, vector.end.y

                p = points[(points.x >= start_x) & (points.x <= end_x) &
                           (points.y >= start_y) & (points.y <= end_y)]

                if not len(p):
                    connections.append((node, neighbor, vector.distance))
                elif len(p) < 5:
                    obs_x, obs_y = p.x.mean(), p.y.mean()
                    distance = LidarVector(node, neighbor).get_distance_from(
                        LidarPoint(obs_x, obs_y))

                    if distance > min_wall_distance:
                        connections.append((node, neighbor, vector.distance))
                    else:
                        obs1 = LidarPoint(obs_x, obs_y + min_wall_distance)
                        obs2 = LidarPoint(obs_x, obs_y - min_wall_distance)

                        lv11 = LidarVector(node, obs1)
                        lv12 = LidarVector(obs1, neighbor)
                        lv21 = LidarVector(node, obs2)
                        lv22 = LidarVector(obs2, neighbor)

                        connections.append((node, obs1, lv11.distance))
                        connections.append((obs1, neighbor, lv12.distance))
                        connections.append((node, obs2, lv21.distance))
                        connections.append((obs2, neighbor, lv22.distance))

        x_s, y_s = [], []

        if verbose:
            print("analyzing shortest path")
        if is_visit_all_rooms:
            paths = []
            Graph(connections).shortest_path_traverse_all(start, paths=paths)

            best_path, best_distance = None, math.inf,
            best_dikstra_path = []
            for bp, bc in paths:
                df_best_path = self.get_optimum_flight_path(start=bp[len(bp) -
                                                                     1],
                                                            end=end)
                prev_row = df_best_path.iloc[0]
                distance = 0
                for _, curr_row in df_best_path[1:].iterrows():
                    distance += ((curr_row.x - prev_row.x)**2 +
                                 (curr_row.y - prev_row.y)**2)**0.5
                    prev_row = curr_row
                if distance < best_distance:
                    best_path, best_distance = bp, distance
                    best_dikstra_path = df_best_path

            for pos in best_path:
                x_s.append(pos.x)
                y_s.append(pos.y)
            for _, pos in best_dikstra_path.iterrows():
                x_s.append(pos.x)
                y_s.append(pos.y)

        else:
            positions = Graph(connections).shortest_path(start, end)

            for pos in positions:
                x_s.append(pos.x)
                y_s.append(pos.y)

        df = pd.DataFrame(data=list(zip(x_s, y_s)), columns=['x', 'y'])

        if fp_csv is not None:
            fp_csv_dir = os.path.dirname(fp_csv)
            if not os.path.exists(fp_csv_dir):
                os.makedirs(fp_csv_dir)

            with open(fp_csv, 'w', newline='') as csvfile:
                spamwriter = csv.writer(csvfile,
                                        delimiter=',',
                                        quotechar='"',
                                        quoting=csv.QUOTE_MINIMAL)
                for i in range(len(df)):
                    row = df.iloc[i]
                    spamwriter.writerow([i, 1])
                    spamwriter.writerow([row.x / 1e3, row.y / 1e3])

            if verbose:
                print("Written the csv file: {}".format(
                    os.path.realpath(fp_csv)))

        if plot:
            df.plot(kind='line', x='x', y='y', color='r', ax=self.ax)
            if verbose:
                print("plotting results")
            self.visualize_lidar_points(drone_pos=False)

        return df
    def get_walls(self, div=100):
        if self.walls is None:
            half_div = div / 2
            df_points = self.get_all_points().copy()
            walls = set()
            for _, corner in self.get_all_corners().iterrows():
                corner = LidarPoint(corner.x, corner.y)
                df = self.get_all_corners().copy()
                df['r'] = df.apply(lambda row: ((row.x - corner.x)**2 +
                                                (row.y - corner.y)**2)**0.5,
                                   axis=1)
                top_corner = df[(df.y > corner.y)
                                & (abs(df.x - corner.x) < div)].sort_values(
                                    by='r')[:1]
                down_corner = df[(df.y < corner.y)
                                 & (abs(df.x - corner.x) < div)].sort_values(
                                     by='r')[:1]
                left_corner = df[(df.x < corner.x)
                                 & (abs(df.y - corner.y) < div)].sort_values(
                                     by='r')[:1]
                right_corner = df[(df.x > corner.x)
                                  & (abs(df.y - corner.y) < div)].sort_values(
                                      by='r')[:1]

                if len(top_corner) and len(down_corner):
                    top_corner = LidarPoint(top_corner.iloc[0].x,
                                            top_corner.iloc[0].y)
                    down_corner = LidarPoint(down_corner.iloc[0].x,
                                             down_corner.iloc[0].y)

                    top_pts = df_points[(df_points.y < top_corner.y - half_div)
                                        & (df_points.y > corner.y + half_div) &
                                        (abs(df_points.x - corner.x) < div)]

                    down_pts = df_points[
                        (df_points.y < corner.y - half_div)
                        & (df_points.y > down_corner.y + half_div) &
                        (abs(df_points.x - corner.x) < div)]

                    if len(top_pts) > len(down_pts):
                        v_corner = LidarPoint(top_corner.x, top_corner.y)
                    else:
                        v_corner = LidarPoint(down_corner.x, down_corner.y)
                elif len(top_corner):
                    v_corner = LidarPoint(top_corner.iloc[0].x,
                                          top_corner.iloc[0].y)
                else:
                    v_corner = LidarPoint(down_corner.iloc[0].x,
                                          down_corner.iloc[0].y)

                if len(right_corner) and len(left_corner):
                    right_corner = LidarPoint(right_corner.iloc[0].x,
                                              right_corner.iloc[0].y)
                    left_corner = LidarPoint(left_corner.iloc[0].x,
                                             left_corner.iloc[0].y)

                    right_pts = df_points[
                        (df_points.x < right_corner.x - half_div)
                        & (df_points.x > corner.x + half_div) &
                        (abs(df_points.y - corner.y) < div)]

                    left_pts = df_points[
                        (df_points.x < corner.x - half_div)
                        & (df_points.x > left_corner.x + half_div) &
                        (abs(df_points.y - corner.y) < div)]

                    if len(right_pts) > len(left_pts):
                        h_corner = LidarPoint(right_corner.x, right_corner.y)
                    else:
                        h_corner = LidarPoint(left_corner.x, left_corner.y)
                elif not len(right_corner):
                    h_corner = LidarPoint(left_corner.iloc[0].x,
                                          left_corner.iloc[0].y)
                else:
                    h_corner = LidarPoint(right_corner.iloc[0].x,
                                          right_corner.iloc[0].y)

                wall_corner = WallCorner(cc=corner, cv=v_corner, ch=h_corner)
                w = wall_corner.get_walls()
                if w[0] not in walls:
                    walls.add(w[0])
                if w[1] not in walls:
                    walls.add(w[1])

            x_start, y_start = [], []
            x_end, y_end = [], []
            for wl in walls:
                x_start.append(wl.start.x)
                y_start.append(wl.start.y)
                x_end.append(wl.end.x)
                y_end.append(wl.end.y)

            self.walls = pd.DataFrame(
                data=list(zip(x_start, y_start, x_end, y_end)),
                columns=['xstart', 'ystart', 'xend', 'yend']).sort_values(
                    by=['xstart', 'ystart', 'xend', 'yend'
                        ]).drop_duplicates().reset_index(drop=True).astype(int)

        return self.walls
                print("plotting results")
            self.visualize_lidar_points(drone_pos=False)

        return df


if __name__ == '__main__':
    dm = DroneMap(lidar_points_csv='./.cache/LIDARPoints.csv',
                  flight_path_csv='./.cache/FlightPath.csv')
    """Visualize Lidar points"""
    # dm.visualize_lidar_points(by_scan_id=False)
    # dm.generate_mapping_csv(csv_file="./.cache/Mapping.csv")
    """Test for connectivity between two points"""
    # lp1 = LidarPoint(5e3, 4e3)
    # lp2 = LidarPoint(15e3, 14e3)
    # assert not dm.is_connected(lp1, lp2), "{} {} should not be connected".format(lp1, lp2)
    # lp1 = LidarPoint(5e3, 4e3)
    # lp2 = LidarPoint(7.5e3, 8e3)
    # assert dm.is_connected(lp1, lp2), "{} {} should be connected".format(lp1, lp2)
    """Get Wall Openings"""
    # print(dm.get_wall_openings())
    """Get nodes of each room"""
    # nodes = dm.get_nodes(start=LidarPoint(7.5e3, 8e3), end=LidarPoint(17.5e3, 8e3))
    # print(nodes)
    """Get Optimum Flight Path"""
    dm.get_optimum_flight_path(
        start=LidarPoint(7.5e3, 8e3),
        end=LidarPoint(17.5e3, 12e3),
        fp_csv='./.cache/fp.csv',
    )
            print("missing flight path csv")
        elif args.lp_csv is None:
            print("missing lidar points csv")
        elif args.start_x is None:
            print("missing starting point x coordinate")
        elif args.start_y is None:
            print("missing starting point y coordinate")
        elif args.end_x is None:
            print("missing end point x coordinate")
        elif args.end_y is None:
            print("missing end point y coordinate")
        else:
            dm = DroneMap(lidar_points_csv=args.lp_csv,
                          flight_path_csv='./.cache/FlightPath.csv')
            dm.get_optimum_flight_path(
                end=LidarPoint(args.end_x, args.end_y),
                start=LidarPoint(args.start_x, args.start_y),
                fp_csv=args.fp_csv,
                plot=True,
                is_visit_all_rooms=args.challenge == 3,
                verbose=args.verbose,
            )

    elif args.challenge == 5:
        """
        Challenge 5: Mapping
            Input:  LidarPoints.csv, FlightPath.csv
            Output: Mapping.csv
        """
        dm = DroneMap(lidar_points_csv=args.lp_csv,
                      flight_path_csv=args.fp_csv)