Skip to main content

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
 

Hi ​@spencergrazz ,

fmeobjects.FMEAggregate class doesn't have a method called "getGeometries", it's the direct reason for the error.

You can get fmeobjects.FMEGeometryIterator object with for statement directly from an fmeobjects.FMEAggreate object, i.e. this code could work as intended.

    geom = feature.getGeometry()
if isinstance(geom, fmeobjects.FMEAggregate):
for subgeom in geom:
# do something

Using code block is recommented when you post a snippet of your script.