Proposal: CLOTHOID segment in WKT (JTS extension)
#4847
grootstebozewolf
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Proposal:
CLOTHOIDsegment in WKT (JTS extension)Status: Draft proposal. No grammar changes in this branch — proposal text only.
Audience: the JTS / NetTopologySuite curve-geometry community, and (later)
antlr/grammars-v4reviewers.Companion: sister proposal under locationtech/jts#1195 — SFA Curve Awareness epic.
1. Summary
Extend WKT — as a JTS extension, beyond OGC SFA / ISO 19125-2 — with a
CLOTHOIDsegment usable insideCOMPOUNDCURVE. A clothoid (Euler / Cornu spiral) is the canonical transition curve in railway and highway alignment design: it interpolates curvature linearly with arc length, smoothing the lateral-jerk discontinuity that occurs at a bare line→arc seam.Restricted scope.
CLOTHOIDis valid only as a non-leading segment insideCOMPOUNDCURVE. It is not a top-level WKT geometry, and not allowed elsewhere. This restriction is what makes the syntax unambiguous, the parser simple, and the implementation tractable.2. Why this is a JTS extension, not an OGC change
OGC SFA and ISO 19125-2 define
LINESTRING,CIRCULARSTRING,COMPOUNDCURVE,CURVEPOLYGON,MULTICURVE,MULTISURFACE. None of them define a clothoid. Civil-engineering domain models do — IFC 4.3 hasIfcClothoid, LandXML has<Spiral spiType="clothoid">— but the GIS/SFA family deliberately stops at circular arcs.This proposal therefore lives in the same space as
CIRCULARSTRINGitself does for OGC SFA: an extension over a base spec, not a modification of it. Existing OGC-conformant WKT readers are expected to fail on the unknownCLOTHOIDkeyword (correct OGC behaviour for an unknown extension). JTS readers in extension mode accept it.If/when ISO publishes a clothoid keyword in a future spec revision, this proposal becomes the migration path; until then, the
CLOTHOIDtoken and grammar live behind aJTS_WKT_CLOTHOID_EXTENSIONopt-in.3. Locked decisions
The v0 sketch left ten engineering decisions open. This section pins each one down. Future review may revisit, but implementations must follow these unless a decision is explicitly overturned.
3.1 Sign convention for curvature
Positive κ = counter-clockwise turn in the standard mathematics XY-up coordinate system (positive y = up, positive x = right). Equivalently: a positive-κ clothoid bends the tangent vector toward the +y half-plane.
For renderers using screen Y-down coordinates, this inverts at the rendering layer; the geometry layer is XY-up by convention.
3.2 First-segment clothoid
Disallowed. A
CLOTHOID(k0, k1, L)may not be the first segment of aCOMPOUNDCURVE. The clothoid inherits its start point, start tangent direction and start curvature from the immediately preceding segment. With no preceding segment, those values are undefined.If a future use case needs a free-standing or leading clothoid, the syntax can be extended additively (e.g. a 6-arg form
CLOTHOID(x0 y0, theta0, k0, k1, L)allowed only in first position), without breaking the restricted form. Not in v1.3.3 Junction tolerance — who wins on coord disagreement
Typed coordinate wins. The next segment's explicitly typed start coordinate is authoritative. The clothoid's analytically computed end coordinate is informational. The parser computes the analytical end; if drift from the typed coord exceeds
1e-9(relative to chord length), it emits a parser warning (configurable to fail instrictmode), but the typed coord is what the constructed geometry uses.Rationale: input WKT typically rounds to 6–9 significant figures; the analytical end of a Fresnel integral is exact to ~15 figures. Forcing the analytical value would silently change user-typed coords, which is worse than tolerating documented drift.
3.4 G2 continuity at clothoid → arc junction
Warn-only at parse time. When a
CLOTHOID(k0, k1, L)segment is followed by aCIRCULARSTRING(...), the parser computes the circumscribed-circle radiusRof the arc's three control points and checks whether|1/R − |k1|| < 1e-6(relative). Mismatch emits a warning.strictmode upgrades to fail.A clothoid → line junction implies
k1 = 0; the same check applies (warn if|k1| > 1e-6).3.5 Numeric backend for integration
Adaptive composite Simpson's rule on the heading function
producing position via
Convergence target:
1e-10relative on each ordinate over[0, L], capped at 14 levels of refinement. No external dependency. Works directly in the geometry's native (x₀, θ₀, κ₀, κ₁, L) parametrisation without translating to canonical Fresnel form.A power-series Fresnel approximation (Heald 1985, ~5 terms, 1e-10 accuracy on [-π/2, π/2]) is acceptable as an alternative for the canonical form, but adaptive Simpson's covers the general case directly and is preferred.
3.6
getCoordinateSequence()semantics on a clothoid-bearing CompoundCurveA
CompoundCurvewhose member list contains aClothoidSegmentreturns, when flattened, just the start and end coordinates of that segment. The interior of the clothoid is not represented in the flat coord sequence.Algorithms that walk
getCoordinates()on such a geometry under-represent its actual extent. The remedy is to calltoLinear(tolerance)first; this is documented as a contract of the type. Library users that mix clothoid-bearing curves with algorithms that don't know abouttoLinearshould densify explicitly.3.7
equalsExactsemanticsClothoidSegment.equalsExact(other)returnstrueiff:otheris also aClothoidSegment, AND(k0, k1, L)are bitwise equal (within ordinaryequalsExacttolerance), AND(startPoint, startTangent, startKappa)— is also equal.Two clothoids with identical
(k0, k1, L)but different start state are different geometries. Inside aCompoundCurve, the start state is implicit; equality of the containingCompoundCurves implies equality of all segment start states.3.8
reverse()CLOTHOID(k0, k1, L)reverses toCLOTHOID(−k1, −k0, L)— the parameter order swaps and the signs flip, because reversing the traversal direction inverts the sense of the tangent rotation, hence inverts the sign of curvature. The reversed segment starts from the original end point with the original end tangent, rotated 180°.Implication:
Geometry.reverse()on aCompoundCurve(line, clothoid, arc)yieldsCompoundCurve(arc.reverse(), clothoid.reverseSigns(), line.reverse())— straightforward member-list reversal with each member's own reverse semantics.3.9 Bounding box
Computed analytically by solving
dx/ds = cos(θ(s)) = 0anddy/ds = sin(θ(s)) = 0fors ∈ [0, L]. These reduce to roots ofθ(s) = (n + ½)πandθ(s) = nπrespectively. Withθ(s) = θ₀ + κ₀·s + ½·(κ₁−κ₀)/L · s²quadratic ins, each equation is a (possibly-empty) finite set of quadratic roots clipped to[0, L].The clothoid's envelope is the bounding box of
{startPoint, endPoint, x(s_root), y(s_root) for each root}.If the analytical solver fails to converge or produces NaN (e.g. for
κ₁ = κ₀, the segment degenerates and the linear / circular bounding-box code applies), fall back to a densified bounding box plus a 1% margin.3.10 Canonical parameter form
WKT carries
(k0, k1, L). The LandXML/IFCAconstant is derived:The Java type provides
getClothoidConstantA()returningA, and a static factoryso users coming from LandXML/IFC don't have to derive the conversion themselves.
κ₁ = κ₀is rejected at construction (it's a circular arc or straight line, not a clothoid; users should useCIRCULARSTRINGor a bare LineString segment instead).4. Syntax
INF/-INF/NANpermitted by the surrounding extension grammar).length > 0. Negative length is rejected.startKappa = endKappais rejected (degenerate; useCIRCULARSTRINGor a line segment).The
CLOTHOIDtoken is only valid as acompoundCurveMembernon-leading position. Other positions error out at parse time.4.1 Worked example (real-world: ProRail track
823_12V_4.3)Sourced from the ProRail Spoorgeometrie open dataset (CC BY 4.0). A real Dutch railway alignment with the canonical line → clothoid → arc → clothoid → line transition that motivates the proposal — Rechtstand → Overgangsboog → Boog → Overgangsboog → Rechtstand in ProRail's
ELEMENT_TYPEvocabulary. Coordinates are in EPSG:28992 (Dutch Rijksdriehoek) — render in any RD-aware viewer, or paste straight into JTS TestBuilder and use Zoom to A to fit.Reads as: a 12.18 m straight, a 48 m entry clothoid taking curvature from 0 to 1/200 (R = 200 m inside the bend, CCW), a 311.4 m circular arc, a 42 m exit clothoid back to straight, and a 290.8 m final straight. Total bend: ≈ 102°. The two
CIRCULARSTRINGand the trailing line-segment start coordinates must agree (within tolerance) with the analytical end of the preceding clothoid (per §3.3).The dataset records 8 677 such transitions across the Dutch network and 757 follow this exact 5-element pattern; this one was picked for its tight 200 m radius (visually obvious arc) at the expense of a small clothoid bow (≈ 1.4 m sagitta on a 48 m clothoid — designed to be imperceptible to passengers, which is the whole point).
Source: ProRail Spoorgeometrie, layer
PVS_Horizontal_Elements(CC BY 4.0). Track reference:823_12V_4.3,SIGMATRAJECT_GUID = 510603bc-fef6-4d29-9a01-2bad891057ca,VOLGNUMMER33–37. Data snapshot retrieved 2026-05-10. (An earlier dataset snapshot called this track823_11V_3.9; ProRail re-numbers periodically.)4.2 What the grammar already enforces
CLOTHOIDtoken if the parser is in OGC-strict mode (extension off).CLOTHOIDappears as the first member of aCOMPOUNDCURVE, or at the top level (matching §3.2).lengthandkappaparse via the existing numeric ordinate rule, soINF/NANare syntactically accepted but semantically rejected at construction (length must be positive finite; kappa must be finite).5. Grammar diff sketch (illustrative — no commit in this branch)
Against the post-#4846
wkt/wkt.g4(which hascompoundCurveGeometryand acompoundCurveMemberproduction):A separate semantic-validation pass (not the grammar) enforces:
clothoidSegmentcannot be the first member of acompoundCurveGeometry,lengthordinate is positive finite,startKappa ≠ endKappa.These belong outside the grammar so error messages can carry meaningful context, and so the pure ANTLR grammar stays decision-free.
6. Compatibility & fallback
Existing OGC WKT readers will fail on the
CLOTHOIDkeyword. This is the correct OGC behaviour for an unknown extension keyword. The proposal does not advocate silent fallback or chord-replacement.For interop with non-extension consumers, a
WKTWriter.writeWithFallback(Geometry, FallbackPolicy)mode is suggested:FAIL(default): emit the geometry as-is includingCLOTHOID; non-extension readers will fail.DENSIFY: replace eachCLOTHOIDsegment with itstoLinear(tolerance)chord polyline before emitting WKT. Result is OGC-validLINESTRING/COMPOUNDCURVEofLINESTRING+CIRCULARSTRING.CHORD: replace eachCLOTHOIDwith a single straight line from start to end. Loses the transition.This is implementation guidance; not part of the grammar proposal itself.
7. Implementation outline
Three small modules, mirrors how
CircularString/CompoundCurvewere structured in the JTS spike:ClothoidSegment(geometry) — extendsLineString, stores(startPoint, startTangent, startKappa, endKappa, length). ImplementsLinearizable.toLinear(tolerance)via §3.5.CompoundCurvemember-list update — acceptClothoidSegmentalongside the existingLineString/CircularStringmember kinds.CurvedWKTReader.readClothoidSegmentText— reads the three scalars and constructs aClothoidSegmentusing the running state from precedingCompoundCurvemembers.CurvedWKTWriter.appendClothoidSegmentText— emits the three scalars; junction coordinates come from the surrounding members.CurvedShapeWriter— extend theCompoundCurvewalker to renderClothoidSegmentmembers viatoLinearthenlineTo-stream (or, optionally, Bezier-approximate per arc-length step for smoother rendering).Estimated surface: ~600–800 LoC across the JTS spike branch, plus tests. Not in this proposal — that's a separate implementation epic.
8. Relationship to the SFA Curve Awareness epic
The SFA Curve Awareness epic (locationtech/jts#1195) is about preserving spec-defined curve types (
CIRCULARSTRING,COMPOUNDCURVE,CURVEPOLYGON, etc.) through every JTS algorithm. This proposal is explicitly out of scope for that epic (and was carved out as a footnote in §3 of that epic for exactly this reason).CLOTHOIDis a separate, sibling effort:The two efforts share infrastructure (
CompoundCurvemember-list architecture,Linearizableinterface,CurvedShapeWriterrendering pipeline), so the SFA work unblocks this; but the SFA epic should not block on this. Land SFA first.9. Open questions / future work
CLOTHOID(...)in a row is well-defined — but no special syntax is needed. Document as a usage pattern in v1.CLOTHOID(x0 y0, theta0, k0, k1, L)form in first position only.ClothoidString(analogue ofCircularString— a chain of clothoid arcs sharing endpoints). Possible v3; covers the rare case of a continuous clothoid sequence not interrupted by circles or straights.toLineardensification and the polyline machinery.10. References
IfcClothoidtype and parametrisation.<Spiral spiType="clothoid" ...>element.Beta Was this translation helpful? Give feedback.
All reactions