diff --git a/packages/drawtonomy-sdk/__tests__/exporter/odrToShapes.test.ts b/packages/drawtonomy-sdk/__tests__/exporter/odrToShapes.test.ts
index 55b2b4e..53b8488 100644
--- a/packages/drawtonomy-sdk/__tests__/exporter/odrToShapes.test.ts
+++ b/packages/drawtonomy-sdk/__tests__/exporter/odrToShapes.test.ts
@@ -663,3 +663,39 @@ describe('esmini parking_demo (fixture)', () => {
}
)
})
+
+describe('odrToShapes signal heading', () => {
+ const SIGNAL_HEADING_MAP = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`
+
+ it('derives headingRad from reference heading, orientation and hOffset', () => {
+ const result = odrToShapes(parseOpenDriveXml(SIGNAL_HEADING_MAP))
+ const byId = new Map(result.trafficSigns.map(ts => [ts.attributes.odr_signal_id, ts]))
+ // Road runs along +x (heading 0).
+ expect(byId.get('s1')?.headingRad).toBeCloseTo(0, 9)
+ // orientation="-" flips by pi.
+ expect(byId.get('s2')?.headingRad).toBeCloseTo(Math.PI, 9)
+ // hOffset adds on top of the oriented direction.
+ expect(byId.get('s3')?.headingRad).toBeCloseTo(0.5, 9)
+ // Missing orientation/hOffset default to the reference direction.
+ expect(byId.get('s4')?.headingRad).toBeCloseTo(0, 9)
+ })
+})
diff --git a/packages/drawtonomy-sdk/__tests__/exporter/opendriveParser.test.ts b/packages/drawtonomy-sdk/__tests__/exporter/opendriveParser.test.ts
index 91de4ed..6632a27 100644
--- a/packages/drawtonomy-sdk/__tests__/exporter/opendriveParser.test.ts
+++ b/packages/drawtonomy-sdk/__tests__/exporter/opendriveParser.test.ts
@@ -150,7 +150,7 @@ describe('parseOpenDriveXml', () => {
const road = parseOpenDriveXml(SAMPLE).roads[0]
expect(road.hasElevation).toBe(true)
expect(road.signals).toEqual([
- { id: 'sig1', s: 95, t: -6, type: '1000001', subtype: '-1', name: 'tl', dynamic: '', country: '', width: 0, height: 0, validity: [], userData: {} },
+ { id: 'sig1', s: 95, t: -6, type: '1000001', subtype: '-1', name: 'tl', dynamic: '', country: '', width: 0, height: 0, orientation: '', hOffset: 0, validity: [], userData: {} },
])
expect(road.objects[0].type).toBe('crosswalk')
})
diff --git a/packages/drawtonomy-sdk/src/exporter/odrToShapes.ts b/packages/drawtonomy-sdk/src/exporter/odrToShapes.ts
index 82bfaaf..1ef9b11 100644
--- a/packages/drawtonomy-sdk/src/exporter/odrToShapes.ts
+++ b/packages/drawtonomy-sdk/src/exporter/odrToShapes.ts
@@ -615,6 +615,9 @@ export function odrToShapes(map: OdrMap, options: OdrToShapesOptions = {}): OdrI
// Unit normal toward +t (left of the reference direction in ENU).
const ex = pose.x - Math.sin(pose.hdg) * sig.t
const ey = pose.y + Math.cos(pose.hdg) * sig.t
+ // World heading the signal applies to (ENU radians): reference-line
+ // heading, flipped for orientation="-", plus the signal's hOffset.
+ const headingRad = pose.hdg + (sig.orientation === '-' ? Math.PI : 0) + sig.hOffset
const affected: string[] = []
resolveAffectedLanes(road, sig.s, sig.validity, affected)
@@ -683,6 +686,7 @@ export function odrToShapes(map: OdrMap, options: OdrToShapesOptions = {}): OdrI
affectedLaneIds: affected,
stopLineId: materializeStopLine(sig.userData['stopLine']),
attributes,
+ headingRad,
}
result.trafficSigns.push(data)
convertedSignalCount++
diff --git a/packages/drawtonomy-sdk/src/exporter/opendriveParser.ts b/packages/drawtonomy-sdk/src/exporter/opendriveParser.ts
index febdfc6..d25079a 100644
--- a/packages/drawtonomy-sdk/src/exporter/opendriveParser.ts
+++ b/packages/drawtonomy-sdk/src/exporter/opendriveParser.ts
@@ -136,6 +136,13 @@ export interface OdrSignal {
/** Physical size (m); 0 when absent. */
width: number
height: number
+ /**
+ * Facing relative to the reference-line direction: "+" applies to traffic
+ * running in the positive s direction, "-" against it ("" when absent).
+ */
+ orientation: string
+ /** Heading offset in radians relative to the (oriented) s direction; 0 when absent. */
+ hOffset: number
/** Lane ranges restricting which lanes the signal applies to (empty = all). */
validity: OdrSignalValidity[]
/** records attached to the signal (code -> value). */
@@ -686,6 +693,8 @@ function parseRoad(el: XmlNode): OdrRoad {
country: sig.attrs.country ?? '',
width: numAttr(sig, 'width', 0),
height: numAttr(sig, 'height', 0),
+ orientation: sig.attrs.orientation ?? '',
+ hOffset: numAttr(sig, 'hOffset', 0),
validity: parseValidity(sig),
userData: parseUserData(sig),
}))
diff --git a/packages/drawtonomy-sdk/src/exporter/osmToShapes.ts b/packages/drawtonomy-sdk/src/exporter/osmToShapes.ts
index 1512b4f..7d7ecf4 100644
--- a/packages/drawtonomy-sdk/src/exporter/osmToShapes.ts
+++ b/packages/drawtonomy-sdk/src/exporter/osmToShapes.ts
@@ -115,6 +115,14 @@ export interface ImportedTrafficSign {
* keeps a speed limit value (e.g. "50 km/h") when present.
*/
attributes: Record
+ /**
+ * World heading the signal applies to, in ENU radians (counter-clockwise
+ * from +x, y-up): reference-line heading at s, plus pi when
+ * orientation="-", plus hOffset. Only set by the OpenDRIVE import path;
+ * undefined for OSM/Lanelet2 sources. Renderers drawing surface markings
+ * (arrows etc.) should align the artwork with this direction.
+ */
+ headingRad?: number
}
export interface ImportedCrosswalk {