You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This document and the companion red-test class modules/curved/src/test/java/org/locationtech/jts/spec/curveawareness/CurveAwarenessSpecTest.java
were largely AI-generated. The human contributor has reviewed and verified the
technical content (cross-module impact, risk register, phase dependencies, TAG
scope) for correctness. The AI-generated portions are made available under CC0-1.0 (public domain dedication) and are not subject to the project's
licence; human curation and edits are subject to the JTS dual licence.
SPDX-License-Identifier: (EPL-2.0 OR EDL-1.0) AND CC0-1.0
Assisted-by: xAI Grok (grok-4.3)
Assisted-by: Claude (Opus-4.7)
Status: Draft v3. Source branch:feature/sfa-curve-buffer-spike on grootstebozewolf/jts. Audience: locationtech/jts maintainers and contributors. Lift verbatim into a GitHub Epic / Discussion.
1. Goal
Make JTS preserve OGC SFA / ISO 19125-2 curve geometries — CIRCULARSTRING, COMPOUNDCURVE, CURVEPOLYGON, MULTICURVE, MULTISURFACE, TRIANGLE — through every algorithm where the math is sound, instead of silently linearising to flat parents on the way in.
Today jts-curved is a Phase-1 stand-in: types parse, round-trip WKT coordinates, and exist in the type hierarchy, but jts-core ignores their identity and densifies on contact. This epic tracks the lift, operation by operation.
2. Why
Performance. Each densification step expands a typical arc to many chord points (≥50 for a half-circle at 1% chord-tolerance); pipelines that buffer → simplify → union compound the cost.
Precision. Densify → operate → densify drifts control points by ULPs and breaks snap-to-grid round-trips against external SFA producers.
Interop. PostGIS, Oracle Spatial, NetTopologySuite emit and consume curve geometries; today JTS round-trips them as visibly different shapes after every operation.
Discoverability. TestBuilder cannot show a curved buffer or a curved boundary today; users of the spec types never see what their input was for.
3. Scope decisions
In scope (this epic):
Algorithms operating on the six SFA curve types in 2-D.
WKT round-trip with member structure (already partially landed).
TestBuilder rendering and drawing tools.
Deferred to sibling epics — explicitly tracked, not pretended-not-to-exist:
WKB curve-aware extensions (extended geometry-type codes 1000-series). PostGIS interop runs on WKB, so the interop benefit listed in §2 is incomplete without it. Not blocking the algorithm work, but the epic isn't complete without a tracked sibling. Action: open a child issue "WKB curve types" before this epic ships.
3-D solids (POLYHEDRALSURFACE, TIN). Their structural / drawing support landed in the spike for visualisation; no 3-D semantics here.
Elliptic arcs (ELLIPSARC) — JTS has no ellipse model; adding one is bigger than this epic.
Z / M ordinate interpolation across densified arcs (today they propagate from the 3 control points unchanged; every TAG that produces densified output should leave room for a future interpolation policy).
These are foundations the rest of the work builds on, not separate sub-issues.
5. Tracking model
We are not opening 49 separate issues. Fragmenting the project board, spamming notifications, and forcing every reviewer to reconstruct the dependency graph is the wrong shape for work this large.
test: drop CurveAwarenessSpecTest#test_BUF_1_* — the dedicated commit that closes a TAG by removing its red-test method.
6. Cross-module impact
Most TAGs ship purely inside jts-curved (extension module, opt-in). A handful require touching jts-core and therefore need a maintainer review up-front. Calling them out before they're proposed:
TAG group
jts-core change?
Notes
F-RD
Possibly
Only if ShapeWriter needs new extension hooks for CurvePolygon rings.
N-AA, N-AL, N-SS
Yes
SegmentString / Noder hierarchy lives in core. Largest core surface in the epic.
OV
Indirect
Depends on N-SS; overlay pipeline itself stays in core.
R-PR, R-CONT
Indirect
RelateOp lives in core; if N-SS is the seam, no further core change needed.
PLG
Yes
Polygonizer lives in core; needs to accept CompoundCurve edges.
PRC-SN
Yes
PrecisionModel.makePrecise integration.
DSF
Yes (or shadow)
Densifier lives in core; alternative is to wrap and shadow it from jts-curved.
Everything else is jts-curved-only or jts-app (TestBuilder).
7. Risks / open questions
Backwards compatibility on structural composites. Algorithms that don't recognise a curve subtype today silently densify. After F-CP, a third-party algorithm that doesn't know about the new structural CurvePolygon ring will see a CompoundCurve shell where it expected a LinearRing, and may throw. We need a fallback contract: the structural ring must implement enough of LinearRing's contract to keep old code limping (read-only chord coords still available), or the structural CurvePolygon must fail fast with a clear message instead of silently going wrong.
equalsExact semantic change (R-EQ). Today CIRCULARSTRING(p0,p1,p2).equalsExact(LINESTRING(p0,p1,p2)) returns true via shared coordinates. Making it false is per spec, but it's a behaviour change for any user comparing-by-WKT-text. Needs a release note.
Non-similarity affine (AT-NS). Sheared arcs are ellipse arcs, which JTS doesn't model. The plan is to detect-and-densify, but: what does getGeometryType() return on the result? If LineString, we've changed the type silently. If still flagged as CircularString, the polyline lies about its identity. Decide before AT-NS lands.
Performance of public arc-arc / arc-line intersection (N-AA, N-AL). The two-circle solve is fine for low cardinality, but MCIndexNoder-equivalent indexing of arc spans (bounding-box pruning of arcs) is non-trivial. Worth a benchmark before committing to a design.
Z / M propagation across densified arcs. Out of scope for behaviour changes here, but every TAG that produces densified output (DSF, AT-NS, …) should choose a Z/M policy that doesn't lock us out of a proper interpolation later.
8. Definition of Done (epic-level)
The epic closes when all of:
CurveAwarenessSpecTest is empty — every TAG's red test deleted, replaced by green tests next to its production code.
The 6 curve types round-trip through every public Geometry operation in the user guide without producing flat output where curve-preserving output is mathematically possible.
The deferred WKB sibling epic has its own tracking issue. (We don't have to ship it inside this epic, but we can't pretend it doesn't exist.)
A release note covers the equalsExact change (see §7) and any other user-visible behaviour shifts.
TB-FN (function-tree curve-awareness badges) is a stretch goal, not a DoD criterion. Annotating every entry in TestBuilder's function tree is a multi-week task with low payoff relative to the algorithm work, and shouldn't gate epic closure.
9. Phases — work breakdown
Phases group TAGs that share dependencies or naturally land together. Within a phase, TAGs are usually independently shippable. Cross-phase dependencies are noted explicitly per phase.
Contributors run it explicitly with mvn -pl modules/curved test -Dtest=CurveAwarenessSpecTest (which overrides the exclude). The remaining-method count is still the live progress meter, but it doesn't break CI on every push.
12. References
OGC Simple Feature Access 1.2.1 / ISO 19125-2.
jts-curved source: modules/curved/.
Spike branch: feature/sfa-curve-buffer-spike on grootstebozewolf/jts.
Epic: SFA / ISO 19125-2 Curve Awareness in JTS
Status: Draft v3.
Source branch:
feature/sfa-curve-buffer-spikeongrootstebozewolf/jts.Audience: locationtech/jts maintainers and contributors. Lift verbatim into a GitHub Epic / Discussion.
1. Goal
Make JTS preserve OGC SFA / ISO 19125-2 curve geometries —
CIRCULARSTRING,COMPOUNDCURVE,CURVEPOLYGON,MULTICURVE,MULTISURFACE,TRIANGLE— through every algorithm where the math is sound, instead of silently linearising to flat parents on the way in.Today
jts-curvedis a Phase-1 stand-in: types parse, round-trip WKT coordinates, and exist in the type hierarchy, butjts-coreignores their identity and densifies on contact. This epic tracks the lift, operation by operation.2. Why
3. Scope decisions
In scope (this epic):
Deferred to sibling epics — explicitly tracked, not pretended-not-to-exist:
POLYHEDRALSURFACE,TIN). Their structural / drawing support landed in the spike for visualisation; no 3-D semantics here.ELLIPSARC) — JTS has no ellipse model; adding one is bigger than this epic.4. What's already landed (
feature/sfa-curve-buffer-spike)arch:CompoundCurve— segment-awarecopy/toLineararch:CompoundCurvearch:CurvedShapeWriterwalksCompoundCurvemembers, arc-rendersfeat:LineHandlingFunctions.mergeCurves(arc-aware sibling ofmergeLines)fix:CompoundCurve.toLinear(no junction drift)fix:spec:CurveAwarenessSpecTestred-test suitebufferCurveWithParamslinearisation hook (Phase-5 spike entry)These are foundations the rest of the work builds on, not separate sub-issues.
5. Tracking model
We are not opening 49 separate issues. Fragmenting the project board, spamming notifications, and forcing every reviewer to reconstruct the dependency graph is the wrong shape for work this large.
Single source of truth:
CurveAwarenessSpecTestmodules/curved/src/test/java/org/locationtech/jts/spec/curveawareness/CurveAwarenessSpecTest.javamvn -pl modules/curved test -Dtest=CurveAwarenessSpecTestfail("TAG: …")documents the spec.mvn -Dtest='!CurveAwarenessSpecTest' test.GitHub layout
Commit / PR convention
Buckets:
fix:,feat:,arch:,test:,spec:,refactor:. (User-facing docs go underspec:.)Examples:
feat: BUF-1 analytical single-arc CircularString buffer → CurvePolygonarch: F-CP CurvePolygon stores CompoundCurve shell + holestest: drop CurveAwarenessSpecTest#test_BUF_1_*— the dedicated commit that closes a TAG by removing its red-test method.6. Cross-module impact
Most TAGs ship purely inside
jts-curved(extension module, opt-in). A handful require touchingjts-coreand therefore need a maintainer review up-front. Calling them out before they're proposed:ShapeWriterneeds new extension hooks forCurvePolygonrings.SegmentString/Noderhierarchy lives in core. Largest core surface in the epic.RelateOplives in core; if N-SS is the seam, no further core change needed.Polygonizerlives in core; needs to acceptCompoundCurveedges.PrecisionModel.makePreciseintegration.Densifierlives in core; alternative is to wrap and shadow it fromjts-curved.Everything else is jts-curved-only or
jts-app(TestBuilder).7. Risks / open questions
CompoundCurveshell where it expected aLinearRing, and may throw. We need a fallback contract: the structural ring must implement enough ofLinearRing's contract to keep old code limping (read-only chord coords still available), or the structural CurvePolygon must fail fast with a clear message instead of silently going wrong.equalsExactsemantic change (R-EQ). TodayCIRCULARSTRING(p0,p1,p2).equalsExact(LINESTRING(p0,p1,p2))returns true via shared coordinates. Making it false is per spec, but it's a behaviour change for any user comparing-by-WKT-text. Needs a release note.getGeometryType()return on the result? IfLineString, we've changed the type silently. If still flagged asCircularString, the polyline lies about its identity. Decide before AT-NS lands.MCIndexNoder-equivalent indexing of arc spans (bounding-box pruning of arcs) is non-trivial. Worth a benchmark before committing to a design.8. Definition of Done (epic-level)
The epic closes when all of:
CurveAwarenessSpecTestis empty — every TAG's red test deleted, replaced by green tests next to its production code.Geometryoperation in the user guide without producing flat output where curve-preserving output is mathematically possible.equalsExactchange (see §7) and any other user-visible behaviour shifts.TB-FN (function-tree curve-awareness badges) is a stretch goal, not a DoD criterion. Annotating every entry in TestBuilder's function tree is a multi-week task with low payoff relative to the algorithm work, and shouldn't gate epic closure.
9. Phases — work breakdown
Phases group TAGs that share dependencies or naturally land together. Within a phase, TAGs are usually independently shippable. Cross-phase dependencies are noted explicitly per phase.
Phase 1 — Foundations (jts-curved structural completeness)
Mirror the
CompoundCurvework onto the remaining composite types.CurvePolygon—CompoundCurveshell + holes;copyInternal,toLinear, WKT reader/writer preserve structure.MultiCurve— preserves member subtypes (LineString/CircularString/CompoundCurve).MultiSurface— preservesPolygonvsCurvePolygonmember subtypes.CurvedShapeWriterforCurvePolygonrings,MultiCurveandMultiSurfacemembers.Hard prereq for: all of Phase 2 (you can't return a
CompoundCurveboundary of aCurvePolygonwhose ring isn't aCompoundCurveto begin with).Phase 2 — Properties (Metrics, Boundary, Validity)
CurvePolygonvalidity (arc self-intersection, sector orientation, holes-in-shell).CircularString/CompoundCurvesimplicity.Depends on: Phase 1 (F-CP is a hard prereq for B-CP, M-AREA-CP, V-CP).
Phase 3 — Measurement (Distance, Centroid, Interior point)
DistanceOpaccepts curved inputs without forced densification.DiscreteHausdorffDistance/DiscreteFrechetDistanceparameterise by arc length.Depends on: Phase 1 (F-CP for
CurvePolygoncases).Phase 4 — Construction (Buffer, Hulls, Simplification, Affine, Linear-Ref, Densifier)
toLinear.Depends on: Phase 1 (output may be
CurvePolygon).Phase 5 — Noding foundation
SegmentString/NodedSegmentStringand integration with theNoderhierarchy.Depends on: Phase 1. Touches
jts-core— see §6.Phase 6 — Overlay, Predicates, Polygonizer, Coverage
union/intersection/difference/symDifference).PolygonizeracceptsCompoundCurveedges and emitsCurvePolygonfaces.CoverageUnionpreserves shared arc edges.Depends on: Phase 5.
Phase 7 — Independent tracks
Three single-theme tracks that depend only on Phase 1 and have no inter-dependencies; can land in parallel with Phases 2–6 once Phase 1 ships.
(R, centre, sweep)still lies on grid; otherwise densify-and-snap chords.DelaunayTriangulationBuilder/VoronoiDiagramBuilderaccept curved boundary input (densify internally viatoLinear(tolerance)).CompoundCurveTool,CurvePolygonTooldrawing UX.10. Suggested order
After Phase 1 finishes, Phases 2 / 3 / 4 / 5 / 7 can run in parallel; Phase 6 waits for Phase 5.
flowchart TD P1[Phase 1: Foundations] P2[Phase 2: Properties<br/>Metrics, Boundary, Validity] P3[Phase 3: Measurement<br/>Distance, Centroid, Interior Point] P4[Phase 4: Construction<br/>Buffer, Hulls, Simplification, ...] P5[Phase 5: Noding<br/>N-AA, N-AL, N-SS] P6[Phase 6: Overlay, Predicates,<br/>Polygonizer, Coverage] P7[Phase 7: Snapping<br/>PRC-SN] P8[Phase 8: Triangulation / Voronoi<br/>TRI-DT, TRI-VR] P9[Phase 9: TestBuilder<br/>TB-T, TB-FN] P1 --> P2 P1 --> P3 P1 --> P4 P1 --> P5 P5 --> P6 P1 --> P7 P1 --> P8 P1 --> P911. Conventions
TAGs are short, unique, and stable. Renaming a TAG renames its test method too.
One TAG per PR (or a tightly-coupled cluster). The PR deletes the corresponding spec method.
CI stays green by default. The spec class is excluded from the default Surefire run via:
Contributors run it explicitly with
mvn -pl modules/curved test -Dtest=CurveAwarenessSpecTest(which overrides the exclude). The remaining-method count is still the live progress meter, but it doesn't break CI on every push.12. References
jts-curvedsource:modules/curved/.feature/sfa-curve-buffer-spikeongrootstebozewolf/jts.CurveAwarenessSpecTest(live progress meter).