@drawtonomy/sdk 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -26,6 +26,7 @@ import { fitPlanView } from './odrGeometryFit';
26
26
  import { originToProjString } from './projection';
27
27
  import { escapeXml, fmt, fmtPrecise, pxToEnuX, pxToEnuY, pxToMeter } from './units';
28
28
  import { extractOdrDocument, hashRoadState, rewriteRoadLinkTargets, } from './odrCarryThrough';
29
+ import { trafficSignCode } from './lanelet2';
29
30
  /** O(1) shape lookup by id. */
30
31
  function buildShapeMap(shapes) {
31
32
  const map = new Map();
@@ -1076,7 +1077,7 @@ function laneIdRanges(ids) {
1076
1077
  ranges.push({ fromLane: start, toLane: prev });
1077
1078
  return ranges;
1078
1079
  }
1079
- function attachShapesToRoads(shapeMap, trafficLights, crosswalks, polygons, roads, laneIdToRoadId, laneIdToOdrLaneId, maxAttachDistanceMeter = 50, signalIdStart = 1) {
1080
+ function attachShapesToRoads(shapeMap, trafficLights, trafficSigns, crosswalks, polygons, roads, laneIdToRoadId, laneIdToOdrLaneId, maxAttachDistanceMeter = 50, signalIdStart = 1) {
1080
1081
  const roadSignals = new Map();
1081
1082
  const roadObjects = new Map();
1082
1083
  const roadSignalRefs = new Map();
@@ -1101,7 +1102,14 @@ function attachShapesToRoads(shapeMap, trafficLights, crosswalks, polygons, road
1101
1102
  }
1102
1103
  return byRoad;
1103
1104
  };
1104
- for (const tl of trafficLights) {
1105
+ // Traffic lights (dynamic signals) and traffic signs (static signals)
1106
+ // attach to roads through the same projection / validity machinery; only
1107
+ // the emitted <signal> attributes differ per kind.
1108
+ const signalShapes = [
1109
+ ...trafficLights.map(shape => ({ kind: 'traffic_light', shape })),
1110
+ ...trafficSigns.map(shape => ({ kind: 'traffic_sign', shape })),
1111
+ ];
1112
+ for (const { kind, shape: tl } of signalShapes) {
1105
1113
  const xG = pxToEnuX(tl.x);
1106
1114
  const yG = pxToEnuY(tl.y);
1107
1115
  // Regulatory layer: a signal that names affected lanes attaches to the
@@ -1131,26 +1139,61 @@ function attachShapesToRoads(shapeMap, trafficLights, crosswalks, polygons, road
1131
1139
  }
1132
1140
  if (!best)
1133
1141
  continue;
1134
- const style = tl.props.style ?? '';
1135
- const isPed = style.startsWith('pedestrian') || style.includes('ped');
1136
- // Conventional signal type codes: 1000001 = vehicle, 1000002 = pedestrian.
1137
- const sigType = isPed ? '1000002' : '1000001';
1138
1142
  const heightM = pxToMeter(tl.props.h);
1139
1143
  const widthM = pxToMeter(tl.props.w);
1140
1144
  const list = roadSignals.get(best.roadId) ?? [];
1141
- const entry = {
1142
- id: signalIdCounter++,
1143
- s: best.proj.s,
1144
- t: best.proj.t,
1145
- zOffset: isPed ? 1.5 : 4.5,
1146
- height: heightM,
1147
- width: widthM,
1148
- name: style,
1149
- type: sigType,
1150
- subtype: '-1',
1151
- dynamic: 'yes',
1152
- orientation: best.proj.t >= 0 ? '+' : '-',
1153
- };
1145
+ let entry;
1146
+ if (kind === 'traffic_light') {
1147
+ const style = tl.props.style ?? '';
1148
+ const isPed = style.startsWith('pedestrian') || style.includes('ped');
1149
+ // Conventional signal type codes: 1000001 = vehicle, 1000002 = pedestrian.
1150
+ const sigType = isPed ? '1000002' : '1000001';
1151
+ entry = {
1152
+ id: signalIdCounter++,
1153
+ s: best.proj.s,
1154
+ t: best.proj.t,
1155
+ zOffset: isPed ? 1.5 : 4.5,
1156
+ height: heightM,
1157
+ width: widthM,
1158
+ name: style,
1159
+ type: sigType,
1160
+ subtype: '-1',
1161
+ dynamic: 'yes',
1162
+ orientation: best.proj.t >= 0 ? '+' : '-',
1163
+ };
1164
+ }
1165
+ else {
1166
+ // Static traffic sign: reuse the exact OpenDRIVE type / subtype /
1167
+ // country recorded at import time; fresh signs fall back to type "-1"
1168
+ // with the sign code as the name. Full attribute round-trip rides on
1169
+ // <userData code="signAttributes">.
1170
+ const attrs = (tl.props.attributes ?? {});
1171
+ entry = {
1172
+ id: signalIdCounter++,
1173
+ s: best.proj.s,
1174
+ t: best.proj.t,
1175
+ zOffset: 2,
1176
+ height: heightM,
1177
+ width: widthM,
1178
+ name: trafficSignCode(attrs),
1179
+ type: attrs.odr_signal_type || '-1',
1180
+ subtype: attrs.odr_signal_subtype || '-1',
1181
+ country: attrs.odr_country || undefined,
1182
+ dynamic: 'no',
1183
+ orientation: best.proj.t >= 0 ? '+' : '-',
1184
+ };
1185
+ const stash = {};
1186
+ for (const [k, v] of Object.entries(attrs)) {
1187
+ if (k === 'type' || k === 'refers_osm_id' || k.startsWith('odr_'))
1188
+ continue;
1189
+ if (v === undefined || v === null || v === '')
1190
+ continue;
1191
+ stash[k] = String(v);
1192
+ }
1193
+ if (Object.keys(stash).length > 0) {
1194
+ entry.userData = [{ code: 'signAttributes', value: JSON.stringify(stash) }];
1195
+ }
1196
+ }
1154
1197
  if (affectedByRoad.size > 0) {
1155
1198
  entry.validity = laneIdRanges(affectedByRoad.get(best.roadId));
1156
1199
  }
@@ -1389,8 +1432,8 @@ function emitSignals(signals, references) {
1389
1432
  const lines = [];
1390
1433
  lines.push(` <signals>`);
1391
1434
  for (const s of signals) {
1392
- const attrs = `id="${s.id}" s="${fmt(s.s)}" t="${fmt(s.t)}" zOffset="${fmt(s.zOffset)}" name="${escapeXml(s.name)}" dynamic="${s.dynamic}" orientation="${s.orientation}" type="${s.type}" subtype="${s.subtype}" country="OpenDRIVE" value="0" height="${fmt(s.height)}" width="${fmt(s.width)}"`;
1393
- if (s.validity?.length || s.stopLinePoints) {
1435
+ const attrs = `id="${s.id}" s="${fmt(s.s)}" t="${fmt(s.t)}" zOffset="${fmt(s.zOffset)}" name="${escapeXml(s.name)}" dynamic="${s.dynamic}" orientation="${s.orientation}" type="${s.type}" subtype="${s.subtype}" country="${escapeXml(s.country ?? 'OpenDRIVE')}" value="0" height="${fmt(s.height)}" width="${fmt(s.width)}"`;
1436
+ if (s.validity?.length || s.stopLinePoints || s.userData?.length) {
1394
1437
  lines.push(` <signal ${attrs}>`);
1395
1438
  for (const v of s.validity ?? []) {
1396
1439
  lines.push(` <validity fromLane="${v.fromLane}" toLane="${v.toLane}"/>`);
@@ -1399,6 +1442,9 @@ function emitSignals(signals, references) {
1399
1442
  const json = JSON.stringify(s.stopLinePoints.map((p) => [roundMm(p.x), roundMm(p.y)]));
1400
1443
  lines.push(` <userData code="stopLine" value="${escapeXml(json)}"/>`);
1401
1444
  }
1445
+ for (const ud of s.userData ?? []) {
1446
+ lines.push(` <userData code="${escapeXml(ud.code)}" value="${escapeXml(ud.value)}"/>`);
1447
+ }
1402
1448
  lines.push(` </signal>`);
1403
1449
  }
1404
1450
  else {
@@ -1610,7 +1656,7 @@ const NO_OVERRIDES = new Map();
1610
1656
  * Roads referencing unrecorded elements (e.g. a selective import) are never
1611
1657
  * carried verbatim, so verbatim output cannot dangle into missing roads.
1612
1658
  */
1613
- function planCarryThrough(sidecar, shapeMap, trafficLights, crosswalks) {
1659
+ function planCarryThrough(sidecar, shapeMap, trafficLights, trafficSigns, crosswalks) {
1614
1660
  const records = sidecar?.roadRecords;
1615
1661
  if (!sidecar || !records || Object.keys(records).length === 0)
1616
1662
  return null;
@@ -1665,6 +1711,17 @@ function planCarryThrough(sidecar, shapeMap, trafficLights, crosswalks) {
1665
1711
  controllerId: tl.props.controllerId ?? '',
1666
1712
  }, tl.props.affectedLaneIds ?? [], tl.props.attributes?.odr_road_id);
1667
1713
  }
1714
+ for (const ts of trafficSigns) {
1715
+ addRegState({
1716
+ kind: 'traffic_sign',
1717
+ shapeId: ts.id,
1718
+ numbers: [ts.x, ts.y, ts.props.w, ts.props.h, ts.rotation || 0],
1719
+ attributes: ts.props.attributes ?? {},
1720
+ affectedLaneIds: ts.props.affectedLaneIds ?? [],
1721
+ stopLinePts: stopLinePts(ts.props.stopLineId),
1722
+ controllerId: '',
1723
+ }, ts.props.affectedLaneIds ?? [], ts.props.attributes?.odr_road_id);
1724
+ }
1668
1725
  for (const cw of crosswalks) {
1669
1726
  addRegState({
1670
1727
  kind: 'crosswalk',
@@ -1872,6 +1929,7 @@ export function exportToOpenDrive(snapshot, options = {}) {
1872
1929
  const shapeMap = buildShapeMap(shapes);
1873
1930
  const lanes = [];
1874
1931
  const trafficLights = [];
1932
+ const trafficSigns = [];
1875
1933
  const crosswalks = [];
1876
1934
  const polygons = [];
1877
1935
  for (const s of shapes) {
@@ -1879,6 +1937,8 @@ export function exportToOpenDrive(snapshot, options = {}) {
1879
1937
  lanes.push(s);
1880
1938
  else if (s.type === 'traffic_light')
1881
1939
  trafficLights.push(s);
1940
+ else if (s.type === 'traffic_sign')
1941
+ trafficSigns.push(s);
1882
1942
  else if (s.type === 'crosswalk')
1883
1943
  crosswalks.push(s);
1884
1944
  else if (s.type === 'polygon') {
@@ -1895,11 +1955,14 @@ export function exportToOpenDrive(snapshot, options = {}) {
1895
1955
  }
1896
1956
  // Carry-through: with an importer sidecar, unedited original roads are
1897
1957
  // re-emitted verbatim and excluded from regeneration.
1898
- const carry = planCarryThrough(options.sidecar, shapeMap, trafficLights, crosswalks);
1958
+ const carry = planCarryThrough(options.sidecar, shapeMap, trafficLights, trafficSigns, crosswalks);
1899
1959
  const regenLanes = carry ? lanes.filter(l => !carry.verbatimLaneIds.has(l.id)) : lanes;
1900
1960
  const regenTrafficLights = carry
1901
1961
  ? trafficLights.filter(t => !carry.consumedShapeIds.has(t.id))
1902
1962
  : trafficLights;
1963
+ const regenTrafficSigns = carry
1964
+ ? trafficSigns.filter(t => !carry.consumedShapeIds.has(t.id))
1965
+ : trafficSigns;
1903
1966
  const regenCrosswalks = carry
1904
1967
  ? crosswalks.filter(c => !carry.consumedShapeIds.has(c.id))
1905
1968
  : crosswalks;
@@ -2113,7 +2176,7 @@ export function exportToOpenDrive(snapshot, options = {}) {
2113
2176
  };
2114
2177
  const plan = planConnectivity(exportBundles, laneIdToRoadId, laneIdToOdrLaneId, nextRoadId, connectingSourceFor, connectingTargetFor, contactWidth, externalLanes);
2115
2178
  const roads = exportBundles.map(b => ({ roadId: roadIdByBundle.get(b), geom: b.geom }));
2116
- const { roadSignals, roadObjects, roadSignalRefs, signalIdByShape } = attachShapesToRoads(shapeMap, regenTrafficLights, regenCrosswalks, polygons, roads, laneIdToRoadId, laneIdToOdrLaneId, undefined, carry?.signalIdBase);
2179
+ const { roadSignals, roadObjects, roadSignalRefs, signalIdByShape } = attachShapesToRoads(shapeMap, regenTrafficLights, regenTrafficSigns, regenCrosswalks, polygons, roads, laneIdToRoadId, laneIdToOdrLaneId, undefined, carry?.signalIdBase);
2117
2180
  // Verbatim road blocks first (original document order). Two minimal
2118
2181
  // rewrites keep their links valid; nothing else is touched:
2119
2182
  // - road links to a dirty road whose lanes regenerated into exactly one