Add all local circuit work — year-by-year SVGs and tooling
This commit is contained in:
@@ -0,0 +1,968 @@
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
data = {
|
||||
"type": "FeatureCollection",
|
||||
"name": "hu-1986",
|
||||
"bbox": [
|
||||
19.242326,
|
||||
47.577571,
|
||||
19.256609,
|
||||
47.588474
|
||||
],
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"id": "hu-1986",
|
||||
"Location": "Budapest",
|
||||
"Name": "Hungaroring",
|
||||
"opened": 1986,
|
||||
"firstgp": 1986,
|
||||
"length": 4381,
|
||||
"altitude": 239
|
||||
},
|
||||
"bbox": [
|
||||
19.242326,
|
||||
47.577571,
|
||||
19.256609,
|
||||
47.588474
|
||||
],
|
||||
"geometry": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
19.245888,
|
||||
47.58026
|
||||
],
|
||||
[
|
||||
19.243226,
|
||||
47.581696
|
||||
],
|
||||
[
|
||||
19.242439,
|
||||
47.582111
|
||||
],
|
||||
[
|
||||
19.242368,
|
||||
47.582191
|
||||
],
|
||||
[
|
||||
19.242326,
|
||||
47.582271
|
||||
],
|
||||
[
|
||||
19.242338,
|
||||
47.582361
|
||||
],
|
||||
[
|
||||
19.242391,
|
||||
47.582431
|
||||
],
|
||||
[
|
||||
19.242468,
|
||||
47.582478
|
||||
],
|
||||
[
|
||||
19.242569,
|
||||
47.582511
|
||||
],
|
||||
[
|
||||
19.242717,
|
||||
47.582526
|
||||
],
|
||||
[
|
||||
19.242936,
|
||||
47.582526
|
||||
],
|
||||
[
|
||||
19.243546,
|
||||
47.582516
|
||||
],
|
||||
[
|
||||
19.243954,
|
||||
47.582469
|
||||
],
|
||||
[
|
||||
19.244327,
|
||||
47.582394
|
||||
],
|
||||
[
|
||||
19.244718,
|
||||
47.58229
|
||||
],
|
||||
[
|
||||
19.245014,
|
||||
47.582177
|
||||
],
|
||||
[
|
||||
19.245238,
|
||||
47.582073
|
||||
],
|
||||
[
|
||||
19.247411,
|
||||
47.580913
|
||||
],
|
||||
[
|
||||
19.24757,
|
||||
47.580857
|
||||
],
|
||||
[
|
||||
19.247724,
|
||||
47.580833
|
||||
],
|
||||
[
|
||||
19.247878,
|
||||
47.580833
|
||||
],
|
||||
[
|
||||
19.248003,
|
||||
47.580862
|
||||
],
|
||||
[
|
||||
19.248139,
|
||||
47.580895
|
||||
],
|
||||
[
|
||||
19.248245,
|
||||
47.580951
|
||||
],
|
||||
[
|
||||
19.248334,
|
||||
47.581017
|
||||
],
|
||||
[
|
||||
19.248393,
|
||||
47.581078
|
||||
],
|
||||
[
|
||||
19.248446,
|
||||
47.581163
|
||||
],
|
||||
[
|
||||
19.248464,
|
||||
47.581281
|
||||
],
|
||||
[
|
||||
19.248458,
|
||||
47.581347
|
||||
],
|
||||
[
|
||||
19.248411,
|
||||
47.581437
|
||||
],
|
||||
[
|
||||
19.24837,
|
||||
47.581526
|
||||
],
|
||||
[
|
||||
19.247748,
|
||||
47.582238
|
||||
],
|
||||
[
|
||||
19.247683,
|
||||
47.58237
|
||||
],
|
||||
[
|
||||
19.247671,
|
||||
47.582474
|
||||
],
|
||||
[
|
||||
19.247707,
|
||||
47.582563
|
||||
],
|
||||
[
|
||||
19.247754,
|
||||
47.582676
|
||||
],
|
||||
[
|
||||
19.247813,
|
||||
47.58278
|
||||
],
|
||||
[
|
||||
19.2485,
|
||||
47.583704
|
||||
],
|
||||
[
|
||||
19.249719,
|
||||
47.585368
|
||||
],
|
||||
[
|
||||
19.249938,
|
||||
47.585632
|
||||
],
|
||||
[
|
||||
19.250193,
|
||||
47.585863
|
||||
],
|
||||
[
|
||||
19.250512,
|
||||
47.586113
|
||||
],
|
||||
[
|
||||
19.250577,
|
||||
47.586183
|
||||
],
|
||||
[
|
||||
19.250625,
|
||||
47.586254
|
||||
],
|
||||
[
|
||||
19.250636,
|
||||
47.586334
|
||||
],
|
||||
[
|
||||
19.250601,
|
||||
47.586424
|
||||
],
|
||||
[
|
||||
19.250045,
|
||||
47.587838
|
||||
],
|
||||
[
|
||||
19.250033,
|
||||
47.587946
|
||||
],
|
||||
[
|
||||
19.25005,
|
||||
47.58805
|
||||
],
|
||||
[
|
||||
19.250092,
|
||||
47.588154
|
||||
],
|
||||
[
|
||||
19.250157,
|
||||
47.588253
|
||||
],
|
||||
[
|
||||
19.250275,
|
||||
47.588333
|
||||
],
|
||||
[
|
||||
19.250417,
|
||||
47.588404
|
||||
],
|
||||
[
|
||||
19.250565,
|
||||
47.588455
|
||||
],
|
||||
[
|
||||
19.250713,
|
||||
47.588474
|
||||
],
|
||||
[
|
||||
19.250897,
|
||||
47.58847
|
||||
],
|
||||
[
|
||||
19.251074,
|
||||
47.588441
|
||||
],
|
||||
[
|
||||
19.251288,
|
||||
47.58838
|
||||
],
|
||||
[
|
||||
19.25153,
|
||||
47.588276
|
||||
],
|
||||
[
|
||||
19.251702,
|
||||
47.588196
|
||||
],
|
||||
[
|
||||
19.251903,
|
||||
47.588097
|
||||
],
|
||||
[
|
||||
19.252116,
|
||||
47.587984
|
||||
],
|
||||
[
|
||||
19.252383,
|
||||
47.587796
|
||||
],
|
||||
[
|
||||
19.253519,
|
||||
47.586966
|
||||
],
|
||||
[
|
||||
19.253572,
|
||||
47.586886
|
||||
],
|
||||
[
|
||||
19.25359,
|
||||
47.586824
|
||||
],
|
||||
[
|
||||
19.253584,
|
||||
47.586758
|
||||
],
|
||||
[
|
||||
19.253525,
|
||||
47.586702
|
||||
],
|
||||
[
|
||||
19.25343,
|
||||
47.586641
|
||||
],
|
||||
[
|
||||
19.253353,
|
||||
47.586589
|
||||
],
|
||||
[
|
||||
19.253324,
|
||||
47.586542
|
||||
],
|
||||
[
|
||||
19.25333,
|
||||
47.586457
|
||||
],
|
||||
[
|
||||
19.253359,
|
||||
47.586363
|
||||
],
|
||||
[
|
||||
19.253773,
|
||||
47.585311
|
||||
],
|
||||
[
|
||||
19.253862,
|
||||
47.58517
|
||||
],
|
||||
[
|
||||
19.253927,
|
||||
47.585113
|
||||
],
|
||||
[
|
||||
19.253992,
|
||||
47.585062
|
||||
],
|
||||
[
|
||||
19.254081,
|
||||
47.585014
|
||||
],
|
||||
[
|
||||
19.254188,
|
||||
47.584991
|
||||
],
|
||||
[
|
||||
19.254389,
|
||||
47.584963
|
||||
],
|
||||
[
|
||||
19.255247,
|
||||
47.584878
|
||||
],
|
||||
[
|
||||
19.255425,
|
||||
47.584831
|
||||
],
|
||||
[
|
||||
19.255537,
|
||||
47.584779
|
||||
],
|
||||
[
|
||||
19.255632,
|
||||
47.584727
|
||||
],
|
||||
[
|
||||
19.255715,
|
||||
47.584637
|
||||
],
|
||||
[
|
||||
19.255744,
|
||||
47.584557
|
||||
],
|
||||
[
|
||||
19.25578,
|
||||
47.584463
|
||||
],
|
||||
[
|
||||
19.25578,
|
||||
47.584359
|
||||
],
|
||||
[
|
||||
19.25575,
|
||||
47.584246
|
||||
],
|
||||
[
|
||||
19.255537,
|
||||
47.583233
|
||||
],
|
||||
[
|
||||
19.255531,
|
||||
47.583053
|
||||
],
|
||||
[
|
||||
19.255549,
|
||||
47.58294
|
||||
],
|
||||
[
|
||||
19.255596,
|
||||
47.582823
|
||||
],
|
||||
[
|
||||
19.255662,
|
||||
47.582709
|
||||
],
|
||||
[
|
||||
19.256502,
|
||||
47.581804
|
||||
],
|
||||
[
|
||||
19.256585,
|
||||
47.581668
|
||||
],
|
||||
[
|
||||
19.256603,
|
||||
47.581573
|
||||
],
|
||||
[
|
||||
19.256609,
|
||||
47.581484
|
||||
],
|
||||
[
|
||||
19.256591,
|
||||
47.58139
|
||||
],
|
||||
[
|
||||
19.256561,
|
||||
47.581295
|
||||
],
|
||||
[
|
||||
19.256502,
|
||||
47.58121
|
||||
],
|
||||
[
|
||||
19.256407,
|
||||
47.581126
|
||||
],
|
||||
[
|
||||
19.254963,
|
||||
47.580032
|
||||
],
|
||||
[
|
||||
19.253679,
|
||||
47.579028
|
||||
],
|
||||
[
|
||||
19.253241,
|
||||
47.578698
|
||||
],
|
||||
[
|
||||
19.253134,
|
||||
47.578656
|
||||
],
|
||||
[
|
||||
19.253034,
|
||||
47.578646
|
||||
],
|
||||
[
|
||||
19.252939,
|
||||
47.578656
|
||||
],
|
||||
[
|
||||
19.252856,
|
||||
47.578689
|
||||
],
|
||||
[
|
||||
19.252773,
|
||||
47.57874
|
||||
],
|
||||
[
|
||||
19.251897,
|
||||
47.57941
|
||||
],
|
||||
[
|
||||
19.251755,
|
||||
47.579504
|
||||
],
|
||||
[
|
||||
19.251086,
|
||||
47.579872
|
||||
],
|
||||
[
|
||||
19.250968,
|
||||
47.579914
|
||||
],
|
||||
[
|
||||
19.25085,
|
||||
47.579947
|
||||
],
|
||||
[
|
||||
19.250707,
|
||||
47.579961
|
||||
],
|
||||
[
|
||||
19.250583,
|
||||
47.579952
|
||||
],
|
||||
[
|
||||
19.250417,
|
||||
47.5799
|
||||
],
|
||||
[
|
||||
19.250317,
|
||||
47.579825
|
||||
],
|
||||
[
|
||||
19.250246,
|
||||
47.579744
|
||||
],
|
||||
[
|
||||
19.250204,
|
||||
47.579674
|
||||
],
|
||||
[
|
||||
19.250193,
|
||||
47.579579
|
||||
],
|
||||
[
|
||||
19.25021,
|
||||
47.579485
|
||||
],
|
||||
[
|
||||
19.250269,
|
||||
47.579396
|
||||
],
|
||||
[
|
||||
19.250346,
|
||||
47.579325
|
||||
],
|
||||
[
|
||||
19.251666,
|
||||
47.578608
|
||||
],
|
||||
[
|
||||
19.251909,
|
||||
47.578458
|
||||
],
|
||||
[
|
||||
19.25198,
|
||||
47.578377
|
||||
],
|
||||
[
|
||||
19.252033,
|
||||
47.578264
|
||||
],
|
||||
[
|
||||
19.252057,
|
||||
47.578132
|
||||
],
|
||||
[
|
||||
19.252039,
|
||||
47.57801
|
||||
],
|
||||
[
|
||||
19.25198,
|
||||
47.577906
|
||||
],
|
||||
[
|
||||
19.251909,
|
||||
47.577807
|
||||
],
|
||||
[
|
||||
19.251797,
|
||||
47.577722
|
||||
],
|
||||
[
|
||||
19.251613,
|
||||
47.577642
|
||||
],
|
||||
[
|
||||
19.251424,
|
||||
47.57759
|
||||
],
|
||||
[
|
||||
19.251217,
|
||||
47.577571
|
||||
],
|
||||
[
|
||||
19.250998,
|
||||
47.57759
|
||||
],
|
||||
[
|
||||
19.250802,
|
||||
47.577642
|
||||
],
|
||||
[
|
||||
19.250619,
|
||||
47.577727
|
||||
],
|
||||
[
|
||||
19.245888,
|
||||
47.58026
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
def rotate(xy, *, angle):
|
||||
"""Rotate coordinates by the given angle."""
|
||||
rot_mat = np.array([[np.cos(angle), np.sin(angle)],
|
||||
[-np.sin(angle), np.cos(angle)]])
|
||||
return np.matmul(xy, rot_mat)
|
||||
|
||||
def calculate_aspect_ratio_rotation(coordinates, preferred_ratio=1.618): # Golden ratio by default
|
||||
"""Find rotation that gives closest match to desired aspect ratio."""
|
||||
best_rotation = 0
|
||||
best_ratio_diff = float('inf')
|
||||
|
||||
for angle in range(0, 180, 5): # Check every 5 degrees
|
||||
rad_angle = np.radians(angle)
|
||||
rotated = rotate(coordinates, angle=rad_angle)
|
||||
|
||||
# Calculate bounding box
|
||||
x_coords = [p[0] for p in rotated]
|
||||
y_coords = [p[1] for p in rotated]
|
||||
width = max(x_coords) - min(x_coords)
|
||||
height = max(y_coords) - min(y_coords)
|
||||
|
||||
current_ratio = width / height
|
||||
ratio_diff = abs(current_ratio - preferred_ratio)
|
||||
|
||||
if ratio_diff < best_ratio_diff:
|
||||
best_ratio_diff = ratio_diff
|
||||
best_rotation = angle
|
||||
|
||||
return best_rotation
|
||||
|
||||
def calculate_minimal_area_rotation(coordinates):
|
||||
"""Find rotation that minimizes the bounding box area."""
|
||||
best_rotation = 0
|
||||
min_area = float('inf')
|
||||
|
||||
for angle in range(0, 180, 5):
|
||||
rad_angle = np.radians(angle)
|
||||
rotated = rotate(coordinates, angle=rad_angle)
|
||||
|
||||
x_coords = [p[0] for p in rotated]
|
||||
y_coords = [p[1] for p in rotated]
|
||||
width = max(x_coords) - min(x_coords)
|
||||
height = max(y_coords) - min(y_coords)
|
||||
|
||||
area = width * height
|
||||
if area < min_area:
|
||||
min_area = area
|
||||
best_rotation = angle
|
||||
|
||||
return best_rotation
|
||||
|
||||
def calculate_pca_rotation(coordinates):
|
||||
"""Use PCA to align the track with its principal axes."""
|
||||
from sklearn.decomposition import PCA
|
||||
|
||||
# Convert coordinates to numpy array if not already
|
||||
coords_array = np.array(coordinates)
|
||||
|
||||
# Fit PCA
|
||||
pca = PCA(n_components=2)
|
||||
pca.fit(coords_array)
|
||||
|
||||
# Calculate rotation angle from first principal component
|
||||
first_component = pca.components_[0]
|
||||
angle = np.arctan2(first_component[1], first_component[0])
|
||||
return np.degrees(angle)
|
||||
|
||||
def calculate_start_straight_rotation(coordinates, straight_length=10):
|
||||
"""Align the track so the start/finish straight is vertical/horizontal."""
|
||||
# Assuming first points are from start/finish straight
|
||||
start_points = coordinates[:straight_length]
|
||||
|
||||
# Calculate direction vector of the straight
|
||||
dx = start_points[-1][0] - start_points[0][0]
|
||||
dy = start_points[-1][1] - start_points[0][1]
|
||||
|
||||
# Calculate angle to horizontal
|
||||
angle = np.degrees(np.arctan2(dy, dx))
|
||||
|
||||
# Return rotation needed to align with horizontal (0°) or vertical (90°)
|
||||
horizontal_rotation = -angle
|
||||
vertical_rotation = 90 - angle
|
||||
|
||||
# Return whichever requires less rotation
|
||||
return horizontal_rotation if abs(horizontal_rotation) < abs(vertical_rotation) else vertical_rotation
|
||||
|
||||
def find_longest_straight(coordinates, window_size=5):
|
||||
"""Find the longest approximately straight section of the track, including wrap-around."""
|
||||
max_distance = 0
|
||||
best_start_idx = 0
|
||||
n = len(coordinates)
|
||||
|
||||
# Helper function to check straightness
|
||||
def is_straight(points, start, end, length):
|
||||
direction = (end - start) / length
|
||||
distances = []
|
||||
for point in points:
|
||||
projection = start + np.dot(point - start, direction) * direction
|
||||
distance = np.linalg.norm(point - projection)
|
||||
distances.append(distance)
|
||||
return max(distances) < length * 0.05 # 5% tolerance
|
||||
|
||||
# Check all possible segments, including wrap-around
|
||||
for i in range(n):
|
||||
# Get window_size points, handling wrap-around
|
||||
segment = []
|
||||
for j in range(window_size):
|
||||
idx = (i + j) % n
|
||||
segment.append(np.array(coordinates[idx]))
|
||||
|
||||
start = np.array(segment[0])
|
||||
end = np.array(segment[-1])
|
||||
length = np.linalg.norm(end - start)
|
||||
|
||||
if length > max_distance:
|
||||
# Check if all points are roughly on the line
|
||||
if is_straight(segment, start, end, length):
|
||||
max_distance = length
|
||||
best_start_idx = i
|
||||
|
||||
# Return indices that might wrap around
|
||||
end_idx = (best_start_idx + window_size) % len(coordinates)
|
||||
return best_start_idx, end_idx
|
||||
|
||||
def calculate_longest_straight_rotation(coordinates):
|
||||
"""Find the rotation that places the longest straight section horizontally at the bottom."""
|
||||
# First find the longest straight section
|
||||
start_idx, end_idx = find_longest_straight(coordinates)
|
||||
|
||||
best_angle = 0
|
||||
min_y_diff = float('inf')
|
||||
coordinates_array = np.array(coordinates)
|
||||
|
||||
# Create a figure for visualization
|
||||
plt.figure(figsize=(15, 5))
|
||||
|
||||
# Try angles in smaller increments for more precision
|
||||
for angle in np.linspace(0, 2*np.pi, 72): # 5-degree increments
|
||||
# Rotate the entire track
|
||||
rotated_track = rotate(coordinates_array, angle=angle)
|
||||
|
||||
# Get y-coordinates of the straight section
|
||||
straight_y1 = rotated_track[start_idx][1]
|
||||
straight_y2 = rotated_track[end_idx][1]
|
||||
y_diff = abs(straight_y1 - straight_y2)
|
||||
|
||||
# If this is the best rotation so far, show it
|
||||
if y_diff < min_y_diff:
|
||||
min_y_diff = y_diff
|
||||
best_angle = angle
|
||||
|
||||
# Clear previous plots
|
||||
plt.clf()
|
||||
|
||||
# Create three subplots
|
||||
plt.subplot(131)
|
||||
plt.title('Original Track')
|
||||
plt.plot(coordinates_array[:, 0], coordinates_array[:, 1], 'k-')
|
||||
plt.plot([coordinates_array[start_idx][0], coordinates_array[end_idx][0]],
|
||||
[coordinates_array[start_idx][1], coordinates_array[end_idx][1]], 'r-', linewidth=2)
|
||||
plt.axis('equal')
|
||||
|
||||
plt.subplot(132)
|
||||
plt.title(f'Current Rotation ({angle:.1f} rad)')
|
||||
plt.plot(rotated_track[:, 0], rotated_track[:, 1], 'k-')
|
||||
plt.plot([rotated_track[start_idx][0], rotated_track[end_idx][0]],
|
||||
[rotated_track[start_idx][1], rotated_track[end_idx][1]], 'r-', linewidth=2)
|
||||
plt.axis('equal')
|
||||
|
||||
# Show y-difference
|
||||
plt.text(0.5, -0.1, f'Y-diff: {y_diff:.2f}',
|
||||
horizontalalignment='center', transform=plt.gca().transAxes)
|
||||
|
||||
plt.subplot(133)
|
||||
plt.title('Y-coordinates of Straight')
|
||||
plt.plot([0, 1], [straight_y1, straight_y2], 'b-')
|
||||
plt.axhline(y=0, color='k', linestyle='--')
|
||||
plt.ylim(min(straight_y1, straight_y2) - 1, max(straight_y1, straight_y2) + 1)
|
||||
|
||||
plt.tight_layout()
|
||||
plt.pause(0.1) # Show the plot for a moment
|
||||
|
||||
# Now rotate all coordinates with best angle
|
||||
rotated_coords = rotate(coordinates_array, angle=best_angle)
|
||||
|
||||
# Check if we need to flip 180 degrees
|
||||
straight_y = np.mean([rotated_coords[start_idx][1], rotated_coords[end_idx][1]])
|
||||
track_center_y = np.mean(rotated_coords[:, 1])
|
||||
|
||||
if straight_y > track_center_y:
|
||||
best_angle += np.pi
|
||||
rotated_coords = rotate(coordinates_array, angle=best_angle)
|
||||
|
||||
# Show final result
|
||||
plt.clf()
|
||||
plt.title('Final Result')
|
||||
plt.plot(rotated_coords[:, 0], rotated_coords[:, 1], 'k-')
|
||||
plt.plot([rotated_coords[start_idx][0], rotated_coords[end_idx][0]],
|
||||
[rotated_coords[start_idx][1], rotated_coords[end_idx][1]], 'r-', linewidth=2)
|
||||
plt.axis('equal')
|
||||
plt.show()
|
||||
|
||||
return best_angle
|
||||
|
||||
def evaluate_rotation_strategies(coordinates):
|
||||
"""Compare different rotation strategies and score them."""
|
||||
strategies = {
|
||||
'aspect_ratio': calculate_aspect_ratio_rotation,
|
||||
'minimal_area': calculate_minimal_area_rotation,
|
||||
'pca': calculate_pca_rotation,
|
||||
'start_straight': calculate_start_straight_rotation,
|
||||
'longest_straight': calculate_longest_straight_rotation,
|
||||
}
|
||||
|
||||
results = {}
|
||||
|
||||
for name, strategy in strategies.items():
|
||||
angle = strategy(coordinates)
|
||||
rotated = rotate(coordinates, angle=np.radians(angle))
|
||||
|
||||
# Calculate metrics
|
||||
x_coords = [p[0] for p in rotated]
|
||||
y_coords = [p[1] for p in rotated]
|
||||
width = max(x_coords) - min(x_coords)
|
||||
height = max(y_coords) - min(y_coords)
|
||||
|
||||
results[name] = {
|
||||
'rotation_angle': angle,
|
||||
'aspect_ratio': width / height,
|
||||
'area': width * height,
|
||||
'width': width,
|
||||
'height': height
|
||||
}
|
||||
|
||||
return results
|
||||
|
||||
def plot_rotation_outcomes(coordinates):
|
||||
# Set up the figure
|
||||
fig = plt.figure(figsize=(15, 12))
|
||||
grid = plt.GridSpec(3, 2, figure=fig)
|
||||
|
||||
axes = [
|
||||
fig.add_subplot(grid[0, 0]),
|
||||
fig.add_subplot(grid[0, 1]),
|
||||
fig.add_subplot(grid[1, 0]),
|
||||
fig.add_subplot(grid[1, 1]),
|
||||
fig.add_subplot(grid[2, :])
|
||||
]
|
||||
|
||||
# Get results from all strategies
|
||||
results = evaluate_rotation_strategies(coordinates)
|
||||
longest_straight_angle = calculate_longest_straight_rotation(coordinates)
|
||||
rotated = rotate(coordinates, angle=np.radians(longest_straight_angle))
|
||||
x_coords = [p[0] for p in rotated]
|
||||
y_coords = [p[1] for p in rotated]
|
||||
width = max(x_coords) - min(x_coords)
|
||||
height = max(y_coords) - min(y_coords)
|
||||
|
||||
results['longest_straight'] = {
|
||||
'rotation_angle': longest_straight_angle,
|
||||
'aspect_ratio': width / height,
|
||||
'area': width * height,
|
||||
'width': width,
|
||||
'height': height
|
||||
}
|
||||
|
||||
# Find the longest straight section once
|
||||
start_idx, end_idx = find_longest_straight(coordinates)
|
||||
|
||||
# Create the straight section handling wrap-around
|
||||
straight_section = []
|
||||
i = start_idx
|
||||
while i != end_idx:
|
||||
straight_section.append(coordinates[i])
|
||||
i = (i + 1) % len(coordinates)
|
||||
straight_section.append(coordinates[end_idx])
|
||||
|
||||
for (name, metrics), ax in zip(results.items(), axes):
|
||||
# Rotate coordinates using the calculated angle
|
||||
rotated = rotate(coordinates, angle=np.radians(metrics['rotation_angle']))
|
||||
rotated_straight = rotate(straight_section, angle=np.radians(metrics['rotation_angle']))
|
||||
|
||||
# Plot the main track in blue
|
||||
x_coords = [p[0] for p in rotated]
|
||||
y_coords = [p[1] for p in rotated]
|
||||
ax.plot(x_coords, y_coords, 'b-', linewidth=2, label='Track')
|
||||
|
||||
# Plot the longest straight section in red
|
||||
straight_x = [p[0] for p in rotated_straight]
|
||||
straight_y = [p[1] for p in rotated_straight]
|
||||
ax.plot(straight_x, straight_y, 'r-', linewidth=3, alpha=0.8, label='Longest straight')
|
||||
|
||||
ax.set_aspect('equal')
|
||||
|
||||
# Add title with metrics
|
||||
title = f"{name}\nRotation: {metrics['rotation_angle']:.1f}°\n"
|
||||
title += f"Aspect Ratio: {metrics['aspect_ratio']:.2f}\n"
|
||||
title += f"Area: {metrics['area']:.0f}"
|
||||
ax.set_title(title)
|
||||
|
||||
# Add bounding box
|
||||
min_x, max_x = min(x_coords), max(x_coords)
|
||||
min_y, max_y = min(y_coords), max(y_coords)
|
||||
bbox = plt.Rectangle((min_x, min_y),
|
||||
max_x - min_x,
|
||||
max_y - min_y,
|
||||
fill=False,
|
||||
color='red',
|
||||
linestyle='--')
|
||||
ax.add_patch(bbox)
|
||||
|
||||
# Add legend
|
||||
ax.legend(loc='upper right')
|
||||
|
||||
plt.tight_layout()
|
||||
plt.show()
|
||||
|
||||
def get_best_rotation(coordinates, preferences):
|
||||
"""
|
||||
Get best rotation based on user preferences.
|
||||
|
||||
preferences: dict with weights for different factors:
|
||||
{
|
||||
'aspect_ratio_weight': 0.3,
|
||||
'area_weight': 0.2,
|
||||
'start_straight_weight': 0.3,
|
||||
'preferred_orientation': 'landscape', # or 'portrait'
|
||||
'preferred_ratio': 1.618 # desired aspect ratio
|
||||
}
|
||||
"""
|
||||
results = evaluate_rotation_strategies(coordinates)
|
||||
scores = {}
|
||||
|
||||
for strategy, metrics in results.items():
|
||||
score = 0
|
||||
|
||||
# Aspect ratio scoring
|
||||
if preferences['preferred_orientation'] == 'landscape':
|
||||
score += (metrics['aspect_ratio'] > 1) * preferences['aspect_ratio_weight']
|
||||
else:
|
||||
score += (metrics['aspect_ratio'] < 1) * preferences['aspect_ratio_weight']
|
||||
|
||||
# Area efficiency scoring
|
||||
min_area = min(r['area'] for r in results.values())
|
||||
score += (min_area / metrics['area']) * preferences['area_weight']
|
||||
|
||||
# Start straight alignment scoring
|
||||
if strategy == 'start_straight':
|
||||
score += preferences['start_straight_weight']
|
||||
|
||||
scores[strategy] = score
|
||||
|
||||
best_strategy = max(scores.items(), key=lambda x: x[1])[0]
|
||||
return results[best_strategy]['rotation_angle']
|
||||
|
||||
coordinates = data.get("features", [])[0].get("geometry", {}).get("coordinates", [])
|
||||
plot_rotation_outcomes(coordinates)
|
||||
|
||||
# preferences = {
|
||||
# 'aspect_ratio_weight': 0.3,
|
||||
# 'area_weight': 0.2,
|
||||
# 'start_straight_weight': 0.3,
|
||||
# 'preferred_orientation': 'landscape',
|
||||
# 'preferred_ratio': 1.618
|
||||
# }
|
||||
# optimal_rotation = get_best_rotation(coordinates, preferences)
|
||||
Reference in New Issue
Block a user