Skip to content

EPIC: SQL/MM Spatial (ISO/IEC 13249-3) Curve Awareness in JTS #1195

@grootstebozewolf

Description

@grootstebozewolf

Epic: Curve and Extended-Geometry Awareness in JTS (OGC SFA + ISO 13249-3 SQL/MM)

AI Disclosure (per the Eclipse Foundation Generative AI Usage Guidelines for Committers)

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:

  1. 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.
  2. 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:
    feat: BUF-1 analytical single-arc CircularString buffer → CurvePolygon
    
    Buckets: feat:, fix:, arch:, test:, spec:, refactor:.
  3. 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.
  4. 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.
  5. 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).

Phase 1 — Foundations (jts-curved structural completeness)

TAG What PR Status
F-CP Structural CurvePolygon — curve shell + holes; copyInternal/reverseInternal/toLinear/WKT preserve structure #1198 🔵
F-MC Structural MultiCurve — preserves member subtypes #1198 🔵
F-MS Structural MultiSurface — preserves Polygon vs CurvePolygon members #1198 🔵
(base) SFA/ISO extension hooks + opt-in jts-curved module #1194 🔵
F-RD CurvedShapeWriter arc-renders CurvePolygon rings + MultiCurve/MultiSurface members

Phase 2 — Properties (Metrics, Boundary, Validity)

TAG What PR Status
M-LEN-CS Analytical CircularString length (r·θ) 🟢
M-LEN-CC CompoundCurve length sums member lengths 🟢
M-AREA-CP CurvePolygon area (Green's theorem + circular-segment correction) 🟢
M-DIM Empty-curve dimension / coordinate-dimension guards
B-CP CurvePolygon.getBoundary() returns a curve (CompoundCurve / MultiCurve) 🟢
B-MS MultiSurface.getBoundary() returns a MultiCurve 🟢
B-CC Open CompoundCurve boundary = its two endpoints; closed = empty
V-CP CurvePolygon validity (arc self-intersection, sector orientation, holes-in-shell)
V-CS CircularString / CompoundCurve simplicity

Phase 3 — Measurement (Distance, Centroid, Interior point)

TAG What PR Status
D-PT Analytical point-to-arc distance (clamped to sweep) 🟢
D-AA Analytical arc-to-arc distance
D-OP DistanceOp accepts curved inputs without forced densification
D-HF DiscreteHausdorffDistance / DiscreteFrechetDistance parameterised by arc length
C-LIN CircularString centroid (arc-length-weighted) 🟢
C-AREA CurvePolygon area centroid (Green's-theorem moments) 🟢
C-IP CurvePolygon interior point (arc-aware scan line) 🟢

Phase 4 — Construction (Buffer, Hulls, Simplification, Affine, Linear-Ref, Densifier)

TAG What PR Status
BUF-1 Analytical single-arc buffer → CurvePolygon
BUF-N Multi-arc / mixed CompoundCurve buffer preserves arcs
BUF-NEG Negative buffer with |d| > R returns EMPTY cleanly
OFF OffsetCurve preserves arc identity (R±d)
VBF VariableBuffer interpolates along arc length
H-CV Convex hull uses arc extreme points
H-CC Concave hull arc-aware
S-DP DouglasPeuckerSimplifier preserves arc identity
S-VW VWSimplifier arc-aware
S-TP TopologyPreservingSimplifier arc-aware
AT-S Similarity affine transform preserves arc
AT-NS Non-similarity affine densifies cleanly (see §7)
LRF-LEN LengthIndexedLine parameterised by arc length
LRF-LOC LocationIndexedLine member-aware on CompoundCurve
DSF Densifier delegates to toLinear for arc input

Phase 5 — Noding foundation (touches jts-core — see §8)

TAG What PR Status
N-AA Public arc-arc intersection utility
N-AL Public arc-line intersection utility
N-SS Arc-aware SegmentString / Noder integration

Phase 6 — Overlay, Predicates, Polygonizer, Coverage (depends on Phase 5)

TAG What PR Status
OV Arc-preserving overlay output (union / intersection / difference / symDifference)
R-PR Arc-aware relate matrix
R-CONT Arc-aware contains / intersects / covers / within / touches / crosses
R-EQ equalsExact distinguishes arc from chord polyline
PLG Polygonizer accepts CompoundCurve edges, emits CurvePolygon faces
COV CoverageUnion preserves shared arc edges

Phase 7 — Independent tracks (depend only on Phase 1)

TAG What PR Status
PRC-SN Snap-to-grid preserves arc when snapped (R, centre, sweep) stays on grid
TRI-DT DelaunayTriangulationBuilder accepts curved input (densify internally)
TRI-VR VoronoiDiagramBuilder accepts curved input
TB-T CompoundCurveTool / CurvePolygonTool drawing UX
TB-FN Function-tree curve-awareness badges (●/◯/✕)

6. Changelog (newest first)

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.

feat: F-CP / F-MC / F-MS — structural composites — #1198

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.

arch: (base) — SFA / ISO 19125-2 extension hooks + opt-in jts-curved#1194

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.

7. Risks / open questions

  • 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).

9. Phases & suggested order

Phase 1 (Foundations)
   │
   ├──> Phase 2 (Properties)
   ├──> Phase 3 (Measurement)
   ├──> Phase 4 (Construction)
   ├──> Phase 5 (Noding)  ──>  Phase 6 (Overlay / Predicates / Polygonizer)
   └──> Phase 7 (Independent tracks: snap / triangulation / 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:

  1. Every TAG in §5 is ✅ (merged) — or explicitly de-scoped in the table with a one-line reason.
  2. 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.
  3. The deferred WKB sibling epic has its own tracking issue.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions