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 was 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.8)
Status: Draft v4 (standard PR-tracked). Source branch:feature/sfa-curve-buffer-spike on grootstebozewolf/jts. Audience: locationtech/jts maintainers and contributors.
1. Goal
Make JTS preserve curved and extended geometry types — 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.
Standards provenance. OGC Simple Feature Access (ISO 19125-1) defines a geometry model with linear interpolation between vertices. Of the types above, SFA 1.2.1 (OGC 06-103r4) defines only the abstract MULTICURVE / MULTISURFACE supertypes and the TRIANGLE surface; the concrete curved types — CIRCULARSTRING, COMPOUNDCURVE, CURVEPOLYGON — and circular interpolation come from ISO/IEC 13249-3 (SQL/MM Spatial), not from SFA. (In OGC 06-103r4 those curved type names appear only in a type-code table that explicitly notes the entries "are for future use and do not reflect types used here.") ISO 19125-2 is the SQL-bindings part of SFA and introduces no curve types, so it is not the source of curve awareness and should not be cited as such.
Today jts-curved is past the parse-only stand-in: the structural composites and several analytical properties already land arc-aware (see §5 table). This epic tracks the remaining lift, operation by operation.
2. Why
Performance. Each densification step expands one arc into a chord polyline — about 12 chords (13 vertices) for a half-circle at the default 1% chord-error (sagitta) tolerance, rising to ~36 at 0.1%; 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 curved / extended geometry types in 2-D.
WKT round-trip with member structure.
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. Action: open a child issue "WKB curve types" before this epic ships.
3-D solids (POLYHEDRALSURFACE, TIN). Their structural / drawing support landed 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.
4. How we work (the standard model)
This epic is large, but we deliberately do not fragment it into dozens of issues, and we do not keep a meta progress fixture in the test tree. Instead:
One TAG, one self-contained PR. Each TAG in the §5 table is shipped by a single PR (or a tightly-coupled cluster) that stands on its own: it compiles, its tests pass, and a reviewer can understand it without reconstructing the rest of the epic. Brain-friendly for a solo maintainer — small enough to review in one sitting.
A PR is a feature, a fix, an arch change, or a spec. Use the bucket prefix and the TAG in the commit subject + PR title:
The §5 table is the live status. Each TAG row carries its PR link (or — if none yet) and a status. That is the progress meter — standard GitHub PR state, nothing bespoke.
The §6 changelog is updated every time a PR lands for review. When a PR opens against dr-jts, add one log entry (newest first). The entry reads like a book — a short narrative a reviewer can read top-to-bottom — and for a feat: it links exactly two files: the source that implements it and the test that proves it (open-in-Codespace blob links). Fixes/arch/spec entries link whatever is relevant.
CI is green by default. There is no excluded "intentionally red" suite. Every test in the tree passes; coverage for a TAG arrives as ordinary green tests in the same PR.
5. TAG status
Status legend: ✅ merged to locationtech/jts · 🔵 PR open for review · 🟢 implemented on fork (PR pending) · ⬜ planned · ✩ stretch.
PR numbers below are filled where known; — means no PR has been opened yet. Maintainer/contributor: update the PR + status cells as PRs land (this is the single source of truth that replaces the old red-test count).
One entry per PR that lands for review. Read like a book. A feat: entry links exactly two files — src (what implements it) and test (what proves it) — as open-in-Codespace blob links. Append, never rewrite history.
CurvePolygon stops throwing its rings away on the way in. It now keeps the structural shell and holes (CircularString / CompoundCurve / LineString) and exposes them through new getExteriorCurve() / getInteriorCurveN(int), while the inherited getExteriorRing() keeps returning a densified LinearRing view so existing Polygon callers and all of jts-core keep working unchanged (Option A from SPEC_F_CP.md). MultiCurve and MultiSurface gain the same subtype-preserving treatment, and the CurvedWKTReader / CurvedWKTWriter round-trip the ring structure (CIRCULARSTRING / COMPOUNDCURVE tags inside the CURVEPOLYGON body). copyInternal, reverseInternal, toLinear, and normalize all carry the curves through.
Adds the seams the rest of the epic hangs off: WKTReader.readOtherGeometryText and WKTWriter.appendOtherGeometryTaggedText extension points (plus a handful of helpers promoted to protected), the canonical type-keyword constants, and the opt-in jts-curved module with the extended geometry types and their CurvedWKTReader / CurvedWKTWriter. No behaviour change for existing core callers.
Backwards compatibility on structural composites. Algorithms that don't recognise a curve subtype today silently densify. With structural CurvePolygon, a third-party algorithm that expects a LinearRing shell may break. Option A (landed in feat: F-CP structural CurvePolygon (Option A) + enable F-MC/F-MS #1198) keeps the inherited getExteriorRing() returning a densified LinearRing view, so old code keeps limping; curve-aware code uses getExteriorCurve().
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. Plan is detect-and-densify; decide what getGeometryType() returns on the result 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; indexing of arc spans (bounding-box pruning) is non-trivial. Benchmark before committing to a design.
Z / M propagation across densified arcs. Every TAG that produces densified output (DSF, AT-NS, …) should choose a Z/M policy that doesn't lock out a proper interpolation later. (toLinear interpolates Z linearly along the sweep as of the F-CP densification work.)
8. Cross-module impact
Most TAGs ship purely inside jts-curved (extension module, opt-in). A handful require touching jts-core and need a maintainer review up-front:
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).
Phase 1 is the hard prerequisite for everything (you can't return a CompoundCurve boundary of a CurvePolygon whose ring isn't a CompoundCurve to begin with). After Phase 1, Phases 2 / 3 / 4 / 5 / 7 can run in parallel; Phase 6 waits for Phase 5.
10. Definition of Done (epic-level)
The epic closes when all of:
Every TAG in §5 is ✅ (merged) — or explicitly de-scoped in the table with a one-line reason.
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.
A release note covers the equalsExact change (§7) and any other user-visible behaviour shifts.
TB-FN (function-tree badges) is a stretch goal, not a DoD criterion.
11. Conventions
TAGs are short, unique, and stable.
One TAG per PR (or a tightly-coupled cluster). The PR is self-contained, green, and reviewable in one sitting.
The PR updates the §5 table (PR link + status) and prepends a §6 changelog entry.
CI stays green — no excluded suites.
12. References
OGC Simple Feature Access 1.2.1 (OGC 06-103r4) / ISO 19125-1 — linear geometry model; abstract MultiCurve / MultiSurface, and Triangle.
ISO/IEC 13249-3 (SQL/MM Spatial) — circular interpolation and the CircularString / CompoundCurve / CurvePolygon curved types.
jts-curved source: modules/curved/.
F-CP design note: modules/curved/SPEC_F_CP.md.
Spike branch: feature/sfa-curve-buffer-spike on grootstebozewolf/jts.
Epic: Curve and Extended-Geometry Awareness in JTS (OGC SFA + ISO 13249-3 SQL/MM)
Status: Draft v4 (standard PR-tracked).
Source branch:
feature/sfa-curve-buffer-spikeongrootstebozewolf/jts.Audience: locationtech/jts maintainers and contributors.
1. Goal
Make JTS preserve curved and extended geometry types —
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 past the parse-only stand-in: the structural composites and several analytical properties already land arc-aware (see §5 table). This epic tracks the remaining 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 for visualisation; no 3-D semantics here.ELLIPSARC) — JTS has no ellipse model; adding one is bigger than this epic.4. How we work (the standard model)
This epic is large, but we deliberately do not fragment it into dozens of issues, and we do not keep a meta progress fixture in the test tree. Instead:
feat:,fix:,arch:,test:,spec:,refactor:.—if none yet) and a status. That is the progress meter — standard GitHub PR state, nothing bespoke.dr-jts, add one log entry (newest first). The entry reads like a book — a short narrative a reviewer can read top-to-bottom — and for afeat:it links exactly two files: the source that implements it and the test that proves it (open-in-Codespace blob links). Fixes/arch/spec entries link whatever is relevant.5. TAG status
Status legend: ✅ merged to
locationtech/jts· 🔵 PR open for review · 🟢 implemented on fork (PR pending) · ⬜ planned · ✩ stretch.Phase 1 — Foundations (jts-curved structural completeness)
CurvePolygon— curve shell + holes;copyInternal/reverseInternal/toLinear/WKT preserve structureMultiCurve— preserves member subtypesMultiSurface— preservesPolygonvsCurvePolygonmembersjts-curvedmoduleCurvedShapeWriterarc-rendersCurvePolygonrings +MultiCurve/MultiSurfacemembersPhase 2 — Properties (Metrics, Boundary, Validity)
CircularStringlength (r·θ)CompoundCurvelength sums member lengthsCurvePolygonarea (Green's theorem + circular-segment correction)CurvePolygon.getBoundary()returns a curve (CompoundCurve / MultiCurve)MultiSurface.getBoundary()returns aMultiCurveCompoundCurveboundary = its two endpoints; closed = emptyCurvePolygonvalidity (arc self-intersection, sector orientation, holes-in-shell)CircularString/CompoundCurvesimplicityPhase 3 — Measurement (Distance, Centroid, Interior point)
DistanceOpaccepts curved inputs without forced densificationDiscreteHausdorffDistance/DiscreteFrechetDistanceparameterised by arc lengthCircularStringcentroid (arc-length-weighted)CurvePolygonarea centroid (Green's-theorem moments)CurvePolygoninterior point (arc-aware scan line)Phase 4 — Construction (Buffer, Hulls, Simplification, Affine, Linear-Ref, Densifier)
CurvePolygonCompoundCurvebuffer preserves arcsOffsetCurvepreserves arc identity (R±d)VariableBufferinterpolates along arc lengthDouglasPeuckerSimplifierpreserves arc identityVWSimplifierarc-awareTopologyPreservingSimplifierarc-awareLengthIndexedLineparameterised by arc lengthLocationIndexedLinemember-aware onCompoundCurveDensifierdelegates totoLinearfor arc inputPhase 5 — Noding foundation (touches
jts-core— see §8)SegmentString/NoderintegrationPhase 6 — Overlay, Predicates, Polygonizer, Coverage (depends on Phase 5)
equalsExactdistinguishes arc from chord polylinePolygonizeracceptsCompoundCurveedges, emitsCurvePolygonfacesCoverageUnionpreserves shared arc edgesPhase 7 — Independent tracks (depend only on Phase 1)
DelaunayTriangulationBuilderaccepts curved input (densify internally)VoronoiDiagramBuilderaccepts curved inputCompoundCurveTool/CurvePolygonTooldrawing UX6. Changelog (newest first)
feat:F-CP / F-MC / F-MS — structural composites — #1198CurvePolygonstops throwing its rings away on the way in. It now keeps the structural shell and holes (CircularString/CompoundCurve/LineString) and exposes them through newgetExteriorCurve()/getInteriorCurveN(int), while the inheritedgetExteriorRing()keeps returning a densifiedLinearRingview so existingPolygoncallers and all ofjts-corekeep working unchanged (Option A from SPEC_F_CP.md).MultiCurveandMultiSurfacegain the same subtype-preserving treatment, and theCurvedWKTReader/CurvedWKTWriterround-trip the ring structure (CIRCULARSTRING / COMPOUNDCURVE tags inside the CURVEPOLYGON body).copyInternal,reverseInternal,toLinear, andnormalizeall carry the curves through.CurvePolygon.javaCurvePolygonStructuralSpec.javaarch:(base) — SFA / ISO 19125-2 extension hooks + opt-injts-curved— #1194Adds the seams the rest of the epic hangs off:
WKTReader.readOtherGeometryTextandWKTWriter.appendOtherGeometryTaggedTextextension points (plus a handful of helpers promoted toprotected), the canonical type-keyword constants, and the opt-injts-curvedmodule with the extended geometry types and theirCurvedWKTReader/CurvedWKTWriter. No behaviour change for existing core callers.WKTReader.java·CurvedWKTReader.javaWKTReaderExtensionHookTest.java7. Risks / open questions
CurvePolygon, a third-party algorithm that expects aLinearRingshell may break. Option A (landed in feat: F-CP structural CurvePolygon (Option A) + enable F-MC/F-MS #1198) keeps the inheritedgetExteriorRing()returning a densifiedLinearRingview, so old code keeps limping; curve-aware code usesgetExteriorCurve().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()returns on the result before AT-NS lands.toLinearinterpolates Z linearly along the sweep as of the F-CP densification work.)8. Cross-module impact
Most TAGs ship purely inside
jts-curved(extension module, opt-in). A handful require touchingjts-coreand need a maintainer review up-front: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).9. Phases & suggested order
Phase 1 is the hard prerequisite for everything (you can't return a
CompoundCurveboundary of aCurvePolygonwhose ring isn't aCompoundCurveto begin with). After Phase 1, Phases 2 / 3 / 4 / 5 / 7 can run in parallel; Phase 6 waits for Phase 5.10. Definition of Done (epic-level)
The epic closes when all of:
Geometryoperation in the user guide without producing flat output where curve-preserving output is mathematically possible.equalsExactchange (§7) and any other user-visible behaviour shifts.TB-FN (function-tree badges) is a stretch goal, not a DoD criterion.
11. Conventions
12. References
MultiCurve/MultiSurface, andTriangle.CircularString/CompoundCurve/CurvePolygoncurved types.jts-curvedsource:modules/curved/.modules/curved/SPEC_F_CP.md.feature/sfa-curve-buffer-spikeongrootstebozewolf/jts.