Here is my code:
import math
import fmeobjects
FEET_PER_M = 3.28084
TURN_THRESH_DEG = 5.0 # minimum angle to count as a turn
def haversine_m(lat1, lon1, lat2, lon2):
"""Great-circle distance in meters."""
R = 6371000.0
phi1, phi2 = math.radians(lat1), math.radians(lat2)
dphi = math.radians(lat2 - lat1)
dl = math.radians(lon2 - lon1)
a = math.sin(dphi/2)**2 + math.cos(phi1)*math.cos(phi2)*math.sin(dl/2)**2
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
return R * c
def bearing_deg(lat1, lon1, lat2, lon2):
"""Bearing from (lat1, lon1) to (lat2, lon2) in degrees 0-360."""
phi1, phi2 = math.radians(lat1), math.radians(lat2)
dl = math.radians(lon2 - lon1)
y = math.sin(dl) * math.cos(phi2)
x = math.cos(phi1) * math.sin(phi2) - math.sin(phi1) * math.cos(phi2) * math.cos(dl)
brng = math.degrees(math.atan2(y, x))
return (brng + 360) % 360
def signed_deflection(prev_bearing, next_bearing):
"""Smallest signed difference between bearings (-180..+180)."""
return (next_bearing - prev_bearing + 180) % 360 - 180
def processFeature(feature):
geom = feature.getGeometry()
if geom is None:
return
coords = []
# Handle Aggregate (multi-geometry)
if isinstance(geom, fmeobjects.FMEAggregate):
for subgeom in geom.getGeometries():
if subgeom.getGeometryType() == fmeobjects.FME_GEOM_LINE:
coords.extend(subgeom.getCoordinates())
else:
# Handle a single LineString
if geom.getGeometryType() == fmeobjects.FME_GEOM_LINE:
coords = geom.getCoordinates()
if not coords or len(coords) < 2:
return
# Convert to (lat, lon)
pts = [(c[1], c[0]) for c in coords]
# Build segments with distance + bearing
segs, cum_ft, cum_at_vertex = [], 0.0, [0.0]
for i in range(len(pts)-1):
lat1, lon1 = pts[i]
lat2, lon2 = pts[i+1]
dist_m = haversine_m(lat1, lon1, lat2, lon2)
brg = bearing_deg(lat1, lon1, lat2, lon2)
seg_len_ft = dist_m * FEET_PER_M
segs.append((seg_len_ft, brg))
cum_ft += seg_len_ft
cum_at_vertex.append(cum_ft)
# Detect turns
events = [{'Turn Number': 'Start', 'Turn Angle': '', 'cum_ft': 0.0}]
turn_counter = 1
for j in range(1, len(segs)):
prev_b, next_b = segs[j-1][1], segs[j][1]
defl = signed_deflection(prev_b, next_b)
if abs(defl) >= TURN_THRESH_DEG:
events.append({
'Turn Number': str(turn_counter),
'Turn Angle': round(defl, 3),
'cum_ft': cum_at_vertex[j]
})
turn_counter += 1
events.append({'Turn Number': 'End', 'Turn Angle': '', 'cum_ft': cum_ft})
# Output rows: distance to next event
for i in range(len(events)-1):
a, b = events[i], events[i+1]
dist = b['cum_ft'] - a['cum_ft']
out = fmeobjects.FMEFeature()
out.setAttribute('Link', feature.getAttribute('kml_name') or 'Route')
out.setAttribute('Turn Number', a['Turn Number'])
out.setAttribute('Turn Angle', a['Turn Angle'])
out.setAttribute('Distance to next turn ', round(dist, 2))
self.pyoutput(out)
Here is the errors i am getting. I have no idea how to fix it.
Restoring 4 feature(s) from FME feature store file `C:\Users\ugdv\AppData\Local\Temp\wb-cache-Route Distance-Wpxajx\Main_GeometryFilter -1 2 fo 0 Curve 0 db228df39dd790538979323913bff398572463ed.ffs'
Python Exception <AttributeError>: 'fmeobjects.FMEAggregate' object has no attribute 'getGeometries'
Error encountered while calling function `processFeature'
PythonCaller (PythonFactory): PythonFactory failed to process feature