locationtech / locationtech/jts
EPIC: SQL/MM Spatial (ISO/IEC 13249-3) Curve Awareness in JTS
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 2.2k
- Forks
- 475
- Avg merge
- 14d 10h
- Merged PRs (30d)
- 1
Description
Epic: SQL/MM (ISO/IEC 13249-3) Curve Awareness in JTS
AI Disclosure (per the Eclipse Foundation Generative AI Usage Guidelines for Committers)
This document and the companion red-test class
modules/curve/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 (cursor grok-4.6) Assisted-by: Claude (Opus-4.7)

JTS logo as curves plus a buffer halo. Not a closed-form buffer laser. Not a guide figure.
Status: Draft v6 (2026-08-16).
Source: #7 feature/sfa-curve-rgr @ b2861a08 — SQL-MM types, WKT, WKB 8–12, OverlayNGCurve, TestBuilder logo-as-curves. #8, #10, #11, #35, #42 (has #41), #43, #44, #45, #46, #14, #22, #31, #39 are on #7. Bar 2 / #38 / curve-rgr-bar2-rc0 / #27 stay off #7. Do not merge #7 to locationtech/jts master from a stacked-PR button.
Origin (historical): feature/sfa-curve-buffer-spike — Draft v3 of this epic and the 49-method spec class.
Audience: locationtech/jts maintainers and contributors.
Field contract (PERF-GATE): Maintainable → Reliable → Faster. Take the curve path only if t_laser ≤ 1.15 × t_chainsaw. The 15% slack is not to be loosened. The overlay is named OverlayNGCurve, not Curved.
Vocabulary. A TAG is a short, stable identifier for one unit of work, each with a matching red test in CurveAwarenessSpecTest. A kit is a closed-form implementation covering one specific pairing of geometries. A named miss is a pairing with no kit yet, given an identifier so it is tracked rather than silently linearised. A laser is the closed-form path; the chordsaw (written chainsaw in the PERF-GATE formula above — the two should be reconciled to one word) is the densify-then-operate fallback. Bar 2 is the follow-on delivery bar, the circular noder and arrangement work (OV-P2), tracked outside this epic.
1. Goal
Make JTS preserve the ISO/IEC 13249-3 (SQL/MM Part 3) curve, surface and solid geometries — CIRCULARSTRING, COMPOUNDCURVE, CURVEPOLYGON, MULTICURVE, MULTISURFACE, POLYHEDRALSURFACE, TIN, TRIANGLE, CIRCLE, GEODESICSTRING, ELLIPTICALCURVE, NURBSCURVE, CLOTHOID, SPIRALCURVE, COMPOUNDSURFACE and BREPSOLID — through every algorithm where the math is sound, instead of silently linearising to flat parents on the way in.
jts-curve is no longer only a parse-and-WKT/WKB stand-in: types exist, WKT round-trips without densify and WKB codes 8–12 do too, and OverlayNGCurve takes a closed form when one exists. Every circular pair that has a closed form has a kit. Everything else is a named miss and the chordsaw. We will not write a circular noder in this PR. jts-core still densifies on contact for everything that is not one of those kits. This epic tracks the lift, operation by operation — full TAG vs closed-form subset, not “good enough as linearised”.
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 SQL/MM producers.
- Interop. PostGIS, Oracle Spatial, NetTopologySuite emit and consume curve geometries; WKB 8–12 now reconstructs the curve and surface subclasses when the factory can. Algorithms that still linearise remain the interop gap.
- Discoverability. TestBuilder can show the JTS wordmark as curves plus a buffer halo (hero). Open-arc / clothoid / compound buffer is still chords. Do not read that halo as a closed-form buffer laser.
3. Scope decisions
In scope (this epic):
-
Algorithms operating on the full ISO/IEC 13249-3 extended type set — the curve, surface and solid types — in 2-D and in their Z / M / ZM forms.
-
WKT round-trip with member structure (landed on #7).
-
WKB type codes, as given by ISO/IEC 13249-3 §5.1.68, Table 15. The extended set is 8–12, 15–24 and 102, plus BrepSolid at 1025 (Z only — the standard defines no 2-D BrepSolid, and 1025 is 25 + the Z offset, so slot 25 is spoken for). Codes 13 and 14 are not used by 13249-3; OGC SFA reserves them for the abstract
CurveandSurfacetypes. Any range spanning 13 or 14 — including the "8–21" this document used to claim — is wrong.Code Type Code Type 8 CircularString 18 Circle 9 CompoundCurve 19 GeodesicString 10 CurvePolygon 20 EllipticalCurve 11 MultiCurve 21 NURBSCurve 12 MultiSurface 22 Clothoid 15 PolyhedralSurface 23 SpiralCurve 16 TIN 24 CompoundSurface 17 Triangle 102 AffinePlacement Dimension offsets are
+1000(Z),+2000(M),+3000(ZM). Separately, and this is a different scheme rather than the dimension offset, Table 15 also allows1000001–1000005as alternate codes for CircularString through MultiSurface. The two do not compose: CircularString Z is1008, not1000001 + 1000. An earlier draft of this document asserted that the 1000-series was only the dimension offset; both schemes are real. -
Landed so far: 8–12 (on #8, now on #7). Core
WKBReadertakes those five the way GEOS does, delegating construction toGeometryFactory;CurveWKBWriteremits them through a hook placed before the instanceof chain. CoreWKBWriteremits none of the extended codes unless that hook or subclass is used. Codes 15–24, 102 and 1025 are in scope and not started. -
TestBuilder rendering and drawing tools.
In scope, sequenced late — the hard parts, named rather than hidden:
POLYHEDRALSURFACE(15),TIN(16) andBREPSOLID(1025). Structural and drawing support landed in the spike for visualisation; the 3-D semantics have not started.ELLIPTICALCURVE(20). JTS has no ellipse model and adding one is the single largest piece of work here. It stays in scope because the epic tracks the whole standard, but nothing earlier should wait on it.
Out of scope:
- Z / M ordinate interpolation across densified arcs. Today they propagate from the control points unchanged; every TAG that produces densified output should leave room for a future interpolation policy.
3.1 Proprietary and encumbered curve forms
SPIRALTYPE is an open value set. 4.2.12 names clothoid, bloss, biquadratic, sine and cosine as the initial set, and §5.1.68 encodes the value as <byte> <letters> — length-prefixed precisely so the set can grow. Some forms that would populate it are encumbered.
The Wiener Bogen is the worked example. It is a transition curve that shapes the alignment around the vehicle's centre of gravity rather than the track centreline, which is what lets it cut lateral acceleration and maintenance cost below what a clothoid achieves. It was invented by Herbert Hasslinger, filed as Austrian application A 1116/2002 on 23 July 2002 (see also EP1523597B1), is held by Wiener Linien and ÖBB, and "Wiener Bogen" has been a registered trademark since 2002.
The scope rule, which costs nothing and requires no licence:
Carry the label, not the math.
- Representation is not implementation. A
SPIRALTYPEvalue is a string. Round-trippingSPIRALCURVE (…, SPIRALTYPE Wiener Bogen)through WKT and WKB stores and returns a name its producer chose. No JTS code computes the curve, so there is no implementation for a claim to read on. This is also why the WKT grammar accepts an arbitrarySPIRALTYPEname rather than a closed enumeration. - Unknown spiral types are opaque, never approximated.
toLinear(tolerance)on a spiral type JTS does not implement must fail with a clear message rather than quietly substituting a clothoid. Substituting is wrong geometry first of all, and it is also the point at which "JTS implements the Wiener Bogen" stops being obviously false. - Implementations arrive from outside, if at all. A
SpiralTypeStrategySPI would let a party that holds or has licensed the rights register the math in their own deployment. Nothing encumbered ships in the JTS distribution, so the EPL-2.0 patent grant is never asked to convey rights no contributor holds — the specific hazard for permissively licensed code, where a downstream user would otherwise receive an implementation with no accompanying grant. - The trademark outlives the patent. A patent filed in 2002 runs its 20-year term out around 2022, and anyone relying on that should confirm it for their own jurisdiction rather than take this document's word. A registered mark renews indefinitely. So treat "Wiener Bogen" as a name for a value a producer wrote, never as a product claim, a branded feature, or anything that implies endorsement by the mark's owners.
This is engineering policy, not legal advice, and the epic should not be read as though it were. It is the ordinary line between interface and implementation, applied to a case where getting it wrong is expensive.
4. What's already landed
4.1 On #7 since the spike (tip b2861a08, 16 Aug 2026)
| Bucket | Summary | TAG honesty |
|---|---|---|
feat: / fix: |
WKB 8–12. Core WKBReader first-class cases: CircularString = count+coords; CompoundCurve / CurvePolygon / MultiCurve / MultiSurface = count + child WKBs (CurvePolygon rings carry their own type). GeometryFactory.createCircularString / createCompoundCurve(LineString[]) / createCurvePolygon(LineString, LineString[]) / createMultiCurve / createMultiSurface — default throws; CurveGeometryFactory implements. new WKBReader(new CurveGeometryFactory()).read(type8) returns CircularString. CurveWKBReader is the no-arg convenience. Locked XDR hex for CIRCULARSTRING (0 0, 5 5, 10 0) unchanged. Disc CurvePolygon area still 25π after round-trip. No toLinear on that path. |
WKB sibling from Draft v3 — landed. Not a new public I/O class beyond the factory surface + existing curve writer/reader convenience. |
feat: |
OverlayNGCurve (OV-P1). Algebra (self/empty) → retention (covers/disjoint) → kits → chordsaw. R1.5 two-disc (crossing lens/blob/crescents and 0-node strict nested annulus, area 16π, covers EEEE / coveredBy EE0E). R1.6 disc vs hole-free plain polygon. R1.7 CompoundCurve shells: two-node clip, two-shell, collinear diameters, 0/1-node, even-n, tangent-odd NSpan, same-outer hole, different-outer punch. R-LL lineal arc vs line. R-AA two CircularStrings at exact nodes. Two-disc matrix is EEEE / EE0E. |
OV-P1 flipped. On #7 the leftover named misses are still H-SHELL-N-MIXED, H-SHELL-HOLE-CROSS, H-SHELL-HOLE-X, H-ANNULUS-TANGENT. Stacked drafts (do not merge into #7) sew HOLE-CROSS / HOLE-X / HOLE-OUTER and take a first OverlayNG cell for H-SHELL-N-MIXED. H-ANNULUS-TANGENT stays a named miss. Circular noder + arrangement is OV-P2 / Bar 2, a different epic. Do not mark the full OV TAG green. |
feat: |
R-CONT certified disc PIP (interior + boundary band) in CurveExact. |
R-CONT partial. |
feat: |
R-PR DE-9IM for disc vs Point, LineString, hole-free Polygon, and two discs. Point: interior 0F2FF1FF2, boundary FF20F1FF2, exterior FF2FF10F2. Line: crossing 1F20F1102, tangent FF20F1102, miss FF2FF1102, endpoint-interior 1020F1102. Polygon: disjoint FF2FF1212, nested 212FF1FF2, disc-in-square 2FF1FF212, crossing 212101212. Two discs: crossing 212101212, disjoint FF2FF1212, nested 212FF1FF2 / 2FF1FF212, ext tangent FF2F01212, int tangent 212F01FF2, equal 2FFF1FFF2. Finish slice: single-member MultiSurface both orders; equalsTopo on equal/crossing/rotated-control discs; full SFS table; crosses of two areas always false. Half-disc / CompoundCurve miss still null → linearise. |
R-PR partial. Not “any combination of curved/flat”. Do not delete test_R_PR_* / test_R_CONT_*. |
feat: |
Distance closed-form helpers in CurveExact (arc-to-segment, overlapping discs nearestPoints 0, decideTolerance arc-aware). |
Helpers only. D-PT / D-AA / D-OP still describe the public DistanceOp TAG — keep those spec methods red. |
feat: |
Disc / single-arc / CompoundCurve convex hull closed forms. CompoundCurve hull is a CurvePolygon (area 61.59119, CircularString shell). Clothoid hull is still a linear POLYGON fallback. |
H-CV / H-CC partial. ConcaveHull stays out. |
feat: |
Public DiscreteHausdorffDistance closed forms for two pairs only (via 0ca71b): CIRCULARSTRING (0 0, 2 3, 10 0) vs LINESTRING (0 0, 10 0) APEX √949/6 − 7/6 ≈ 3.967640600249787; two r=5 discs, centres 0 and 10 → 10.0. Exact path skips densify. Public DHD still sees chords in general. Fréchet still open. |
D-HF partial (two pairs). Keep fail(). No new public pair. |
feat: |
Disc CurvePolygon area 25π. |
M-AREA-CP partial (circular discs). Keep the spec method. |
feat: |
LEC typed obstacle distance (point / segment / polygon / arc / disc). Disc closed form; 3-4-5 pins. | LEC partial. Keep the spec method if the public TAG is wider. |
arch: |
PERF-GATE: identity/chord-path rows use assertChordPath; slack stays 15% (1.15). |
Contract, not a TAG. |
These closed-form subsets of OV and R-PR did not wait for N-SS. Line–circle clip is used inside R1.6; there is still no public arc-arc / arc-line utility and no arc SegmentString. That is OV-P2, not a hole in OV-P1.
Still open (say so, do not close as “future work” a TAG that already has a named subset):
- Named overlay miss still stamped:
H-ANNULUS-TANGENT - On #7 only (sewn on stacked drafts, not merged):
H-SHELL-HOLE-CROSS,H-SHELL-HOLE-X,H-SHELL-HOLE-OUTER;H-SHELL-N-MIXEDfirst OverlayNG cell - General circular noding (N-AA, N-AL, N-SS) — OV-P2 / Bar 2. Do not start in this PR.
- Open-arc buffer. Clothoid hull is a linear fallback
- OFF = concentric arc (refactor)
- RocqRefRunner SQL/MM suite for public Curve predicates
- TestBuilder inspector
ClassCastException(visual QA #3) equalsExactarc-vs-chord (R-EQ)- Most of Phase 2 / 4 / 7 TAGs
4.2 Historical — feature/sfa-curve-buffer-spike (Draft v3 origin)
| Bucket | Summary |
|---|---|
arch: |
Structural CompoundCurve — segment-aware copy / toLinear |
arch: |
Member-structured WKT round-trip for CompoundCurve |
arch: |
CurveShapeWriter walks CompoundCurve members, arc-renders |
feat: |
LineHandlingFunctions.mergeCurves (arc-aware sibling of mergeLines) |
fix: |
Anchor member control points in CompoundCurve.toLinear (no junction drift) |
fix: |
Block-comment paste regression in WKT panel |
spec: |
This epic + 49-method CurveAwarenessSpecTest red-test suite |
| (orig) | bufferCurveWithParams linearisation hook (Phase-5 spike entry) |
#7 carried the SFA/SQL-MM types, WKT, and structural-type work forward from that spike. #8 stacked the PERF-GATE and the first lasers, then merged into #7. #10 (nested annulus) and #11 (odd-n tangent NSpan) merged after that. #12 / #13 stamp H-ANNULUS-TANGENT and related named misses. OV-P2 stacked drafts #16–#30 (nodes, faces, hole cells, OverlayNG-for-circles, leftover kits, Curve HotPixel) stay off #7.
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.
Full-TAG red list: CurveAwarenessSpecTest
- Path:
modules/curve/src/test/java/org/locationtech/jts/spec/curveawareness/CurveAwarenessSpecTest.java - Run:
mvn -pl modules/curve test -Dtest=CurveAwarenessSpecTest - Still has all 49
fail()methods. That “delete the method when a TAG ships” meter froze when work moved to stacked PRs and closed-form lasers. - The class is the full-TAG red list, excluded from default Surefire. It is not the live scoreboard. Do not delete a method because a closed-form subset landed (OV Phase 0, R-PR disc cells, disc area, TestBuilder Hausdorff, …). Delete only when the full TAG ships.
- Local silence while working:
mvn -Dtest='!CurveAwarenessSpecTest' test.
Live progress meter
Remaining full TAGs (still red in the spec class) plus the green tests next to production code on #7. A maintainer reading this epic should look at OverlayNGCurve / CurveExact / WKB tests on #7, not at “49 methods remaining”.
GitHub layout
- This document is the suggested epic body — adapt as the maintainers see fit.
- Optional milestone-shaped issues per phase with checklists referencing TAGs — at most 8 of them, never 49.
- Day-to-day work via PRs that:
- Reference this epic.
- Use the TAG in commit subject + PR title.
- Delete the corresponding red-test method only when the full TAG lands (its own commit, see conventions below). Partial lasers keep the spec method.
Commit / PR convention
bucket: TAG short description
Buckets: fix:, feat:, arch:, test:, spec:, refactor:. (User-facing docs go under spec:.)
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 full TAG by removing its red-test method.
6. Cross-module impact
Most TAGs ship purely inside jts-curve (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 |
|---|---|---|
| WKB 8–12 | Yes (landed, now on #7) | WKBReader switch, WKBConstants 8–12, GeometryFactory create* stubs (default throw). CurveGeometryFactory / CurveWKBWriter / CurveWKBReader stay in jts-curve. |
| 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 remaining core surface in the epic. |
| OV | Indirect | OV-P1 (kits + named misses) shipped in jts-curve without a public noder. General overlay (OV-P2) still depends on N-SS; the pipeline itself stays in core. |
| R-PR, R-CONT | Indirect | RelateOp lives in core. Disc cells shipped via CurveExact in jts-curve. General “any curved/flat pair” still wants N-SS. |
| 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-curve. |
Everything else is jts-curve-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
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. Still open.- 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? IfLineString, we've changed the type silently. If still flagged asCircularString, 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. R1.6 uses line–circle clip internally; that is not the public utility. - 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.
- ESRI
Bezier3Curve. Not an ISO type: 13249-3 allocates it no code, and the first genuinely free slot is 26, since 25 belongs to BrepSolid (1025 is 25 + the Z offset). Two options, undecided. Either carry it as a first-class extension type, which means proposing a code allocation somewhere it can actually be registered; or keep it out of the persisted model entirely and use cubic Bézier only as an internal, performance-oriented approximation at segment seams, never written to WKB. The second costs nothing in interop and is the default until someone needs the first.
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.- Every ISO/IEC 13249-3 extended type listed in §3 round-trips through every public
Geometryoperation in the user guide without producing flat output where curve-preserving output is mathematically possible. - WKB type codes. All of §3's codes read through core
WKBReaderwith a curve-capable factory, andCurveWKBWriteremits them. Codes 8–12 are done on #7; 15–24, 102 and 1025 remain. (Z / M interpolation stays out of scope, as in §3.) - A release note covers the
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.
Closed-form subsets of Phase 6 (and pieces of Phases 2–4) already landed on #7 without Phase 5. That does not turn those TAGs green. Annotate partial and name the subset. OV-P1 is flipped (kits + named misses). General overlay / polygonizer / coverage / equalsExact still wait on Phase 5 (OV-P2).
Phase 1 — Foundations (jts-curve structural completeness)
Mirror the CompoundCurve work onto the remaining composite types.
- F-CP Structural
CurvePolygon—CompoundCurveshell + holes;copyInternal,toLinear, WKT reader/writer preserve structure. - F-MC Structural
MultiCurve— preserves member subtypes (LineString/CircularString/CompoundCurve). - F-MS Structural
MultiSurface— preservesPolygonvsCurvePolygonmember subtypes. - F-RD
CurveShapeWriterforCurvePolygonrings,MultiCurveandMultiSurfacemembers.
Hard prereq for: all of Phase 2 (you can't return a CompoundCurve boundary of a CurvePolygon whose ring isn't a CompoundCurve to begin with).
Phase 2 — Properties (Metrics, Boundary, Validity)
- M-LEN-CS / M-LEN-CC — analytical arc length.
- M-AREA-CP — Green's-theorem area with circular-segment correction. Partial (circular discs): disc
CurvePolygonarea is 25π. Keep the spec method. - M-DIM — empty-curve dimension/coordinate-dimension guards.
- B-CP / B-MS / B-CC — curved boundaries.
- V-CP —
CurvePolygonvalidity (arc self-intersection, sector orientation, holes-in-shell). - V-CS —
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)
- D-PT / D-AA — analytical point-arc and arc-arc distance. Closed-form helpers exist in
CurveExact; the publicDistanceOpTAG is still red. - D-OP —
DistanceOpaccepts curved inputs without forced densification. Still the public TAG; keep the spec method. - D-HF — public
DiscreteHausdorffDistancetwo-pair lock via0ca71b(APEX; two discs). Exact path skips densify. Public DHD still chords in general. Fréchet still open. Keepfail(). Not “parameterise by arc length”. - C-LIN / C-AREA / C-IP — centroids and interior point.
Depends on: Phase 1 (F-CP for CurvePolygon cases).
Phase 4 — Construction (Buffer, Hulls, Simplification, Affine, Linear-Ref, Densifier)
- BUF-1 / BUF-N / BUF-NEG — analytical buffer. Open-arc buffer still open.
- OFF / VBF — offset curve and variable buffer. OFF = concentric arc (refactor) still open.
- H-CV / H-CC — convex hull. H-CV / H-CC partial: disc, single-arc, and CompoundCurve hulls landed. Clothoid hull is a linear fallback. ConcaveHull stays out.
- S-DP / S-VW / S-TP — simplification preserves arc identity.
- AT-S / AT-NS — affine transforms (similarity preserves; non-similarity densifies — see §7 risk).
- LRF-LEN / LRF-LOC — linear referencing parameterised by arc length.
- DSF — densifier delegates to
toLinear.
Depends on: Phase 1 (output may be CurvePolygon).
Phase 5 — Noding foundation
- N-AA — public arc-arc intersection utility. Still red.
- N-AL — public arc-line intersection utility. Still red. Line–circle clip is used inside R1.6; that is not this TAG.
- N-SS —
CurveSegmentStringinsidejts-curveonly; no publicSegmentStringor noder. #27 stays a dead-end. Still off #7.
Depends on: Phase 1. Touches jts-core — see §6. Phase 6 general overlay still depends on N-SS.
Phase 6 — Overlay, Predicates, Polygonizer, Coverage
- OV — arc-preserving overlay output (
union/intersection/difference/symDifference). OV-P1 flipped: OverlayNGCurve kits (R0 / R1 / R1.5 discs + nested annulus / R1.6 disc-vs-polygon / R1.7 CompoundCurve shells including even-n and tangent-odd NSpan / R-LL / R-AA). On #7 the leftover named misses are stillH-SHELL-N-MIXED,H-SHELL-HOLE-CROSS,H-SHELL-HOLE-X,H-ANNULUS-TANGENT; stacked drafts sew the hole cells and take a first OverlayNG cell for H-SHELL-N-MIXED.H-ANNULUS-TANGENTstays a named miss. Not a general circular noder. Do not mark the full OV TAG green. OV-P2 is Bar 2. - R-PR / R-CONT / R-EQ — arc-aware relate, predicates, exact equality. R-PR / R-CONT partial: certified disc PIP; DE-9IM for disc vs Point / LineString / hole-free Polygon / second disc; MultiSurface unwrap; SFS table; two areas never
crosses. Half-disc / CompoundCurve still null → linearise. R-EQ (arc-vs-chordequalsExact) still open. - PLG —
PolygonizeracceptsCompoundCurveedges and emitsCurvePolygonfaces. - COV —
CoverageUnionpreserves shared arc edges.
Depends on: Phase 5 for general OV / PLG / COV / R-EQ. Phase 6 subsets already landed without N-SS — see §4.1 and §10.
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.
- Snapping
- PRC-SN — snap-to-grid preserves arc when the snapped
(R, centre, sweep)still lies on grid; otherwise densify-and-snap chords.
- PRC-SN — snap-to-grid preserves arc when the snapped
- Triangulation / Voronoi
- TRI-DT / TRI-VR —
DelaunayTriangulationBuilder/VoronoiDiagramBuilderaccept curved boundary input (densify internally viatoLinear(tolerance)).
- TRI-DT / TRI-VR —
- TestBuilder
- TB-T —
CompoundCurveTool,CurvePolygonTooldrawing UX. - TB-FN — function-tree curve-awareness badges (●/◯/✕). Stretch goal — see §8.
- Visual QA still open: TestBuilder inspector
ClassCastException.
- TB-T —
Most of Phase 2 / 4 / 7 TAGs are still the full red list. Also still open (not a phase of their own): RocqRefRunner SQL/MM suite for public Curve predicates. LEC (typed obstacle distance for point / segment / polygon / arc / disc) landed on #7 — keep the spec method if the public TAG is wider than that subset.
10. Suggested order
Phase 1 (Foundations)
│
├──> Phase 2 (Properties) M-AREA-CP partial: circular discs
├──> Phase 3 (Measurement) D-HF two-pair (Fréchet still open); LEC typed obstacles
├──> Phase 4 (Construction) H-CV / H-CC partial: disc / single-arc / CompoundCurve hulls
├──> Phase 5 (Noding) ──> Phase 6 *general* OV-P2 / PLG / COV / R-EQ
│
└──> Phase 6 *OV-P1* (already on #7, did not wait for N-SS)
OverlayNGCurve kits (R0 / R1 / R1.5 / R1.6 / R1.7 / R-LL / R-AA)
named R2 misses; R-PR / R-CONT closed-form disc cells
└──> Phase 7 (Independent tracks: snap / triangulation / TestBuilder)
After Phase 1 finishes, Phases 2 / 3 / 4 / 5 / 7 can run in parallel. OV-P2 (circular noder + arrangement) still waits for Phase 5. OV-P1 (kits + named misses) already shipped on #7 without a public noder. The phase graph must not say “overlay requires a noder” as if those kits were blocked.
11. 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 only when the full TAG lands. Partial lasers keep the method.
-
CI stays green by default. The spec class is excluded from the default Surefire run via:
<plugin> <artifactId>maven-surefire-plugin</artifactId> <configuration> <excludes> <exclude>**/spec/curveawareness/*.java</exclude> </excludes> </configuration> </plugin>Contributors run it explicitly with
mvn -pl modules/curve test -Dtest=CurveAwarenessSpecTest(which overrides the exclude). The remaining-method count is the full-TAG red list, not the live progress meter (see §5). It does not break CI on every push.
12. References
- ISO/IEC 13249-3 (SQL/MM Part 3: Spatial) — §5.1.67
<well-known text representation>, §5.1.68<well-known binary representation>and its Table 15 of<uint32>type codes. - Wiener Bogen — Wikipedia (de) and EP1523597B1, for the encumbered-form example in §3.1.
jts-curvesource:modules/curve/.- #7
feature/sfa-curve-rgr@b2861a08— SFA/SQL-MM types, WKT, WKB 8–12, OverlayNGCurve OV-P1, logo-as-curves. - #8 (merged into #7) — PERF-GATE + first lasers. #10 annulus, #11 odd-n. Bar 2 / #38 / #27 stay off #7.
- OverlayNGCurve (package-private overlay helpers in
org.locationtech.jts.operation.overlayng.curve). CurveExact(package-private inorg.locationtech.jts.geom.curve).- GEOS
src/io/WKBReader.cpp/WKBWriter.cpp/include/geos/io/WKBConstants.h— type codes 8–12 and child-WKB layout. - Spike branch (origin):
feature/sfa-curve-buffer-spikeongrootstebozewolf/jts. CurveAwarenessSpecTest— full-TAG red list (49fail()methods still present; excluded from default Surefire). Live meter: #7 green tests + remaining full TAGs.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
This is an epic rather than a self-contained task; begin with modules/curve/src/test/java/org/locationtech/jts/spec/curveawareness/CurveAwarenessSpecTest.java and the listed source pull requests to identify an unclaimed TAG. Read the relevant entry point, such as WKBReader or OverlayNGCurve, and its tests; done is a narrowly scoped TAG with its red test passing and no silent linearisation beyond the stated fallback.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- backend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 20/100