mirror of
https://github.com/pim-n/pg-rad
synced 2026-03-11 19:58:11 +01:00
177 lines
5.1 KiB
Python
177 lines
5.1 KiB
Python
import logging
|
|
from typing import List, Self
|
|
|
|
from pg_rad.dataloader.dataloader import load_data
|
|
from pg_rad.exceptions.exceptions import OutOfBoundsError
|
|
from pg_rad.objects.sources import PointSource
|
|
from pg_rad.path.path import Path, path_from_RT90
|
|
|
|
from road_gen.generators.segmented_road_generator import SegmentedRoadGenerator
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class Landscape:
|
|
"""
|
|
A generic Landscape that can contain a Path and sources.
|
|
"""
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
path: Path,
|
|
point_sources: list[PointSource] = [],
|
|
size: tuple[int, int, int] = [500, 500, 50],
|
|
air_density: float = 1.243
|
|
):
|
|
"""Initialize a landscape.
|
|
|
|
Args:
|
|
path (Path): A Path object.
|
|
point_sources (list[PointSource], optional): List of point sources.
|
|
air_density (float, optional): Air density in kg/m^3.
|
|
Defaults to 1.243.
|
|
size (tuple[int, int, int], optional): (x,y,z) dimensions of world
|
|
in meters. Defaults to [500, 500, 50].
|
|
|
|
Raises:
|
|
TypeError: _description_
|
|
"""
|
|
|
|
self.name = name
|
|
self.path = path
|
|
self.point_sources = point_sources
|
|
self.size = size
|
|
self.air_density = air_density
|
|
|
|
logger.debug(f"Landscape created: {self.name}")
|
|
|
|
|
|
class LandscapeBuilder:
|
|
def __init__(self, name: str = "Unnamed landscape"):
|
|
self.name = name
|
|
self._path = None
|
|
self._point_sources = []
|
|
self._size = None
|
|
self._air_density = None
|
|
|
|
logger.debug(f"LandscapeBuilder initialized: {self.name}")
|
|
|
|
def set_air_density(self, air_density) -> Self:
|
|
"""Set the air density of the world."""
|
|
self._air_density = air_density
|
|
return self
|
|
|
|
def set_landscape_size(self, size: tuple[int, int, int]) -> Self:
|
|
"""Set the size of the landscape in meters (x,y,z)."""
|
|
if self._path and any(p > s for p, s in zip(self._path.size, size)):
|
|
raise OutOfBoundsError(
|
|
"Cannot set landscape size smaller than the path."
|
|
)
|
|
|
|
self._size = size
|
|
logger.debug("Size of the landscape has been updated.")
|
|
|
|
return self
|
|
|
|
def get_path(self):
|
|
return self._path
|
|
|
|
def set_path_from_segments(
|
|
self,
|
|
length: int | float,
|
|
speed: int | float,
|
|
acquisition_time: int,
|
|
segments: List,
|
|
angles: List,
|
|
alpha: int | float = None
|
|
):
|
|
sg = SegmentedRoadGenerator(
|
|
length=length,
|
|
ds=speed*acquisition_time,
|
|
velocity=speed
|
|
)
|
|
|
|
x, y = sg.generate(
|
|
segments=segments
|
|
)
|
|
|
|
self._path = Path(list(zip(x, y)))
|
|
self._fit_landscape_to_path()
|
|
return self
|
|
|
|
def set_path_from_experimental_data(
|
|
self,
|
|
file: str,
|
|
z: int,
|
|
east_col_name: str = "East",
|
|
north_col_name: str = "North"
|
|
) -> Self:
|
|
df = load_data(file)
|
|
self._path = path_from_RT90(
|
|
df=df,
|
|
east_col=east_col_name,
|
|
north_col=north_col_name,
|
|
z=z
|
|
)
|
|
|
|
self._fit_landscape_to_path()
|
|
|
|
return self
|
|
|
|
def set_point_sources(self, *sources):
|
|
"""Add one or more point sources to the world.
|
|
|
|
Args:
|
|
*sources (pg_rad.sources.PointSource): One or more sources,
|
|
passed as Source1, Source2, ...
|
|
Raises:
|
|
OutOfBoundsError: If any source is outside the boundaries of the
|
|
landscape.
|
|
"""
|
|
if any(
|
|
any(p < 0 or p >= s for p, s in zip(source.pos, self._size))
|
|
for source in sources
|
|
):
|
|
raise OutOfBoundsError(
|
|
"One or more sources attempted to "
|
|
"be placed outside the landscape."
|
|
)
|
|
|
|
self._point_sources = sources
|
|
|
|
def _fit_landscape_to_path(self) -> None:
|
|
"""The size of the landscape will be updated if
|
|
1) _size is not set, or
|
|
2) _size is too small to contain the path."""
|
|
|
|
needs_resize = (
|
|
not self._size
|
|
or any(p > s for p, s in zip(self._path.size, self._size))
|
|
)
|
|
|
|
if needs_resize:
|
|
if not self._size:
|
|
logger.debug("Because no Landscape size was set, "
|
|
"it will now set to path dimensions.")
|
|
else:
|
|
logger.warning(
|
|
"Path exceeds current landscape size. "
|
|
"Landscape size will be expanded to accommodate path."
|
|
)
|
|
|
|
max_size = max(self._path.size)
|
|
self.set_landscape_size((max_size, max_size))
|
|
|
|
def build(self):
|
|
landscape = Landscape(
|
|
name=self.name,
|
|
path=self._path,
|
|
point_sources=self._point_sources,
|
|
size=self._size,
|
|
air_density=self._air_density
|
|
)
|
|
|
|
logger.info(f"Landscape built successfully: {landscape.name}")
|
|
return landscape
|