HighwayData XML — Schema 1.1

Draft for review · 2026-08-28 (revision 2) · technical peer audience (Grasshopper reader implementers)

This document is the normative wire-format contract between the ioLabs detection pipeline (producer) and HBW's Grasshopper 2D-plan script (consumer): which elements exist, in what order, with what cardinality, and what their values mean. Schema 1.1 is strictly additive over 1.0. Status: draft for review, 2026-08-28 (revision 2). This document is the source of truth — where it disagrees with any design plan, memo or implementation, this document wins.

Normative specification. Status: draft for review (2026-08-28, revision 2). Reference producer: iolabs_point_cloud_modelling_export.write_xml.create_xml / prettify_xml (package iolabs-point-cloud-modelling-export; schema 1.0 as of 0.9.0, schema 1.1 targeted at 0.10.0).

Basis: docs/hbw_feature_classes_xml_plan.md (design), owner decisions of 2026-08-28 rounds 1 and 2 (binding; round 2 overrides round 1, and both override the plan — see Appendix A), poster "2D plans overview" (18 columns).


1. Scope & terminology

1.1 Purpose

1.2 Terminology

RFC 2119: MUST, MUST NOT, SHOULD, SHOULD NOT, MAY, REQUIRED, OPTIONAL.

Term Meaning
Producer Code that emits a document (cluster_stepper, guardrails, future HBW detectors).
Writer The reference implementation, create_xml + prettify_xml.
Consumer Anything reading the document: HBW's Grasshopper script, guardrails/lane_xml.py.
Legacy producer A producer written against schema 1.0 that supplies no 1.1 key.
Feature class One row of §7 = one Feature/Type + one container + one profile.
Container One of Lines, Points, Polylines, Splines.
Geometry element One Line, Point, Polyline, or Spline element.
Input shape One of the poster's generic layouts: point symbol, polyline, spline, annotation-only.
<Shape> The 1.1 solid-geometry element under <Point> (§4.20). Distinct from "input shape".
Export frame The geoshifted local right-handed frame, metres, as emitted today; Geoshift restores absolutes.
1.0 child An element a 0.9.0 writer already emits for that parent.
NEW An element introduced by this specification.
Frozen profile A feature class whose serialisation MUST NOT change in 1.1 (§7.21–§7.23).
1.1-touched Defined in R-2.6.1.

2. Versioning & compatibility

2.1 Version identification

2.2 What 1.1 does not do

2.3 Trailing-placement rule

2.4 Legacy producer output under 1.1

2.5 Class-key resolution

2.6 The "1.1-touched" scope


3. Document structure

Complete element tree. 1.0 = present in 0.9.0; NEW = introduced by 1.1. Children are listed in the order in which they MUST be emitted.

Cardinality markers: 1 exactly one · ? 0..1 · * 0..n · + 1..n · 2+ 2..n · 1+/2+ 1..n for legacy content, 2..n required by every 1.1 profile (§7).

HighwayData                                   1.0   1
├─ MetaData                                   1.0   1
│  ├─ CreationDate                            1.0   1
│  ├─ Source                                  1.0   1
│  ├─ SoftwareVersion                         1.0   1
│  ├─ PipelineVersions                        1.0   ?
│  │  └─ Step  @name @date?                   1.0   *
│  │     ├─ SegmentRange  @segments @date     1.0   *      (variant A)
│  │     │  └─ Package  @name @version        1.0   *
│  │     └─ Package  @name @version           1.0   *      (variant B)
│  └─ SchemaVersion                           NEW   1
├─ Geoshift                                   1.0   ?
│  ├─ X                                       1.0   1
│  ├─ Y                                       1.0   1
│  └─ Z                                       1.0   1
└─ Features                                   1.0   1
   └─ Feature                                 1.0   *
      ├─ Type                                 1.0   1
      ├─ ID                                   1.0   1
      ├─ LaneID                               1.0   1
      ├─ Lines                                1.0   1
      │  └─ Line                              1.0   *
      │     ├─ ID                             1.0   1
      │     ├─ LaneID                         1.0   1
      │     ├─ Type                           1.0   ?
      │     ├─ StartPoint (X, Y, Z)           1.0   1
      │     ├─ EndPoint   (X, Y, Z)           1.0   1
      │     ├─ RoadPoint  (From, To)          1.0   1
      │     ├─ Width                          1.0   ?
      │     │  ├─ Value                       1.0   ?
      │     │  ├─ Sigma                       1.0   ?
      │     │  ├─ Status                      1.0   ?
      │     │  └─ Flagged                     1.0   ?
      │     ├─ Annotation                     NEW   ?
      │     └─ Attributes                     NEW   ?
      ├─ NumberOfLines                        1.0   1
      ├─ Points                               1.0   1
      │  └─ Point                             1.0   *
      │     ├─ ID                             1.0   1
      │     ├─ LaneID                         1.0   1
      │     ├─ PointerNumber                  1.0   1
      │     ├─ X                              1.0   1
      │     ├─ Y                              1.0   1
      │     ├─ Z                              1.0   1
      │     ├─ Color                          1.0   1
      │     ├─ FeatureClass                   1.0   1
      │     ├─ FeatureID                      1.0   1
      │     ├─ RoadPoint                      1.0   ?   (1 in 1.0; ? for 1.1 producers)
      │     ├─ Direction (X, Y, Z)            NEW   ?
      │     ├─ Shapes                         NEW   ?
      │     │  └─ Shape  @role @type          NEW   +
      │     │     ├─ Base (X, Y, Z)           NEW   1
      │     │     ├─ Top  (X, Y, Z)           NEW   ?   (exactly one of Top | Apex,
      │     │     ├─ Apex (X, Y, Z)           NEW   ?    selected by @type — R-4.20.3)
      │     │     └─ Diameter                 NEW   1
      │     ├─ Annotation                     NEW   ?
      │     └─ Attributes                     NEW   ?
      ├─ NumberOfPoints                       1.0   1
      ├─ Polylines                            1.0   1
      │  └─ Polyline                          1.0   *
      │     ├─ ID                             1.0   1
      │     ├─ LaneID                         1.0   1
      │     ├─ Type                           NEW   ?   (position per R-2.3.2)
      │     ├─ RoadPoint (From, To)           1.0   ?   (1 in 1.0; ? for 1.1 producers)
      │     ├─ Vertices                       1.0   1
      │     │  └─ Vertex                      1.0   1+/2+
      │     │     ├─ ID                       1.0   1
      │     │     ├─ X                        1.0   1
      │     │     ├─ Y                        1.0   1
      │     │     └─ Z                        1.0   1
      │     ├─ SortingCode                    NEW   ?
      │     ├─ Closed                         NEW   ?
      │     ├─ Annotation                     NEW   ?
      │     └─ Attributes                     NEW   ?
      ├─ NumberOfPolylines                    1.0   1
      ├─ Splines                              1.0   1
      │  └─ Spline                            1.0   *
      │     ├─ ID                             1.0   1
      │     ├─ LaneID                         1.0   1
      │     ├─ Type                           1.0   ?
      │     ├─ RoadPoint (From, To)           1.0   ?   (1 in 1.0; ? for 1.1 producers)
      │     ├─ Width  (as Line/Width)         1.0   ?
      │     ├─ Fit_Points                     1.0   1   (MAY be empty)
      │     │  └─ Point (ID, X, Y, Z)         1.0   *
      │     ├─ Control_Points                 1.0   1
      │     │  └─ Point (ID, X, Y, Z)         1.0   1+/2+
      │     ├─ Annotation                     NEW   ?
      │     └─ Attributes                     NEW   ?
      ├─ NumberOfSplines                      1.0   1
      └─ Attributes                           NEW   ?

Shared NEW sub-trees, referenced above:

Annotation                                    NEW
├─ Display                                    NEW   1
├─ Legible                                    NEW   ?   (enumeration — R-5.10.1)
└─ Text                                       NEW   *

Attributes                                    NEW
└─ Attribute  @name @type                     NEW   +

4. Element reference

"Position" is relative to siblings. Value rules referencing §5 are normative.

4.1 HighwayData — 1.0

Root, cardinality 1. Children in order: MetaData, Geoshift?, Features.

4.2 MetaData — 1.0

Parent HighwayData, 1, first. Children: CreationDate, Source, SoftwareVersion, PipelineVersions?, SchemaVersion.

4.3 CreationDate, Source, SoftwareVersion — 1.0

Parent MetaData, 1 each, positions 1–3. Text, emitted verbatim from the producer (no str() coercion; producers supply strings). CreationDate SHOULD be ISO-8601.

4.4 PipelineVersions / Step / SegmentRange / Package — 1.0

Unchanged from 1.0. - R-4.4.1 Step/@name REQUIRED. - R-4.4.2 A Step uses either the SegmentRange variant or the flat variant, never both. In the flat variant Step/@date is always set — to the empty string when the producer supplies no date — and Package children follow. In the SegmentRange variant Step carries no @date. - R-4.4.3 SegmentRange/@segments is the segment list joined with ", ", where a range a..b renders a-b and a singleton (a == b) renders a. SegmentRange/@date REQUIRED.

4.5 SchemaVersion — NEW

Parent MetaData, 1, last child. Text 1.1 (R-2.1.1). Example: <SchemaVersion>1.1</SchemaVersion>.

4.6 Geoshift (X,Y,Z) — 1.0

Parent HighwayData, 0..1, after MetaData. Emitted iff the producer supplies it. Numeric text per §5.1. Adding Geoshift to an export-frame coordinate yields the absolute coordinate.

4.7 Features — 1.0

Parent HighwayData, 1, last. Contains 0..n Feature. Always emitted, even when empty.

4.8 Feature — 1.0

Parent Features, 0..n, document order = producer order. Children per §3.

4.9 Feature/Type — 1.0

1, first child. Text, non-empty. The stable semantic class id and the sole class key (R-2.5.1). Values in use: Cross Section, Center Lines, Axis of the Edge, Central Axis, Both-Sides Central Axis, Single-Side Central Axis, guardrail, guardrail_support, guardrail_top_rail, noise_wall, plus the 1.1 classes of §7. Existing lower-case strings are preserved as-is; NEW classes use English singular Title Case. - R-4.9.1 The writer MUST NOT validate Type against a closed vocabulary; producers may add classes without a writer release.

4.10 Feature/ID — 1.0

1, second. Text, non-empty, document-unique (R-6.1.2; producer obligation, writer warns).

4.11 Feature/LaneID — 1.0

1, third. Text. "0" when the feature is not lane-bound. Always emitted. - R-4.11.1 Every geometry element carries its own LaneID, always emitted. A legacy dict MUST supply the geometry-level lane_id (the 0.9.0 writer raises without it, and R-2.4.1 depends on it). A 1.1-touched geometry MAY omit it, in which case the writer defaults it to the owning Feature/LaneID.

4.12 Feature/Attributes — NEW

0..1, last child of Feature (after NumberOfSplines). Feature-level metadata; see §4.28. Rare — per-geometry Attributes is preferred.

4.13 (reserved — Feature/Layer was removed by R-2.2.4)

No element occupies this slot. Documents produced from pre-release drafts may contain a <Layer> child of Feature; consumers ignore it (R-2.5.3).

4.14 Line and children — 1.0

ID, LaneID text; Type 0..1 immediately after LaneID; StartPoint/EndPoint with X,Y,Z; RoadPoint with From,To; Width 0..1. 1.1 appends Annotation? then Attributes? after Width. - R-4.14.1 Line/Type is an open string; the writer emits whatever the producer supplies and MUST NOT restrict it. - N-4.14.2 The vocabulary in use today is solid | dashed (lane markings). This is informative.

4.15 Width (Value,Sigma,Status,Flagged) — 1.0

Parent Line or Spline, 0..1. Each child is emitted only when its source value is not None. Flagged is str(bool(v)).lower(). Unchanged in 1.1.

4.16 Point — 1.0 (extended)

Parent Points, 0..n. In 1.0 a Point was only a cross-section sample; in 1.1 it is either that (frozen, §7.23) or a point symbol (§7 point classes).

4.17 Point legacy children — 1.0

ID (text, non-empty), LaneID (text), PointerNumber (integer text), X,Y,Z (numeric text, export frame), Color, FeatureClass, FeatureID (text; str()-coerced — hence the literal None seen in legacy cross-section output), RoadPoint (scalar numeric text — not a From/To pair, unlike Line/Polyline/Spline). - R-4.17.1 PointerNumber, Color, FeatureClass, FeatureID MUST always be emitted. When a 1.1 producer omits the source key the writer MUST default: PointerNumber = 1-based index of the point within its Points container; Color = empty text; FeatureClass = the owning Feature/Type; FeatureID = the owning Feature/ID. - R-4.17.2 These defaults apply only when the key is absent. A key present with value None is str()-coerced to the text None, preserving 1.0 behaviour (R-2.4.1). - R-4.17.3 RoadPoint MUST be emitted iff the producer supplies it. Point symbols with no chainage omit it.

4.18 Point/Direction — NEW

0..1, immediately after RoadPoint (or after FeatureID when RoadPoint is omitted). Children X,Y,Z in that order, numeric text. Value rules: §5.5. Example:

<Direction><X>0.963767</X><Y>-0.221946</Y><Z>0.147964</Z></Direction>

4.19 Point/Shapes — NEW

0..1, after Direction. Contains 1..n Shape. Emitted iff the producer supplies a non-empty shape list. Producer order is preserved.

4.20 Point/Shapes/Shape — NEW

1..n. Attributes: role (REQUIRED, free text; known: trunk, crown, post) and type (REQUIRED, cylinder | cone). - R-4.20.1 type="cylinder" — children in order: Base (X,Y,Z), Top (X,Y,Z), Diameter. - R-4.20.2 type="cone" — children in order: Base (X,Y,Z), Apex (X,Y,Z), Diameter. - R-4.20.3 Exactly one of Top / Apex MUST be present, selected by @type. Emitting both, neither, or the one that does not match @type is non-conforming. Geometry semantics: §5.8. Example:

<Shapes>
  <Shape role="trunk" type="cylinder">
    <Base><X>-109.06</X><Y>-383.29</Y><Z>0.02</Z></Base>
    <Top><X>-109.04</X><Y>-383.27</Y><Z>2.52</Z></Top>
    <Diameter>0.3</Diameter>
  </Shape>
  <Shape role="crown" type="cone">
    <Base><X>-109.04</X><Y>-383.27</Y><Z>2.52</Z></Base>
    <Apex><X>-109.01</X><Y>-383.22</Y><Z>8.52</Z></Apex>
    <Diameter>7.0</Diameter>
  </Shape>
</Shapes>

4.21 Annotation — NEW

Parent: Point, Polyline, Spline, Line. 0..1. Position: after all 1.0 children and after Direction/Shapes/SortingCode/Closed where those exist; immediately before Attributes. Children in order: Display (1), Legible (0..1), Text (0..n). Emitted iff the producer supplies it — legacy producers are unaffected. - R-4.21.1 A producer MUST supply Annotation for every geometry whose §7.1 "Annot." column reads true or false, including when the value is false, so the poster's boolean is explicit in the document. Classes whose "Annot." column reads (noise_wall, guardrail_support, guardrail_top_rail, and all frozen profiles) MUST NOT carry Annotation.

4.22 Annotation/Display — NEW

1, first. Boolean text, lower-case (§5.2). Corresponds to the poster's "has annotation display" / "has a description". - R-4.22.1 Display=true means the consumer renders a label at this geometry. Whether the label text travels in the document is profile-dependent (§4.24, §7).

4.23 Annotation/Legible — NEW

0..1, second child of Annotation. Enumeration, not a boolean: full | partial | none (R-5.10.1). It reports how much of the text carried by the physical object was recoverable from the point cloud.

Value Meaning Text
full Every character of the physical text was read. complete lines, no substitution marks
partial Some of the text was read. the lines that were read, with unreadable characters marked per R-4.24.5; entirely unreadable lines omitted
none Nothing readable. MUST be omitted

4.24 Annotation/Text — NEW

0..n, after Display/Legible. Text, UTF-8. One element per rendered line, in draw order. - R-4.24.1 Text absent while Display=true is conforming and profile-dependent. It means either the consumer derives the label itself (elevation labels — §7.13, §7.23, N-5.7.2) or the label was unrecoverable (Legible=none — R-4.23.3). - R-4.24.2 Text MUST NOT be emitted when Display=false (R-6.2.4). - R-4.24.3 A producer MUST NOT put \n or \r inside a Text value; multi-line labels use multiple Text elements. The writer does not split such a value; it SHOULD warn (R-6.3.4). - R-4.24.4 Text is a display string produced upstream. The writer MUST NOT format, localise, round, or reorder it. - R-4.24.5 (substitution marks) Under Legible=partial, an unreadable character inside an emitted line MUST be written as a single ? — one ? per unreadable character. When the number of unreadable characters is unknown, the run MUST be written as ?… (U+003F followed by U+2026). These two forms are the only substitution marks; a line that was entirely unreadable is omitted rather than filled with marks. - N-4.24.6 A consumer SHOULD render partial text verbatim, ? and ?… included: the marks tell the plan reader that a sign exists and how much of it was recovered. Stripping them would silently fabricate a complete label. Example (KM sign, 3 lines):

<Annotation><Display>true</Display><Legible>full</Legible>
  <Text>KM 46.5</Text><Text>Abs. 90</Text><Text>Station 0,25</Text></Annotation>

4.25 Polyline/Type — NEW

0..1, immediately after LaneID (R-2.3.2). Open string. Known values: top, bottom (trench/slope outlines) and guard-rail sub-kinds. Redundant with SortingCode on purpose (human readability); the code is the contract.

4.26 Polyline/SortingCode — NEW

0..1, immediately after Vertices. Text matching R-5.6.1. Absent = the poster's null.

4.27 Polyline/Closed — NEW

0..1, after SortingCode. Boolean text, lower-case. true = the vertex list describes a closed ring; the consumer closes it. - R-4.27.1 When Closed is true the producer MUST NOT repeat the first vertex as the last, and the polyline MUST have at least 3 vertices. The writer SHOULD warn on either (R-6.3.8, R-6.3.9).

4.28 Attributes / Attribute — NEW

Parent: Feature, Point, Polyline, Spline, Line. 0..1, always the last child of its parent. Contains 1..n Attribute; Attributes MUST NOT be emitted empty (R-6.2.9). Attribute/@name REQUIRED, snake_case, unique within its Attributes; units belong in the name (length_m). Attribute/@type REQUIRED, one of string | integer | float | boolean (§5.9). Text = the serialised value. - R-4.28.1 Attribute children MUST be emitted sorted ascending by @name (deterministic output). - R-4.28.2 An attribute whose value is None MUST be skipped entirely. Example:

<Attributes>
  <Attribute name="guardrail_type" type="string">EDSP</Attribute>
  <Attribute name="post_count" type="integer">37</Attribute>
</Attributes>

4.29 Polyline legacy children — 1.0

ID (text), LaneID (text), RoadPoint (From,To), Vertices. - R-4.29.1 RoadPoint MUST be emitted iff the producer supplies it (optional for 1.1 producers, always present for legacy ones). - R-4.29.2 Vertices MUST always be emitted; vertex count per R-3.7. - R-4.29.3 The writer MUST generate Vertex/ID as f"{polyline_id}-{index+1}" (1-based). Producers MUST NOT supply vertex ids.

4.30 Spline legacy children — 1.0

ID, LaneID, Type?, RoadPoint?, Width?, Fit_Points, Control_Points. - R-4.30.1 Fit_Points MUST always be emitted; it MAY be empty (<Fit_Points/>), so the Spline child layout never varies. - R-4.30.2 Control_Points MUST always be emitted; point count per R-3.7. - R-4.30.3 The writer MUST generate ids f"{spline_id}_{n+1}", where n is the 0-based running index across Fit_Points followed by Control_Points — control-point numbering continues after the fit points. - R-4.30.4 RoadPoint MUST be emitted iff the producer supplies it. - R-4.30.5 Spline/Type is an open string (as R-4.14.1).


5. Value types & formatting

5.1 Numbers

5.2 Booleans

5.3 Text & encoding

5.4 Identifiers

5.5 Direction vectors

5.6 Sorting-code grammar

5.7 Annotation text conventions

5.8 Shape geometry semantics

5.9 Attribute/@type vocabulary

@type Source value Text form
string str verbatim
integer int (not bool) str(v)
float float str(v) (§5.1)
boolean bool true / false

5.10 Enumerations


6. Conformance & validation

§6.1 defines a conforming document — the contract of R-1.1.2, checkable by any consumer or validator. §6.2 and §6.3 state what the reference writer does about each rule; they are a partial projection of §6.1 and never widen or narrow it (R-1.1.3). Every §6.1 rule appears in exactly one of §6.2, §6.3, or §6.4.

6.1 Document conformance

Rule A conforming document satisfies Writer
R-6.1.1 Structure, order, and cardinality of §3; NumberOf* matches its container (R-3.4). reject (R-6.2.1)
R-6.1.2 Every Feature/ID and geometry ID is non-empty and document-unique (R-5.4.1). warn (R-6.3.2)
R-6.1.3 Every SortingCode matches R-5.6.1. reject (R-6.2.2)
R-6.1.4 No two SortingCode values are equal (R-5.6.4). reject (R-6.2.3)
R-6.1.5 Each (family, index) occurs exactly once as A and once as B (R-5.6.3). warn (R-6.3.7)
R-6.1.6 Every Polyline has ≥2 Vertex and every Spline ≥2 Control_Points/Point (R-3.7). reject if 1.1-touched, else warn (R-2.6.2, R-6.3.1)
R-6.1.7 Direction components are finite and the vector is non-zero (R-5.5.2). reject (R-6.2.5)
R-6.1.8 Direction is unit length within 1e-6 (R-5.5.1). warn (R-6.3.5)
R-6.1.9 Annotation has exactly one Display with boolean text; Text is absent when Display=false (R-4.24.2). reject (R-6.2.4)
R-6.1.10 Legible appears only on the classes of R-4.23.1. warn (R-6.3.6)
R-6.1.11 No Text value contains \n or \r (R-4.24.3). warn (R-6.3.4)
R-6.1.12 Annotation is present exactly for the geometries required by R-4.21.1 and absent otherwise. warn (R-6.3.11)
R-6.1.13 Each Shape has @role, a @type in {cylinder, cone}, the matching Top/Apex (R-4.20.3), a finite positive Diameter, finite Base/Top/Apex, and a non-degenerate axis (R-5.8.5). reject (R-6.2.7)
R-6.1.14 A Tree carries exactly one trunk and one crown shape with the types of R-5.8.7. warn (R-6.3.10)
R-6.1.15 Attributes is non-empty; @name is unique within it; @type matches the value per §5.9; values are scalar. reject (R-6.2.8, R-6.2.9)
R-6.1.16 Attribute children are sorted ascending by @name (R-4.28.1). reject (R-6.2.1)
R-6.1.17 No NEW element carries empty or whitespace-only text where §4 requires content (R-5.3.5). reject (R-6.2.10)
R-6.1.18 Frozen-profile features (§7.21–§7.23) carry no NEW element (R-2.2.3). warn (R-6.3.12)
R-6.1.19 Boolean text is lower-case true/false (R-5.2.1); numeric text uses . (R-5.1.4). reject for NEW elements (R-6.2.1); guaranteed by construction otherwise
R-6.1.20 Closed=true implies ≥3 vertices and no repeated closing vertex (R-4.27.1). warn (R-6.3.8, R-6.3.9)
R-6.1.21 No coordinate or length is NaN/±Inf (R-5.1.3). reject inside NEW elements; warn otherwise (R-6.3.3)
R-6.1.22 MetaData/SchemaVersion is present and matches R-2.1.3. reject (R-6.2.11)
R-6.1.23 Every KM Sign annotation carries Legible (R-4.23.1, §7.18). producer MUST; not detectable without Type — advisory §7.1 lookup only (R-6.3.14)
R-6.1.24 No Text is emitted when Legible=none (R-4.23.3). warn; detectable without Type (R-6.3.15)
R-6.1.26 Legible text is one of full | partial | none (R-5.10.1). reject (R-6.2.16)
R-6.1.27 No Text under Legible=full contains a substitution mark ? or ?… (R-4.23.4, R-4.24.5). warn, advisory only (R-6.3.16)
R-6.1.25 Terrain Point and Cross Section carry no Annotation/Text; the consumer renders the label from Z (§7.13, §7.23, N-5.7.2). producer MUST; not detectable without Type — advisory §7.1 lookup only (R-6.3.14)

6.2 Writer MUST reject (hard errors, nothing is emitted)

Rule Condition
R-6.2.1 The writer would emit a tree violating §3 order/cardinality, R-4.28.1 sorting, or §5.2/§5.1.4 formatting of a NEW element. (Structural; a correct writer cannot trip it.)
R-6.2.2 A sorting_code — supplied or composed — not matching R-5.6.1.
R-6.2.3 Two equal SortingCode values within one document.
R-6.2.4 annotation without a display key, display not a bool, or display=false while any Text would be emitted.
R-6.2.5 A Direction component that is non-finite, or a zero-length direction vector.
R-6.2.6 Both sorting_code and any of (sort_class, pair_index, pair_role) on the same polyline; or the triple supplied incompletely; or pair_role outside {top, bottom}, pair_index not a non-negative integer, sort_class not matching ^[A-Z]+$.
R-6.2.7 A Shape missing @role; @type outside {cylinder, cone}; the wrong or both of Top/Apex; a missing, non-positive or non-finite Diameter; a non-finite Base/Top/Apex component; or a zero-length axis.
R-6.2.8 An attribute value that is not str/int/float/bool/None, or a duplicate @name within one Attributes.
R-6.2.9 Attributes would be emitted with zero Attribute children.
R-6.2.10 An empty or whitespace-only text value for a NEW element that requires content (R-5.3.5).
R-6.2.11 metadata.schema_version present and different from the version the writer implements (R-2.1.4).
R-6.2.12 A required 1.0 key missing (metadata.creation_date/source/software_version, feature.type/id/lane_id, geometry id, geometry lane_id on a geometry that is not
1.1-touched (R-4.11.1), point.position, polyline.vertices, spline.control_points/fit_points, line.start_point/end_point/road_point) — 1.0 behaviour, preserved.
R-6.2.13 A 1.1-touched polyline with fewer than 2 vertices, or a 1.1-touched spline with fewer than 2 control points (R-2.6.2).
R-6.2.16 An annotation.legible value outside {full, partial, none} (R-5.10.1), or of a type other than str/None.

6.3 Writer SHOULD warn (document is still emitted)

Rule Condition
R-6.3.1 A polyline with <2 vertices or a spline with <2 control points that is not 1.1-touched (legacy content; R-2.6.2).
R-6.3.2 An empty or duplicated ID anywhere in the document (R-6.1.2).
R-6.3.3 A non-finite coordinate or length outside a NEW element (R-6.1.21).
R-6.3.4 A Text value containing \n or \r. The writer MUST NOT split it (R-4.24.3).
R-6.3.5 Direction length outside 1 ± 1e-6 (R-6.1.8).
R-6.3.6 Legible on a class outside R-4.23.1.
R-6.3.7 A (family, index) sorting-code pair that is not exactly one A plus one B in this document (R-5.6.3). Warn once per incomplete pair.
R-6.3.8 Closed=true while the first and last vertex coincide (redundant closing vertex).
R-6.3.9 Closed=true on a polyline with fewer than 3 vertices.
R-6.3.10 A Tree point whose shapes are not exactly one trunk cylinder plus one crown cylinder/cone, or that repeats a @role (R-5.8.7).
R-6.3.11 A geometry that R-4.21.1 requires to carry Annotation and does not, or that carries one where R-4.21.1 forbids it — determined from the producer-supplied Feature/Type against the §7.1 table, without the writer hard-coding a vocabulary (R-6.4.1).
R-6.3.12 Any 1.1 key supplied for a feature whose Type is a frozen profile (R-2.2.3, R-7.0.2). The writer emits the key as given; the producer is at fault.
R-6.3.13 An @name that is not snake_case.
R-6.3.14 A geometry whose Feature/Type row in §7.1 requires Legible and omits it, or whose §7.1 Text cell reads /none while Text is emitted (R-6.1.23, R-6.1.25). This is the same advisory §7.1 table lookup as R-6.3.11; the writer hard-codes no vocabulary (R-6.4.1). A writer that implements no such lookup performs no check here — these remain producer obligations.
R-6.3.15 Legible=none while one or more Text elements are emitted (R-6.1.24). Detectable without Type, so every writer SHOULD check it.
R-6.3.16 Legible=full while some Text contains ? or ?… (R-6.1.27). Advisory only — a ? may legitimately be part of the sign's own text; the writer never rewrites the value.

6.4 Explicit non-validations


7. Feature-class profiles

7.1 Summary table

"Annot." = the required Annotation/Display value, or when the class MUST NOT carry Annotation (R-4.21.1). "Text" = whether label text travels in the document.

§ Poster col Feature/Type Container Required geometry Annot. Text Shapes Attributes
7.2 1a Tree Line Splines ≥2 control points false
7.3 1b Tree Group Polylines ≥3 vertices, Closed=true false
7.4 2 Bush Splines ≥2 control points false
7.5 3 Tree Points position, Direction true 1 line trunk + crown opt. species_class
7.6 4 Fence Polylines ≥2 vertices false
7.7 5 Paving Edge Polylines ≥2 vertices false
7.8 6 noise_wall Polylines ≥2 vertices height_m, length_m
7.9 7 top Trench Polylines ≥2 vertices, G<i>$A false
7.10 7 bottom Trench Bottom Polylines ≥2 vertices, G<i>$B false
7.11 8 Slope Polylines ×2 B<i>$A + B<i>$B false
7.12 9 Pedestrian Road Polylines or Splines ≥2 vertices / ctrl points false
7.13 10 Terrain Point Points position, Direction true none (consumer renders Z)
7.14 11 Traffic Sign Points position, Direction false opt. post cylinder opt. height_m, plate_class
7.15 12 Shaft Points position, Direction false
7.16 14 Emergency Call Box Points position, Direction false
7.17 16 Gully Points position, Direction false width_m
7.18 15 KM Sign Points position, Direction true + Legible enum 0..3 lines km, section, station
7.19 17 Wall Polylines ≥2 vertices false height_m, length_m
7.20 18 guardrail (1.0 geom.) Polylines as today true 1 line guardrail_type, …
7.21 Axis of the Edge, Center LinesFROZEN Splines + Lines as today
7.22 Central Axis, Both-Sides Central Axis, Single-Side Central AxisFROZEN Polylines as today
7.23 13 Cross SectionFROZEN Points as today
7.24 guardrail_support, guardrail_top_rail Polylines as today cadence / corridor keys
7.25 Verge Post Points position, Direction false opt. cylinder height_m, width_m
7, 8 Hatches never exported; derived from the A/B pair

7.2 Tree Line

One Feature per tree line, one Spline with Fit_Points empty and ≥2 Control_Points. Annotation: <Display>false</Display>, no Text. Spline/Type, Width, RoadPoint omitted. Crown symbols are placed by the consumer along the spline from its catalog.

7.3 Tree Group

Decision Q10 (ALT 1): the group outline is exported. One Feature per group; one Polyline with ≥3 vertices and <Closed>true</Closed>. Annotation Display=false. SortingCode omitted. Status: specified, producer TBD — the outline comes from clustering tree detections, a later pipeline step. Consumers may implement against this profile now; documents will not contain Tree Group features until that detector ships.

7.4 Bush

As §7.2.

7.5 Tree

One Point. Direction REQUIRED (road-axis tangent, R-5.5.5). Annotation Display=true, exactly one Text (N-5.7.3). Legible SHOULD NOT be emitted — the label is computed, not read (R-4.23.2). Shapes REQUIRED: exactly one role="trunk" type="cylinder" and exactly one role="crown" type="cylinder"|"cone" (R-5.8.7). Trunk and crown diameters travel as Shape/Diameter; the numeric attributes trunk_diameter_m / crown_diameter_m are not emitted (decision Q2). Attributes MAY carry a future species_class.

7.6 Fence, 7.7 Paving Edge

One Polyline, ≥2 vertices, Annotation Display=false, no SortingCode, no Polyline/Type.

7.8 noise_wall

Decision Q7: the existing lower-case Type string noise_wall is kept (not renamed to Noise Wall), and the geometry stays Polylines — the detector emits polylines; the poster's spline form is not followed. No Annotation is emitted; the object-name label is deferred to post-processing (§10 O-17). Attributes MAY carry height_m, length_m.

7.9 Trench / 7.10 Trench Bottom

Two separate Features per trench couple, tied by the pair index <i>: top = Trench with G<i>$A, bottom = Trench Bottom with G<i>$B. Polyline/Type SHOULD be top / bottom respectively (human readability; the code is the contract). Annotation Display=false. The hatch between the outlines is derived by the consumer and MUST NOT be exported.

7.11 Slope

One Feature containing two Polylines: SortingCode B<i>$A with Type top, and B<i>$B with Type bottom. Annotation Display=false. Decision Q6: A/B plus the two outlines suffice to derive the hatch direction; no explicit downhill vector.

7.12 Pedestrian Road

One Feature per path, using either Polylines or Splines. The same path MUST NOT be emitted in both forms. Annotation Display=false. - R-7.12.1 The consumer selects the CAD layer from the container used (PolylinespPLC_Gehweg, SplinessPLC_Gehweg); see Appendix C.

7.13 Terrain Point

One Point per elevation point. Direction REQUIRED and always emitted although catalog mode NO ignores it (R-5.5.6). Annotation Display=true with no Text: the consumer renders the elevation label from Point/Z per N-5.7.2 (decision Q4 revised). Legible SHOULD NOT be emitted.

7.14 Traffic Sign

One Point. Direction REQUIRED = plate normal facing oncoming traffic (R-5.5.5). Annotation Display=false, no Text — plate text is not transcribed today. If plate transcription ships, this class moves to the §7.18 pattern (Display=true + Legible) per R-4.23.1. Shapes OPTIONAL: a producer MAY emit a role="post" type="cylinder" shape for the sign post. Attributes MAY carry height_m, plate_class, direction_source.

7.15 Shaft, 7.16 Emergency Call Box, 7.17 Gully

One Point each. Direction REQUIRED (road-axis tangent). Annotation Display=false, no Text. Gully Attributes MAY carry width_m. - N-7.15.1 The poster's leftover Text km placeholder in column 16 is a copy-paste artefact and is not reproduced.

7.18 KM Sign

One Point. Direction REQUIRED = plate normal facing oncoming traffic. Annotation: Display=true; Legible REQUIRED with one of full | partial | none (§4.23) — sign text is often only partly recoverable from the point cloud; Text 0..3 lines per R-5.7.4, marked per R-4.24.5 under partial. Under none the Text elements are omitted and Display stays true, so the symbol is still placed. Attributes SHOULD carry the machine values km (float), section (string/integer), station (float) when known, so the consumer can re-format without a re-export (§10 O-19).

7.19 Wall

One Polyline, ≥2 vertices, Annotation Display=false, Attributes MAY carry height_m, length_m.

7.20 guardrail — annotation on existing geometry

No new geometry (poster col 18: "part of original XML"). The existing guardrail polyline gains: Attributes with guardrail_type (machine value, e.g. EDSP, ESP 2.0) plus the migrated legacy keys of N-8.6.3; and Annotation with Display=true and one Text = the drawn label (EDSP, ESP 2,0). Decision Q8: the label source is our guardrail_type attribute; the legacy FeatureClass=Guard Rails_<type> convention is a consumer-side fallback for 1.0 files only (N-2.5.4). LaneID stays "0"; RoadPoint is kept as today.

7.21 Lane markings — FROZEN

Axis of the Edge and Center Lines: solid segments as Splines, dashed as Lines, exactly as today, including per-segment Type and Width. Decision Q12: no NEW element of any kind. Output byte-identical to 0.9.0 except MetaData/SchemaVersion (R-2.4.1).

7.22 Road axes — FROZEN

Central Axis, Both-Sides Central Axis, Single-Side Central Axis as Polylines, exactly as today. Polyline/RoadPoint remains present for these features. No NEW element.

7.23 Cross SectionFROZEN

Decision Q4 (revised): cross-section features are fine as they are. Type stays Cross Section; the 1.0 semantics of PointerNumber (cross-section plane index), RoadPoint (distance along road), Color, FeatureClass, FeatureID are unchanged. No Direction, no Annotation, no Attributes. The consumer renders the elevation label from Point/Z per N-5.7.2 and picks the layer from Type via Appendix C.

7.24 guardrail_support, guardrail_top_rail

Unchanged geometry, no Annotation. Their stand-in attribute keys (parent_guardrail_id, post_spacing_m, post_count, post_confidence, halfwidth_m, lateral_offset_m) MUST be emitted through the uniform <Attribute name= type=> form of §4.28 — this is what makes them survive the Nexus exporter.

7.25 Verge Post

Excel-only class (Randpfosten), not on the poster. Standard point-symbol profile: Direction REQUIRED, Annotation Display=false, Attributes height_m, width_m; MAY carry a role="post" type="cylinder" Shape. Class name and layer are provisional (§10 O-16).


8. Producer contract (create_xml(data))

8.1 Top level

data = {
  "metadata": {"creation_date": str,       # REQUIRED
               "source": str,              # REQUIRED
               "software_version": str,    # REQUIRED
               "pipeline_versions": [...], # OPTIONAL, 1.0 shape unchanged
               "schema_version": str},     # OPTIONAL, must be "1.1"
  "geoshift": {"x": float, "y": float, "z": float},   # OPTIONAL
  "features": [ feature, ... ],            # REQUIRED (MAY be empty)
}

pipeline_versions entry: {"step": str, "date": str?, "packages": {name: version}} or {"step": str, "segment_ranges": [{"segments": [[a, b], ...], "date": str, "packages": {...}}]} (§4.4).

8.2 feature

Key Type Req. Default / note
type str yes Feature/Type
id str yes document-unique
lane_id str yes "0" when not lane-bound
attributes Mapping no Feature/Attributes, last child
lines / points / polylines / splines list no absent ⇒ empty container + count 0

There is no layer key (R-2.2.4). A layer key, if supplied, is ignored (R-6.4.2).

8.3 point

Key Type Req. Default / note
id str yes
position [x, y, z] yes export frame, metres → X,Y,Z
lane_id str legacy yes absent ⇒ owning Feature/LaneID for 1.1-touched geometry (R-4.11.1); "0" for 1.1 profiles
pointer_number int no absent ⇒ 1-based index within the container (R-4.17.1)
color any no absent ⇒ empty text; present as None ⇒ text None (R-4.17.2)
feature_class str no absent ⇒ owning Feature/Type; present None ⇒ text None
feature_id str no absent ⇒ owning Feature/ID; present None ⇒ text None
road_point float no element omitted when absent
direction [dx, dy, dz] 1.1 point classes unit, finite, non-zero; dz MAY be non-zero
shapes list of shape dicts no §8.7
annotation dict per R-4.21.1 §8.8
attributes Mapping no

8.4 polyline

Key Type Req. Default / note
id str yes
vertices list of [x,y,z] yes ≥2 for 1.1 profiles (R-3.7); vertex ids generated
lane_id str legacy yes absent ⇒ owning Feature/LaneID for 1.1-touched geometry (R-4.11.1); "0" for 1.1 profiles
type str no Polyline/Type (open string)
road_point [from, to] no element omitted when absent
sorting_code str no XOR with the triple below
sort_class, pair_index, pair_role str, int, top|bottom no composed per R-5.6.5, all-or-none
closed bool no Polyline/Closed
annotation dict per R-4.21.1
attributes Mapping no

8.5 spline, line, width

spline: id (req), control_points (req; ≥2 for 1.1 profiles), fit_points (req in 1.0, default [] for 1.1 producers), type, road_point, width, annotation, attributes. line: 1.0 keys unchanged — id, start_point, end_point, road_point (all required), type (optional, open string), width (optional) — plus OPTIONAL annotation, attributes. lane_id on a spline or line: as R-4.11.1 — required for legacy dicts, defaulted from the feature for 1.1-touched geometry.

width dict → <Width>; each child is emitted only when its source value is not None:

Key Element Note
width Value str(v)
sigma_width Sigma str(v)
status Status str(v)
flagged Flagged str(bool(v)).lower()

Other keys carried in the same dict (center, blur_left, blur_right, n_points) are ignored (R-8.10.1).

8.6 Attributes are explicit only

8.7 shape dict

{"role": str, "type": "cylinder"|"cone",
 "base": [x, y, z],
 "top":  [x, y, z],   # cylinder only
 "apex": [x, y, z],   # cone only
 "diameter": float}

8.8 annotation dict

{"display": bool, "legible": "full" | "partial" | "none" | None, "text": str | list[str] | None}. - R-8.8.1 A str text yields one Text; a list[str] yields one Text per element in order; None or [] yields none. - R-8.8.2 legible is a str from the enumeration of R-5.10.1; absent or NoneLegible not emitted. A bool is not accepted (R-6.2.16). - R-8.8.3 attributes is Mapping[str, str | int | float | bool | None]. - N-8.8.4 (informative — numpy scalars) Wherever §8 declares an int, a float or a bool, the numpy scalar equivalents (bool_, integer, floating) are accepted and mapped to boolean/integer/float — boolean first, so np.bool_ never becomes an integer (R-5.9.1) — and are serialised exactly as the equivalent Python scalar would be (R-5.1.1, R-5.2.1).

8.9 Producer responsibilities (not the writer's)

8.10 What the writer ignores


9. Complete examples

All examples conform to §§3–8. Whitespace is prettify_xml's (two-space indent).

9.1 Point symbols — Tree with Shapes, KM Sign with Legible

<?xml version="1.0" encoding="UTF-8"?>
<HighwayData>
  <MetaData>
    <CreationDate>2026-08-28T09:12:00</CreationDate>
    <Source>hbw_detectors</Source>
    <SoftwareVersion>0.10.0</SoftwareVersion>
    <SchemaVersion>1.1</SchemaVersion>
  </MetaData>
  <Geoshift>
    <X>2600000.0</X>
    <Y>1200000.0</Y>
    <Z>400.0</Z>
  </Geoshift>
  <Features>
    <Feature>
      <Type>Tree</Type>
      <ID>TREE-12</ID>
      <LaneID>0</LaneID>
      <Lines/>
      <NumberOfLines>0</NumberOfLines>
      <Points>
        <Point>
          <ID>TREE-12-1</ID>
          <LaneID>0</LaneID>
          <PointerNumber>1</PointerNumber>
          <X>-109.06</X>
          <Y>-383.29</Y>
          <Z>0.02</Z>
          <Color/>
          <FeatureClass>Tree</FeatureClass>
          <FeatureID>TREE-12</FeatureID>
          <Direction>
            <X>0.974467</X>
            <Y>-0.224529</Y>
            <Z>0.0</Z>
          </Direction>
          <Shapes>
            <Shape role="trunk" type="cylinder">
              <Base>
                <X>-109.06</X>
                <Y>-383.29</Y>
                <Z>0.02</Z>
              </Base>
              <Top>
                <X>-109.04</X>
                <Y>-383.27</Y>
                <Z>2.52</Z>
              </Top>
              <Diameter>0.3</Diameter>
            </Shape>
            <Shape role="crown" type="cone">
              <Base>
                <X>-109.04</X>
                <Y>-383.27</Y>
                <Z>2.52</Z>
              </Base>
              <Apex>
                <X>-109.01</X>
                <Y>-383.22</Y>
                <Z>8.52</Z>
              </Apex>
              <Diameter>7.0</Diameter>
            </Shape>
          </Shapes>
          <Annotation>
            <Display>true</Display>
            <Text>Ø= 0,30/7,0m</Text>
          </Annotation>
        </Point>
      </Points>
      <NumberOfPoints>1</NumberOfPoints>
      <Polylines/>
      <NumberOfPolylines>0</NumberOfPolylines>
      <Splines/>
      <NumberOfSplines>0</NumberOfSplines>
    </Feature>
    <Feature>
      <Type>KM Sign</Type>
      <ID>KMS-3</ID>
      <LaneID>0</LaneID>
      <Lines/>
      <NumberOfLines>0</NumberOfLines>
      <Points>
        <Point>
          <ID>KMS-3-1</ID>
          <LaneID>0</LaneID>
          <PointerNumber>1</PointerNumber>
          <X>-88.31</X>
          <Y>-401.77</Y>
          <Z>1.92</Z>
          <Color/>
          <FeatureClass>KM Sign</FeatureClass>
          <FeatureID>KMS-3</FeatureID>
          <Direction>
            <X>-0.221946</X>
            <Y>-0.963767</Y>
            <Z>0.147964</Z>
          </Direction>
          <Annotation>
            <Display>true</Display>
            <Legible>partial</Legible>
            <Text>KM 46.5</Text>
            <Text>Abs. ??</Text>
          </Annotation>
          <Attributes>
            <Attribute name="direction_source" type="string">measured</Attribute>
          </Attributes>
        </Point>
      </Points>
      <NumberOfPoints>1</NumberOfPoints>
      <Polylines/>
      <NumberOfPolylines>0</NumberOfPolylines>
      <Splines/>
      <NumberOfSplines>0</NumberOfSplines>
    </Feature>
  </Features>
</HighwayData>

Notes: the KM Sign direction is a tilted plate normal (Z != 0, R-5.5.1). Legible=partial — the kilometre line was read in full, two characters of the section line were not and are marked ?? (R-4.24.5), and the station line was entirely unreadable and is therefore omitted (R-4.23.1, §7.18).

The same sign with nothing readable at all carries no Text, and Display stays true so the symbol is still placed (R-4.23.3):

<Annotation>
  <Display>true</Display>
  <Legible>none</Legible>
</Annotation>

9.2 Paired polylines — trench top + bottom (G4$A / G4$B)

<?xml version="1.0" encoding="UTF-8"?>
<HighwayData>
  <MetaData>
    <CreationDate>2026-08-28T09:12:00</CreationDate>
    <Source>hbw_detectors</Source>
    <SoftwareVersion>0.10.0</SoftwareVersion>
    <SchemaVersion>1.1</SchemaVersion>
  </MetaData>
  <Features>
    <Feature>
      <Type>Trench</Type>
      <ID>TRN-4-TOP</ID>
      <LaneID>0</LaneID>
      <Lines/>
      <NumberOfLines>0</NumberOfLines>
      <Points/>
      <NumberOfPoints>0</NumberOfPoints>
      <Polylines>
        <Polyline>
          <ID>TRN-4-TOP-1</ID>
          <LaneID>0</LaneID>
          <Type>top</Type>
          <Vertices>
            <Vertex>
              <ID>TRN-4-TOP-1-1</ID>
              <X>10.0</X>
              <Y>0.0</Y>
              <Z>1.2</Z>
            </Vertex>
            <Vertex>
              <ID>TRN-4-TOP-1-2</ID>
              <X>18.0</X>
              <Y>0.4</Y>
              <Z>1.25</Z>
            </Vertex>
          </Vertices>
          <SortingCode>G4$A</SortingCode>
          <Annotation>
            <Display>false</Display>
          </Annotation>
        </Polyline>
      </Polylines>
      <NumberOfPolylines>1</NumberOfPolylines>
      <Splines/>
      <NumberOfSplines>0</NumberOfSplines>
    </Feature>
    <Feature>
      <Type>Trench Bottom</Type>
      <ID>TRN-4-BOT</ID>
      <LaneID>0</LaneID>
      <Lines/>
      <NumberOfLines>0</NumberOfLines>
      <Points/>
      <NumberOfPoints>0</NumberOfPoints>
      <Polylines>
        <Polyline>
          <ID>TRN-4-BOT-1</ID>
          <LaneID>0</LaneID>
          <Type>bottom</Type>
          <Vertices>
            <Vertex>
              <ID>TRN-4-BOT-1-1</ID>
              <X>10.2</X>
              <Y>-1.1</Y>
              <Z>0.35</Z>
            </Vertex>
            <Vertex>
              <ID>TRN-4-BOT-1-2</ID>
              <X>18.1</X>
              <Y>-0.7</Y>
              <Z>0.4</Z>
            </Vertex>
          </Vertices>
          <SortingCode>G4$B</SortingCode>
          <Annotation>
            <Display>false</Display>
          </Annotation>
        </Polyline>
      </Polylines>
      <NumberOfPolylines>1</NumberOfPolylines>
      <Splines/>
      <NumberOfSplines>0</NumberOfSplines>
    </Feature>
  </Features>
</HighwayData>

9.3 Spline — Tree Line

<?xml version="1.0" encoding="UTF-8"?>
<HighwayData>
  <MetaData>
    <CreationDate>2026-08-28T09:12:00</CreationDate>
    <Source>hbw_detectors</Source>
    <SoftwareVersion>0.10.0</SoftwareVersion>
    <SchemaVersion>1.1</SchemaVersion>
  </MetaData>
  <Features>
    <Feature>
      <Type>Tree Line</Type>
      <ID>TRL-2</ID>
      <LaneID>0</LaneID>
      <Lines/>
      <NumberOfLines>0</NumberOfLines>
      <Points/>
      <NumberOfPoints>0</NumberOfPoints>
      <Polylines/>
      <NumberOfPolylines>0</NumberOfPolylines>
      <Splines>
        <Spline>
          <ID>TRL-2-1</ID>
          <LaneID>0</LaneID>
          <Fit_Points/>
          <Control_Points>
            <Point>
              <ID>TRL-2-1_1</ID>
              <X>-20.0</X>
              <Y>7.5</Y>
              <Z>0.9</Z>
            </Point>
            <Point>
              <ID>TRL-2-1_2</ID>
              <X>-8.0</X>
              <Y>8.1</Y>
              <Z>0.95</Z>
            </Point>
            <Point>
              <ID>TRL-2-1_3</ID>
              <X>4.0</X>
              <Y>8.4</Y>
              <Z>1.0</Z>
            </Point>
          </Control_Points>
          <Annotation>
            <Display>false</Display>
          </Annotation>
        </Spline>
      </Splines>
      <NumberOfSplines>1</NumberOfSplines>
    </Feature>
  </Features>
</HighwayData>

9.4 Closed polyline — Tree Group (profile specified, producer TBD)

<?xml version="1.0" encoding="UTF-8"?>
<HighwayData>
  <MetaData>
    <CreationDate>2026-08-28T09:12:00</CreationDate>
    <Source>hbw_detectors</Source>
    <SoftwareVersion>0.10.0</SoftwareVersion>
    <SchemaVersion>1.1</SchemaVersion>
  </MetaData>
  <Features>
    <Feature>
      <Type>Tree Group</Type>
      <ID>TGR-1</ID>
      <LaneID>0</LaneID>
      <Lines/>
      <NumberOfLines>0</NumberOfLines>
      <Points/>
      <NumberOfPoints>0</NumberOfPoints>
      <Polylines>
        <Polyline>
          <ID>TGR-1-1</ID>
          <LaneID>0</LaneID>
          <Vertices>
            <Vertex>
              <ID>TGR-1-1-1</ID>
              <X>30.0</X>
              <Y>12.0</Y>
              <Z>1.1</Z>
            </Vertex>
            <Vertex>
              <ID>TGR-1-1-2</ID>
              <X>44.0</X>
              <Y>14.5</Y>
              <Z>1.2</Z>
            </Vertex>
            <Vertex>
              <ID>TGR-1-1-3</ID>
              <X>41.0</X>
              <Y>25.0</Y>
              <Z>1.15</Z>
            </Vertex>
            <Vertex>
              <ID>TGR-1-1-4</ID>
              <X>28.5</X>
              <Y>22.0</Y>
              <Z>1.05</Z>
            </Vertex>
          </Vertices>
          <Closed>true</Closed>
          <Annotation>
            <Display>false</Display>
          </Annotation>
        </Polyline>
      </Polylines>
      <NumberOfPolylines>1</NumberOfPolylines>
      <Splines/>
      <NumberOfSplines>0</NumberOfSplines>
    </Feature>
  </Features>
</HighwayData>

9.5 Guard-rail annotation on an existing guardrail polyline

<?xml version="1.0" encoding="UTF-8"?>
<HighwayData>
  <MetaData>
    <CreationDate>2026-08-28T09:12:00</CreationDate>
    <Source>guardrails</Source>
    <SoftwareVersion>0.10.0</SoftwareVersion>
    <SchemaVersion>1.1</SchemaVersion>
  </MetaData>
  <Features>
    <Feature>
      <Type>guardrail</Type>
      <ID>GR-7</ID>
      <LaneID>0</LaneID>
      <Lines/>
      <NumberOfLines>0</NumberOfLines>
      <Points/>
      <NumberOfPoints>0</NumberOfPoints>
      <Polylines>
        <Polyline>
          <ID>GR-7-1</ID>
          <LaneID>0</LaneID>
          <RoadPoint>
            <From>120.0</From>
            <To>184.5</To>
          </RoadPoint>
          <Vertices>
            <Vertex>
              <ID>GR-7-1-1</ID>
              <X>0.0</X>
              <Y>-6.2</Y>
              <Z>0.75</Z>
            </Vertex>
            <Vertex>
              <ID>GR-7-1-2</ID>
              <X>64.5</X>
              <Y>-6.4</Y>
              <Z>0.78</Z>
            </Vertex>
          </Vertices>
          <Annotation>
            <Display>true</Display>
            <Text>ESP 2,0</Text>
          </Annotation>
          <Attributes>
            <Attribute name="faces" type="string">right</Attribute>
            <Attribute name="guardrail_type" type="string">ESP 2.0</Attribute>
            <Attribute name="length_m" type="float">64.5</Attribute>
            <Attribute name="side" type="string">right</Attribute>
            <Attribute name="top_height_p90_m" type="float">0.78</Attribute>
          </Attributes>
        </Polyline>
      </Polylines>
      <NumberOfPolylines>1</NumberOfPolylines>
      <Splines/>
      <NumberOfSplines>0</NumberOfSplines>
    </Feature>
  </Features>
</HighwayData>

Attribute children are sorted by @name (R-4.28.1).

9.6 Legacy cross-section feature under 1.1 — frozen profile §7.23

Produced by an unmodified legacy producer. The tree is exactly the 1.0 shape; the only difference from 0.9.0 output is the SchemaVersion line (R-2.4.1).

<?xml version="1.0" encoding="UTF-8"?>
<HighwayData>
  <MetaData>
    <CreationDate>2026-08-28T09:12:00</CreationDate>
    <Source>cluster_stepper</Source>
    <SoftwareVersion>0.10.0</SoftwareVersion>
    <SchemaVersion>1.1</SchemaVersion>
  </MetaData>
  <Features>
    <Feature>
      <Type>Cross Section</Type>
      <ID>CS-left</ID>
      <LaneID>1</LaneID>
      <Lines/>
      <NumberOfLines>0</NumberOfLines>
      <Points>
        <Point>
          <ID>CS-left-1</ID>
          <LaneID>1</LaneID>
          <PointerNumber>0</PointerNumber>
          <X>-3.51</X>
          <Y>0.0</Y>
          <Z>0.12</Z>
          <Color>None</Color>
          <FeatureClass>None</FeatureClass>
          <FeatureID>None</FeatureID>
          <RoadPoint>0.0</RoadPoint>
        </Point>
        <Point>
          <ID>CS-left-2</ID>
          <LaneID>1</LaneID>
          <PointerNumber>1</PointerNumber>
          <X>-3.49</X>
          <Y>1.0</Y>
          <Z>0.13</Z>
          <Color>None</Color>
          <FeatureClass>None</FeatureClass>
          <FeatureID>None</FeatureID>
          <RoadPoint>1.0</RoadPoint>
        </Point>
      </Points>
      <NumberOfPoints>2</NumberOfPoints>
      <Polylines/>
      <NumberOfPolylines>0</NumberOfPolylines>
      <Splines/>
      <NumberOfSplines>0</NumberOfSplines>
    </Feature>
  </Features>
</HighwayData>

The literal None texts are the 1.0 str() coercion of present-but-None keys (R-4.17.2); PointerNumber is the cross-section plane index, not a 1-based counter (frozen semantics, §7.23). The consumer renders each point's elevation label from Z (N-5.7.2) and resolves the layer from Type (Appendix C).


10. Open items

10.1 Still open

Each item states the spec's default behaviour (already normative above) and the decider. None blocks implementation.

# Item Source Default in this spec Decider
O-14 Emit direction_source="fallback" when plate orientation is unknown? As an Attribute entry or an XML attribute on <Direction>? Q5 Emit, as <Attribute name="direction_source"> (R-5.5.7) Miro
O-15 Tree label glyph (Ø vs ), spacing, precision, decimal comma Q2 Ø= 0,30/7,0m reference rendering, producer-formatted (N-5.7.3, N-5.3.6) HBW
O-16 Verge Post class name and its informative layer PLC_Randpfosten Excel only Provisional (§7.25, Appendix C) HBW
O-17 Noise-wall "object name" annotation content Q7 No Annotation emitted (§7.8) HBW
O-18 Which Grasshopper XML component HBW uses; tolerance of unknown trailing children Q3 Assume name-based; one sample-file acceptance run before release (N-2.3.4) HBW
O-19 KM-sign km / section / station source (our chainage vs HBW reference axis) Q1 Attributes carry whatever the producer knows; unknown lines omitted (R-5.7.4) HBW
O-20 Confirm HBW renders elevation labels from Z per N-5.7.2 (absolute, 3 decimals, dot) Q4/Q9 revised Consumer-side rendering; no Text in the document (§7.13, §7.23) HBW
O-21 Appendix C layer names — confirm the full Type → layer table with HBW Q3 revised Poster/Excel values as listed HBW
O-22 Spline definition is underspecified for exact 3D reconstruction: Fit_Points/Control_Points are given but degree, knots and weights are not (pre-existing 1.0 gap). 3D-model review No rule; consumer and producer assume the same convention (informative) Miro / HBW
O-23 3D-model completeness: heights are optional attributes (height_m walls/noise walls), symbol classes (shaft, gully, call box, sign plate) have no extents, no surface concept beyond Shape. Add Shapes / mandatory heights if a 3D model becomes a deliverable. 3D-model review Not required by the 2D-plan contract Miro

10.2 Closed items (IDs are never reused)

Revision 1 of this spec numbered its open items O-1…O-12. Those IDs are retired: five were resolved in round 2, and the seven that stayed open were re-issued as O-13…O-21 in §10.1 rather than renumbered in place. O-13 has since been closed by the Legible enumeration decision, leaving O-14…O-21 open.

Retired ID Item Disposition
O-1 Emit Legible on all point annotations, or only object-transcribed ones? Resolved — object-transcribed classes only (R-4.23.1).
O-2 <Text/> vs omit when not legible Re-issued as O-13, now resolvedText is omitted under Legible=none (R-4.23.3).
O-3 direction_source="fallback" marker Still open → re-issued as O-14.
O-4 Multi-file merge sorting index segment*1000 + i Resolved — dropped; unique per family per document (R-5.6.2).
O-5 Cone apex up Resolved — moot; Apex is an explicit 3D point (R-4.20.2).
O-6 Tree label glyph / precision Still open → re-issued as O-15.
O-7 Q4's "nothing else changes" for Cross Section vs poster column 13 Resolved — Cross Section fully frozen; the consumer renders the label from Z (§7.23).
O-8 Verge Post layer name Still open → re-issued as O-16.
O-9 Noise-wall object-name annotation Still open → re-issued as O-17.
O-10 Which Grasshopper XML component HBW uses Still open → re-issued as O-18.
O-11 KM-sign km/section/station source Still open → re-issued as O-19.
O-12 Generic <Shape> vs tree-specific tags Resolved — generic (R-5.8.6).
O-13 <Text/> vs omit when not legible (re-issue of O-2) Resolved — omit; superseded by the Legible enumeration, where none forbids Text (R-4.23.3).

O-20 (confirm consumer-side elevation rendering) and O-21 (confirm the Appendix C layer table) are new in revision 2 and have no revision-1 predecessor.

Appendix A — 1.0 → 1.1 change list

A.1 New elements

Element Parent Position
SchemaVersion MetaData last
Attributes / Attribute Feature, Point, Polyline, Spline, Line last child of parent
Direction (X,Y,Z) Point after RoadPoint
Shapes / Shape (Base, Top|Apex, Diameter) Point after Direction
Annotation (Display, Legible enum, Text*) Point, Polyline, Spline, Line before Attributes
Type Polyline after LaneID (exception R-2.3.2)
SortingCode Polyline after Vertices
Closed Polyline after SortingCode

No Feature/Layer (R-2.2.4).

A.2 Relaxations (1.1 producers only; legacy output unchanged)

A.3 Removed / rejected

A.4 Where the decisions override the plan

Plan said Decision Effect
Tree carries trunk_diameter_m / crown_diameter_m attributes Q2 3D solids instead: <Shapes>/<Shape> (§4.19–4.20, §5.8, §7.5). The numeric attributes are not emitted.
<Annotation> = Display + Text*, "Text required when Display=true" Q1 Adds <Legible>; Text may be absent while Display=true (R-4.24.1).
Tree Group not exported Q10 Exported as a closed outline polyline; new Polyline/Closed (§7.3, §4.27).
Rename noise_wallNoise Wall, maybe splines Q7 Keep noise_wall, keep polylines, no annotation (§7.8).
Add Layer to lane-marking features Q12 Lane/axis features frozen (§7.21, §7.22, R-7.0.2).
Q9 open (local vs absolute Z, comma vs dot) Q9 Absolute, 3 decimals, dot — as consumer guidance (N-5.7.2).
Q11 open (index scope, >9 pairs) Q11 Unique per family per document (R-5.6.2).
Q3 possible compatibility export mode Q3 No compat flag; acceptance run instead (R-2.4.4).
Q5 direction semantics open Q5 Unit vector, finite, non-zero, always emitted (§5.5).
Q6 possible explicit downhill vector Q6 A/B + outlines suffice (§7.11).
Q8 label source open Q8 guardrail_type attribute + Annotation/Text (§7.20).

A.5 Where round 2 overrides round 1

Round 1 Round 2 Effect
<Feature/Layer> carries the customer catalog key; consumer falls back LayerType Q3 revised: the layer is a CAD artefact, constant per class, derivable from Type Layer removed entirely (R-2.2.4); Type is the only class key (R-2.5.1); Appendix C is informative
Cross Section gains Layer, Direction, per-point Annotation Q4 revised: fine as it is Fully frozen (§7.23, R-2.2.3); the revision-1 open item on Cross Section's scope is closed
Elevation label text emitted by the producer (47.333) Q4/Q9 revised: consumer renders it from Z Terrain Point and Cross Section carry no Text; format is informative (N-5.7.2, §7.13)
Direction lies in the XY plane, Z MUST be 0 Q5 revised Full 3D unit vector, Z MAY be non-zero (R-5.5.1)
Shape = Diameter + Height + BaseZ, vertical axis, cone apex up Q2 revised: lock position in 3D explicitly Base + Top|Apex + Diameter; any orientation; BaseZ/Height and the vertical-axis and apex-up rules removed (§4.20, §5.8)
Legible scope open (all annotations vs object-transcribed) Q1 final Object-transcribed classes only (R-4.23.1)
Sorting index may later become segment*1000 + i Q11 final Dropped; per-family-per-document uniqueness only (R-5.6.2)
Tree Group producer implied near-term Q10 final Profile retained, marked "specified, producer TBD" (§7.3)

A.6 Later owner decisions (after round 2)

Was Decision Effect
Annotation/Legible is a boolean (true/false) Legibility is not binary — sign text is frequently only partly recoverable Legible becomes the enumeration full | partial | none (§4.23, R-5.10.1); none replaces the old false and forbids Text; partial emits the recovered lines with ? / ?… substitution marks (R-4.24.5). Open item O-13 closes.

Appendix B — poster → XML field mapping

B.1 Point classes (poster cols 3, 10, 11, 12, 13, 14, 15, 16; + verge post)

Poster index Poster item XML
0 layer name not in the document — consumer derives it from Feature/Type (Appendix C)
1 X coordinate Point/X
2 Y coordinate Point/Y
3 Z coordinate Point/Z
4 vector — direction of symbol in drawing Point/Direction/X, /Y, /Z (3D; project to XY for a plan rotation)
5 has annotation display Point/Annotation/Display
6 annotation text Point/Annotation/Text (one per line; absent for consumer-rendered labels)
(1.1 addition, no poster index) Point/Annotation/Legible — enumeration full | partial | none (§4.23)
(1.1 addition, no poster index) Point/Shapes/Shape
(1.1 addition, no poster index) Point/Attributes/Attribute

B.2 Polyline classes (poster cols 4, 5, 7, 8, 9, 17; + tree group)

Poster index Poster item XML
0 layer name consumer-derived from Feature/Type (+ container for Pedestrian Road)
1 has a description Polyline/Annotation/Display
2 sorting code (or null) Polyline/SortingCode (omitted = null)
3..n polyline point coordinates (XYZ) Polyline/Vertices/Vertex/X, /Y, /Z
(1.1 addition) Polyline/Type, Polyline/Closed, Polyline/Attributes

B.3 Spline classes (poster cols 1a, 2, 6, 9)

Poster index Poster item XML
0 layer name consumer-derived from Feature/Type (+ container for Pedestrian Road)
1 has a description Spline/Annotation/Display
2..n spline control point coordinates (XYZ) Spline/Control_Points/Point/X, /Y, /Z
(always emitted, may be empty) Spline/Fit_Points

B.4 Annotation-only / special (poster cols 7, 8 hatches; col 18 guard rails; col 1b group symbol)

Poster item XML
Trench / slope hatches — "no input data necessary" nothing exported; derived from the SortingCode A/B pair
Guard-rail labels EDSP, ESP 2,0 Polyline/Annotation/Text + Attribute name="guardrail_type" on the existing guardrail polyline
Tree group symbol — "no input geometry" superseded by decision Q10: closed Tree Group polyline (§7.3)
Elevation labels (cols 10, 13) no Text in the document; consumer renders from Point/Z (N-5.7.2)
Data catalog (block name, rotation mode) never in the XML (R-1.1.5)

Appendix C — Feature/Type → CAD layer name (INFORMATIVE)

This table is not normative and not part of the document. The layer name is a drawing artefact; it is constant per feature class and cannot be derived from point-cloud data, so it lives on the consumer side. The table lets HBW build its Type → layer → Data-catalog lookup independently. Values come from the poster and the Excel catalog; confirm before release (§10 O-21).

Feature/Type Container Expected layer Poster col
Tree Line Splines sPLC_Laubbaumreihe 1a
Tree Group Polylines Nutzungsart 1b
Bush Splines sPLC_Hecke 2
Tree Points PLC_Laubbaum 3
Fence Polylines pPLC_Zaun 4
Paving Edge Polylines pPLC_Pflasterkante 5
noise_wall Polylines pPLC_Lärmschutzwand 6
Trench Polylines pPLC_Graben 7 top
Trench Bottom Polylines pPLC_Grabensohle 7 bottom
Slope Polylines pPLC_Böschung 8
Pedestrian Road Polylines pPLC_Gehweg 9
Pedestrian Road Splines sPLC_Gehweg 9
Terrain Point Points PLC_Geländepunkte 10
Traffic Sign Points PLC_Verkehrszeichen 11
Shaft Points PLC_Schacht 12
Cross Section Points PLC_Punkte_Symbole 13
Emergency Call Box Points PLC_Notrufsäule 14
KM Sign Points PLC_Kilometer-Tafeln 15
Gully Points PLC_Gullys 16
Wall Polylines pPLC_Mauerwerk 17
guardrail, guardrail_support, guardrail_top_rail Polylines Schutzeinrichtung 18
Verge Post Points PLC_Randpfosten (provisional, O-16)
Axis of the Edge Splines + Lines Fahrbahnmarkierung_durchgezogen
Center Lines Splines + Lines Fahrbahnmarkierung_gestrichelt
Central Axis, Both-Sides Central Axis, Single-Side Central Axis Polylines Fahrbahnrand (to confirm)