Repository navigation
Story: Encode Polygon geometries and validate ring orientation #13
Description
Objective
Encode Polygon rings and reject incompatible orientation without modifying the geometry.
Acceptance criteria
- Encode {"type":"Polygon","coordinates":[[[0,0],[2,0],[0,2],[0,0]]]} unchanged.
- Encode {"type":"Polygon","coordinates":[[[0,0,3],[2,0,3],[0,2,3],[0,0,3]]]} unchanged.
- Preserve a correctly oriented exterior and hole: {"type":"Polygon","coordinates":[[[0,0],[4,0],[4,4],[0,4],[0,0]],[[1,1],[1,2],[2,2],[2,1],[1,1]]]}.
- Reject a clockwise exterior ring such as [[0,0],[0,2],[2,0],[0,0]] with UnsupportedGeometryStructureException.
- Reject a counterclockwise interior ring with UnsupportedGeometryStructureException.
- EMPTY XY and XYZ Polygons produce {"type":"Polygon","coordinates":[]}.
- Do not reverse rings, reorder vertices, close or repair rings, discard members or cut at the antimeridian.
- Verify ring requirements against the actual model invariants. Escalate a genuinely unresolved normative case instead of inventing a repair or acceptance policy.
The shared acceptance criteria below also apply.
Implementation and integration
Develop and count the shared ring checks here once. Their private location and algorithms remain implementation decisions. MultiPolygon may reuse them after integration. No functional dependency on MultiPoint. Full verification requires the common infrastructure and operational ring checks; development may begin immediately.
Shared acceptance criteria
- Cover Geometry and Geography families using real objects supported by the applicable spatial-types version.
- Support XY and XYZ; preserve coordinate values, position order, member order and Z. Do not reproject, convert units, reorient, split, flatten, omit members or implicitly convert geometry types.
- Reject XYM and XYZM, including EMPTY, with UnsupportedDimensionException.
- SRID and declared-reference metadata do not by themselves cause rejection or coordinate changes. Do not serialize them as a GeoJSON coordinate-system extension. The caller supplies coordinates with RFC 7946 semantics and units.
- Reject LongitudeOne geometry types absent from RFC 7946 with public UnsupportedSpatialTypeException.
- Reject the approved incompatible structures with UnsupportedGeometryStructureException. Do not return partial output.
- Expose JSON encoding failures through JsonEncodingException, preserving the original exception as previous.
- Keep UnsupportedSpatialInterfaceException unchanged; do not implicitly promote internal exceptions.
- Verify actual model invariants before selecting invalid-value tests. Do not invent a separate NaN/Infinity policy or bypass model invariants to obtain fixtures.
- Use explicit constructors, literal coordinates and literal expected results, without fixture factories, loops, generators or dynamic class names. Verify JSON structure, numeric values and array order; no canonical whitespace or object-member ordering contract is introduced.
- Update English documentation with usage, supported dimensions, EMPTY handling, failures and caller responsibilities.
- Run applicable automated tests and composer quality, preserving existing strategy behavior.
Approved public exception contracts
All classes below are in LongitudeOne\SpatialWriter\Exception and implement ExceptionInterface:
- UnsupportedDimensionException directly extends \InvalidArgumentException.
- UnsupportedSpatialTypeException retains its existing \Exception inheritance and signatures; its public promotion is explicit.
- UnsupportedGeometryStructureException extends \InvalidArgumentException.
- JsonEncodingException extends \RuntimeException and retains the original exception as previous.
Standards and approved decisions
RFC 7946 defines the output: geometry objects only, with no Feature or FeatureCollection support and no proprietary types.
The caller prepares compatible geometries. Non-RFC types are rejected rather than converted. EMPTY geometries are encoded using an empty coordinates array, or an empty geometries array for GeometryCollection. Measured geometries remain rejected even when EMPTY.
MultiPoint, MultiLineString and MultiPolygon containing any EMPTY member are rejected; aggregates with no members remain accepted.
Polygon exterior rings must be counterclockwise and interior rings clockwise; incompatible orientation is rejected without repair.
Antimeridian crossings are encoded unchanged, without detection for rejection or cutting. Document the deliberate non-application of the SHOULD in RFC 7946 section 3.1.9 and its interoperability implications.
GeometryCollection nesting, singleton forms and homogeneous forms are preserved despite the SHOULD guidance in section 3.1.8; document the interoperability implications.
The future spatial-transformer work is not an implementation dependency.
Delivery and review
The architecture, normative behavior, public API and allocation of shared work above were approved by Alexandre.
All seven geometry contributions may start in parallel against these contracts. Integration prerequisites are not a required order of development.
Private helpers and algorithms remain implementation decisions. Escalate any genuinely uncovered architecture, API, compatibility or normative decision discovered during implementation.
Independent review, resolution of blocking findings and merge remain required; implementation completion alone does not make the work Done.
Activity
Alexandre-T commentedon Sep 21, 2026
Developer escalation — Normative decision required
Implementation work has started on
feat/13-geojson-polygon, but a normative decision is required before proceeding.The current geometry model accepts the following ring structures:
[[0,0],[1,0],[2,0],[0,0]];[[0,0],[2,2],[0,2],[2,0],[0,0]].The constructor currently validates that a ring contains at least four positions, is closed, and contains no consecutive duplicate positions. It does not validate ring simplicity or non-zero area.
RFC 7946 §3.1.6 requires Polygon rings to follow the right-hand rule, but the Story does not explicitly define how these degenerate or self-intersecting cases should be handled. AC8 requires such cases to be escalated rather than introducing an implicit validation policy.
Proposed decision: determine ring orientation from the signed area of the XY coordinates. Require a strictly positive signed area for the exterior ring and a strictly negative signed area for interior rings. Reject rings with a zero signed area using
UnsupportedGeometryStructureException. Do not introduce general topological validation in the encoder; simplicity and other topology checks remain the caller's responsibility.Should this policy be adopted for the GeoJSON Polygon encoder?
Alexandre-T commentedon Sep 21, 2026
Approved decision — GeoJSON Polygon ring orientation
The GeoJSON encoder MUST generate valid GeoJSON conforming to RFC 7946.
RFC 7946 §3.1.6 requires Polygon linear rings to follow the right-hand rule: exterior rings MUST be counterclockwise and interior rings MUST be clockwise. Although RFC 7946 recommends that parsers do not reject Polygons that violate this rule for backward compatibility, this tolerance applies to consumers of existing GeoJSON and does not relax the requirements for an encoder producing RFC 7946 output.
Therefore, the encoder MUST verify the orientation of every Polygon ring before encoding it.
Ring orientation SHALL be determined from the signed area computed from the XY coordinates:
A ring whose signed area is zero has no determinable clockwise or counterclockwise orientation and therefore cannot satisfy the RFC 7946 right-hand-rule requirement. Such a ring MUST be rejected with
UnsupportedGeometryStructureException.This rejection is a LongitudeOne encoding policy derived from the requirement to produce RFC 7946-compliant GeoJSON; RFC 7946 does not itself explicitly prescribe a signed-area calculation or explicitly require zero-area rings to be rejected.
The signed-area check MUST NOT be treated as general topological validation. In particular, the encoder is not responsible for determining ring simplicity, detecting self-intersections, or performing other general Polygon validity checks. Such validation remains outside the responsibility of the GeoJSON encoder.
Consequently, a self-intersecting ring MAY be rejected when its signed area is zero as a consequence of the orientation check, but the encoder MUST NOT implement a separate self-intersection or simplicity check as part of this Story.
This decision resolves the structural-validation escalation defined by AC8 while preserving the responsibility boundary of
spatial-writer: the encoder guarantees that the GeoJSON it emits satisfies its RFC 7946 encoding requirements; it is not a general-purpose geometry topology validator.Alexandre-T commentedon Sep 21, 2026
PR created: #22
[-]Encode Polygon geometries and validate ring orientation[/-][+]Story: Encode Polygon geometries and validate ring orientation[/+]