mirror of
https://github.com/pim-n/road-gen.git
synced 2026-02-03 09:23:09 +01:00
Initial commit
This commit is contained in:
0
src/road_gen/generators/__init__.py
Normal file
0
src/road_gen/generators/__init__.py
Normal file
50
src/road_gen/generators/base_road_generator.py
Normal file
50
src/road_gen/generators/base_road_generator.py
Normal file
@ -0,0 +1,50 @@
|
||||
import numpy as np
|
||||
import secrets
|
||||
|
||||
class BaseRoadGenerator:
|
||||
"""A base generator object for generating a road of a specified length."""
|
||||
def __init__(
|
||||
self,
|
||||
length: int | float,
|
||||
ds: int | float,
|
||||
velocity: int | float,
|
||||
mu: float = 0.7,
|
||||
g: float = 9.81,
|
||||
seed: int | None = None
|
||||
):
|
||||
"""Initialize a BaseGenerator with a given or random seed.
|
||||
|
||||
Args:
|
||||
length (int | float): The total length of the road in meters.
|
||||
ds (int | float): The step size in meters.
|
||||
velocity (int | float): Velocity in meters per second.
|
||||
mu (float): Coefficient of friction. Defaults to 0.7 (dry asphalt).
|
||||
g (float): Acceleration due to gravity (m/s^2). Defaults to 9.81.
|
||||
seed (int | None, optional): Set a seed for the generator. Default is a random seed.
|
||||
"""
|
||||
if seed == None:
|
||||
seed = secrets.randbits(32)
|
||||
|
||||
if not isinstance(seed, int):
|
||||
raise TypeError("seed must be an integer or None.")
|
||||
|
||||
if not isinstance(length, int | float):
|
||||
raise TypeError("Length must be an integer or float in meters.")
|
||||
|
||||
if not isinstance(ds, int | float):
|
||||
raise TypeError("Step size must be integer or float in meters.")
|
||||
|
||||
if not isinstance(velocity, int | float):
|
||||
raise TypeError("Velocity must be integer or float in meters per second.")
|
||||
|
||||
self.length = length
|
||||
self.ds = ds
|
||||
|
||||
self.velocity = velocity
|
||||
self.min_radius = (velocity ** 2) / (g * mu)
|
||||
|
||||
self.seed = seed
|
||||
self._rng = np.random.default_rng(seed)
|
||||
|
||||
def generate(self):
|
||||
pass
|
||||
70
src/road_gen/generators/random_road_generator.py
Normal file
70
src/road_gen/generators/random_road_generator.py
Normal file
@ -0,0 +1,70 @@
|
||||
from typing import Tuple
|
||||
import numpy as np
|
||||
|
||||
from .base_road_generator import BaseRoadGenerator
|
||||
from ..integrator.integrator import integrate_road
|
||||
|
||||
class RandomRoadGenerator(BaseRoadGenerator):
|
||||
def __init__(
|
||||
self,
|
||||
length: int | float,
|
||||
ds: int | float,
|
||||
velocity: int | float,
|
||||
mu: float = 0.7,
|
||||
g: float = 9.81,
|
||||
seed: int | None = None
|
||||
):
|
||||
|
||||
"""Initialize a RandomRoadGenerator with a given or random seed.
|
||||
|
||||
Args:
|
||||
length (int | float): The total length of the road in meters.
|
||||
ds (int | float): The step size in meters.
|
||||
velocity (int | float): Velocity in meters per second.
|
||||
mu (float): Coefficient of friction. Defaults to 0.7 (dry asphalt).
|
||||
g (float): Acceleration due to gravity (m/s^2). Defaults to 9.81.
|
||||
seed (int | None, optional): Set a seed for the generator. Default is a random seed.
|
||||
"""
|
||||
|
||||
super().__init__(length, ds, velocity, mu, g, seed)
|
||||
|
||||
def generate(
|
||||
self,
|
||||
straight_section_prob: float = 0.05,
|
||||
straight_section_max_rel_size: float = 0.1
|
||||
) -> Tuple[np.ndarray, np.ndarray]:
|
||||
"""Generate a random road according to specified parameters.
|
||||
|
||||
Args:
|
||||
straight_section_prob (float, optional): Probability at every step i that a straight section will start. Defaults to 0.05.
|
||||
straight_section_max_rel_size (float, optional): The maximum size that straight section(s) can have relative to the total length of the path. Defaults to 0.1.
|
||||
|
||||
Returns:
|
||||
Tuple[np.ndarray, np.ndarray]: x and y coordinates of the waypoints describing the random road.
|
||||
"""
|
||||
|
||||
self.straight_section_prob = straight_section_prob
|
||||
self.straight_section_max_rel_size = straight_section_max_rel_size
|
||||
|
||||
num_points = int(self.length / (self.ds))
|
||||
max_curvature = 1 / self.min_radius
|
||||
|
||||
white_noise = self._rng.standard_normal(num_points)
|
||||
curvature = np.clip(white_noise, -max_curvature, max_curvature)
|
||||
|
||||
# Randomly add multiple straight sections
|
||||
i = 0
|
||||
while i < num_points:
|
||||
if np.random.rand() < straight_section_prob:
|
||||
# Random straight segment length
|
||||
max_len = int(num_points * straight_section_max_rel_size)
|
||||
seg_len = np.random.randint(1, max_len + 1)
|
||||
curvature[i:i+seg_len] = 0 # set curvature to zero for straight
|
||||
i += seg_len # skip over straight segment
|
||||
else:
|
||||
i += 1
|
||||
|
||||
x, y = integrate_road(curvature)
|
||||
|
||||
return x * self.ds, y * self.ds
|
||||
|
||||
99
src/road_gen/generators/segmented_road_generator.py
Normal file
99
src/road_gen/generators/segmented_road_generator.py
Normal file
@ -0,0 +1,99 @@
|
||||
from typing import Tuple
|
||||
|
||||
import numpy as np
|
||||
from matplotlib import pyplot as plt
|
||||
|
||||
from .base_road_generator import BaseRoadGenerator
|
||||
from ..prefabs import prefabs
|
||||
from ..integrator.integrator import integrate_road
|
||||
|
||||
class SegmentedRoadGenerator(BaseRoadGenerator):
|
||||
def __init__(
|
||||
self,
|
||||
length: int | float,
|
||||
ds: int | float,
|
||||
velocity: int | float,
|
||||
mu: float = 0.7,
|
||||
g: float = 9.81,
|
||||
seed: int | None = None
|
||||
):
|
||||
"""Initialize a SegmentedRoadGenerator with given or random seed.
|
||||
|
||||
Args:
|
||||
length (int | float): The total length of the road in meters.
|
||||
ds (int | float): The step size in meters.
|
||||
velocity (int | float): Velocity in meters per second.
|
||||
mu (float): Coefficient of friction. Defaults to 0.7 (dry asphalt).
|
||||
g (float): Acceleration due to gravity (m/s^2). Defaults to 9.81.
|
||||
seed (int | None, optional): Set a seed for the generator. Default is a random seed.
|
||||
"""
|
||||
|
||||
super().__init__(length, ds, velocity, mu, g, seed)
|
||||
|
||||
def generate(
|
||||
self,
|
||||
segments: list[str],
|
||||
alpha: float = 1.0
|
||||
) -> Tuple[np.ndarray, np.ndarray]:
|
||||
"""Generate a curvature profile from a list of segments.
|
||||
|
||||
Args:
|
||||
segments (list[str]): List of segments.
|
||||
length (int): Total length of the path in meters.
|
||||
velocity (float): Velocity of the car in km/h.
|
||||
ds (float, optional): Step size. Defaults to 1.0.
|
||||
alpha (float, optional): Dirichlet concentration parameter. A higher value leads to more uniform apportionment of the length amongst the segments, while a lower value allows more random apportionment. Defaults to 1.0.
|
||||
|
||||
Raises:
|
||||
ValueError: _description_
|
||||
ValueError: _description_
|
||||
|
||||
Returns:
|
||||
np.ndarray: _description_
|
||||
"""
|
||||
if not all(segment in prefabs.PREFABS.keys() for segment in segments):
|
||||
raise ValueError(f"Invalid segment type provided. Available choices: {prefabs.SEGMENTS.keys()}")
|
||||
|
||||
num_points = int(self.length / self.ds)
|
||||
|
||||
# divide num_points into len(segments) randomly sized parts.
|
||||
parts = self._rng.dirichlet(np.full(len(segments), alpha), size=1)[0]
|
||||
parts = parts * num_points
|
||||
parts = np.round(parts).astype(int)
|
||||
|
||||
# correct round off so the sum of parts is still total length L.
|
||||
if sum(parts) != num_points:
|
||||
parts[0] += num_points - sum(parts)
|
||||
|
||||
|
||||
curvature = np.zeros(num_points)
|
||||
current_index = 0
|
||||
|
||||
for seg_name, seg_length in zip(segments, parts):
|
||||
seg_function = prefabs.PREFABS[seg_name]
|
||||
|
||||
if seg_name == 'straight':
|
||||
curvature_s = seg_function(seg_length)
|
||||
else:
|
||||
R_min_angle = seg_length / (np.pi / 2)
|
||||
R_max_angle = seg_length / (np.pi / 6)
|
||||
|
||||
# physics limit
|
||||
R_min = max(self.min_radius, R_min_angle)
|
||||
|
||||
if R_min > R_max_angle:
|
||||
raise ValueError("No valid radius for this turn segment")
|
||||
|
||||
rand_radius = np.random.uniform(R_min, R_max_angle)
|
||||
|
||||
if seg_name.startswith("u_turn"):
|
||||
curvature_s = seg_function(rand_radius)
|
||||
else:
|
||||
curvature_s = seg_function(seg_length, rand_radius)
|
||||
|
||||
curvature[current_index:(current_index + seg_length)] = curvature_s
|
||||
current_index += seg_length
|
||||
|
||||
x, y = integrate_road(curvature)
|
||||
|
||||
return x * self.ds, y * self.ds
|
||||
Reference in New Issue
Block a user