Compute the average turning angle of a line.
The average turning angle measures the mean absolute angular change between
consecutive segments of a line. A straight line has an average turning angle
of 0, while a line with many sharp turns has a higher value.
Note
This function calculates the absolute angle between consecutive segments
using the arctan2 method, which gives angles in the range [0, 180] degrees.
Parameters:
Name |
Type |
Description |
Default |
line
|
Union[LineString, MultiLineString]
|
Input shapely LineString or MultiLineString object.
|
required
|
Returns:
Name | Type |
Description |
float |
Optional[float]
|
The average turning angle in degrees. Returns None if the line has
fewer than 3 points (insufficient to calculate any angles).
|
Source code in src/histolytics/spatial_geom/line_metrics.py
| def average_turning_angle(line: Union[LineString, MultiLineString]) -> Optional[float]:
"""Compute the average turning angle of a line.
The average turning angle measures the mean absolute angular change between
consecutive segments of a line. A straight line has an average turning angle
of 0, while a line with many sharp turns has a higher value.
Note:
This function calculates the absolute angle between consecutive segments
using the arctan2 method, which gives angles in the range [0, 180] degrees.
Parameters:
line (Union[LineString, MultiLineString]):
Input shapely LineString or MultiLineString object.
Returns:
float:
The average turning angle in degrees. Returns None if the line has
fewer than 3 points (insufficient to calculate any angles).
"""
if isinstance(line, LineString):
# Extract coordinates
coords = list(line.coords)
# Need at least 3 points to calculate angles
if len(coords) < 3:
return None
# Calculate angles between consecutive segments
angles = []
for i in range(len(coords) - 2):
# Get three consecutive points
p1 = coords[i]
p2 = coords[i + 1]
p3 = coords[i + 2]
# Calculate direction angles of the two segments using arctan2
# arctan2 gives angle in radians with respect to the positive x-axis
angle1 = np.arctan2(p2[1] - p1[1], p2[0] - p1[0])
angle2 = np.arctan2(p3[1] - p2[1], p3[0] - p2[0])
# Calculate the absolute difference in angles (turning angle)
# This handles the circular nature of angles
diff = np.abs(angle2 - angle1)
if diff > np.pi:
diff = 2 * np.pi - diff
# Convert to degrees
angle_deg = np.degrees(diff)
angles.append(angle_deg)
# Return the average turning angle
return np.mean(angles) if angles else None
elif isinstance(line, MultiLineString):
# Combine all angles from individual LineStrings
all_angles = []
for geom in line.geoms:
# Process each LineString
coords = list(geom.coords)
if len(coords) < 3:
continue
for i in range(len(coords) - 2):
p1 = coords[i]
p2 = coords[i + 1]
p3 = coords[i + 2]
angle1 = np.arctan2(p2[1] - p1[1], p2[0] - p1[0])
angle2 = np.arctan2(p3[1] - p2[1], p3[0] - p2[0])
diff = np.abs(angle2 - angle1)
if diff > np.pi:
diff = 2 * np.pi - diff
angle_deg = np.degrees(diff)
all_angles.append(angle_deg)
# Return the average turning angle across all LineStrings
return np.mean(all_angles) if all_angles else None
else:
return None
|