Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/immutability.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ for point ownership, propagation and arc indexing. Mutable `CompoundCurve` class
reference. See [mutable compound curves](instantiable-spatial-types.md#mutable-compound-curves)
for component editing and recursive context propagation. Mutable `Polygon` and `Triangle` classes retain rings and points by reference;
see [mutable surfaces](instantiable-spatial-types.md#mutable-polygons-and-triangles)
for ring editing and failure atomicity. Other mutable aggregates
for ring editing and failure atomicity. Mutable `PolyhedralSurface` classes in XYZ and XYZM retain polygon patches, rings
and points by reference; see [mutable polyhedral surfaces](instantiable-spatial-types.md#mutable-polyhedral-surfaces)
for patch editing, shared vertices and propagation. Other mutable aggregates
arrive in later Stories of Epic #38.
Existing factories create the explicitly named immutable classes and expose
immutable return contracts.
Expand Down
85 changes: 85 additions & 0 deletions docs/instantiable-spatial-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -856,3 +856,88 @@ geometry. ISO/IEC CD 13249-3:201x(E), clauses 8.3.1 and 8.4.1, define the polygo
and triangle models; representing a value does not establish conformity with
those geometric definitions. Request functional checks through
[spatial-validator](validators.md) explicitly.


## Mutable polyhedral surfaces

`Types\Dimension3z\PolyhedralSurface` (XYZ) and
`Types\Dimension4zm\PolyhedralSurface` (XYZM) implement
`MutablePolyhedralSurfaceInterface`. XY and XYM surfaces are not introduced.

```php
new PolyhedralSurface(array $patches = [], int|SpatialReference $srid = 0,
SpatialModelEnum $family = SpatialModelEnum::GEOMETRY);
```

Each patch is a `MutablePolygonInterface` or an array of rings, using the
[mutable polygon input contract](#mutable-polygons-and-triangles).
Arrays create mutable polygons, rings and points in the surface's layout,
family and reference. Supplied components retain their own context and identity;
immutable patches, rings and points are rejected without conversion, including
immutable descendants exposed by custom mutable components.

| Operation | Effect |
| --- | --- |
| `addPatch($patch)` | Append a mutable polygon |
| `insertPatch($index, $patch)` | Insert before a patch; the patch count appends |
| `removePatch($index)` | Remove an existing patch and reindex |
| `setPatch($index, $patch)` | Replace one existing patch |
| `addRing($patchIndex, $ring)` | Append a ring in an existing patch |
| `insertRing($patchIndex, $ringIndex, $ring)` | Insert a ring; the addressed patch's ring count appends |
| `removeRing($patchIndex, $ringIndex)` | Remove an existing ring from a patch |
| `setRing($patchIndex, $ringIndex, $ring)` | Replace one existing ring |
| `setPoint($patchIndex, $ringIndex, $pointIndex, $point)` | Replace one point in an existing ring |
| `setArrayOfCoordinates($coordinates)` | Replace the complete patch sequence from coordinates; `[]` empties it |
| `getPatch($index)`, `getPatches()`, `getElements()` | Expose the retained mutable patches |

Every successful mutation returns the receiver as `static`. Indices start at
zero at each nesting level; access, replacement and removal require existing
elements. Negative or out-of-range indices raise `OutOfBoundsException`.
Failed addressing and technical input checks leave the surface and shared
descendants unchanged, including a late invalid coordinate in full replacement.

```php
use LongitudeOne\Core\Enum\SpatialModelEnum;
use LongitudeOne\SpatialTypes\Types\Dimension3z\LineString;
use LongitudeOne\SpatialTypes\Types\Dimension3z\Point;
use LongitudeOne\SpatialTypes\Types\Dimension3z\Polygon;
use LongitudeOne\SpatialTypes\Types\Dimension3z\PolyhedralSurface;

$vertex = new Point(0, null, 5, 4326);
$equalVertex = new Point(0, null, 5, 4326);
$first = new Polygon([new LineString([$vertex, $equalVertex], 4326)], 4326);
$second = new Polygon([new LineString([$vertex], 4326)], 4326);
$surface = new PolyhedralSurface([$first, $second], 4326);
$otherOwner = new PolyhedralSurface([$second], 4326);

$vertex->setX(7);
assert([[[[7, null, 5], [0, null, 5]]], [[[7, null, 5]]]] === $surface->toArray());
assert([[[[7, null, 5]]]] === $otherOwner->toArray());
assert($first === $surface->getPatch(0));
assert($vertex === $surface->getPatch(1)->getRing(0)->getPoint(0));

$surface->setFamily(SpatialModelEnum::GEOGRAPHY, false); // Only the surface.
$surface->setSrid(2154); // Surface, patches, rings and points.
assert(2154 === $otherOwner->getPatch(0)->getSrid());
assert(4326 === $otherOwner->getSrid());
```

Construction, insertion and replacement never clone supplied components.
Editing one shared point changes every occurrence of that instance; distinct
points with equal coordinates remain independent. Replacing a point at one
address only changes that ring entry. No vertex detection, merging,
synchronization or neighbouring-patch repair occurs. Complete coordinate
replacement creates new descendants and leaves previous components and their
other owners unchanged.

`setSrid()`, `setSpatialReference()` and `setFamily()` each accept
`bool $propagate = true`. By default they update the receiver and all patches,
rings and points in place; `false` updates only the receiver. Ordinates never
change. Spatial type and layout stay fixed, and mutable surfaces expose neither
`with…` methods nor a dimension setter. Immutable counterparts retain their
separate copy contracts and reject mutable components.

EMPTY or incomplete patches, open rings and null ordinates remain representable.
Mutation does not enforce face validity, adjacency, closure or manifold rules.
ISO/IEC CD 13249-3:201x(E), clause 8.5.1, defines polyhedral-surface validity;
this representation API leaves its evaluation to explicit spatial-validator use.
15 changes: 15 additions & 0 deletions docs/type-class-hierarchy.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,18 @@ and provides ring editing and ring-then-point replacement. `MutableTriangleInter
adds the distinct triangle type contract. Both concrete variants retain mutable
rings and points by reference and expose no immutable copy operations. Their
layout and spatial type are fixed; geometric validity remains explicit.


Mutable polyhedral surfaces use a separate patch-owning base:

```text
AbstractSpatialType
└── AbstractPolyhedralSurface
└── Dimension{3z,4zm}\PolyhedralSurface
```

`MutablePolyhedralSurfaceInterface` refines patch accessors to
`MutablePolygonInterface` and exposes patch editing plus patch-then-ring-then-point
addressing. The base retains patches and their mutable descendants by reference.
Coordinate arrays create mutable polygons in the fixed XYZ or XYZM layout;
immutable surface implementations and their copy contracts remain separate.
3 changes: 3 additions & 0 deletions lib/Interfaces/MutablePolygonInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,7 @@ public function setSpatialReference(SpatialReference $reference, bool $propagate
* @param bool $propagate Whether to update rings and their points
*/
public function setSrid(int $srid, bool $propagate = true): static;

/** @return (null|float|int)[][][] */
public function toArray(): array;
}
94 changes: 93 additions & 1 deletion lib/Interfaces/MutablePolyhedralSurfaceInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,99 @@

namespace LongitudeOne\SpatialTypes\Interfaces;

/** Mutable polyhedralsurface contract; type-specific editing is introduced separately. */
use LongitudeOne\Core\Enum\SpatialModelEnum;
use LongitudeOne\SpatialTypes\Reference\SpatialReference;

/** In-place patch, ring and point editing without geometric validation.
*
* @phpstan-type CoordinateTuple array{0: null|float|int|string, 1: null|float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}
* @phpstan-type PatchCoordinates array<array<CoordinateTuple>>
* @phpstan-type PatchInput MutablePolygonInterface|array<MutableLineStringInterface|array<MutablePointInterface|CoordinateTuple>>
*/
interface MutablePolyhedralSurfaceInterface extends MutableSpatialInterface, PolyhedralSurfaceInterface
{
/** @param MutablePolygonInterface $patch Patch retained without cloning */
public function addPatch(MutablePolygonInterface $patch): static;

/**
* @param int $patchIndex Existing zero-based patch index
* @param MutableLineStringInterface $ring Ring retained without cloning
*/
public function addRing(int $patchIndex, MutableLineStringInterface $ring): static;

/** @return list<MutablePolygonInterface> */
public function getElements(): array;

/** @param int $index Existing zero-based patch index */
public function getPatch(int $index): MutablePolygonInterface;

/** @return list<MutablePolygonInterface> */
public function getPatches(): array;

/**
* @param int $index Zero-based insertion index, up to the patch count
* @param MutablePolygonInterface $patch Patch retained without cloning
*/
public function insertPatch(int $index, MutablePolygonInterface $patch): static;

/**
* @param int $patchIndex Existing zero-based patch index
* @param int $ringIndex Zero-based insertion index, up to the ring count
* @param MutableLineStringInterface $ring Ring retained without cloning
*/
public function insertRing(int $patchIndex, int $ringIndex, MutableLineStringInterface $ring): static;

/** @param int $index Existing zero-based patch index */
public function removePatch(int $index): static;

/**
* @param int $patchIndex Existing zero-based patch index
* @param int $ringIndex Existing zero-based ring index
*/
public function removeRing(int $patchIndex, int $ringIndex): static;

/** @param array<PatchCoordinates> $coordinates Complete replacement patch coordinates */
public function setArrayOfCoordinates(array $coordinates): static;

/**
* @param SpatialModelEnum $family Declared family
* @param bool $propagate Whether to update patches, rings and points
*/
public function setFamily(SpatialModelEnum $family, bool $propagate = true): static;

/**
* @param int $index Existing zero-based patch index
* @param MutablePolygonInterface $patch Patch retained without cloning
*/
public function setPatch(int $index, MutablePolygonInterface $patch): static;

/**
* @param int $patchIndex Existing zero-based patch index
* @param int $ringIndex Existing zero-based ring index within the patch
* @param int $pointIndex Existing zero-based point index within the ring
* @param MutablePointInterface $point Point retained without cloning
*/
public function setPoint(int $patchIndex, int $ringIndex, int $pointIndex, MutablePointInterface $point): static;

/**
* @param int $patchIndex Existing zero-based patch index
* @param int $ringIndex Existing zero-based ring index within the patch
* @param MutableLineStringInterface $ring Ring retained without cloning
*/
public function setRing(int $patchIndex, int $ringIndex, MutableLineStringInterface $ring): static;

/**
* @param SpatialReference $reference Declared reference
* @param bool $propagate Whether to update patches, rings and points
*/
public function setSpatialReference(SpatialReference $reference, bool $propagate = true): static;

/**
* @param int $srid Declared reference identifier
* @param bool $propagate Whether to update patches, rings and points
*/
public function setSrid(int $srid, bool $propagate = true): static;

/** @return (null|float|int)[][][][] */
public function toArray(): array;
}
2 changes: 1 addition & 1 deletion lib/Interfaces/PolyhedralSurfaceInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ interface PolyhedralSurfaceInterface extends SpatialInterface
public function getElements(): array;

/**
* Return a patch using wrapped indexes, with negative indexes from the end.
* Return a patch by index; indexing conventions are defined by the variant.
*
* @param int $index Patch index
*/
Expand Down
Loading
Loading