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
15 changes: 14 additions & 1 deletion docs/immutability.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,20 @@ the contained points. Editing an endpoint does not automatically edit the
other end of a closed circular string.

Compound curves retain immutable ordered `CurveInterface` components with
numeric coordinate tuples. Their `withSpatialReference()` and legacy
numeric or null coordinate tuples. Their `withSpatialReference()` and legacy
`withSrid()` operations deeply copy components without checking continuity.
Construct a new compound from replacement curves to change its path; modifying
a returned `getCurves()` array does not affect the original.

### Null point ordinates

`Coordinates` constructors and `withX()`, `withY()`, `withZ()`, and `withM()`
accept null for axes supported by the declared dimension. The original value
is preserved. Replacing point coordinates with an all-null value creates an
EMPTY copy; partial nulls preserve a non-empty point and its dimension.

```php
$partial = $paris->withCoordinates(Coordinates::xy(0, null));
assert([0, null] === $partial->toArray());
assert(!$partial->isEmpty());
```
41 changes: 31 additions & 10 deletions docs/instantiable-spatial-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,12 +175,14 @@ new Point($x, $y, $z, $m, int|SpatialReference $srid = 0); // Dimension4
```

`$x` and `$y` accept `int`, `float`, or a coordinate string accepted by the
geo-parser. `$z` and `$m` accept `int|float`. For Geography, `$x` means
geo-parser. `$z` and `$m` accept `int|float|null`. For Geography, `$x` means
longitude and `$y` means latitude.

Each coordinate parameter is nullable. Omitting every ordinate (or passing
`null` for every ordinate) creates an empty point; incomplete tuples are
invalid. This preserves the selected coordinate layout and optional SRID:
`null` for every ordinate) creates an empty point. Any combination of supplied
and null ordinates is preserved. A partially supplied point is not EMPTY, and
zero is a supplied value. Null ordinates do not change the declared dimension.
This preserves the selected coordinate layout and optional SRID:

```php
$emptyPoint = new Point(srid: 4326);
Expand All @@ -189,26 +191,43 @@ assert([] === $emptyPoint->toArray());
```

`FromIndexedArrayFactory::createPoint([])` creates the same empty point in its
requested family, dimension, and spatial-reference context. Empty points cannot
be added to point-defined aggregates such as `LineString` or `MultiPoint`.
requested family, dimension, and spatial-reference context. A full-length tuple
containing only nulls also creates an EMPTY point. Indexed tuples must still
contain every index declared by the requested dimension; an explicit null is
different from an absent index.

```php
use LongitudeOne\SpatialTypes\Types\Dimension3z\Geography\Point;

$point = new Point(2.3522, 48.8566, 35, 4326); // longitude, latitude, elevation
```

```php
$partialPoint = new Point(0, null, null, 4326); // XYZ Geography
assert(!$partialPoint->isEmpty());
assert([0, null, null] === $partialPoint->toArray());
assert($partialPoint->hasZ()); // The Z axis exists even when its value is null.
```

Representing these states does not establish geometric conformity with
ISO/IEC 13249-3 or OGC requirements. Request coordinate-completeness validation
explicitly through `spatial-validator`; this library does not invoke it or
depend on it. See [Validation](validators.md).

### Immutable point coordinates

`LongitudeOne\SpatialTypes\Value\Coordinates` is a public immutable value
object for normalized numeric point coordinates. Its named constructors encode
object for numeric or null point coordinates. Its named constructors encode
the coordinate dimension: `Coordinates::xy()`, `Coordinates::xym()`,
`Coordinates::xyz()`, and `Coordinates::xyzm()`.

`PointInterface::getCoordinates()` returns this value object.
`PointInterface::withCoordinates(Coordinates $coordinates)` returns a new point
of the same concrete class and SRID. The supplied coordinates must have the
same dimension as the point; numeric geographic ranges are not validated.
same dimension as the point. Its accessors and `toArray()` retain null values
for axes in that dimension; reading or replacing an axis outside the dimension
still throws `InvalidDimensionException`. All-null replacement coordinates
produce an EMPTY point, whose `toArray()` is `[]` and `getCoordinates()` is null.

```php
use LongitudeOne\SpatialTypes\Value\Coordinates;
Expand Down Expand Up @@ -523,9 +542,11 @@ $empty = new CompoundCurve([], 4326);

`new CompoundCurve()` creates an empty XY Geometry value with SRID 0 when
using the class imported above. Components must implement `CurveInterface`
and expose numeric point tuples. Family, dimension, reference, component kind,
non-empty endpoints and continuity are not validated automatically. Components
are preserved as objects without flattening, reversal or linear approximation.
and expose point tuples with numeric or null ordinates. Null entries retain
their positions through array access and immutable reference copies. Family,
dimension, reference, component kind, non-empty endpoints and continuity are
not validated automatically. Components are preserved as objects without
flattening, reversal or linear approximation.

`getCurves()` and `getElements()` expose the ordered components as
`CurveInterface[]`. `getCurve($index)` uses wrapping indexes and negative
Expand Down
2 changes: 1 addition & 1 deletion docs/type-class-hierarchy.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,5 @@ extend `CurveInterface`, which exposes nullable start/end points and closure
in addition to `SpatialInterface`. Circular strings have their own point
storage; they are not line strings and do not inherit point-set
simplicity checks. Compound curves retain ordered `CurveInterface` components
and require curve interfaces and numeric point tuples; functional compatibility
and require curve interfaces and point tuples with numeric or null ordinates; functional compatibility
and endpoint continuity are not checked.
10 changes: 6 additions & 4 deletions docs/validators.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

`spatial-types` represents spatial values without automatic functional validation.
Constructors and immutable replacement methods retain technical typing checks:
member interfaces, numeric coordinate tuples, required ordinates, and the
coordinate dimension of a concrete point. EMPTY points still require all
ordinates to be null together. Indexed operations still reject empty containers.
member interfaces, numeric or null coordinate ordinates, required tuple indices,
and the coordinate dimension of a concrete point. Every combination of supplied
and null point ordinates is representable. Only an all-null point is EMPTY; a
partial null does not remove an axis or change the dimension. Indexed operations
still reject empty containers.

Geometry validity is an explicit consumer operation. Functional rules such as
ring closure, triangle cardinality, geographic numeric ranges, aggregate
point coordinate completeness, ring closure, triangle cardinality, geographic numeric ranges, aggregate
family/dimension/reference compatibility, curve continuity and surface topology
are not enforced by this package.

Expand Down
12 changes: 6 additions & 6 deletions lib/Factory/Coordinates.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@
final readonly class Coordinates
{
/**
* @param float|int|string $x X coordinate or longitude
* @param float|int|string $y Y coordinate or latitude
* @param null|float|int $z Z coordinate or elevation
* @param null|float|int $m M coordinate or measure
* @param null|float|int|string $x X coordinate or longitude
* @param null|float|int|string $y Y coordinate or latitude
* @param null|float|int $z Z coordinate or elevation
* @param null|float|int $m M coordinate or measure
*/
public function __construct(
public float|int|string $x,
public float|int|string $y,
public float|int|string|null $x,
public float|int|string|null $y,
public float|int|null $z = null,
public float|int|null $m = null
) {
Expand Down
24 changes: 12 additions & 12 deletions lib/Factory/FromIndexedArrayFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ class FromIndexedArrayFactory
/**
* Create a line string from an indexed array.
*
* @param (array{0: float|int|string, 1: float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}|PointInterface)[] $indexedArray indexed array
* @param int|SpatialReference $srid SRID
* @param SpatialModelEnum $family family
* @param CoordinateDimensionEnum $dimension dimension
* @param (array{0: null|float|int|string, 1: null|float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}|PointInterface)[] $indexedArray indexed array
* @param int|SpatialReference $srid SRID
* @param SpatialModelEnum $family family
* @param CoordinateDimensionEnum $dimension dimension
*
* @throws SpatialTypeExceptionInterface when the point or line string cannot be created
*/
Expand All @@ -49,10 +49,10 @@ public static function createLineString(array $indexedArray, int|SpatialReferenc
/**
* Create a point from an array of coordinates.
*
* @param array{0: float|int|string, 1: float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}|array{} $coordinates array of coordinates; an empty array creates an empty point
* @param int|SpatialReference $srid SRID
* @param SpatialModelEnum $family family
* @param CoordinateDimensionEnum $dimensionEnum dimension
* @param array{0: null|float|int|string, 1: null|float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}|array{} $coordinates array of coordinates; an empty array creates an empty point
* @param int|SpatialReference $srid SRID
* @param SpatialModelEnum $family family
* @param CoordinateDimensionEnum $dimensionEnum dimension
*
* @throws SpatialTypeExceptionInterface when something goes wrong during the creation of the point
*/
Expand All @@ -64,10 +64,10 @@ public static function createPoint(array $coordinates, int|SpatialReference $sri
/**
* Create a polygon from an indexed array of closed line strings.
*
* @param (array{0: float|int|string, 1: float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}[]|LineStringInterface)[] $indexedArray indexed array of rings
* @param int|SpatialReference $srid SRID
* @param SpatialModelEnum $family family
* @param CoordinateDimensionEnum $dimension dimension
* @param (array{0: null|float|int|string, 1: null|float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}[]|LineStringInterface)[] $indexedArray indexed array of rings
* @param int|SpatialReference $srid SRID
* @param SpatialModelEnum $family family
* @param CoordinateDimensionEnum $dimension dimension
*
* @throws SpatialTypeExceptionInterface when something goes wrong during the creation of a line string or the polygon
*/
Expand Down
24 changes: 12 additions & 12 deletions lib/Factory/Hydrator/CoordinatesHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ final class CoordinatesHydrator
/**
* Hydrate coordinates from an indexed array.
*
* @param array{0: float|int|string, 1: float|int|string, 2 ?: null|float|int, 3 ?: null|float|int} $coordinates coordinates
* @param SpatialContext $context expected dimension and target SRID
* @param array{0: null|float|int|string, 1: null|float|int|string, 2 ?: null|float|int, 3 ?: null|float|int} $coordinates coordinates
* @param SpatialContext $context expected dimension and target SRID
*
* @throws InvalidDimensionException when the array contains too many coordinates
* @throws InvalidValueException when a Z or M coordinate is not numeric
* @throws InvalidValueException when a Z or M coordinate is neither numeric nor null
* @throws MissingValueException when a required coordinate is missing
*/
public function hydrate(array $coordinates, SpatialContext $context): Coordinates
Expand All @@ -57,19 +57,19 @@ public function hydrate(array $coordinates, SpatialContext $context): Coordinate
}

/**
* Ensure a Z or M coordinate is numeric.
* Ensure a Z or M coordinate is numeric or null.
*
* @param mixed $coordinate coordinate to validate
* @param string $name coordinate name used in the error message
*
* @return float|int validated coordinate
* @return null|float|int validated coordinate
*
* @throws InvalidValueException when the coordinate is not numeric
* @throws InvalidValueException when the coordinate is neither numeric nor null
*/
private function numericCoordinate(mixed $coordinate, string $name): float|int
private function numericCoordinate(mixed $coordinate, string $name): float|int|null
{
if (!is_float($coordinate) && !is_int($coordinate)) {
throw new InvalidValueException(sprintf('The %s coordinate must be a number.', $name));
if (null !== $coordinate && !is_float($coordinate) && !is_int($coordinate)) {
throw new InvalidValueException(sprintf('The %s coordinate must be a number or null.', $name));
}

return $coordinate;
Expand All @@ -79,7 +79,7 @@ private function numericCoordinate(mixed $coordinate, string $name): float|int
* Return an optional Z or M coordinate for the current dimension.
*
* A null index means that the dimension does not contain this coordinate.
* Otherwise, validate the already-required indexed value as numeric before
* Otherwise, validate the already-required indexed value as numeric or null before
* returning it to the factory DTO.
*
* @param array<array-key, mixed> $coordinates coordinates to read
Expand All @@ -88,7 +88,7 @@ private function numericCoordinate(mixed $coordinate, string $name): float|int
*
* @return null|float|int the coordinate, or null when absent from the dimension
*
* @throws InvalidValueException when the coordinate is not numeric
* @throws InvalidValueException when the coordinate is neither numeric nor null
*/
private function optionalNumericCoordinate(array $coordinates, ?int $index, string $name): float|int|null
{
Expand All @@ -112,7 +112,7 @@ private function validateRequiredCoordinates(array $coordinates, int $coordinate
$coordinateNames = ['first', 'second', 'third', 'fourth'];

foreach (range(0, $coordinateCount - 1) as $index) {
if (!array_key_exists($index, $coordinates) || null === $coordinates[$index]) {
if (!array_key_exists($index, $coordinates)) {
throw new MissingValueException(sprintf('The %s coordinate of array is missing.', $coordinateNames[$index] ?? 'unknown'));
}
}
Expand Down
8 changes: 4 additions & 4 deletions lib/Factory/Hydrator/SpatialArrayHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ public function __construct(
/**
* Hydrate the points of a line string.
*
* @param array<array{0: float|int|string, 1: float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}|PointInterface> $points points
* @param SpatialContext $context family, dimension, and SRID expected for every point
* @param array<array{0: null|float|int|string, 1: null|float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}|PointInterface> $points points
* @param SpatialContext $context family, dimension, and SRID expected for every point
*
* @return PointInterface[]
*
Expand Down Expand Up @@ -81,8 +81,8 @@ public function hydrateLineString(array $points, SpatialContext $context): array
/**
* Hydrate the rings of a polygon.
*
* @param array<array<array{0: float|int|string, 1: float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}|PointInterface>|LineStringInterface> $rings rings
* @param SpatialContext $context family, dimension, and SRID expected for every ring
* @param array<array<array{0: null|float|int|string, 1: null|float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}|PointInterface>|LineStringInterface> $rings rings
* @param SpatialContext $context family, dimension, and SRID expected for every ring
*
* @return LineStringInterface[]
*
Expand Down
48 changes: 0 additions & 48 deletions lib/Factory/Internal/AbstractPointFactory.php

This file was deleted.

Loading
Loading