Cone Ordering
Cone ordering is the process of organizing scattered cone observations into sorted left and right track boundaries. This is a critical preprocessing step that transforms unordered cone detections into a structured representation suitable for trajectory optimization.
Problem Statement
Input: Unordered cone positions with color labels (blue = left, yellow = right)
Output: Two sorted lists of cones representing the left and right track boundaries, ordered by distance along the track
Challenge: Cones may be observed in any order, and observations may have:
Missing cones (occlusions, limited sensor range)
Noisy positions (sensor measurement errors)
Ambiguous pairings (wide track sections, complex geometries)
Distance-Based Ordering
The current implementation uses a greedy nearest-neighbor algorithm:
Algorithm Description
def distance_cone_order(cones, vehicle_state):
1. Filter out cones behind the vehicle
2. Start from vehicle position
3. Repeat until all cones processed:
a. Find nearest cone to current position
b. Add cone to ordered list
c. Move current position to that cone
4. Interpolate to get N evenly-spaced points
5. Return ordered left and right boundaries
Step 1: Filter Cones Behind Vehicle
Transform cones to vehicle frame and reject those with negative x-coordinates:
# Rotation matrix to vehicle frame
R = [[cos(-heading), -sin(-heading)],
[sin(-heading), cos(-heading)]]
cone_vehicle_frame = R @ (cone_position - vehicle_position)
if cone_vehicle_frame[0] > 0: # Forward of vehicle
keep_cone()
This prevents the algorithm from ordering cones in the wrong direction.
Step 2: Greedy Nearest Neighbor
Starting from the vehicle position, iteratively select the closest unprocessed cone:
current_position = vehicle_position
ordered_cones = []
while unprocessed_cones:
distances = [||cone - current_position|| for cone in unprocessed_cones]
nearest_idx = argmin(distances)
nearest_cone = unprocessed_cones[nearest_idx]
ordered_cones.append(nearest_cone)
unprocessed_cones.remove(nearest_cone)
current_position = nearest_cone
This produces a path that follows the cone boundary.
Step 3: Interpolation
The ordered cones are not evenly spaced, but the trajectory optimizer expects N uniformly distributed points. Linear interpolation creates this uniform distribution:
# Compute arc length parameterization
t = [0]
for i in range(1, len(ordered_cones)):
dx = ordered_cones[i][0] - ordered_cones[i-1][0]
dy = ordered_cones[i][1] - ordered_cones[i-1][1]
t.append(t[-1] + sqrt(dx² + dy²))
# Interpolate at uniform spacing
t_uniform = linspace(0, t[-1], N)
interpolated_cones = interp1d(t, ordered_cones)(t_uniform)
This ensures the optimization has evenly distributed boundary points.
See: local_path/local_path/distance_cone_order.py:9-102
Implementation Details
Filtering
The filtering step is critical for correct operation:
heading = state[2] # Vehicle heading angle
rotation_matrix = rotation(-heading)
for cone in cones:
cone_rel = cone - vehicle_position
cone_in_car_frame = rotation_matrix @ cone_rel
if cone_in_car_frame[0] > 0: # x > 0 means forward
filtered_cones.append(cone)
Without this filter, the algorithm might order cones backwards or create loops.
Separate Left/Right Processing
Left and right boundaries are ordered independently:
Blue cones (left boundary) processed separately from yellow cones (right)
Each boundary ordered using the same greedy algorithm
Both start from the vehicle position
This prevents incorrect pairing between left and right sides.
Minimum Cone Requirements
The algorithm requires at least 2 cones per side:
if len(left_sorted) < 2 or len(right_sorted) < 2:
return None, None # Insufficient cones
With only 1 cone per side, interpolation is not possible.
Algorithm Characteristics
This is a simple greedy nearest-neighbor implementation that provides fast computation (~5-20ms) and is easy to understand. The algorithm works well for the majority of typical race track scenarios, handling straight sections and moderate curves effectively.
Known Limitations: We acknowledge that this distance-based approach does not handle all edge cases perfectly. The greedy nature of the algorithm can fail on complex track geometries such as sharp hairpins, very wide sections, or ambiguous cone pairings. We are currently working on developing a more robust cone ordering solution.
For the 2025 competition, this distance-based algorithm was selected as the primary approach due to its simplicity and consistent performance on typical tracks, despite its known limitations.
Edge Case Failures
Greedy Approach
The nearest-neighbor strategy can fail when:
Track boundaries cross or converge (e.g., sharp hairpins)
Wide track sections with ambiguous nearest cone
Cones form complex patterns not captured by simple distance
Example failure case:
Left boundary: A -------- B
|
Right boundary: C D
If starting at A, algorithm might go A → C (nearest)
instead of A → B (correct track direction)
No Global Optimization
The algorithm processes cones sequentially without considering the entire set:
Cannot backtrack if early decisions were suboptimal
May create zigzag patterns in ambiguous situations
Doesn’t enforce smoothness or consistency
Edge Case Failures
Known problematic scenarios:
Very wide sections: Distance ambiguity between left/right sides
Hairpin turns: Cones may be geometrically close but far along track
Missing cones: Gaps in observations can break the ordering
Dense cone placement: Multiple cones at similar distances
Alternative Algorithm
An alternative cone ordering algorithm (funny_cone_ordering
) is under development to address these limitations. It uses more sophisticated logic for pairing and ordering cones, but has not been fully validated on all edge cases.
Note: The system is configured to use distance-based ordering (distance_cone_order
) as the primary algorithm. While the distance-based approach has known limitations, it provides consistent performance on typical race tracks. We acknowledge that neither the current distance-based algorithm nor the alternative algorithm handles all edge cases perfectly.
Visualization
For debugging cone ordering, the local planner publishes visualization markers:
Topic: /path/local/vizconeorder
Type: visualization_msgs/Marker
(LINE_LIST)
Content: Red lines connecting paired left-right boundary points
This allows visual inspection of the cone ordering result in RViz:
# Each pair of left-right cones shown as a line
points = []
for left_cone, right_cone in zip(ordered_left, ordered_right):
points.append(Point(x=left_cone[0], y=left_cone[1]))
points.append(Point(x=right_cone[0], y=right_cone[1]))
If the lines are smooth and roughly perpendicular to the track, ordering is good. If lines cross or zigzag, ordering has failed.
Configuration
From all_settings.py
:
class LocalOptSettings(metaclass=Settings):
N: int = 10 # Number of interpolated points
distance_cone_order: bool = True # Use distance-based ordering
To disable distance-based ordering and use the alternative algorithm:
distance_cone_order: bool = False
However, this is not recommended for production use until the alternative is fully validated.