from collections.abc import Sequence import logging import math from matplotlib import pyplot as plt import numpy as np import pandas as pd logger = logging.getLogger(__name__) class PathSegment: def __init__(self, a: tuple[float, float], b: tuple[float, float]): """A straight Segment of a Path, from (x_a, y_a) to (x_b, y_b). Args: a (tuple[float, float]): The starting point (x_a, y_a). b (tuple[float, float]): The final point (x_b, y_b). """ self.a = a self.b = b def get_length(self) -> float: return math.dist(self.a, self.b) length = property(get_length) def __str__(self) -> str: return str(f"({self.a}, {self.b})") def __getitem__(self, index) -> float: if index == 0: return self.a elif index == 1: return self.b else: raise IndexError class Path: def __init__( self, coord_list: Sequence[tuple[float, float]], opposite_direction: bool, z: float = 0., z_box: float = 50. ): """Construct a path of sequences based on a list of coordinates. Args: coord_list (Sequence[tuple[float, float]]): List of x,y coordinates. z (float, optional): position of the path in z-direction in meters. Defaults to 0 meters. z_box (float, optional): How much empty space to set above the path in meters. Defaults to 50 meters. """ if len(coord_list) < 2: raise ValueError("Must provide at least two coordinates as a \ of tuples, e.g. [(x1, y1), (x2, y2)]") x, y = tuple(zip(*coord_list)) self.x_list = list(x) self.y_list = list(y) coord_list = list(zip(x, y)) self.segments = [ PathSegment(i, ip1) for i, ip1 in zip(coord_list, coord_list[1:]) ] self.z = z self.opposite_direction = opposite_direction self.size = ( np.ceil(max(self.x_list)), np.ceil(max(self.y_list)), z + z_box ) logger.debug("Path created.") def get_length(self) -> float: return sum([s.length for s in self.segments]) length = property(get_length) def __getitem__(self, index) -> PathSegment: return self.segments[index] def __str__(self) -> str: return str([str(s) for s in self.segments]) def plot(self, **kwargs): """ Plot the path using matplotlib. """ plt.plot(self.x_list, self.y_list, **kwargs) def path_from_RT90( df: pd.DataFrame, east_col: str = "East", north_col: str = "North", **kwargs ) -> Path: """Construct a path from East and North formatted coordinates (RT90) in a Pandas DataFrame. Args: df (pandas.DataFrame): DataFrame containing at least the two columns noted in the cols argument. east_col (str): The column name for the East coordinates. north_col (str): The column name for the North coordinates. Returns: Path: A Path object built from the aquisition coordinates in the DataFrame. """ east_arr = np.array(df[east_col]) - min(df[east_col]) north_arr = np.array(df[north_col]) - min(df[north_col]) coord_pairs = list(zip(east_arr, north_arr)) path = Path(coord_pairs, **kwargs) logger.debug("Loaded path from provided RT90 coordinates.") return path