diff --git a/docs/immutability.md b/docs/immutability.md index 9c64466..736c641 100644 --- a/docs/immutability.md +++ b/docs/immutability.md @@ -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()); +``` diff --git a/docs/instantiable-spatial-types.md b/docs/instantiable-spatial-types.md index 464a61a..8ec3887 100644 --- a/docs/instantiable-spatial-types.md +++ b/docs/instantiable-spatial-types.md @@ -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); @@ -189,8 +191,10 @@ 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; @@ -198,17 +202,32 @@ 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; @@ -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 diff --git a/docs/type-class-hierarchy.md b/docs/type-class-hierarchy.md index 53f9585..b2f09f7 100644 --- a/docs/type-class-hierarchy.md +++ b/docs/type-class-hierarchy.md @@ -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. diff --git a/docs/validators.md b/docs/validators.md index bc8ffb6..a6edf9f 100644 --- a/docs/validators.md +++ b/docs/validators.md @@ -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. diff --git a/lib/Factory/Coordinates.php b/lib/Factory/Coordinates.php index 7e04806..4206691 100644 --- a/lib/Factory/Coordinates.php +++ b/lib/Factory/Coordinates.php @@ -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 ) { diff --git a/lib/Factory/FromIndexedArrayFactory.php b/lib/Factory/FromIndexedArrayFactory.php index 3f956ad..f973151 100644 --- a/lib/Factory/FromIndexedArrayFactory.php +++ b/lib/Factory/FromIndexedArrayFactory.php @@ -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 */ @@ -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 */ @@ -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 */ diff --git a/lib/Factory/Hydrator/CoordinatesHydrator.php b/lib/Factory/Hydrator/CoordinatesHydrator.php index b961676..903a418 100644 --- a/lib/Factory/Hydrator/CoordinatesHydrator.php +++ b/lib/Factory/Hydrator/CoordinatesHydrator.php @@ -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 @@ -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; @@ -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 $coordinates coordinates to read @@ -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 { @@ -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')); } } diff --git a/lib/Factory/Hydrator/SpatialArrayHydrator.php b/lib/Factory/Hydrator/SpatialArrayHydrator.php index d2ef28d..72ed4f1 100644 --- a/lib/Factory/Hydrator/SpatialArrayHydrator.php +++ b/lib/Factory/Hydrator/SpatialArrayHydrator.php @@ -42,8 +42,8 @@ public function __construct( /** * Hydrate the points of a line string. * - * @param array $points points - * @param SpatialContext $context family, dimension, and SRID expected for every point + * @param array $points points + * @param SpatialContext $context family, dimension, and SRID expected for every point * * @return PointInterface[] * @@ -81,8 +81,8 @@ public function hydrateLineString(array $points, SpatialContext $context): array /** * Hydrate the rings of a polygon. * - * @param array|LineStringInterface> $rings rings - * @param SpatialContext $context family, dimension, and SRID expected for every ring + * @param array|LineStringInterface> $rings rings + * @param SpatialContext $context family, dimension, and SRID expected for every ring * * @return LineStringInterface[] * diff --git a/lib/Factory/Internal/AbstractPointFactory.php b/lib/Factory/Internal/AbstractPointFactory.php deleted file mode 100644 index b67c1fb..0000000 --- a/lib/Factory/Internal/AbstractPointFactory.php +++ /dev/null @@ -1,48 +0,0 @@ - 2024-2026 - * Copyright Longitude One 2024-2026 - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - * - */ - -declare(strict_types=1); - -namespace LongitudeOne\SpatialTypes\Factory\Internal; - -use LongitudeOne\SpatialTypes\Exception\MissingValueException; - -/** - * Shares required-coordinate validation between point factories. - * - * @internal - */ -abstract class AbstractPointFactory implements PointFactoryInterface -{ - /** - * Return a required coordinate. - * - * @template T of float|int - * - * @param null|T $coordinate coordinate - * @param string $ordinal coordinate ordinal - * - * @return T - * - * @throws MissingValueException when the coordinate is missing - */ - protected static function requiredCoordinate(float|int|null $coordinate, string $ordinal): float|int - { - if (null === $coordinate) { - throw new MissingValueException(sprintf('The %s coordinate is missing.', $ordinal)); - } - - return $coordinate; - } -} diff --git a/lib/Factory/Internal/GeographicPointFactory.php b/lib/Factory/Internal/GeographicPointFactory.php index 998f92f..ad4055e 100644 --- a/lib/Factory/Internal/GeographicPointFactory.php +++ b/lib/Factory/Internal/GeographicPointFactory.php @@ -30,7 +30,7 @@ * * @internal */ -final class GeographicPointFactory extends AbstractPointFactory +final class GeographicPointFactory implements PointFactoryInterface { /** * Create a geographic point. @@ -42,9 +42,9 @@ public function create(Coordinates $coordinates, SpatialContext $context): Point { return match ($context->dimension) { CoordinateDimensionEnum::XY => new Point2D($coordinates->x, $coordinates->y, $context->reference), - CoordinateDimensionEnum::XYM => new Point3Dm($coordinates->x, $coordinates->y, self::requiredCoordinate($coordinates->m, 'third'), $context->reference), - CoordinateDimensionEnum::XYZ => new Point3Dz($coordinates->x, $coordinates->y, self::requiredCoordinate($coordinates->z, 'third'), $context->reference), - CoordinateDimensionEnum::XYZM => new Point4Dzm($coordinates->x, $coordinates->y, self::requiredCoordinate($coordinates->z, 'third'), self::requiredCoordinate($coordinates->m, 'fourth'), $context->reference), + CoordinateDimensionEnum::XYM => new Point3Dm($coordinates->x, $coordinates->y, $coordinates->m, $context->reference), + CoordinateDimensionEnum::XYZ => new Point3Dz($coordinates->x, $coordinates->y, $coordinates->z, $context->reference), + CoordinateDimensionEnum::XYZM => new Point4Dzm($coordinates->x, $coordinates->y, $coordinates->z, $coordinates->m, $context->reference), }; } diff --git a/lib/Factory/Internal/GeometricPointFactory.php b/lib/Factory/Internal/GeometricPointFactory.php index 10de323..7565983 100644 --- a/lib/Factory/Internal/GeometricPointFactory.php +++ b/lib/Factory/Internal/GeometricPointFactory.php @@ -30,7 +30,7 @@ * * @internal */ -final class GeometricPointFactory extends AbstractPointFactory +final class GeometricPointFactory implements PointFactoryInterface { /** * Create a geometric point. @@ -42,9 +42,9 @@ public function create(Coordinates $coordinates, SpatialContext $context): Point { return match ($context->dimension) { CoordinateDimensionEnum::XY => new Point2D($coordinates->x, $coordinates->y, $context->reference), - CoordinateDimensionEnum::XYM => new Point3Dm($coordinates->x, $coordinates->y, self::requiredCoordinate($coordinates->m, 'third'), $context->reference), - CoordinateDimensionEnum::XYZ => new Point3Dz($coordinates->x, $coordinates->y, self::requiredCoordinate($coordinates->z, 'third'), $context->reference), - CoordinateDimensionEnum::XYZM => new Point4Dzm($coordinates->x, $coordinates->y, self::requiredCoordinate($coordinates->z, 'third'), self::requiredCoordinate($coordinates->m, 'fourth'), $context->reference), + CoordinateDimensionEnum::XYM => new Point3Dm($coordinates->x, $coordinates->y, $coordinates->m, $context->reference), + CoordinateDimensionEnum::XYZ => new Point3Dz($coordinates->x, $coordinates->y, $coordinates->z, $context->reference), + CoordinateDimensionEnum::XYZM => new Point4Dzm($coordinates->x, $coordinates->y, $coordinates->z, $coordinates->m, $context->reference), }; } diff --git a/lib/Factory/SpatialFactory.php b/lib/Factory/SpatialFactory.php index 650d698..93df932 100644 --- a/lib/Factory/SpatialFactory.php +++ b/lib/Factory/SpatialFactory.php @@ -65,8 +65,8 @@ public function createLineString(array $points, SpatialContext $context): LineSt /** * Create a line string from an indexed array. * - * @param array $points points - * @param SpatialContext $context family, dimension, and SRID to apply + * @param array $points points + * @param SpatialContext $context family, dimension, and SRID to apply */ public function createLineStringFromIndexedArray(array $points, SpatialContext $context): LineStringInterface { @@ -93,8 +93,8 @@ public function createPoint(Coordinates $coordinates, SpatialContext $context): /** * Create a point from an indexed array. * - * @param array{0: float|int|string, 1: float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}|array{} $coordinates coordinates - * @param SpatialContext $context family, dimension, and SRID to apply + * @param array{0: null|float|int|string, 1: null|float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}|array{} $coordinates coordinates + * @param SpatialContext $context family, dimension, and SRID to apply */ public function createPointFromIndexedArray(array $coordinates, SpatialContext $context): PointInterface { @@ -127,8 +127,8 @@ public function createPolygon(array $lineStrings, SpatialContext $context): Poly /** * Create a polygon from an indexed array. * - * @param array|LineStringInterface> $rings rings - * @param SpatialContext $context family, dimension, and SRID to apply + * @param array|LineStringInterface> $rings rings + * @param SpatialContext $context family, dimension, and SRID to apply */ public function createPolygonFromIndexedArray(array $rings, SpatialContext $context): PolygonInterface { diff --git a/lib/Interfaces/CircularStringInterface.php b/lib/Interfaces/CircularStringInterface.php index 9e3ec81..5d4cfbd 100644 --- a/lib/Interfaces/CircularStringInterface.php +++ b/lib/Interfaces/CircularStringInterface.php @@ -58,7 +58,7 @@ public function isEmpty(): bool; /** * Return an array of coordinates. * - * @return (float|int)[][] + * @return (null|float|int)[][] */ public function toArray(): array; @@ -69,7 +69,7 @@ public function toArray(): array; * and Spatial Reference Identifier (SRID). Each tuple must therefore match * this instance's coordinate layout: XY, XYM, XYZ, or XYZM. * - * @param array $coordinates replacement coordinates + * @param array $coordinates replacement coordinates */ public function withArrayOfCoordinates(array $coordinates): static; diff --git a/lib/Interfaces/CompoundCurveInterface.php b/lib/Interfaces/CompoundCurveInterface.php index dac1b28..d372379 100644 --- a/lib/Interfaces/CompoundCurveInterface.php +++ b/lib/Interfaces/CompoundCurveInterface.php @@ -32,6 +32,6 @@ public function getCurves(): array; /** @return CurveInterface[] */ public function getElements(): array; - /** @return (float|int)[][][] */ + /** @return (null|float|int)[][][] */ public function toArray(): array; } diff --git a/lib/Interfaces/LineStringInterface.php b/lib/Interfaces/LineStringInterface.php index 2558895..ddb9360 100644 --- a/lib/Interfaces/LineStringInterface.php +++ b/lib/Interfaces/LineStringInterface.php @@ -69,7 +69,7 @@ public function isLine(): bool; /** * Return an array of coordinates. * - * @return (float|int)[][] + * @return (null|float|int)[][] */ public function toArray(): array; @@ -80,7 +80,7 @@ public function toArray(): array; * and Spatial Reference Identifier (SRID). Each tuple must therefore match * this instance's coordinate layout: XY, XYM, XYZ, or XYZM. * - * @param array $coordinates replacement coordinates + * @param array $coordinates replacement coordinates */ public function withArrayOfCoordinates(array $coordinates): static; diff --git a/lib/Interfaces/MultiLineStringInterface.php b/lib/Interfaces/MultiLineStringInterface.php index c1c99f5..b83c98e 100644 --- a/lib/Interfaces/MultiLineStringInterface.php +++ b/lib/Interfaces/MultiLineStringInterface.php @@ -44,7 +44,7 @@ public function getLineStrings(): array; /** * Return an array of coordinates. * - * @return (float|int)[][][] + * @return (null|float|int)[][][] */ public function toArray(): array; @@ -55,8 +55,8 @@ public function toArray(): array; * dimension, and Spatial Reference Identifier (SRID). Every replacement * coordinate tuple must match this instance's coordinate layout. * - * @param int $lineStringIndex index of the line string to replace; negative indexes count from the end - * @param array $coordinates replacement line-string coordinates + * @param int $lineStringIndex index of the line string to replace; negative indexes count from the end + * @param array $coordinates replacement line-string coordinates */ public function withLineString(int $lineStringIndex, array $coordinates): static; diff --git a/lib/Interfaces/MultiPointInterface.php b/lib/Interfaces/MultiPointInterface.php index 0b19696..e98403d 100644 --- a/lib/Interfaces/MultiPointInterface.php +++ b/lib/Interfaces/MultiPointInterface.php @@ -52,7 +52,7 @@ public function isSimple(): bool; /** * Return an array of coordinates. * - * @return (float|int)[][] + * @return (null|float|int)[][] */ public function toArray(): array; diff --git a/lib/Interfaces/MultiPolygonInterface.php b/lib/Interfaces/MultiPolygonInterface.php index 7197323..03baf89 100644 --- a/lib/Interfaces/MultiPolygonInterface.php +++ b/lib/Interfaces/MultiPolygonInterface.php @@ -44,7 +44,7 @@ public function getPolygons(): array; /** * Return an array of coordinates. * - * @return (float|int)[][][][] + * @return (null|float|int)[][][][] */ public function toArray(): array; @@ -61,17 +61,17 @@ public function withPoint(int $polygonIndex, int $ringIndex, int $pointIndex, Co /** * Return a new multi-polygon with one replacement polygon. * - * @param int $polygonIndex index of the polygon to replace; negative indexes count from the end - * @param array> $coordinates replacement polygon coordinates + * @param int $polygonIndex index of the polygon to replace; negative indexes count from the end + * @param array> $coordinates replacement polygon coordinates */ public function withPolygon(int $polygonIndex, array $coordinates): static; /** * Return a new multi-polygon with one replacement ring. * - * @param int $polygonIndex index of the polygon to replace; negative indexes count from the end - * @param int $ringIndex index of the ring to replace; negative indexes count from the end - * @param array $coordinates replacement ring coordinates + * @param int $polygonIndex index of the polygon to replace; negative indexes count from the end + * @param int $ringIndex index of the ring to replace; negative indexes count from the end + * @param array $coordinates replacement ring coordinates */ public function withRing(int $polygonIndex, int $ringIndex, array $coordinates): static; } diff --git a/lib/Interfaces/PointInterface.php b/lib/Interfaces/PointInterface.php index 60215e9..a22839b 100644 --- a/lib/Interfaces/PointInterface.php +++ b/lib/Interfaces/PointInterface.php @@ -73,7 +73,7 @@ public function getZ(): float|int|null; /** * Return an array of all coordinates. * - * @return (float|int)[] + * @return (null|float|int)[] */ public function toArray(): array; diff --git a/lib/Interfaces/PolygonInterface.php b/lib/Interfaces/PolygonInterface.php index 6b57982..fb00fbd 100644 --- a/lib/Interfaces/PolygonInterface.php +++ b/lib/Interfaces/PolygonInterface.php @@ -44,7 +44,7 @@ public function getRings(): array; /** * Return an array of coordinates. * - * @return (float|int)[][][] + * @return (null|float|int)[][][] */ public function toArray(): array; @@ -55,7 +55,7 @@ public function toArray(): array; * Spatial Reference Identifier (SRID). Every coordinate tuple must match * this instance's coordinate layout; ring closure is not checked. * - * @param array> $coordinates replacement ring coordinates + * @param array> $coordinates replacement ring coordinates */ public function withArrayOfCoordinates(array $coordinates): static; @@ -79,8 +79,8 @@ public function withPoint(int $ringIndex, int $pointIndex, Coordinates $coordina * Spatial Reference Identifier (SRID). The replacement coordinates must * use this polygon's coordinate layout; ring closure is not checked. * - * @param int $ringIndex index of the ring to replace; negative indexes count from the end - * @param array $coordinates replacement ring coordinates + * @param int $ringIndex index of the ring to replace; negative indexes count from the end + * @param array $coordinates replacement ring coordinates */ public function withRing(int $ringIndex, array $coordinates): static; } diff --git a/lib/Interfaces/PolyhedralSurfaceInterface.php b/lib/Interfaces/PolyhedralSurfaceInterface.php index 0ee0227..bba1cba 100644 --- a/lib/Interfaces/PolyhedralSurfaceInterface.php +++ b/lib/Interfaces/PolyhedralSurfaceInterface.php @@ -45,22 +45,22 @@ public function getPatches(): array; /** * Return an array of coordinates. * - * @return (float|int)[][][][] + * @return (null|float|int)[][][][] */ public function toArray(): array; /** * Return an independent surface with replacement patch coordinates. * - * @param array>> $coordinates Patch coordinates + * @param array>> $coordinates Patch coordinates */ public function withArrayOfCoordinates(array $coordinates): static; /** * Return a new polyhedral surface with one replacement polygon. * - * @param int $patchIndex index of the polygon to replace; negative indexes count from the end - * @param array> $coordinates replacement polygon coordinates + * @param int $patchIndex index of the polygon to replace; negative indexes count from the end + * @param array> $coordinates replacement polygon coordinates */ public function withPatch(int $patchIndex, array $coordinates): static; @@ -77,9 +77,9 @@ public function withPoint(int $patchIndex, int $ringIndex, int $pointIndex, Coor /** * Return a new polyhedral surface with one replacement ring. * - * @param int $patchIndex index of the polygon to replace; negative indexes count from the end - * @param int $ringIndex index of the ring to replace; negative indexes count from the end - * @param array $coordinates replacement ring coordinates + * @param int $patchIndex index of the polygon to replace; negative indexes count from the end + * @param int $ringIndex index of the ring to replace; negative indexes count from the end + * @param array $coordinates replacement ring coordinates */ public function withRing(int $patchIndex, int $ringIndex, array $coordinates): static; } diff --git a/lib/Interfaces/SpatialInterface.php b/lib/Interfaces/SpatialInterface.php index e9eb205..00fce8f 100644 --- a/lib/Interfaces/SpatialInterface.php +++ b/lib/Interfaces/SpatialInterface.php @@ -95,7 +95,7 @@ public function isEmpty(): bool; * * Use the longitude-one/spatial-writer library to export all data. * - * @return (float|int)[]|(float|int)[][]|(float|int)[][][]|(float|int)[][][][] + * @return (null|float|int)[]|(null|float|int)[][]|(null|float|int)[][][]|(null|float|int)[][][][] */ public function toArray(): array; diff --git a/lib/Types/AbstractCircularString.php b/lib/Types/AbstractCircularString.php index 68fc206..3b1667b 100644 --- a/lib/Types/AbstractCircularString.php +++ b/lib/Types/AbstractCircularString.php @@ -37,8 +37,8 @@ abstract class AbstractCircularString extends AbstractSpatialType implements Cir protected array $points = []; /** - * @param array $points Defining points or tuples - * @param int|SpatialReference $srid Spatial reference + * @param array $points Defining points or tuples + * @param int|SpatialReference $srid Spatial reference */ final public function __construct(array $points, int|SpatialReference $srid = 0) { @@ -105,7 +105,7 @@ public function isEmpty(): bool } /** - * @return (float|int)[][] + * @return (null|float|int)[][] */ public function toArray(): array { @@ -116,7 +116,7 @@ public function toArray(): array } /** - * @param array $coordinates Replacement tuples + * @param array $coordinates Replacement tuples */ public function withArrayOfCoordinates(array $coordinates): static { @@ -155,7 +155,7 @@ public function withSpatialReference(SpatialReference $reference): static } /** - * @param array{0: float|int|string, 1: float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}|PointInterface $point Point or coordinate tuple to add + * @param array{0: null|float|int|string, 1: null|float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}|PointInterface $point Point or coordinate tuple to add * * @throws InvalidDimensionException When a coordinate tuple has an incompatible layout * @throws InvalidValueException When coordinates are invalid @@ -173,7 +173,7 @@ protected function addPoint(array|PointInterface $point): static } /** - * @param array $points Points or tuples to add + * @param array $points Points or tuples to add */ protected function addPoints(array $points): static { diff --git a/lib/Types/AbstractCollection.php b/lib/Types/AbstractCollection.php index 3f51319..733bcd3 100644 --- a/lib/Types/AbstractCollection.php +++ b/lib/Types/AbstractCollection.php @@ -78,7 +78,7 @@ public function isEmpty(): bool /** * Convert the collection to an array. * - * @return (float|(float|int|string)[]|(float|int|string)[][]|(float|int|string)[][][]|int|string)[][] + * @return (null|float|int|(null|float|int|string)[]|(null|float|int|string)[][]|(null|float|int|string)[][][]|string)[][] */ public function toArray(): array { diff --git a/lib/Types/AbstractCompoundCurve.php b/lib/Types/AbstractCompoundCurve.php index a327da2..25a4ed6 100644 --- a/lib/Types/AbstractCompoundCurve.php +++ b/lib/Types/AbstractCompoundCurve.php @@ -111,7 +111,7 @@ public function isEmpty(): bool return [] === $this->curves; } - /** @return (float|int)[][][] */ + /** @return (null|float|int)[][][] */ public function toArray(): array { return array_map( @@ -149,7 +149,7 @@ private function addCurve(CurveInterface $curve): void * * @param CurveInterface $curve Component whose coordinates are requested * - * @return (float|int)[][] + * @return (null|float|int)[][] */ private function componentCoordinates(CurveInterface $curve): array { @@ -160,8 +160,8 @@ private function componentCoordinates(CurveInterface $curve): array } $tuple = []; foreach ($point as $ordinate) { - if (!is_int($ordinate) && !is_float($ordinate)) { - throw new InvalidValueException('A compound curve component must expose numeric ordinates.'); + if (null !== $ordinate && !is_int($ordinate) && !is_float($ordinate)) { + throw new InvalidValueException('A compound curve component must expose numeric or null ordinates.'); } $tuple[] = $ordinate; } diff --git a/lib/Types/AbstractLineString.php b/lib/Types/AbstractLineString.php index 61df824..e73892e 100644 --- a/lib/Types/AbstractLineString.php +++ b/lib/Types/AbstractLineString.php @@ -73,7 +73,7 @@ public function isLine(): bool /** * Return an array representation of the line string. * - * @return (float|int)[][] + * @return (null|float|int)[][] */ public function toArray(): array { @@ -93,7 +93,7 @@ public function toArray(): array * (SRID); the existing point hydrator validates each tuple against that * retained context. * - * @param array $coordinates replacement coordinates + * @param array $coordinates replacement coordinates */ public function withArrayOfCoordinates(array $coordinates): static { diff --git a/lib/Types/AbstractMultiLineString.php b/lib/Types/AbstractMultiLineString.php index 0c33a41..2ac1911 100644 --- a/lib/Types/AbstractMultiLineString.php +++ b/lib/Types/AbstractMultiLineString.php @@ -38,8 +38,8 @@ abstract class AbstractMultiLineString extends AbstractLineStringCollection impl /** * AbstractMultiLineString constructor. * - * @param (array{0: float|int|string, 1: float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}[]|LineStringInterface|PointInterface[])[] $lineStrings lineStrings of the multiLineString - * @param int|SpatialReference $srid Spatial Reference Identifier + * @param (array{0: null|float|int|string, 1: null|float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}[]|LineStringInterface|PointInterface[])[] $lineStrings lineStrings of the multiLineString + * @param int|SpatialReference $srid Spatial Reference Identifier * * @throws InvalidDimensionException when the point dimension is not compatible with the multiLineStlineString dimension * @throws InvalidValueException when coordinates of the point are invalid @@ -102,7 +102,7 @@ public function isEmpty(): bool /** * Return an array representation of the multiLineString. * - * @return (float|int)[][][] + * @return (null|float|int)[][][] */ public function toArray(): array { @@ -121,8 +121,8 @@ public function toArray(): array * The replacement coordinates are created through the existing family, * dimension, and Spatial Reference Identifier (SRID) context. * - * @param int $lineStringIndex index of the line string to replace; negative indexes count from the end - * @param array $coordinates replacement line-string coordinates + * @param int $lineStringIndex index of the line string to replace; negative indexes count from the end + * @param array $coordinates replacement line-string coordinates * * @throws OutOfBoundsException when the multi-line string has no line strings */ @@ -193,7 +193,7 @@ public function withSrid(int $srid): static /** * Add a line string to the spatial collection. * - * @param array{0: float|int|string, 1: float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}[]|LineStringInterface|PointInterface[] $lineString line string to add + * @param array{0: null|float|int|string, 1: null|float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}[]|LineStringInterface|PointInterface[] $lineString line string to add * * @throws InvalidValueException when the line string dimension is not compatible with the current dimension */ @@ -205,7 +205,7 @@ protected function addLineString(array|LineStringInterface $lineString): static /** * Add line strings to the spatial collection. * - * @param array{0: float|int|string, 1: float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}[][]|LineStringInterface[]|PointInterface[][] $lineStrings the ring to add + * @param array{0: null|float|int|string, 1: null|float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}[][]|LineStringInterface[]|PointInterface[][] $lineStrings the ring to add * * @throws SpatialTypeExceptionInterface when something is wrong during the addition */ diff --git a/lib/Types/AbstractMultiPoint.php b/lib/Types/AbstractMultiPoint.php index 8af10d5..5e45afb 100644 --- a/lib/Types/AbstractMultiPoint.php +++ b/lib/Types/AbstractMultiPoint.php @@ -36,8 +36,8 @@ abstract class AbstractMultiPoint extends AbstractPointCollection implements Mul /** * AbstractMultiPoint constructor. * - * @param (array{0: float|int|string, 1: float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}|PointInterface)[] $points points of the multipoint - * @param int|SpatialReference $srid Spatial Reference Identifier + * @param (array{0: null|float|int|string, 1: null|float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}|PointInterface)[] $points points of the multipoint + * @param int|SpatialReference $srid Spatial Reference Identifier * * @throws InvalidDimensionException when the point dimension is not compatible with the multipoint dimension * @throws InvalidValueException when coordinates of the point are invalid diff --git a/lib/Types/AbstractMultiPolygon.php b/lib/Types/AbstractMultiPolygon.php index 02ce26a..b95d675 100644 --- a/lib/Types/AbstractMultiPolygon.php +++ b/lib/Types/AbstractMultiPolygon.php @@ -29,7 +29,7 @@ /** * Abstract multi-polygon class. * - * @phpstan-type IndexedCoordinates array{0: float|int|string, 1: float|int|string, 2 ?: null|float|int, 3 ?: null|float|int} + * @phpstan-type IndexedCoordinates array{0: null|float|int|string, 1: null|float|int|string, 2 ?: null|float|int, 3 ?: null|float|int} * @phpstan-type IndexedPolygon array> * * @internal this class provides common behaviour for geometry and geography multi-polygons @@ -112,7 +112,7 @@ public function isEmpty(): bool /** * Return an array representation of this multi-polygon. * - * @return (float|int)[][][][] + * @return (null|float|int)[][][][] */ public function toArray(): array { @@ -147,8 +147,8 @@ public function withPoint(int $polygonIndex, int $ringIndex, int $pointIndex, Co /** * Return a deep copy of this multi-polygon with one replacement polygon. * - * @param int $polygonIndex index of the polygon to replace; negative indexes count from the end - * @param array> $coordinates replacement polygon coordinates + * @param int $polygonIndex index of the polygon to replace; negative indexes count from the end + * @param array> $coordinates replacement polygon coordinates * * @throws OutOfBoundsException when the multi-polygon has no polygons */ @@ -168,9 +168,9 @@ public function withPolygon(int $polygonIndex, array $coordinates): static /** * Return a deep copy of this multi-polygon with one replacement ring. * - * @param int $polygonIndex index of the polygon to replace; negative indexes count from the end - * @param int $ringIndex index of the ring to replace; negative indexes count from the end - * @param array $coordinates replacement ring coordinates + * @param int $polygonIndex index of the polygon to replace; negative indexes count from the end + * @param int $ringIndex index of the ring to replace; negative indexes count from the end + * @param array $coordinates replacement ring coordinates * * @throws OutOfBoundsException when the multi-polygon has no polygons */ diff --git a/lib/Types/AbstractPoint.php b/lib/Types/AbstractPoint.php index 2e82ffe..6f7126a 100644 --- a/lib/Types/AbstractPoint.php +++ b/lib/Types/AbstractPoint.php @@ -40,12 +40,12 @@ abstract class AbstractPoint extends AbstractSpatialType implements PointInterfa /** * The X coordinate or the longitude. */ - protected float|int $x; + protected float|int|null $x = null; /** * The Y coordinate or the latitude. */ - protected float|int $y; + protected float|int|null $y = null; /** Whether this point corresponds to the empty set. */ private bool $empty = false; @@ -166,7 +166,7 @@ public function withCoordinates(Coordinates $coordinates): static $point->initializeM($coordinates->getM()); } - $point->empty = false; + $point->hasOnlyNullCoordinates(...$coordinates->toArray()); return $point; } @@ -209,36 +209,25 @@ protected function geoParse(string $coordinate): float|int * Determine whether all coordinates describe an empty point. * * A point is empty only when every ordinate in its coordinate layout is - * null. Supplying only some coordinates would produce an invalid point. + * null. Partially supplied coordinates are preserved without validation. * * @param (null|float|int|string) ...$coordinates Point ordinates - * - * @throws InvalidValueException when only a subset of ordinates is null */ final protected function hasOnlyNullCoordinates(float|int|string|null ...$coordinates): bool { - $nullCount = count(array_filter($coordinates, static fn (float|int|string|null $coordinate): bool => null === $coordinate)); - if (count($coordinates) === $nullCount) { - $this->empty = true; - - return true; - } + $this->empty = count($coordinates) === count(array_filter($coordinates, static fn (float|int|string|null $coordinate): bool => null === $coordinate)); - if (0 !== $nullCount) { - throw new InvalidValueException('All point coordinates must be provided, or all must be null for an empty point.'); - } - - return false; + return $this->empty; } /** * Set the M coordinate on a point that supports a measure ordinate. * - * @param float|int $m M coordinate or measure + * @param null|float|int $m M coordinate or measure * * @throws BadMethodCallException when the point has no M ordinate */ - protected function initializeM(float|int $m): static + protected function initializeM(float|int|null $m): static { // @codeCoverageIgnoreStart throw new BadMethodCallException(sprintf('The M ordinate "%s" cannot be assigned to a point with the %s dimension.', $m, $this->getDimension()->value)); @@ -248,11 +237,11 @@ protected function initializeM(float|int $m): static /** * X setter. (Latitude setter). * - * @param float|int|string $x the new X + * @param null|float|int|string $x the new X * * @throws InvalidValueException when x is not valid */ - protected function initializeX(float|int|string $x): static + protected function initializeX(float|int|string|null $x): static { $this->x = $this->setCartesianCoordinate($x); @@ -262,11 +251,11 @@ protected function initializeX(float|int|string $x): static /** * Y setter. Longitude Setter. * - * @param float|int|string $y the new Y value + * @param null|float|int|string $y the new Y value * * @throws InvalidValueException when Y is invalid, not in valid range */ - protected function initializeY(float|int|string $y): static + protected function initializeY(float|int|string|null $y): static { $this->y = $this->setCartesianCoordinate($y); @@ -276,11 +265,11 @@ protected function initializeY(float|int|string $y): static /** * Set the Z coordinate on a point that supports an elevation ordinate. * - * @param float|int $z Z coordinate or elevation + * @param null|float|int $z Z coordinate or elevation * * @throws BadMethodCallException when the point has no Z ordinate */ - protected function initializeZ(float|int $z): static + protected function initializeZ(float|int|null $z): static { // @codeCoverageIgnoreStart throw new BadMethodCallException(sprintf('The Z ordinate "%s" cannot be assigned to a point with the %s dimension.', $z, $this->getDimension()->value)); @@ -291,13 +280,13 @@ protected function initializeZ(float|int $z): static * Set a cartesian coordinate. * Abscissa or ordinate. * - * @param float|int|string $coordinate the coordinate to set + * @param null|float|int|string $coordinate the coordinate to set * * @throws InvalidValueException when coordinate is invalid, RangeException is never thrown */ - protected function setCartesianCoordinate(float|int|string $coordinate): float|int + protected function setCartesianCoordinate(float|int|string|null $coordinate): float|int|null { - if (is_integer($coordinate) || is_float($coordinate)) { + if (null === $coordinate || is_integer($coordinate) || is_float($coordinate)) { // We don't check the range of the value. return $coordinate; } diff --git a/lib/Types/AbstractPolygon.php b/lib/Types/AbstractPolygon.php index d0edf80..44f2fd2 100644 --- a/lib/Types/AbstractPolygon.php +++ b/lib/Types/AbstractPolygon.php @@ -41,8 +41,8 @@ abstract class AbstractPolygon extends AbstractSpatialType implements PolygonInt /** * AbstractPolygon constructor. * - * @param (array{0: float|int|string, 1: float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}[]|LineStringInterface|PointInterface[])[] $rings rings of the polygon - * @param int|SpatialReference $srid Spatial Reference Identifier + * @param (array{0: null|float|int|string, 1: null|float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}[]|LineStringInterface|PointInterface[])[] $rings rings of the polygon + * @param int|SpatialReference $srid Spatial Reference Identifier * * @throws InvalidDimensionException when a coordinate tuple does not match the polygon dimension * @throws InvalidValueException when coordinates of the point are invalid @@ -96,7 +96,7 @@ public function isEmpty(): bool /** * Return an array representation of this polygon. * - * @return (float|int)[][][] + * @return (null|float|int)[][][] */ public function toArray(): array { @@ -116,7 +116,7 @@ public function toArray(): array * (SRID); the existing line-string hydrator creates members from each tuple * against that retained context. * - * @param array> $coordinates replacement ring coordinates + * @param array> $coordinates replacement ring coordinates */ public function withArrayOfCoordinates(array $coordinates): static { @@ -166,8 +166,8 @@ public function withPoint(int $ringIndex, int $pointIndex, Coordinates $coordina * coordinates are created through the polygon's existing family, dimension, * and Spatial Reference Identifier (SRID) context without enforcing ring validity. * - * @param int $ringIndex index of the ring to replace; negative indexes count from the end - * @param array $coordinates replacement ring coordinates + * @param int $ringIndex index of the ring to replace; negative indexes count from the end + * @param array $coordinates replacement ring coordinates * * @throws OutOfBoundsException when the polygon has no rings */ @@ -216,7 +216,7 @@ public function withSrid(int $srid): static /** * Get the line strings of the spatial collection. * - * @param array{0: float|int|string, 1: float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}[]|LineStringInterface|PointInterface[] $ring the ring to add + * @param array{0: null|float|int|string, 1: null|float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}[]|LineStringInterface|PointInterface[] $ring the ring to add * * @throws SpatialTypeExceptionInterface when something is wrong during the addition of the ring */ @@ -234,7 +234,7 @@ protected function addRing(array|LineStringInterface $ring): static /** * Add a ring to the spatial collection. * - * @param array{0: float|int|string, 1: float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}[][]|LineStringInterface[]|PointInterface[][] $rings the ring to add + * @param array{0: null|float|int|string, 1: null|float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}[][]|LineStringInterface[]|PointInterface[][] $rings the ring to add * * @throws SpatialTypeExceptionInterface when an input does not provide a line-string interface or coordinate tuples */ diff --git a/lib/Types/AbstractPolyhedralSurface.php b/lib/Types/AbstractPolyhedralSurface.php index 028f798..b85ebb7 100644 --- a/lib/Types/AbstractPolyhedralSurface.php +++ b/lib/Types/AbstractPolyhedralSurface.php @@ -29,7 +29,7 @@ /** * Abstract polyhedral surface class. * - * @phpstan-type IndexedCoordinates array{0: float|int|string, 1: float|int|string, 2 ?: null|float|int, 3 ?: null|float|int} + * @phpstan-type IndexedCoordinates array{0: null|float|int|string, 1: null|float|int|string, 2 ?: null|float|int, 3 ?: null|float|int} * @phpstan-type IndexedPolygon array> * * @internal this class provides common behaviour for geometry and geography polyhedral surfaces @@ -110,7 +110,7 @@ public function isEmpty(): bool /** * Return an array representation of this polyhedral surface. * - * @return (float|int)[][][][] + * @return (null|float|int)[][][][] */ public function toArray(): array { @@ -141,8 +141,8 @@ public function withArrayOfCoordinates(array $coordinates): static /** * Return a deep copy of this polyhedral surface with one replacement polygon. * - * @param int $patchIndex index of the polygon to replace; negative indexes count from the end - * @param array> $coordinates replacement polygon coordinates + * @param int $patchIndex index of the polygon to replace; negative indexes count from the end + * @param array> $coordinates replacement polygon coordinates * * @throws OutOfBoundsException when the polyhedral surface has no patches * @throws InvalidValueException when the replacement violates surface constraints @@ -184,9 +184,9 @@ public function withPoint(int $patchIndex, int $ringIndex, int $pointIndex, Coor /** * Return a deep copy of this polyhedral surface with one replacement ring. * - * @param int $patchIndex index of the polygon to replace; negative indexes count from the end - * @param int $ringIndex index of the ring to replace; negative indexes count from the end - * @param array $coordinates replacement ring coordinates + * @param int $patchIndex index of the polygon to replace; negative indexes count from the end + * @param int $ringIndex index of the ring to replace; negative indexes count from the end + * @param array $coordinates replacement ring coordinates * * @throws OutOfBoundsException when the polyhedral surface has no patches * @throws InvalidValueException when the replacement violates surface constraints diff --git a/lib/Types/AbstractSpatialType.php b/lib/Types/AbstractSpatialType.php index a53434d..b36f122 100644 --- a/lib/Types/AbstractSpatialType.php +++ b/lib/Types/AbstractSpatialType.php @@ -82,7 +82,7 @@ public function hasZ(): bool /** * Define elements for the JSON serialization. * - * @return array{type: string, coordinates: (float|int)[]|(float|int)[][]|(float|int)[][][]|(float|int)[][][][]|SpatialInterface[], srid: ?int} + * @return array{type: string, coordinates: (null|float|int)[]|(null|float|int)[][]|(null|float|int)[][][]|(null|float|int)[][][][]|SpatialInterface[], srid: ?int} */ public function jsonSerialize(): array { @@ -122,7 +122,7 @@ public function withSrid(int $srid): static /** * Hydrate a line-string tuple collection in this spatial value's context. * - * @param array $coordinates Point tuples + * @param array $coordinates Point tuples */ final protected function createLineStringFromCoordinates(array $coordinates): LineStringInterface { @@ -135,7 +135,7 @@ final protected function createLineStringFromCoordinates(array $coordinates): Li /** * Hydrate a point tuple in this spatial value's complete context. * - * @param array{0: float|int|string, 1: float|int|string, 2 ?: null|float|int, 3 ?: null|float|int} $coordinates Point tuple + * @param array{0: null|float|int|string, 1: null|float|int|string, 2 ?: null|float|int, 3 ?: null|float|int} $coordinates Point tuple */ final protected function createPointFromCoordinates(array $coordinates): PointInterface { @@ -148,7 +148,7 @@ final protected function createPointFromCoordinates(array $coordinates): PointIn /** * Hydrate polygon rings in this spatial value's context. * - * @param array|LineStringInterface> $rings Polygon rings + * @param array|LineStringInterface> $rings Polygon rings */ final protected function createPolygonFromCoordinates(array $rings): PolygonInterface { @@ -195,7 +195,7 @@ abstract public function isEmpty(): bool; /** * Convert any spatial object to its array representation. * - * @return (float|int)[]|(float|int)[][]|(float|int)[][][]|(float|int)[][][][]|SpatialInterface[] + * @return (null|float|int)[]|(null|float|int)[][]|(null|float|int)[][][]|(null|float|int)[][][][]|SpatialInterface[] */ abstract public function toArray(): array; } diff --git a/lib/Types/Collection/AbstractLineStringCollection.php b/lib/Types/Collection/AbstractLineStringCollection.php index 6db1295..c6cf80e 100644 --- a/lib/Types/Collection/AbstractLineStringCollection.php +++ b/lib/Types/Collection/AbstractLineStringCollection.php @@ -32,7 +32,7 @@ abstract class AbstractLineStringCollection extends AbstractSpatialType protected array $lineStrings = []; /** - * @param array|LineStringInterface $lineString Line string or its point tuples + * @param array|LineStringInterface $lineString Line string or its point tuples * * @throws SpatialTypeExceptionInterface When the member is incompatible */ @@ -48,7 +48,7 @@ protected function addLineStringMember(array|LineStringInterface $lineString): s } /** - * @param array>|LineStringInterface[] $lineStrings Line strings or their point tuples + * @param array>|LineStringInterface[] $lineStrings Line strings or their point tuples */ protected function addLineStringMembers(array $lineStrings): static { diff --git a/lib/Types/Collection/AbstractPointCollection.php b/lib/Types/Collection/AbstractPointCollection.php index 7e8cff3..442ba26 100644 --- a/lib/Types/Collection/AbstractPointCollection.php +++ b/lib/Types/Collection/AbstractPointCollection.php @@ -80,7 +80,7 @@ public function isSimple(): bool } /** - * @return (float|int)[][] + * @return (null|float|int)[][] */ public function toArray(): array { @@ -91,7 +91,7 @@ public function toArray(): array } /** - * @param array{0: float|int|string, 1: float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}|PointInterface $point Point or coordinate tuple to add + * @param array{0: null|float|int|string, 1: null|float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}|PointInterface $point Point or coordinate tuple to add * * @throws InvalidDimensionException When a coordinate tuple has an incompatible layout * @throws InvalidValueException When coordinates are invalid @@ -109,7 +109,7 @@ protected function addPoint(array|PointInterface $point): static } /** - * @param array $points Points or tuples to add + * @param array $points Points or tuples to add */ protected function addPoints(array $points): static { diff --git a/lib/Types/Dimension2/AbstractLineString.php b/lib/Types/Dimension2/AbstractLineString.php index 78d1f83..c1b1d42 100644 --- a/lib/Types/Dimension2/AbstractLineString.php +++ b/lib/Types/Dimension2/AbstractLineString.php @@ -31,8 +31,8 @@ abstract class AbstractLineString extends ParentLineString implements LineString /** * AbstractLineString constructor. * - * @param (array{0: float|int|string, 1: float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}|PointInterface)[] $points points of the line string - * @param int|SpatialReference $srid Spatial Reference Identifier + * @param (array{0: null|float|int|string, 1: null|float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}|PointInterface)[] $points points of the line string + * @param int|SpatialReference $srid Spatial Reference Identifier * * @throws InvalidDimensionException when a coordinate tuple does not match the line string dimension * @throws InvalidValueException when coordinates of the point are invalid diff --git a/lib/Types/Dimension2/AbstractMultiPoint.php b/lib/Types/Dimension2/AbstractMultiPoint.php index 5594d6d..7671051 100644 --- a/lib/Types/Dimension2/AbstractMultiPoint.php +++ b/lib/Types/Dimension2/AbstractMultiPoint.php @@ -31,8 +31,8 @@ abstract class AbstractMultiPoint extends ParentMultiPoint implements MultiPoint /** * AbstractMultiPoint constructor. * - * @param (array{0: float|int|string, 1: float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}|PointInterface)[] $points points of the multipoint - * @param int|SpatialReference $srid Spatial Reference Identifier + * @param (array{0: null|float|int|string, 1: null|float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}|PointInterface)[] $points points of the multipoint + * @param int|SpatialReference $srid Spatial Reference Identifier * * @throws InvalidDimensionException when the point dimension is not compatible with the multipoint dimension * @throws InvalidValueException when coordinates of the point are invalid diff --git a/lib/Types/Dimension2/Geography/Point.php b/lib/Types/Dimension2/Geography/Point.php index 53ed064..73b8917 100644 --- a/lib/Types/Dimension2/Geography/Point.php +++ b/lib/Types/Dimension2/Geography/Point.php @@ -52,10 +52,6 @@ public function __construct(float|int|string|null $x = null, float|int|string|nu return; } - if (null === $x || null === $y) { - throw new InvalidValueException('All point coordinates must be provided, or all must be null for an empty point.'); - } - $this->initializeX($x); $this->initializeY($y); $this->initializeSpatialReference($srid); @@ -113,7 +109,7 @@ public function getZ(): float|int|null * Convert this point to an array containing longitude (X) and latitude (Y). * The SRID is not exported. * - * @return array{0 : float|int, 1 : float|int}|array{} + * @return array{0 : null|float|int, 1 : null|float|int}|array{} */ public function toArray(): array { diff --git a/lib/Types/Dimension2/Geometry/Point.php b/lib/Types/Dimension2/Geometry/Point.php index b0f6016..412ff56 100644 --- a/lib/Types/Dimension2/Geometry/Point.php +++ b/lib/Types/Dimension2/Geometry/Point.php @@ -52,10 +52,6 @@ public function __construct(float|int|string|null $x = null, float|int|string|nu return; } - if (null === $x || null === $y) { - throw new InvalidValueException('All point coordinates must be provided, or all must be null for an empty point.'); - } - $this->initializeX($x); $this->initializeY($y); $this->initializeSpatialReference($srid); @@ -113,7 +109,7 @@ public function getZ(): float|int|null * Convert this point to an array containing X and Y coordinates. * The SRID is not exported. * - * @return array{0 : float|int, 1 : float|int}|array{} + * @return array{0 : null|float|int, 1 : null|float|int}|array{} */ public function toArray(): array { diff --git a/lib/Types/Dimension3m/AbstractLineString.php b/lib/Types/Dimension3m/AbstractLineString.php index 12fd286..747a88c 100644 --- a/lib/Types/Dimension3m/AbstractLineString.php +++ b/lib/Types/Dimension3m/AbstractLineString.php @@ -32,8 +32,8 @@ abstract class AbstractLineString extends ParentLineString implements LineStringInterface { /** - * @param (array{0: float|int|string, 1: float|int|string, 2: float|int}|PointInterface)[] $points points of the line string - * @param int|SpatialReference $srid Spatial Reference Identifier + * @param (array{0: null|float|int|string, 1: null|float|int|string, 2: null|float|int}|PointInterface)[] $points points of the line string + * @param int|SpatialReference $srid Spatial Reference Identifier * * @throws InvalidDimensionException when a point dimension is incompatible * @throws InvalidValueException when point coordinates are invalid diff --git a/lib/Types/Dimension3m/AbstractMultiPoint.php b/lib/Types/Dimension3m/AbstractMultiPoint.php index 84411ce..c2fb438 100644 --- a/lib/Types/Dimension3m/AbstractMultiPoint.php +++ b/lib/Types/Dimension3m/AbstractMultiPoint.php @@ -32,8 +32,8 @@ abstract class AbstractMultiPoint extends ParentMultiPoint implements MultiPointInterface { /** - * @param (array{0: float|int|string, 1: float|int|string, 2: float|int}|PointInterface)[] $points points of the multipoint - * @param int|SpatialReference $srid Spatial Reference Identifier + * @param (array{0: null|float|int|string, 1: null|float|int|string, 2: null|float|int}|PointInterface)[] $points points of the multipoint + * @param int|SpatialReference $srid Spatial Reference Identifier * * @throws InvalidDimensionException when a point dimension is incompatible * @throws InvalidValueException when point coordinates are invalid diff --git a/lib/Types/Dimension3m/Geography/Point.php b/lib/Types/Dimension3m/Geography/Point.php index 01373f9..f9a067f 100644 --- a/lib/Types/Dimension3m/Geography/Point.php +++ b/lib/Types/Dimension3m/Geography/Point.php @@ -33,7 +33,7 @@ class Point extends AbstractPoint implements PointInterface /** * The M coordinate. */ - protected float|int $m; + protected float|int|null $m = null; /** * @param null|float|int|string $x longitude @@ -51,10 +51,6 @@ public function __construct(float|int|string|null $x = null, float|int|string|nu return; } - if (null === $x || null === $y || null === $m) { - throw new InvalidValueException('All point coordinates must be provided, or all must be null for an empty point.'); - } - $this->initializeX($x); $this->initializeY($y); $this->initializeM($m); @@ -102,7 +98,7 @@ public function getZ(): float|int|null } /** - * @return array{0: float|int, 1: float|int, 2: float|int}|array{} + * @return array{0: null|float|int, 1: null|float|int, 2: null|float|int}|array{} */ public function toArray(): array { @@ -116,9 +112,9 @@ public function toArray(): array /** * Set the M coordinate. * - * @param float|int $m M coordinate + * @param null|float|int $m M coordinate */ - protected function initializeM(float|int $m): static + protected function initializeM(float|int|null $m): static { $this->m = $m; diff --git a/lib/Types/Dimension3m/Geometry/Point.php b/lib/Types/Dimension3m/Geometry/Point.php index 4dd4715..52558e4 100644 --- a/lib/Types/Dimension3m/Geometry/Point.php +++ b/lib/Types/Dimension3m/Geometry/Point.php @@ -33,7 +33,7 @@ class Point extends AbstractPoint implements PointInterface /** * The M coordinate. */ - protected float|int $m; + protected float|int|null $m = null; /** * @param null|float|int|string $x X coordinate @@ -51,10 +51,6 @@ public function __construct(float|int|string|null $x = null, float|int|string|nu return; } - if (null === $x || null === $y || null === $m) { - throw new InvalidValueException('All point coordinates must be provided, or all must be null for an empty point.'); - } - $this->initializeX($x); $this->initializeY($y); $this->initializeM($m); @@ -102,7 +98,7 @@ public function getZ(): float|int|null } /** - * @return array{0: float|int, 1: float|int, 2: float|int}|array{} + * @return array{0: null|float|int, 1: null|float|int, 2: null|float|int}|array{} */ public function toArray(): array { @@ -116,9 +112,9 @@ public function toArray(): array /** * Set the M coordinate. * - * @param float|int $m M coordinate + * @param null|float|int $m M coordinate */ - protected function initializeM(float|int $m): static + protected function initializeM(float|int|null $m): static { $this->m = $m; diff --git a/lib/Types/Dimension3z/AbstractLineString.php b/lib/Types/Dimension3z/AbstractLineString.php index da441c8..671b34b 100644 --- a/lib/Types/Dimension3z/AbstractLineString.php +++ b/lib/Types/Dimension3z/AbstractLineString.php @@ -31,8 +31,8 @@ abstract class AbstractLineString extends ParentLineString implements LineString /** * AbstractLineString constructor. * - * @param (array{0: float|int|string, 1: float|int|string, 2 ?: null|float|int}|PointInterface)[] $points points of the line string - * @param int|SpatialReference $srid Spatial Reference Identifier + * @param (array{0: null|float|int|string, 1: null|float|int|string, 2 ?: null|float|int}|PointInterface)[] $points points of the line string + * @param int|SpatialReference $srid Spatial Reference Identifier * * @throws InvalidDimensionException when a coordinate tuple does not match the line string dimension * @throws InvalidValueException when coordinates of the point are invalid diff --git a/lib/Types/Dimension3z/AbstractMultiPoint.php b/lib/Types/Dimension3z/AbstractMultiPoint.php index d06b86f..f822e7c 100644 --- a/lib/Types/Dimension3z/AbstractMultiPoint.php +++ b/lib/Types/Dimension3z/AbstractMultiPoint.php @@ -31,8 +31,8 @@ abstract class AbstractMultiPoint extends ParentMultiPoint implements MultiPoint /** * AbstractMultiPoint constructor. * - * @param (array{0: float|int|string, 1: float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}|PointInterface)[] $points points of the multipoint - * @param int|SpatialReference $srid Spatial Reference Identifier + * @param (array{0: null|float|int|string, 1: null|float|int|string, 2 ?: null|float|int, 3 ?: null|float|int}|PointInterface)[] $points points of the multipoint + * @param int|SpatialReference $srid Spatial Reference Identifier * * @throws InvalidDimensionException when the point dimension is not compatible with the multipoint dimension * @throws InvalidValueException when coordinates of the point are invalid diff --git a/lib/Types/Dimension3z/Geography/Point.php b/lib/Types/Dimension3z/Geography/Point.php index 58e6408..96abae1 100644 --- a/lib/Types/Dimension3z/Geography/Point.php +++ b/lib/Types/Dimension3z/Geography/Point.php @@ -33,7 +33,7 @@ class Point extends AbstractPoint implements PointInterface /** * The Z coordinate (altitude or elevation). */ - protected float|int $z; + protected float|int|null $z = null; /** * New point constructor. @@ -58,10 +58,6 @@ public function __construct(float|int|string|null $x = null, float|int|string|nu return; } - if (null === $x || null === $y || null === $z) { - throw new InvalidValueException('All point coordinates must be provided, or all must be null for an empty point.'); - } - $this->initializeX($x); $this->initializeY($y); $this->initializeZ($z); @@ -120,7 +116,7 @@ public function getZ(): float|int|null * Convert this point to an array containing longitude (X), latitude (Y), and Z. * The SRID is not exported. * - * @return array{0 : float|int, 1 : float|int, 2 : float|int}|array{} + * @return array{0 : null|float|int, 1 : null|float|int, 2 : null|float|int}|array{} */ public function toArray(): array { @@ -134,11 +130,11 @@ public function toArray(): array /** * Set the Z coordinate. In geography, the Z coordinate is the elevation. * - * @param float|int $z Z coordinate + * @param null|float|int $z Z coordinate * * @throws InvalidValueException when y is not valid */ - protected function initializeZ(float|int $z): static + protected function initializeZ(float|int|null $z): static { $this->z = $z; diff --git a/lib/Types/Dimension3z/Geometry/Point.php b/lib/Types/Dimension3z/Geometry/Point.php index 3e74551..79b22ac 100644 --- a/lib/Types/Dimension3z/Geometry/Point.php +++ b/lib/Types/Dimension3z/Geometry/Point.php @@ -33,7 +33,7 @@ class Point extends AbstractPoint implements PointInterface /** * The Z coordinate or the elevation. */ - protected float|int $z; + protected float|int|null $z = null; /** * New point constructor. @@ -59,10 +59,6 @@ public function __construct(float|int|string|null $x = null, float|int|string|nu return; } - if (null === $x || null === $y || null === $z) { - throw new InvalidValueException('All point coordinates must be provided, or all must be null for an empty point.'); - } - $this->initializeX($x); $this->initializeY($y); $this->initializeZ($z); @@ -120,7 +116,7 @@ public function getZ(): float|int|null * Convert this point to an array containing X, Y, and Z coordinates. * The SRID is not exported. * - * @return array{0 : float|int, 1 : float|int, 2 : float|int}|array{} + * @return array{0 : null|float|int, 1 : null|float|int, 2 : null|float|int}|array{} */ public function toArray(): array { @@ -134,9 +130,9 @@ public function toArray(): array /** * Set the Z coordinate (elevation). * - * @param float|int $z Z (elevation) coordinate + * @param null|float|int $z Z (elevation) coordinate */ - protected function initializeZ(float|int $z): static + protected function initializeZ(float|int|null $z): static { $this->z = $z; diff --git a/lib/Types/Dimension4zm/AbstractLineString.php b/lib/Types/Dimension4zm/AbstractLineString.php index 8bc3964..12e55fb 100644 --- a/lib/Types/Dimension4zm/AbstractLineString.php +++ b/lib/Types/Dimension4zm/AbstractLineString.php @@ -26,8 +26,8 @@ abstract class AbstractLineString extends ParentLineString implements LineStringInterface { /** - * @param (array{0: float|int|string, 1: float|int|string, 2: float|int, 3: float|int}|PointInterface)[] $points points of the line string - * @param int|SpatialReference $srid Spatial Reference Identifier + * @param (array{0: null|float|int|string, 1: null|float|int|string, 2: null|float|int, 3: null|float|int}|PointInterface)[] $points points of the line string + * @param int|SpatialReference $srid Spatial Reference Identifier */ public function __construct(array $points, int|SpatialReference $srid = 0) { diff --git a/lib/Types/Dimension4zm/AbstractMultiPoint.php b/lib/Types/Dimension4zm/AbstractMultiPoint.php index 4c2b140..6a50d3a 100644 --- a/lib/Types/Dimension4zm/AbstractMultiPoint.php +++ b/lib/Types/Dimension4zm/AbstractMultiPoint.php @@ -31,8 +31,8 @@ abstract class AbstractMultiPoint extends ParentMultiPoint implements MultiPoint /** * Create a multipoint from XYZM coordinate arrays or point instances. * - * @param (array{0: float|int|string, 1: float|int|string, 2: float|int, 3: float|int}|PointInterface)[] $points points of the multipoint - * @param int|SpatialReference $srid Spatial Reference Identifier + * @param (array{0: null|float|int|string, 1: null|float|int|string, 2: null|float|int, 3: null|float|int}|PointInterface)[] $points points of the multipoint + * @param int|SpatialReference $srid Spatial Reference Identifier * * @throws InvalidDimensionException when a point has an incompatible dimension * @throws InvalidValueException when point coordinates are invalid diff --git a/lib/Types/Dimension4zm/Geography/Point.php b/lib/Types/Dimension4zm/Geography/Point.php index 8a6e8b8..2155513 100644 --- a/lib/Types/Dimension4zm/Geography/Point.php +++ b/lib/Types/Dimension4zm/Geography/Point.php @@ -26,9 +26,9 @@ class Point extends AbstractPoint implements PointInterface { - protected float|int $m; + protected float|int|null $m = null; - protected float|int $z; + protected float|int|null $z = null; /** * Create a four-dimensional geographic point. @@ -52,10 +52,6 @@ public function __construct(float|int|string|null $x = null, float|int|string|nu return; } - if (null === $x || null === $y || null === $z || null === $m) { - throw new InvalidValueException('All point coordinates must be provided, or all must be null for an empty point.'); - } - $this->initializeX($x); $this->initializeY($y); $this->initializeZ($z); @@ -106,7 +102,7 @@ public function getZ(): float|int|null /** * Convert the point to its ordered longitude-latitude-Z-M coordinates; the SRID is omitted. * - * @return array{0: float|int, 1: float|int, 2: float|int, 3: float|int}|array{} + * @return array{0: null|float|int, 1: null|float|int, 2: null|float|int, 3: null|float|int}|array{} */ public function toArray(): array { @@ -120,9 +116,9 @@ public function toArray(): array /** * Set the M (measure) coordinate. * - * @param float|int $m M (measure) coordinate + * @param null|float|int $m M (measure) coordinate */ - protected function initializeM(float|int $m): static + protected function initializeM(float|int|null $m): static { $this->m = $m; @@ -132,9 +128,9 @@ protected function initializeM(float|int $m): static /** * Set the Z (elevation) coordinate. * - * @param float|int $z Z (elevation) coordinate + * @param null|float|int $z Z (elevation) coordinate */ - protected function initializeZ(float|int $z): static + protected function initializeZ(float|int|null $z): static { $this->z = $z; diff --git a/lib/Types/Dimension4zm/Geometry/Point.php b/lib/Types/Dimension4zm/Geometry/Point.php index bf8e414..45c5127 100644 --- a/lib/Types/Dimension4zm/Geometry/Point.php +++ b/lib/Types/Dimension4zm/Geometry/Point.php @@ -26,9 +26,9 @@ class Point extends AbstractPoint implements PointInterface { - protected float|int $m; + protected float|int|null $m = null; - protected float|int $z; + protected float|int|null $z = null; /** * Create a four-dimensional geometric point. @@ -52,10 +52,6 @@ public function __construct(float|int|string|null $x = null, float|int|string|nu return; } - if (null === $x || null === $y || null === $z || null === $m) { - throw new InvalidValueException('All point coordinates must be provided, or all must be null for an empty point.'); - } - $this->initializeX($x); $this->initializeY($y); $this->initializeZ($z); @@ -106,7 +102,7 @@ public function getZ(): float|int|null /** * Convert the point to its ordered XYZM coordinates; the SRID is omitted. * - * @return array{0: float|int, 1: float|int, 2: float|int, 3: float|int}|array{} + * @return array{0: null|float|int, 1: null|float|int, 2: null|float|int, 3: null|float|int}|array{} */ public function toArray(): array { @@ -120,9 +116,9 @@ public function toArray(): array /** * Set the M (measure) coordinate. * - * @param float|int $m M (measure) coordinate + * @param null|float|int $m M (measure) coordinate */ - protected function initializeM(float|int $m): static + protected function initializeM(float|int|null $m): static { $this->m = $m; @@ -132,9 +128,9 @@ protected function initializeM(float|int $m): static /** * Set the Z (elevation) coordinate. * - * @param float|int $z Z (elevation) coordinate + * @param null|float|int $z Z (elevation) coordinate */ - protected function initializeZ(float|int $z): static + protected function initializeZ(float|int|null $z): static { $this->z = $z; diff --git a/lib/Value/Coordinates.php b/lib/Value/Coordinates.php index cf1dda4..94722ea 100644 --- a/lib/Value/Coordinates.php +++ b/lib/Value/Coordinates.php @@ -23,14 +23,14 @@ * Immutable, normalized ordinates of a point. * * Unlike the internal Factory\Coordinates input DTO, this value object only - * accepts numeric ordinates and always carries its coordinate dimension. + * accepts numeric or null ordinates and always carries its coordinate dimension. */ final readonly class Coordinates { /** * @param CoordinateDimensionEnum $dimension coordinate layout represented by these ordinates - * @param float|int $x X coordinate or longitude - * @param float|int $y Y coordinate or latitude + * @param null|float|int $x X coordinate or longitude + * @param null|float|int $y Y coordinate or latitude * @param null|float|int $z Z coordinate or elevation * @param null|float|int $m M coordinate or measure * @@ -38,12 +38,12 @@ */ public function __construct( public CoordinateDimensionEnum $dimension, - public float|int $x, - public float|int $y, + public float|int|null $x, + public float|int|null $y, public float|int|null $z = null, public float|int|null $m = null ) { - if ($dimension->hasZ() !== (null !== $z) || $dimension->hasM() !== (null !== $m)) { + if ((!$dimension->hasZ() && null !== $z) || (!$dimension->hasM() && null !== $m)) { throw new InvalidDimensionException(sprintf('The supplied ordinates do not match the %s coordinate dimension.', $dimension->value)); } } @@ -51,10 +51,10 @@ public function __construct( /** * Create a two-dimensional XY coordinate value. * - * @param float|int $x X coordinate or longitude - * @param float|int $y Y coordinate or latitude + * @param null|float|int $x X coordinate or longitude + * @param null|float|int $y Y coordinate or latitude */ - public static function xy(float|int $x, float|int $y): self + public static function xy(float|int|null $x, float|int|null $y): self { return new self(CoordinateDimensionEnum::XY, $x, $y); } @@ -62,11 +62,11 @@ public static function xy(float|int $x, float|int $y): self /** * Create a three-dimensional XYM coordinate value. * - * @param float|int $x X coordinate or longitude - * @param float|int $y Y coordinate or latitude - * @param float|int $m M coordinate or measure + * @param null|float|int $x X coordinate or longitude + * @param null|float|int $y Y coordinate or latitude + * @param null|float|int $m M coordinate or measure */ - public static function xym(float|int $x, float|int $y, float|int $m): self + public static function xym(float|int|null $x, float|int|null $y, float|int|null $m): self { return new self(CoordinateDimensionEnum::XYM, $x, $y, null, $m); } @@ -74,11 +74,11 @@ public static function xym(float|int $x, float|int $y, float|int $m): self /** * Create a three-dimensional XYZ coordinate value. * - * @param float|int $x X coordinate or longitude - * @param float|int $y Y coordinate or latitude - * @param float|int $z Z coordinate or elevation + * @param null|float|int $x X coordinate or longitude + * @param null|float|int $y Y coordinate or latitude + * @param null|float|int $z Z coordinate or elevation */ - public static function xyz(float|int $x, float|int $y, float|int $z): self + public static function xyz(float|int|null $x, float|int|null $y, float|int|null $z): self { return new self(CoordinateDimensionEnum::XYZ, $x, $y, $z); } @@ -86,12 +86,12 @@ public static function xyz(float|int $x, float|int $y, float|int $z): self /** * Create a four-dimensional XYZM coordinate value. * - * @param float|int $x X coordinate or longitude - * @param float|int $y Y coordinate or latitude - * @param float|int $z Z coordinate or elevation - * @param float|int $m M coordinate or measure + * @param null|float|int $x X coordinate or longitude + * @param null|float|int $y Y coordinate or latitude + * @param null|float|int $z Z coordinate or elevation + * @param null|float|int $m M coordinate or measure */ - public static function xyzm(float|int $x, float|int $y, float|int $z, float|int $m): self + public static function xyzm(float|int|null $x, float|int|null $y, float|int|null $z, float|int|null $m): self { return new self(CoordinateDimensionEnum::XYZM, $x, $y, $z, $m); } @@ -101,9 +101,9 @@ public static function xyzm(float|int $x, float|int $y, float|int $z, float|int * * @throws InvalidDimensionException when the coordinate value has no M ordinate */ - public function getM(): float|int + public function getM(): float|int|null { - if (null === $this->m) { + if (!$this->dimension->hasM()) { throw new InvalidDimensionException(sprintf('The %s coordinate dimension has no M ordinate.', $this->dimension->value)); } @@ -115,9 +115,9 @@ public function getM(): float|int * * @throws InvalidDimensionException when the coordinate value has no Z ordinate */ - public function getZ(): float|int + public function getZ(): float|int|null { - if (null === $this->z) { + if (!$this->dimension->hasZ()) { throw new InvalidDimensionException(sprintf('The %s coordinate dimension has no Z ordinate.', $this->dimension->value)); } @@ -127,7 +127,7 @@ public function getZ(): float|int /** * Return the ordinates in the order defined by the coordinate dimension. * - * @return array{0: float|int, 1: float|int, 2: float|int, 3: float|int}|array{0: float|int, 1: float|int, 2: float|int}|array{0: float|int, 1: float|int} + * @return array{0: null|float|int, 1: null|float|int, 2: null|float|int, 3: null|float|int}|array{0: null|float|int, 1: null|float|int, 2: null|float|int}|array{0: null|float|int, 1: null|float|int} */ public function toArray(): array { @@ -142,11 +142,11 @@ public function toArray(): array /** * Return a copy with a different M coordinate. * - * @param float|int $m M coordinate or measure + * @param null|float|int $m M coordinate or measure * * @throws InvalidDimensionException when the coordinate value has no M ordinate */ - public function withM(float|int $m): self + public function withM(float|int|null $m): self { if (!$this->dimension->hasM()) { throw new InvalidDimensionException(sprintf('The %s coordinate dimension has no M ordinate.', $this->dimension->value)); @@ -158,9 +158,9 @@ public function withM(float|int $m): self /** * Return a copy with a different X coordinate. * - * @param float|int $x X coordinate or longitude + * @param null|float|int $x X coordinate or longitude */ - public function withX(float|int $x): self + public function withX(float|int|null $x): self { return new self($this->dimension, $x, $this->y, $this->z, $this->m); } @@ -168,9 +168,9 @@ public function withX(float|int $x): self /** * Return a copy with a different Y coordinate. * - * @param float|int $y Y coordinate or latitude + * @param null|float|int $y Y coordinate or latitude */ - public function withY(float|int $y): self + public function withY(float|int|null $y): self { return new self($this->dimension, $this->x, $y, $this->z, $this->m); } @@ -178,11 +178,11 @@ public function withY(float|int $y): self /** * Return a copy with a different Z coordinate. * - * @param float|int $z Z coordinate or elevation + * @param null|float|int $z Z coordinate or elevation * * @throws InvalidDimensionException when the coordinate value has no Z ordinate */ - public function withZ(float|int $z): self + public function withZ(float|int|null $z): self { if (!$this->dimension->hasZ()) { throw new InvalidDimensionException(sprintf('The %s coordinate dimension has no Z ordinate.', $this->dimension->value)); diff --git a/tests/Issue/PartiallyNullCompoundCurveTest.php b/tests/Issue/PartiallyNullCompoundCurveTest.php new file mode 100644 index 0000000..90dc9e0 --- /dev/null +++ b/tests/Issue/PartiallyNullCompoundCurveTest.php @@ -0,0 +1,309 @@ + 2024-2026 + * Copyright Longitude One 2024-2026 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +declare(strict_types=1); + +namespace LongitudeOne\SpatialTypes\Tests\Issue; + +use LongitudeOne\Core\Enum\CoordinateDimensionEnum; +use LongitudeOne\Core\Enum\SpatialModelEnum; +use LongitudeOne\SpatialTypes\Reference\SpatialReference; +use LongitudeOne\SpatialTypes\Types\Dimension3z\Geography\CircularString; +use LongitudeOne\SpatialTypes\Types\Dimension3z\Geography\CompoundCurve; +use LongitudeOne\SpatialTypes\Types\Dimension3z\Geography\LineString; +use PHPUnit\Framework\TestCase; + +/** + * Regression coverage for the compound-curve shared path in issue #36. + * + * @internal + * + * @covers \LongitudeOne\SpatialTypes\Types\AbstractCompoundCurve + */ +class PartiallyNullCompoundCurveTest extends TestCase +{ + /** Null ordinates survive construction, access and independent reference copies. */ + public function testElevationGeographyComponentsPreserveNullOrdinates(): void + { + $line = new LineString([[0, null, null], [null, 2, 3.5]], 4326); + $circular = new CircularString([[null, 2, 3.5], [1.5, null, 4], [0, 5, null]], 4326); + $compound = new CompoundCurve([$line, $circular], 4326); + $copy = $compound->withSpatialReference(SpatialReference::epsg(3857)); + $legacyCopy = $compound->withSrid(2154); + + static::assertSame([[[0, null, null], [null, 2, 3.5]], [[null, 2, 3.5], [1.5, null, 4], [0, 5, null]]], $compound->toArray()); + static::assertSame([[[0, null, null], [null, 2, 3.5]], [[null, 2, 3.5], [1.5, null, 4], [0, 5, null]]], $copy->toArray()); + static::assertSame([[[0, null, null], [null, 2, 3.5]], [[null, 2, 3.5], [1.5, null, 4], [0, 5, null]]], $legacyCopy->toArray()); + static::assertSame([[0, null, null], [null, 2, 3.5]], $copy->getCurve(0)->toArray()); + static::assertSame([[null, 2, 3.5], [1.5, null, 4], [0, 5, null]], $copy->getCurve(1)->toArray()); + static::assertSame(CoordinateDimensionEnum::XYZ, $copy->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $copy->getFamily()); + static::assertFalse($compound->isEmpty()); + static::assertNotSame($compound, $copy); + static::assertNotSame($line, $copy->getCurve(0)); + static::assertNotSame($line->getPoint(0), $copy->getStartPoint()); + static::assertNotSame($circular, $copy->getCurve(1)); + static::assertSame(CompoundCurve::class, $copy::class); + static::assertSame(LineString::class, $copy->getCurve(0)::class); + static::assertSame(CircularString::class, $copy->getCurve(1)::class); + static::assertSame(4326, $compound->getSrid()); + static::assertSame(4326, $line->getPoint(0)->getSrid()); + static::assertSame(3857, $copy->getSrid()); + static::assertSame(3857, $copy->getStartPoint()?->getSrid()); + static::assertSame(3857, $copy->getEndPoint()?->getSrid()); + static::assertSame(2154, $legacyCopy->getStartPoint()?->getSrid()); + } + + /** Null ordinates survive construction, access and independent reference copies. */ + public function testElevationGeometryComponentsPreserveNullOrdinates(): void + { + $line = new \LongitudeOne\SpatialTypes\Types\Dimension3z\Geometry\LineString([[0, null, null], [null, 2, 3.5]], 4326); + $circular = new \LongitudeOne\SpatialTypes\Types\Dimension3z\Geometry\CircularString([[null, 2, 3.5], [1.5, null, 4], [0, 5, null]], 4326); + $compound = new \LongitudeOne\SpatialTypes\Types\Dimension3z\Geometry\CompoundCurve([$line, $circular], 4326); + $copy = $compound->withSpatialReference(SpatialReference::epsg(3857)); + $legacyCopy = $compound->withSrid(2154); + + static::assertSame([[[0, null, null], [null, 2, 3.5]], [[null, 2, 3.5], [1.5, null, 4], [0, 5, null]]], $compound->toArray()); + static::assertSame([[[0, null, null], [null, 2, 3.5]], [[null, 2, 3.5], [1.5, null, 4], [0, 5, null]]], $copy->toArray()); + static::assertSame([[[0, null, null], [null, 2, 3.5]], [[null, 2, 3.5], [1.5, null, 4], [0, 5, null]]], $legacyCopy->toArray()); + static::assertSame([[0, null, null], [null, 2, 3.5]], $copy->getCurve(0)->toArray()); + static::assertSame([[null, 2, 3.5], [1.5, null, 4], [0, 5, null]], $copy->getCurve(1)->toArray()); + static::assertSame(CoordinateDimensionEnum::XYZ, $copy->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $copy->getFamily()); + static::assertFalse($compound->isEmpty()); + static::assertNotSame($compound, $copy); + static::assertNotSame($line, $copy->getCurve(0)); + static::assertNotSame($line->getPoint(0), $copy->getStartPoint()); + static::assertNotSame($circular, $copy->getCurve(1)); + static::assertSame(\LongitudeOne\SpatialTypes\Types\Dimension3z\Geometry\CompoundCurve::class, $copy::class); + static::assertSame(\LongitudeOne\SpatialTypes\Types\Dimension3z\Geometry\LineString::class, $copy->getCurve(0)::class); + static::assertSame(\LongitudeOne\SpatialTypes\Types\Dimension3z\Geometry\CircularString::class, $copy->getCurve(1)::class); + static::assertSame(4326, $compound->getSrid()); + static::assertSame(4326, $line->getPoint(0)->getSrid()); + static::assertSame(3857, $copy->getSrid()); + static::assertSame(3857, $copy->getStartPoint()?->getSrid()); + static::assertSame(3857, $copy->getEndPoint()?->getSrid()); + static::assertSame(2154, $legacyCopy->getStartPoint()?->getSrid()); + } + + /** An EMPTY member keeps its empty tuple beside a partially supplied point. */ + public function testEmptyPointMemberRemainsEmptyDuringReferenceCopy(): void + { + $line = new \LongitudeOne\SpatialTypes\Types\Dimension3m\Geometry\LineString([[0, null, 3], [null, null, null]], 4326); + $compound = new \LongitudeOne\SpatialTypes\Types\Dimension3m\Geometry\CompoundCurve([$line], 4326); + $copy = $compound->withSpatialReference(SpatialReference::epsg(3857)); + + static::assertFalse($compound->isEmpty()); + static::assertSame([[[0, null, 3], []]], $compound->toArray()); + static::assertSame([[[0, null, 3], []]], $copy->toArray()); + static::assertTrue($compound->getEndPoint()?->isEmpty()); + static::assertTrue($copy->getEndPoint()?->isEmpty()); + static::assertSame(CoordinateDimensionEnum::XYM, $copy->getEndPoint()->getDimension()); + static::assertSame(4326, $compound->getEndPoint()->getSrid()); + static::assertSame(3857, $copy->getEndPoint()->getSrid()); + static::assertNotSame($compound->getEndPoint(), $copy->getEndPoint()); + } + + /** Null ordinates survive construction, access and independent reference copies. */ + public function testFourDimensionalGeographyComponentsPreserveNullOrdinates(): void + { + $line = new \LongitudeOne\SpatialTypes\Types\Dimension4zm\Geography\LineString([[0, null, 3.5, null], [null, 2, null, 4]], 4326); + $circular = new \LongitudeOne\SpatialTypes\Types\Dimension4zm\Geography\CircularString([[null, 2, null, 4], [1.5, null, 3, null], [0, 5, null, null]], 4326); + $compound = new \LongitudeOne\SpatialTypes\Types\Dimension4zm\Geography\CompoundCurve([$line, $circular], 4326); + $copy = $compound->withSpatialReference(SpatialReference::epsg(3857)); + $legacyCopy = $compound->withSrid(2154); + + static::assertSame([[[0, null, 3.5, null], [null, 2, null, 4]], [[null, 2, null, 4], [1.5, null, 3, null], [0, 5, null, null]]], $compound->toArray()); + static::assertSame([[[0, null, 3.5, null], [null, 2, null, 4]], [[null, 2, null, 4], [1.5, null, 3, null], [0, 5, null, null]]], $copy->toArray()); + static::assertSame([[[0, null, 3.5, null], [null, 2, null, 4]], [[null, 2, null, 4], [1.5, null, 3, null], [0, 5, null, null]]], $legacyCopy->toArray()); + static::assertSame([[0, null, 3.5, null], [null, 2, null, 4]], $copy->getCurve(0)->toArray()); + static::assertSame([[null, 2, null, 4], [1.5, null, 3, null], [0, 5, null, null]], $copy->getCurve(1)->toArray()); + static::assertSame(CoordinateDimensionEnum::XYZM, $copy->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $copy->getFamily()); + static::assertFalse($compound->isEmpty()); + static::assertNotSame($compound, $copy); + static::assertNotSame($line, $copy->getCurve(0)); + static::assertNotSame($line->getPoint(0), $copy->getStartPoint()); + static::assertNotSame($circular, $copy->getCurve(1)); + static::assertSame(\LongitudeOne\SpatialTypes\Types\Dimension4zm\Geography\CompoundCurve::class, $copy::class); + static::assertSame(\LongitudeOne\SpatialTypes\Types\Dimension4zm\Geography\LineString::class, $copy->getCurve(0)::class); + static::assertSame(\LongitudeOne\SpatialTypes\Types\Dimension4zm\Geography\CircularString::class, $copy->getCurve(1)::class); + static::assertSame(4326, $compound->getSrid()); + static::assertSame(4326, $line->getPoint(0)->getSrid()); + static::assertSame(3857, $copy->getSrid()); + static::assertSame(3857, $copy->getStartPoint()?->getSrid()); + static::assertSame(3857, $copy->getEndPoint()?->getSrid()); + static::assertSame(2154, $legacyCopy->getStartPoint()?->getSrid()); + } + + /** Null ordinates survive construction, access and independent reference copies. */ + public function testFourDimensionalGeometryComponentsPreserveNullOrdinates(): void + { + $line = new \LongitudeOne\SpatialTypes\Types\Dimension4zm\Geometry\LineString([[0, null, 3.5, null], [null, 2, null, 4]], 4326); + $circular = new \LongitudeOne\SpatialTypes\Types\Dimension4zm\Geometry\CircularString([[null, 2, null, 4], [1.5, null, 3, null], [0, 5, null, null]], 4326); + $compound = new \LongitudeOne\SpatialTypes\Types\Dimension4zm\Geometry\CompoundCurve([$line, $circular], 4326); + $copy = $compound->withSpatialReference(SpatialReference::epsg(3857)); + $legacyCopy = $compound->withSrid(2154); + + static::assertSame([[[0, null, 3.5, null], [null, 2, null, 4]], [[null, 2, null, 4], [1.5, null, 3, null], [0, 5, null, null]]], $compound->toArray()); + static::assertSame([[[0, null, 3.5, null], [null, 2, null, 4]], [[null, 2, null, 4], [1.5, null, 3, null], [0, 5, null, null]]], $copy->toArray()); + static::assertSame([[[0, null, 3.5, null], [null, 2, null, 4]], [[null, 2, null, 4], [1.5, null, 3, null], [0, 5, null, null]]], $legacyCopy->toArray()); + static::assertSame([[0, null, 3.5, null], [null, 2, null, 4]], $copy->getCurve(0)->toArray()); + static::assertSame([[null, 2, null, 4], [1.5, null, 3, null], [0, 5, null, null]], $copy->getCurve(1)->toArray()); + static::assertSame(CoordinateDimensionEnum::XYZM, $copy->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $copy->getFamily()); + static::assertFalse($compound->isEmpty()); + static::assertNotSame($compound, $copy); + static::assertNotSame($line, $copy->getCurve(0)); + static::assertNotSame($line->getPoint(0), $copy->getStartPoint()); + static::assertNotSame($circular, $copy->getCurve(1)); + static::assertSame(\LongitudeOne\SpatialTypes\Types\Dimension4zm\Geometry\CompoundCurve::class, $copy::class); + static::assertSame(\LongitudeOne\SpatialTypes\Types\Dimension4zm\Geometry\LineString::class, $copy->getCurve(0)::class); + static::assertSame(\LongitudeOne\SpatialTypes\Types\Dimension4zm\Geometry\CircularString::class, $copy->getCurve(1)::class); + static::assertSame(4326, $compound->getSrid()); + static::assertSame(4326, $line->getPoint(0)->getSrid()); + static::assertSame(3857, $copy->getSrid()); + static::assertSame(3857, $copy->getStartPoint()?->getSrid()); + static::assertSame(3857, $copy->getEndPoint()?->getSrid()); + static::assertSame(2154, $legacyCopy->getStartPoint()?->getSrid()); + } + + /** Null ordinates survive construction, access and independent reference copies. */ + public function testMeasureGeographyComponentsPreserveNullOrdinates(): void + { + $line = new \LongitudeOne\SpatialTypes\Types\Dimension3m\Geography\LineString([[0, null, null], [null, 2, 3.5]], 4326); + $circular = new \LongitudeOne\SpatialTypes\Types\Dimension3m\Geography\CircularString([[null, 2, 3.5], [1.5, null, 4], [0, 5, null]], 4326); + $compound = new \LongitudeOne\SpatialTypes\Types\Dimension3m\Geography\CompoundCurve([$line, $circular], 4326); + $copy = $compound->withSpatialReference(SpatialReference::epsg(3857)); + $legacyCopy = $compound->withSrid(2154); + + static::assertSame([[[0, null, null], [null, 2, 3.5]], [[null, 2, 3.5], [1.5, null, 4], [0, 5, null]]], $compound->toArray()); + static::assertSame([[[0, null, null], [null, 2, 3.5]], [[null, 2, 3.5], [1.5, null, 4], [0, 5, null]]], $copy->toArray()); + static::assertSame([[[0, null, null], [null, 2, 3.5]], [[null, 2, 3.5], [1.5, null, 4], [0, 5, null]]], $legacyCopy->toArray()); + static::assertSame([[0, null, null], [null, 2, 3.5]], $copy->getCurve(0)->toArray()); + static::assertSame([[null, 2, 3.5], [1.5, null, 4], [0, 5, null]], $copy->getCurve(1)->toArray()); + static::assertSame(CoordinateDimensionEnum::XYM, $copy->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $copy->getFamily()); + static::assertFalse($compound->isEmpty()); + static::assertNotSame($compound, $copy); + static::assertNotSame($line, $copy->getCurve(0)); + static::assertNotSame($line->getPoint(0), $copy->getStartPoint()); + static::assertNotSame($circular, $copy->getCurve(1)); + static::assertSame(\LongitudeOne\SpatialTypes\Types\Dimension3m\Geography\CompoundCurve::class, $copy::class); + static::assertSame(\LongitudeOne\SpatialTypes\Types\Dimension3m\Geography\LineString::class, $copy->getCurve(0)::class); + static::assertSame(\LongitudeOne\SpatialTypes\Types\Dimension3m\Geography\CircularString::class, $copy->getCurve(1)::class); + static::assertSame(4326, $compound->getSrid()); + static::assertSame(4326, $line->getPoint(0)->getSrid()); + static::assertSame(3857, $copy->getSrid()); + static::assertSame(3857, $copy->getStartPoint()?->getSrid()); + static::assertSame(3857, $copy->getEndPoint()?->getSrid()); + static::assertSame(2154, $legacyCopy->getStartPoint()?->getSrid()); + } + + /** Null ordinates survive construction, access and independent reference copies. */ + public function testMeasureGeometryComponentsPreserveNullOrdinates(): void + { + $line = new \LongitudeOne\SpatialTypes\Types\Dimension3m\Geometry\LineString([[0, null, null], [null, 2, 3.5]], 4326); + $circular = new \LongitudeOne\SpatialTypes\Types\Dimension3m\Geometry\CircularString([[null, 2, 3.5], [1.5, null, 4], [0, 5, null]], 4326); + $compound = new \LongitudeOne\SpatialTypes\Types\Dimension3m\Geometry\CompoundCurve([$line, $circular], 4326); + $copy = $compound->withSpatialReference(SpatialReference::epsg(3857)); + $legacyCopy = $compound->withSrid(2154); + + static::assertSame([[[0, null, null], [null, 2, 3.5]], [[null, 2, 3.5], [1.5, null, 4], [0, 5, null]]], $compound->toArray()); + static::assertSame([[[0, null, null], [null, 2, 3.5]], [[null, 2, 3.5], [1.5, null, 4], [0, 5, null]]], $copy->toArray()); + static::assertSame([[[0, null, null], [null, 2, 3.5]], [[null, 2, 3.5], [1.5, null, 4], [0, 5, null]]], $legacyCopy->toArray()); + static::assertSame([[0, null, null], [null, 2, 3.5]], $copy->getCurve(0)->toArray()); + static::assertSame([[null, 2, 3.5], [1.5, null, 4], [0, 5, null]], $copy->getCurve(1)->toArray()); + static::assertSame(CoordinateDimensionEnum::XYM, $copy->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $copy->getFamily()); + static::assertFalse($compound->isEmpty()); + static::assertNotSame($compound, $copy); + static::assertNotSame($line, $copy->getCurve(0)); + static::assertNotSame($line->getPoint(0), $copy->getStartPoint()); + static::assertNotSame($circular, $copy->getCurve(1)); + static::assertSame(\LongitudeOne\SpatialTypes\Types\Dimension3m\Geometry\CompoundCurve::class, $copy::class); + static::assertSame(\LongitudeOne\SpatialTypes\Types\Dimension3m\Geometry\LineString::class, $copy->getCurve(0)::class); + static::assertSame(\LongitudeOne\SpatialTypes\Types\Dimension3m\Geometry\CircularString::class, $copy->getCurve(1)::class); + static::assertSame(4326, $compound->getSrid()); + static::assertSame(4326, $line->getPoint(0)->getSrid()); + static::assertSame(3857, $copy->getSrid()); + static::assertSame(3857, $copy->getStartPoint()?->getSrid()); + static::assertSame(3857, $copy->getEndPoint()?->getSrid()); + static::assertSame(2154, $legacyCopy->getStartPoint()?->getSrid()); + } + + /** Null ordinates survive construction, access and independent reference copies. */ + public function testPlanarGeographyComponentsPreserveNullOrdinates(): void + { + $line = new \LongitudeOne\SpatialTypes\Types\Dimension2\Geography\LineString([[0, null], [null, 2]], 4326); + $circular = new \LongitudeOne\SpatialTypes\Types\Dimension2\Geography\CircularString([[null, 2], [1.5, null], [0, null]], 4326); + $compound = new \LongitudeOne\SpatialTypes\Types\Dimension2\Geography\CompoundCurve([$line, $circular], 4326); + $copy = $compound->withSpatialReference(SpatialReference::epsg(3857)); + $legacyCopy = $compound->withSrid(2154); + + static::assertSame([[[0, null], [null, 2]], [[null, 2], [1.5, null], [0, null]]], $compound->toArray()); + static::assertSame([[[0, null], [null, 2]], [[null, 2], [1.5, null], [0, null]]], $copy->toArray()); + static::assertSame([[[0, null], [null, 2]], [[null, 2], [1.5, null], [0, null]]], $legacyCopy->toArray()); + static::assertSame([[0, null], [null, 2]], $copy->getCurve(0)->toArray()); + static::assertSame([[null, 2], [1.5, null], [0, null]], $copy->getCurve(1)->toArray()); + static::assertSame(CoordinateDimensionEnum::XY, $copy->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $copy->getFamily()); + static::assertFalse($compound->isEmpty()); + static::assertNotSame($compound, $copy); + static::assertNotSame($line, $copy->getCurve(0)); + static::assertNotSame($line->getPoint(0), $copy->getStartPoint()); + static::assertNotSame($circular, $copy->getCurve(1)); + static::assertSame(\LongitudeOne\SpatialTypes\Types\Dimension2\Geography\CompoundCurve::class, $copy::class); + static::assertSame(\LongitudeOne\SpatialTypes\Types\Dimension2\Geography\LineString::class, $copy->getCurve(0)::class); + static::assertSame(\LongitudeOne\SpatialTypes\Types\Dimension2\Geography\CircularString::class, $copy->getCurve(1)::class); + static::assertSame(4326, $compound->getSrid()); + static::assertSame(4326, $line->getPoint(0)->getSrid()); + static::assertSame(3857, $copy->getSrid()); + static::assertSame(3857, $copy->getStartPoint()?->getSrid()); + static::assertSame(3857, $copy->getEndPoint()?->getSrid()); + static::assertSame(2154, $legacyCopy->getStartPoint()?->getSrid()); + } + + /** Null ordinates survive construction, access and independent reference copies. */ + public function testPlanarGeometryComponentsPreserveNullOrdinates(): void + { + $line = new \LongitudeOne\SpatialTypes\Types\Dimension2\Geometry\LineString([[0, null], [null, 2]], 4326); + $circular = new \LongitudeOne\SpatialTypes\Types\Dimension2\Geometry\CircularString([[null, 2], [1.5, null], [0, null]], 4326); + $compound = new \LongitudeOne\SpatialTypes\Types\Dimension2\Geometry\CompoundCurve([$line, $circular], 4326); + $copy = $compound->withSpatialReference(SpatialReference::epsg(3857)); + $legacyCopy = $compound->withSrid(2154); + + static::assertSame([[[0, null], [null, 2]], [[null, 2], [1.5, null], [0, null]]], $compound->toArray()); + static::assertSame([[[0, null], [null, 2]], [[null, 2], [1.5, null], [0, null]]], $copy->toArray()); + static::assertSame([[[0, null], [null, 2]], [[null, 2], [1.5, null], [0, null]]], $legacyCopy->toArray()); + static::assertSame([[0, null], [null, 2]], $copy->getCurve(0)->toArray()); + static::assertSame([[null, 2], [1.5, null], [0, null]], $copy->getCurve(1)->toArray()); + static::assertSame(CoordinateDimensionEnum::XY, $copy->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $copy->getFamily()); + static::assertFalse($compound->isEmpty()); + static::assertNotSame($compound, $copy); + static::assertNotSame($line, $copy->getCurve(0)); + static::assertNotSame($line->getPoint(0), $copy->getStartPoint()); + static::assertNotSame($circular, $copy->getCurve(1)); + static::assertSame(\LongitudeOne\SpatialTypes\Types\Dimension2\Geometry\CompoundCurve::class, $copy::class); + static::assertSame(\LongitudeOne\SpatialTypes\Types\Dimension2\Geometry\LineString::class, $copy->getCurve(0)::class); + static::assertSame(\LongitudeOne\SpatialTypes\Types\Dimension2\Geometry\CircularString::class, $copy->getCurve(1)::class); + static::assertSame(4326, $compound->getSrid()); + static::assertSame(4326, $line->getPoint(0)->getSrid()); + static::assertSame(3857, $copy->getSrid()); + static::assertSame(3857, $copy->getStartPoint()?->getSrid()); + static::assertSame(3857, $copy->getEndPoint()?->getSrid()); + static::assertSame(2154, $legacyCopy->getStartPoint()?->getSrid()); + } +} diff --git a/tests/Issue/PartiallyNullXYMPointTest.php b/tests/Issue/PartiallyNullXYMPointTest.php new file mode 100644 index 0000000..b4e9a42 --- /dev/null +++ b/tests/Issue/PartiallyNullXYMPointTest.php @@ -0,0 +1,467 @@ + 2024-2026 + * Copyright Longitude One 2024-2026 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +declare(strict_types=1); + +namespace LongitudeOne\SpatialTypes\Tests\Issue; + +use LongitudeOne\Core\Enum\CoordinateDimensionEnum; +use LongitudeOne\Core\Enum\SpatialModelEnum; +use LongitudeOne\SpatialTypes\Factory\FromIndexedArrayFactory; +use LongitudeOne\SpatialTypes\Types\Dimension3m\Geography\LineString; +use LongitudeOne\SpatialTypes\Types\Dimension3m\Geography\MultiPoint; +use LongitudeOne\SpatialTypes\Types\Dimension3m\Geography\Point as GeographyPoint; +use LongitudeOne\SpatialTypes\Types\Dimension3m\Geometry\Point as GeometryPoint; +use LongitudeOne\SpatialTypes\Value\Coordinates; +use PHPUnit\Framework\TestCase; + +/** + * Regression coverage for issue #36. + * + * @internal + * + * @covers \LongitudeOne\SpatialTypes\Types\AbstractPoint + */ +class PartiallyNullXYMPointTest extends TestCase +{ + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyAbscissaNullOrdinateMeasure(): void + { + $point = new GeographyPoint(0, null, 3, 4326); + $coordinates = Coordinates::xym(0, null, 3); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, null, 3], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYM); + $original = new GeographyPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, null, 3], $point->toArray()); + static::assertSame([0, null, 3], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertNull($point->getY()); + static::assertSame(3, $point->getM()); + static::assertSame(CoordinateDimensionEnum::XYM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyAbscissaNullOrdinateNullMeasure(): void + { + $point = new GeographyPoint(0, null, null, 4326); + $coordinates = Coordinates::xym(0, null, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, null, null], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYM); + $original = new GeographyPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, null, null], $point->toArray()); + static::assertSame([0, null, null], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertNull($point->getY()); + static::assertNull($point->getM()); + static::assertSame(CoordinateDimensionEnum::XYM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyAbscissaOrdinateMeasure(): void + { + $point = new GeographyPoint(0, 2, 3, 4326); + $coordinates = Coordinates::xym(0, 2, 3); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, 2, 3], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYM); + $original = new GeographyPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, 2, 3], $point->toArray()); + static::assertSame([0, 2, 3], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertSame(2, $point->getY()); + static::assertSame(3, $point->getM()); + static::assertSame(CoordinateDimensionEnum::XYM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyAbscissaOrdinateNullMeasure(): void + { + $point = new GeographyPoint(0, 2, null, 4326); + $coordinates = Coordinates::xym(0, 2, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, 2, null], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYM); + $original = new GeographyPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, 2, null], $point->toArray()); + static::assertSame([0, 2, null], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertSame(2, $point->getY()); + static::assertNull($point->getM()); + static::assertSame(CoordinateDimensionEnum::XYM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Shared aggregate constructors and replacements preserve null ordinates. */ + public function testGeographyAggregatePathsPreserveNullOrdinates(): void + { + $line = new LineString([[0, null, 3], [null, 2, null]], 4326); + $replacement = $line->withPoint(1, Coordinates::xym(null, null, 4)); + $multiPoint = new MultiPoint([[0, null, 3], [null, 2, null]], 4326); + + static::assertSame([[0, null, 3], [null, 2, null]], $line->toArray()); + static::assertSame([[0, null, 3], [null, null, 4]], $replacement->toArray()); + static::assertSame([[0, null, 3], [null, 2, null]], $multiPoint->toArray()); + static::assertSame(4326, $replacement->getPoint(1)->getSrid()); + static::assertSame(CoordinateDimensionEnum::XYM, $replacement->getPoint(1)->getDimension()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyNullAbscissaNullOrdinateMeasure(): void + { + $point = new GeographyPoint(null, null, 3, 4326); + $coordinates = Coordinates::xym(null, null, 3); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, null, 3], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYM); + $original = new GeographyPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([null, null, 3], $point->toArray()); + static::assertSame([null, null, 3], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertNull($point->getY()); + static::assertSame(3, $point->getM()); + static::assertSame(CoordinateDimensionEnum::XYM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyNullAbscissaNullOrdinateNullMeasure(): void + { + $point = new GeographyPoint(null, null, null, 4326); + $coordinates = Coordinates::xym(null, null, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, null, null], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYM); + $original = new GeographyPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertTrue($point->isEmpty()); + static::assertSame([], $point->toArray()); + static::assertSame([null, null, null], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertNull($point->getY()); + static::assertNull($point->getM()); + static::assertSame(CoordinateDimensionEnum::XYM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertNull($point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyNullAbscissaOrdinateMeasure(): void + { + $point = new GeographyPoint(null, 2, 3, 4326); + $coordinates = Coordinates::xym(null, 2, 3); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, 2, 3], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYM); + $original = new GeographyPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([null, 2, 3], $point->toArray()); + static::assertSame([null, 2, 3], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertSame(2, $point->getY()); + static::assertSame(3, $point->getM()); + static::assertSame(CoordinateDimensionEnum::XYM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyNullAbscissaOrdinateNullMeasure(): void + { + $point = new GeographyPoint(null, 2, null, 4326); + $coordinates = Coordinates::xym(null, 2, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, 2, null], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYM); + $original = new GeographyPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([null, 2, null], $point->toArray()); + static::assertSame([null, 2, null], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertSame(2, $point->getY()); + static::assertNull($point->getM()); + static::assertSame(CoordinateDimensionEnum::XYM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryAbscissaNullOrdinateMeasure(): void + { + $point = new GeometryPoint(0, null, 3, 4326); + $coordinates = Coordinates::xym(0, null, 3); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, null, 3], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYM); + $original = new GeometryPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, null, 3], $point->toArray()); + static::assertSame([0, null, 3], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertNull($point->getY()); + static::assertSame(3, $point->getM()); + static::assertSame(CoordinateDimensionEnum::XYM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryAbscissaNullOrdinateNullMeasure(): void + { + $point = new GeometryPoint(0, null, null, 4326); + $coordinates = Coordinates::xym(0, null, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, null, null], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYM); + $original = new GeometryPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, null, null], $point->toArray()); + static::assertSame([0, null, null], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertNull($point->getY()); + static::assertNull($point->getM()); + static::assertSame(CoordinateDimensionEnum::XYM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryAbscissaOrdinateMeasure(): void + { + $point = new GeometryPoint(0, 2, 3, 4326); + $coordinates = Coordinates::xym(0, 2, 3); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, 2, 3], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYM); + $original = new GeometryPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, 2, 3], $point->toArray()); + static::assertSame([0, 2, 3], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertSame(2, $point->getY()); + static::assertSame(3, $point->getM()); + static::assertSame(CoordinateDimensionEnum::XYM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryAbscissaOrdinateNullMeasure(): void + { + $point = new GeometryPoint(0, 2, null, 4326); + $coordinates = Coordinates::xym(0, 2, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, 2, null], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYM); + $original = new GeometryPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, 2, null], $point->toArray()); + static::assertSame([0, 2, null], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertSame(2, $point->getY()); + static::assertNull($point->getM()); + static::assertSame(CoordinateDimensionEnum::XYM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryNullAbscissaNullOrdinateMeasure(): void + { + $point = new GeometryPoint(null, null, 3, 4326); + $coordinates = Coordinates::xym(null, null, 3); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, null, 3], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYM); + $original = new GeometryPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([null, null, 3], $point->toArray()); + static::assertSame([null, null, 3], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertNull($point->getY()); + static::assertSame(3, $point->getM()); + static::assertSame(CoordinateDimensionEnum::XYM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryNullAbscissaNullOrdinateNullMeasure(): void + { + $point = new GeometryPoint(null, null, null, 4326); + $coordinates = Coordinates::xym(null, null, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, null, null], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYM); + $original = new GeometryPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertTrue($point->isEmpty()); + static::assertSame([], $point->toArray()); + static::assertSame([null, null, null], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertNull($point->getY()); + static::assertNull($point->getM()); + static::assertSame(CoordinateDimensionEnum::XYM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertNull($point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryNullAbscissaOrdinateMeasure(): void + { + $point = new GeometryPoint(null, 2, 3, 4326); + $coordinates = Coordinates::xym(null, 2, 3); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, 2, 3], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYM); + $original = new GeometryPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([null, 2, 3], $point->toArray()); + static::assertSame([null, 2, 3], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertSame(2, $point->getY()); + static::assertSame(3, $point->getM()); + static::assertSame(CoordinateDimensionEnum::XYM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryNullAbscissaOrdinateNullMeasure(): void + { + $point = new GeometryPoint(null, 2, null, 4326); + $coordinates = Coordinates::xym(null, 2, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, 2, null], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYM); + $original = new GeometryPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([null, 2, null], $point->toArray()); + static::assertSame([null, 2, null], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertSame(2, $point->getY()); + static::assertNull($point->getM()); + static::assertSame(CoordinateDimensionEnum::XYM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } +} diff --git a/tests/Issue/PartiallyNullXYPointTest.php b/tests/Issue/PartiallyNullXYPointTest.php new file mode 100644 index 0000000..32a3883 --- /dev/null +++ b/tests/Issue/PartiallyNullXYPointTest.php @@ -0,0 +1,235 @@ + 2024-2026 + * Copyright Longitude One 2024-2026 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +declare(strict_types=1); + +namespace LongitudeOne\SpatialTypes\Tests\Issue; + +use LongitudeOne\Core\Enum\CoordinateDimensionEnum; +use LongitudeOne\Core\Enum\SpatialModelEnum; +use LongitudeOne\SpatialTypes\Factory\FromIndexedArrayFactory; +use LongitudeOne\SpatialTypes\Types\Dimension2\Geography\Point as GeographyPoint; +use LongitudeOne\SpatialTypes\Types\Dimension2\Geometry\Point as GeometryPoint; +use LongitudeOne\SpatialTypes\Value\Coordinates; +use PHPUnit\Framework\TestCase; + +/** + * Regression coverage for issue #36. + * + * @internal + * + * @covers \LongitudeOne\SpatialTypes\Types\AbstractPoint + */ +class PartiallyNullXYPointTest extends TestCase +{ + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyAbscissaNullOrdinate(): void + { + $point = new GeographyPoint(0, null, 4326); + $coordinates = Coordinates::xy(0, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, null], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XY); + $original = new GeographyPoint(1, 2, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, null], $point->toArray()); + static::assertSame([0, null], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertNull($point->getY()); + static::assertSame(CoordinateDimensionEnum::XY, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyAbscissaOrdinate(): void + { + $point = new GeographyPoint(0, 2, 4326); + $coordinates = Coordinates::xy(0, 2); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, 2], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XY); + $original = new GeographyPoint(1, 2, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, 2], $point->toArray()); + static::assertSame([0, 2], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertSame(2, $point->getY()); + static::assertSame(CoordinateDimensionEnum::XY, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyNullAbscissaNullOrdinate(): void + { + $point = new GeographyPoint(null, null, 4326); + $coordinates = Coordinates::xy(null, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, null], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XY); + $original = new GeographyPoint(1, 2, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertTrue($point->isEmpty()); + static::assertSame([], $point->toArray()); + static::assertSame([null, null], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertNull($point->getY()); + static::assertSame(CoordinateDimensionEnum::XY, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertNull($point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyNullAbscissaOrdinate(): void + { + $point = new GeographyPoint(null, 2, 4326); + $coordinates = Coordinates::xy(null, 2); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, 2], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XY); + $original = new GeographyPoint(1, 2, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([null, 2], $point->toArray()); + static::assertSame([null, 2], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertSame(2, $point->getY()); + static::assertSame(CoordinateDimensionEnum::XY, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryAbscissaNullOrdinate(): void + { + $point = new GeometryPoint(0, null, 4326); + $coordinates = Coordinates::xy(0, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, null], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XY); + $original = new GeometryPoint(1, 2, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, null], $point->toArray()); + static::assertSame([0, null], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertNull($point->getY()); + static::assertSame(CoordinateDimensionEnum::XY, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryAbscissaOrdinate(): void + { + $point = new GeometryPoint(0, 2, 4326); + $coordinates = Coordinates::xy(0, 2); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, 2], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XY); + $original = new GeometryPoint(1, 2, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, 2], $point->toArray()); + static::assertSame([0, 2], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertSame(2, $point->getY()); + static::assertSame(CoordinateDimensionEnum::XY, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryNullAbscissaNullOrdinate(): void + { + $point = new GeometryPoint(null, null, 4326); + $coordinates = Coordinates::xy(null, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, null], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XY); + $original = new GeometryPoint(1, 2, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertTrue($point->isEmpty()); + static::assertSame([], $point->toArray()); + static::assertSame([null, null], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertNull($point->getY()); + static::assertSame(CoordinateDimensionEnum::XY, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertNull($point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryNullAbscissaOrdinate(): void + { + $point = new GeometryPoint(null, 2, 4326); + $coordinates = Coordinates::xy(null, 2); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, 2], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XY); + $original = new GeometryPoint(1, 2, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([null, 2], $point->toArray()); + static::assertSame([null, 2], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertSame(2, $point->getY()); + static::assertSame(CoordinateDimensionEnum::XY, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2], $original->toArray()); + static::assertFalse($original->isEmpty()); + } +} diff --git a/tests/Issue/PartiallyNullXYZMPointTest.php b/tests/Issue/PartiallyNullXYZMPointTest.php new file mode 100644 index 0000000..9caa198 --- /dev/null +++ b/tests/Issue/PartiallyNullXYZMPointTest.php @@ -0,0 +1,911 @@ + 2024-2026 + * Copyright Longitude One 2024-2026 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +declare(strict_types=1); + +namespace LongitudeOne\SpatialTypes\Tests\Issue; + +use LongitudeOne\Core\Enum\CoordinateDimensionEnum; +use LongitudeOne\Core\Enum\SpatialModelEnum; +use LongitudeOne\SpatialTypes\Factory\FromIndexedArrayFactory; +use LongitudeOne\SpatialTypes\Types\Dimension4zm\Geography\Point as GeographyPoint; +use LongitudeOne\SpatialTypes\Types\Dimension4zm\Geometry\Point as GeometryPoint; +use LongitudeOne\SpatialTypes\Types\Dimension4zm\Geometry\Polygon; +use LongitudeOne\SpatialTypes\Value\Coordinates; +use PHPUnit\Framework\TestCase; + +/** + * Regression coverage for issue #36. + * + * @internal + * + * @covers \LongitudeOne\SpatialTypes\Types\AbstractPoint + */ +class PartiallyNullXYZMPointTest extends TestCase +{ + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyAbscissaNullOrdinateElevationMeasure(): void + { + $point = new GeographyPoint(0, null, 3, 4, 4326); + $coordinates = Coordinates::xyzm(0, null, 3, 4); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, null, 3, 4], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYZM); + $original = new GeographyPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, null, 3, 4], $point->toArray()); + static::assertSame([0, null, 3, 4], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertNull($point->getY()); + static::assertSame(3, $point->getZ()); + static::assertSame(4, $point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyAbscissaNullOrdinateElevationNullMeasure(): void + { + $point = new GeographyPoint(0, null, 3, null, 4326); + $coordinates = Coordinates::xyzm(0, null, 3, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, null, 3, null], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYZM); + $original = new GeographyPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, null, 3, null], $point->toArray()); + static::assertSame([0, null, 3, null], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertNull($point->getY()); + static::assertSame(3, $point->getZ()); + static::assertNull($point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyAbscissaNullOrdinateNullElevationMeasure(): void + { + $point = new GeographyPoint(0, null, null, 4, 4326); + $coordinates = Coordinates::xyzm(0, null, null, 4); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, null, null, 4], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYZM); + $original = new GeographyPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, null, null, 4], $point->toArray()); + static::assertSame([0, null, null, 4], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertNull($point->getY()); + static::assertNull($point->getZ()); + static::assertSame(4, $point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyAbscissaNullOrdinateNullElevationNullMeasure(): void + { + $point = new GeographyPoint(0, null, null, null, 4326); + $coordinates = Coordinates::xyzm(0, null, null, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, null, null, null], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYZM); + $original = new GeographyPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, null, null, null], $point->toArray()); + static::assertSame([0, null, null, null], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertNull($point->getY()); + static::assertNull($point->getZ()); + static::assertNull($point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyAbscissaOrdinateElevationMeasure(): void + { + $point = new GeographyPoint(0, 2, 3, 4, 4326); + $coordinates = Coordinates::xyzm(0, 2, 3, 4); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, 2, 3, 4], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYZM); + $original = new GeographyPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, 2, 3, 4], $point->toArray()); + static::assertSame([0, 2, 3, 4], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertSame(2, $point->getY()); + static::assertSame(3, $point->getZ()); + static::assertSame(4, $point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyAbscissaOrdinateElevationNullMeasure(): void + { + $point = new GeographyPoint(0, 2, 3, null, 4326); + $coordinates = Coordinates::xyzm(0, 2, 3, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, 2, 3, null], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYZM); + $original = new GeographyPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, 2, 3, null], $point->toArray()); + static::assertSame([0, 2, 3, null], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertSame(2, $point->getY()); + static::assertSame(3, $point->getZ()); + static::assertNull($point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyAbscissaOrdinateNullElevationMeasure(): void + { + $point = new GeographyPoint(0, 2, null, 4, 4326); + $coordinates = Coordinates::xyzm(0, 2, null, 4); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, 2, null, 4], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYZM); + $original = new GeographyPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, 2, null, 4], $point->toArray()); + static::assertSame([0, 2, null, 4], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertSame(2, $point->getY()); + static::assertNull($point->getZ()); + static::assertSame(4, $point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyAbscissaOrdinateNullElevationNullMeasure(): void + { + $point = new GeographyPoint(0, 2, null, null, 4326); + $coordinates = Coordinates::xyzm(0, 2, null, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, 2, null, null], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYZM); + $original = new GeographyPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, 2, null, null], $point->toArray()); + static::assertSame([0, 2, null, null], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertSame(2, $point->getY()); + static::assertNull($point->getZ()); + static::assertNull($point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyNullAbscissaNullOrdinateElevationMeasure(): void + { + $point = new GeographyPoint(null, null, 3, 4, 4326); + $coordinates = Coordinates::xyzm(null, null, 3, 4); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, null, 3, 4], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYZM); + $original = new GeographyPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([null, null, 3, 4], $point->toArray()); + static::assertSame([null, null, 3, 4], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertNull($point->getY()); + static::assertSame(3, $point->getZ()); + static::assertSame(4, $point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyNullAbscissaNullOrdinateElevationNullMeasure(): void + { + $point = new GeographyPoint(null, null, 3, null, 4326); + $coordinates = Coordinates::xyzm(null, null, 3, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, null, 3, null], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYZM); + $original = new GeographyPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([null, null, 3, null], $point->toArray()); + static::assertSame([null, null, 3, null], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertNull($point->getY()); + static::assertSame(3, $point->getZ()); + static::assertNull($point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyNullAbscissaNullOrdinateNullElevationMeasure(): void + { + $point = new GeographyPoint(null, null, null, 4, 4326); + $coordinates = Coordinates::xyzm(null, null, null, 4); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, null, null, 4], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYZM); + $original = new GeographyPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([null, null, null, 4], $point->toArray()); + static::assertSame([null, null, null, 4], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertNull($point->getY()); + static::assertNull($point->getZ()); + static::assertSame(4, $point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyNullAbscissaNullOrdinateNullElevationNullMeasure(): void + { + $point = new GeographyPoint(null, null, null, null, 4326); + $coordinates = Coordinates::xyzm(null, null, null, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, null, null, null], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYZM); + $original = new GeographyPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertTrue($point->isEmpty()); + static::assertSame([], $point->toArray()); + static::assertSame([null, null, null, null], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertNull($point->getY()); + static::assertNull($point->getZ()); + static::assertNull($point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertNull($point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyNullAbscissaOrdinateElevationMeasure(): void + { + $point = new GeographyPoint(null, 2, 3, 4, 4326); + $coordinates = Coordinates::xyzm(null, 2, 3, 4); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, 2, 3, 4], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYZM); + $original = new GeographyPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([null, 2, 3, 4], $point->toArray()); + static::assertSame([null, 2, 3, 4], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertSame(2, $point->getY()); + static::assertSame(3, $point->getZ()); + static::assertSame(4, $point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyNullAbscissaOrdinateElevationNullMeasure(): void + { + $point = new GeographyPoint(null, 2, 3, null, 4326); + $coordinates = Coordinates::xyzm(null, 2, 3, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, 2, 3, null], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYZM); + $original = new GeographyPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([null, 2, 3, null], $point->toArray()); + static::assertSame([null, 2, 3, null], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertSame(2, $point->getY()); + static::assertSame(3, $point->getZ()); + static::assertNull($point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyNullAbscissaOrdinateNullElevationMeasure(): void + { + $point = new GeographyPoint(null, 2, null, 4, 4326); + $coordinates = Coordinates::xyzm(null, 2, null, 4); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, 2, null, 4], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYZM); + $original = new GeographyPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([null, 2, null, 4], $point->toArray()); + static::assertSame([null, 2, null, 4], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertSame(2, $point->getY()); + static::assertNull($point->getZ()); + static::assertSame(4, $point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyNullAbscissaOrdinateNullElevationNullMeasure(): void + { + $point = new GeographyPoint(null, 2, null, null, 4326); + $coordinates = Coordinates::xyzm(null, 2, null, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, 2, null, null], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYZM); + $original = new GeographyPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([null, 2, null, null], $point->toArray()); + static::assertSame([null, 2, null, null], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertSame(2, $point->getY()); + static::assertNull($point->getZ()); + static::assertNull($point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryAbscissaNullOrdinateElevationMeasure(): void + { + $point = new GeometryPoint(0, null, 3, 4, 4326); + $coordinates = Coordinates::xyzm(0, null, 3, 4); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, null, 3, 4], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYZM); + $original = new GeometryPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, null, 3, 4], $point->toArray()); + static::assertSame([0, null, 3, 4], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertNull($point->getY()); + static::assertSame(3, $point->getZ()); + static::assertSame(4, $point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryAbscissaNullOrdinateElevationNullMeasure(): void + { + $point = new GeometryPoint(0, null, 3, null, 4326); + $coordinates = Coordinates::xyzm(0, null, 3, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, null, 3, null], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYZM); + $original = new GeometryPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, null, 3, null], $point->toArray()); + static::assertSame([0, null, 3, null], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertNull($point->getY()); + static::assertSame(3, $point->getZ()); + static::assertNull($point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryAbscissaNullOrdinateNullElevationMeasure(): void + { + $point = new GeometryPoint(0, null, null, 4, 4326); + $coordinates = Coordinates::xyzm(0, null, null, 4); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, null, null, 4], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYZM); + $original = new GeometryPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, null, null, 4], $point->toArray()); + static::assertSame([0, null, null, 4], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertNull($point->getY()); + static::assertNull($point->getZ()); + static::assertSame(4, $point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryAbscissaNullOrdinateNullElevationNullMeasure(): void + { + $point = new GeometryPoint(0, null, null, null, 4326); + $coordinates = Coordinates::xyzm(0, null, null, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, null, null, null], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYZM); + $original = new GeometryPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, null, null, null], $point->toArray()); + static::assertSame([0, null, null, null], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertNull($point->getY()); + static::assertNull($point->getZ()); + static::assertNull($point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryAbscissaOrdinateElevationMeasure(): void + { + $point = new GeometryPoint(0, 2, 3, 4, 4326); + $coordinates = Coordinates::xyzm(0, 2, 3, 4); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, 2, 3, 4], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYZM); + $original = new GeometryPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, 2, 3, 4], $point->toArray()); + static::assertSame([0, 2, 3, 4], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertSame(2, $point->getY()); + static::assertSame(3, $point->getZ()); + static::assertSame(4, $point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryAbscissaOrdinateElevationNullMeasure(): void + { + $point = new GeometryPoint(0, 2, 3, null, 4326); + $coordinates = Coordinates::xyzm(0, 2, 3, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, 2, 3, null], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYZM); + $original = new GeometryPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, 2, 3, null], $point->toArray()); + static::assertSame([0, 2, 3, null], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertSame(2, $point->getY()); + static::assertSame(3, $point->getZ()); + static::assertNull($point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryAbscissaOrdinateNullElevationMeasure(): void + { + $point = new GeometryPoint(0, 2, null, 4, 4326); + $coordinates = Coordinates::xyzm(0, 2, null, 4); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, 2, null, 4], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYZM); + $original = new GeometryPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, 2, null, 4], $point->toArray()); + static::assertSame([0, 2, null, 4], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertSame(2, $point->getY()); + static::assertNull($point->getZ()); + static::assertSame(4, $point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryAbscissaOrdinateNullElevationNullMeasure(): void + { + $point = new GeometryPoint(0, 2, null, null, 4326); + $coordinates = Coordinates::xyzm(0, 2, null, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, 2, null, null], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYZM); + $original = new GeometryPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, 2, null, null], $point->toArray()); + static::assertSame([0, 2, null, null], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertSame(2, $point->getY()); + static::assertNull($point->getZ()); + static::assertNull($point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryNullAbscissaNullOrdinateElevationMeasure(): void + { + $point = new GeometryPoint(null, null, 3, 4, 4326); + $coordinates = Coordinates::xyzm(null, null, 3, 4); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, null, 3, 4], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYZM); + $original = new GeometryPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([null, null, 3, 4], $point->toArray()); + static::assertSame([null, null, 3, 4], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertNull($point->getY()); + static::assertSame(3, $point->getZ()); + static::assertSame(4, $point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryNullAbscissaNullOrdinateElevationNullMeasure(): void + { + $point = new GeometryPoint(null, null, 3, null, 4326); + $coordinates = Coordinates::xyzm(null, null, 3, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, null, 3, null], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYZM); + $original = new GeometryPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([null, null, 3, null], $point->toArray()); + static::assertSame([null, null, 3, null], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertNull($point->getY()); + static::assertSame(3, $point->getZ()); + static::assertNull($point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryNullAbscissaNullOrdinateNullElevationMeasure(): void + { + $point = new GeometryPoint(null, null, null, 4, 4326); + $coordinates = Coordinates::xyzm(null, null, null, 4); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, null, null, 4], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYZM); + $original = new GeometryPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([null, null, null, 4], $point->toArray()); + static::assertSame([null, null, null, 4], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertNull($point->getY()); + static::assertNull($point->getZ()); + static::assertSame(4, $point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryNullAbscissaNullOrdinateNullElevationNullMeasure(): void + { + $point = new GeometryPoint(null, null, null, null, 4326); + $coordinates = Coordinates::xyzm(null, null, null, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, null, null, null], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYZM); + $original = new GeometryPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertTrue($point->isEmpty()); + static::assertSame([], $point->toArray()); + static::assertSame([null, null, null, null], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertNull($point->getY()); + static::assertNull($point->getZ()); + static::assertNull($point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertNull($point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryNullAbscissaOrdinateElevationMeasure(): void + { + $point = new GeometryPoint(null, 2, 3, 4, 4326); + $coordinates = Coordinates::xyzm(null, 2, 3, 4); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, 2, 3, 4], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYZM); + $original = new GeometryPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([null, 2, 3, 4], $point->toArray()); + static::assertSame([null, 2, 3, 4], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertSame(2, $point->getY()); + static::assertSame(3, $point->getZ()); + static::assertSame(4, $point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryNullAbscissaOrdinateElevationNullMeasure(): void + { + $point = new GeometryPoint(null, 2, 3, null, 4326); + $coordinates = Coordinates::xyzm(null, 2, 3, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, 2, 3, null], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYZM); + $original = new GeometryPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([null, 2, 3, null], $point->toArray()); + static::assertSame([null, 2, 3, null], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertSame(2, $point->getY()); + static::assertSame(3, $point->getZ()); + static::assertNull($point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryNullAbscissaOrdinateNullElevationMeasure(): void + { + $point = new GeometryPoint(null, 2, null, 4, 4326); + $coordinates = Coordinates::xyzm(null, 2, null, 4); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, 2, null, 4], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYZM); + $original = new GeometryPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([null, 2, null, 4], $point->toArray()); + static::assertSame([null, 2, null, 4], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertSame(2, $point->getY()); + static::assertNull($point->getZ()); + static::assertSame(4, $point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryNullAbscissaOrdinateNullElevationNullMeasure(): void + { + $point = new GeometryPoint(null, 2, null, null, 4326); + $coordinates = Coordinates::xyzm(null, 2, null, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, 2, null, null], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYZM); + $original = new GeometryPoint(1, 2, 3, 4, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([null, 2, null, null], $point->toArray()); + static::assertSame([null, 2, null, null], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertSame(2, $point->getY()); + static::assertNull($point->getZ()); + static::assertNull($point->getM()); + static::assertSame(CoordinateDimensionEnum::XYZM, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3, 4], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Shared polygon hydration and replacement preserve partially supplied points. */ + public function testGeometryPolygonPathsPreserveNullOrdinates(): void + { + $polygon = new Polygon([[[0, null, 3, null], [null, 2, null, 4], [0, null, 3, null]]], 3857); + $replacement = $polygon->withPoint(0, 1, Coordinates::xyzm(null, null, 5, null)); + + static::assertSame([[[0, null, 3, null], [null, 2, null, 4], [0, null, 3, null]]], $polygon->toArray()); + static::assertSame([[[0, null, 3, null], [null, null, 5, null], [0, null, 3, null]]], $replacement->toArray()); + static::assertSame(3857, $replacement->getSrid()); + } +} diff --git a/tests/Issue/PartiallyNullXYZPointTest.php b/tests/Issue/PartiallyNullXYZPointTest.php new file mode 100644 index 0000000..70475bd --- /dev/null +++ b/tests/Issue/PartiallyNullXYZPointTest.php @@ -0,0 +1,451 @@ + 2024-2026 + * Copyright Longitude One 2024-2026 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +declare(strict_types=1); + +namespace LongitudeOne\SpatialTypes\Tests\Issue; + +use LongitudeOne\Core\Enum\CoordinateDimensionEnum; +use LongitudeOne\Core\Enum\SpatialModelEnum; +use LongitudeOne\SpatialTypes\Factory\FromIndexedArrayFactory; +use LongitudeOne\SpatialTypes\Types\Dimension3z\Geography\Point as GeographyPoint; +use LongitudeOne\SpatialTypes\Types\Dimension3z\Geometry\Point as GeometryPoint; +use LongitudeOne\SpatialTypes\Value\Coordinates; +use PHPUnit\Framework\TestCase; + +/** + * Regression coverage for issue #36. + * + * @internal + * + * @covers \LongitudeOne\SpatialTypes\Types\AbstractPoint + */ +class PartiallyNullXYZPointTest extends TestCase +{ + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyAbscissaNullOrdinateElevation(): void + { + $point = new GeographyPoint(0, null, 3, 4326); + $coordinates = Coordinates::xyz(0, null, 3); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, null, 3], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYZ); + $original = new GeographyPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, null, 3], $point->toArray()); + static::assertSame([0, null, 3], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertNull($point->getY()); + static::assertSame(3, $point->getZ()); + static::assertSame(CoordinateDimensionEnum::XYZ, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyAbscissaNullOrdinateNullElevation(): void + { + $point = new GeographyPoint(0, null, null, 4326); + $coordinates = Coordinates::xyz(0, null, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, null, null], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYZ); + $original = new GeographyPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, null, null], $point->toArray()); + static::assertSame([0, null, null], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertNull($point->getY()); + static::assertNull($point->getZ()); + static::assertSame(CoordinateDimensionEnum::XYZ, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyAbscissaOrdinateElevation(): void + { + $point = new GeographyPoint(0, 2, 3, 4326); + $coordinates = Coordinates::xyz(0, 2, 3); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, 2, 3], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYZ); + $original = new GeographyPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, 2, 3], $point->toArray()); + static::assertSame([0, 2, 3], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertSame(2, $point->getY()); + static::assertSame(3, $point->getZ()); + static::assertSame(CoordinateDimensionEnum::XYZ, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyAbscissaOrdinateNullElevation(): void + { + $point = new GeographyPoint(0, 2, null, 4326); + $coordinates = Coordinates::xyz(0, 2, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, 2, null], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYZ); + $original = new GeographyPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, 2, null], $point->toArray()); + static::assertSame([0, 2, null], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertSame(2, $point->getY()); + static::assertNull($point->getZ()); + static::assertSame(CoordinateDimensionEnum::XYZ, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyNullAbscissaNullOrdinateElevation(): void + { + $point = new GeographyPoint(null, null, 3, 4326); + $coordinates = Coordinates::xyz(null, null, 3); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, null, 3], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYZ); + $original = new GeographyPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([null, null, 3], $point->toArray()); + static::assertSame([null, null, 3], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertNull($point->getY()); + static::assertSame(3, $point->getZ()); + static::assertSame(CoordinateDimensionEnum::XYZ, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyNullAbscissaNullOrdinateNullElevation(): void + { + $point = new GeographyPoint(null, null, null, 4326); + $coordinates = Coordinates::xyz(null, null, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, null, null], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYZ); + $original = new GeographyPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertTrue($point->isEmpty()); + static::assertSame([], $point->toArray()); + static::assertSame([null, null, null], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertNull($point->getY()); + static::assertNull($point->getZ()); + static::assertSame(CoordinateDimensionEnum::XYZ, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertNull($point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyNullAbscissaOrdinateElevation(): void + { + $point = new GeographyPoint(null, 2, 3, 4326); + $coordinates = Coordinates::xyz(null, 2, 3); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, 2, 3], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYZ); + $original = new GeographyPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([null, 2, 3], $point->toArray()); + static::assertSame([null, 2, 3], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertSame(2, $point->getY()); + static::assertSame(3, $point->getZ()); + static::assertSame(CoordinateDimensionEnum::XYZ, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeographyNullAbscissaOrdinateNullElevation(): void + { + $point = new GeographyPoint(null, 2, null, 4326); + $coordinates = Coordinates::xyz(null, 2, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, 2, null], 4326, SpatialModelEnum::GEOGRAPHY, CoordinateDimensionEnum::XYZ); + $original = new GeographyPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([null, 2, null], $point->toArray()); + static::assertSame([null, 2, null], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertSame(2, $point->getY()); + static::assertNull($point->getZ()); + static::assertSame(CoordinateDimensionEnum::XYZ, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOGRAPHY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryAbscissaNullOrdinateElevation(): void + { + $point = new GeometryPoint(0, null, 3, 4326); + $coordinates = Coordinates::xyz(0, null, 3); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, null, 3], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYZ); + $original = new GeometryPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, null, 3], $point->toArray()); + static::assertSame([0, null, 3], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertNull($point->getY()); + static::assertSame(3, $point->getZ()); + static::assertSame(CoordinateDimensionEnum::XYZ, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryAbscissaNullOrdinateNullElevation(): void + { + $point = new GeometryPoint(0, null, null, 4326); + $coordinates = Coordinates::xyz(0, null, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, null, null], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYZ); + $original = new GeometryPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, null, null], $point->toArray()); + static::assertSame([0, null, null], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertNull($point->getY()); + static::assertNull($point->getZ()); + static::assertSame(CoordinateDimensionEnum::XYZ, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryAbscissaOrdinateElevation(): void + { + $point = new GeometryPoint(0, 2, 3, 4326); + $coordinates = Coordinates::xyz(0, 2, 3); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, 2, 3], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYZ); + $original = new GeometryPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, 2, 3], $point->toArray()); + static::assertSame([0, 2, 3], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertSame(2, $point->getY()); + static::assertSame(3, $point->getZ()); + static::assertSame(CoordinateDimensionEnum::XYZ, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryAbscissaOrdinateNullElevation(): void + { + $point = new GeometryPoint(0, 2, null, 4326); + $coordinates = Coordinates::xyz(0, 2, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([0, 2, null], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYZ); + $original = new GeometryPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([0, 2, null], $point->toArray()); + static::assertSame([0, 2, null], $coordinates->toArray()); + static::assertSame(0, $point->getX()); + static::assertSame(2, $point->getY()); + static::assertNull($point->getZ()); + static::assertSame(CoordinateDimensionEnum::XYZ, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryNullAbscissaNullOrdinateElevation(): void + { + $point = new GeometryPoint(null, null, 3, 4326); + $coordinates = Coordinates::xyz(null, null, 3); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, null, 3], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYZ); + $original = new GeometryPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([null, null, 3], $point->toArray()); + static::assertSame([null, null, 3], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertNull($point->getY()); + static::assertSame(3, $point->getZ()); + static::assertSame(CoordinateDimensionEnum::XYZ, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryNullAbscissaNullOrdinateNullElevation(): void + { + $point = new GeometryPoint(null, null, null, 4326); + $coordinates = Coordinates::xyz(null, null, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, null, null], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYZ); + $original = new GeometryPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertTrue($point->isEmpty()); + static::assertSame([], $point->toArray()); + static::assertSame([null, null, null], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertNull($point->getY()); + static::assertNull($point->getZ()); + static::assertSame(CoordinateDimensionEnum::XYZ, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertNull($point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryNullAbscissaOrdinateElevation(): void + { + $point = new GeometryPoint(null, 2, 3, 4326); + $coordinates = Coordinates::xyz(null, 2, 3); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, 2, 3], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYZ); + $original = new GeometryPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([null, 2, 3], $point->toArray()); + static::assertSame([null, 2, 3], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertSame(2, $point->getY()); + static::assertSame(3, $point->getZ()); + static::assertSame(CoordinateDimensionEnum::XYZ, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } + + /** Preserve supplied and null ordinates through every public point path. */ + public function testGeometryNullAbscissaOrdinateNullElevation(): void + { + $point = new GeometryPoint(null, 2, null, 4326); + $coordinates = Coordinates::xyz(null, 2, null); + $factoryPoint = FromIndexedArrayFactory::createPoint([null, 2, null], 4326, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYZ); + $original = new GeometryPoint(1, 2, 3, 4326); + $replacement = $original->withCoordinates($coordinates); + + static::assertFalse($point->isEmpty()); + static::assertSame([null, 2, null], $point->toArray()); + static::assertSame([null, 2, null], $coordinates->toArray()); + static::assertNull($point->getX()); + static::assertSame(2, $point->getY()); + static::assertNull($point->getZ()); + static::assertSame(CoordinateDimensionEnum::XYZ, $point->getDimension()); + static::assertSame(SpatialModelEnum::GEOMETRY, $point->getFamily()); + static::assertSame(4326, $point->getSrid()); + static::assertEquals($coordinates, $point->getCoordinates()); + static::assertEquals($point, $factoryPoint); + static::assertEquals($point, $replacement); + static::assertNotSame($original, $replacement); + static::assertSame([1, 2, 3], $original->toArray()); + static::assertFalse($original->isEmpty()); + } +} diff --git a/tests/Unit/Factory/FactoryPipelineTest.php b/tests/Unit/Factory/FactoryPipelineTest.php index 006436a..1deb7e0 100644 --- a/tests/Unit/Factory/FactoryPipelineTest.php +++ b/tests/Unit/Factory/FactoryPipelineTest.php @@ -20,7 +20,6 @@ use LongitudeOne\Core\Enum\SpatialModelEnum; use LongitudeOne\SpatialTypes\Exception\InvalidDimensionException; use LongitudeOne\SpatialTypes\Exception\InvalidValueException; -use LongitudeOne\SpatialTypes\Exception\MissingValueException; use LongitudeOne\SpatialTypes\Factory\Coordinates; use LongitudeOne\SpatialTypes\Factory\DefaultSpatialFactoryFactory; use LongitudeOne\SpatialTypes\Factory\FamilyFactories; @@ -50,7 +49,6 @@ * @covers \LongitudeOne\SpatialTypes\Factory\FamilyFactories * @covers \LongitudeOne\SpatialTypes\Factory\Hydrator\CoordinatesHydrator * @covers \LongitudeOne\SpatialTypes\Factory\Hydrator\SpatialArrayHydrator - * @covers \LongitudeOne\SpatialTypes\Factory\Internal\AbstractPointFactory * @covers \LongitudeOne\SpatialTypes\Factory\SpatialFactory * @covers \LongitudeOne\SpatialTypes\Factory\SpatialFactoryRegistry */ @@ -126,13 +124,26 @@ public function testCoordinatesHydratorLeavesUnsupportedOrdinatesNull(): void static::assertSame(6, $fourDimensionalCoordinates->m); } + /** The measure diagnostic describes both permitted ordinate types. */ + public function testCoordinatesHydratorRejectsNonNumericMeasureWithNullableDiagnostic(): void + { + self::expectException(InvalidValueException::class); + self::expectExceptionMessageIsOrContains('The M coordinate must be a number or null.'); + + (new \ReflectionMethod(CoordinatesHydrator::class, 'hydrate'))->invoke( + new CoordinatesHydrator(), + [1, 2, false], + new SpatialContext(0, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYM) + ); + } + /** * The coordinate hydrator rejects a non-numeric Z or M ordinate before point construction. */ public function testCoordinatesHydratorRejectsNonNumericOptionalOrdinate(): void { self::expectException(InvalidValueException::class); - self::expectExceptionMessageIsOrContains('Z coordinate must be a number'); + self::expectExceptionMessageIsOrContains('The Z coordinate must be a number or null.'); (new \ReflectionMethod(CoordinatesHydrator::class, 'hydrate'))->invoke( new CoordinatesHydrator(), @@ -196,17 +207,18 @@ static function (SpatialFactory $factory): void { } /** - * A point factory requires every ordinate declared by its dimension. + * A point factory preserves null ordinates in the declared dimension. */ - public function testPointFactoryRejectsMissingRequiredOrdinate(): void + public function testPointFactoryPreservesNullOrdinate(): void { - self::expectException(MissingValueException::class); - self::expectExceptionMessageIsOrContains('third coordinate is missing'); - - (new GeometricPointFactory())->create( + $point = (new GeometricPointFactory())->create( new Coordinates(1, 2), new SpatialContext(0, SpatialModelEnum::GEOMETRY, CoordinateDimensionEnum::XYZ) ); + + static::assertFalse($point->isEmpty()); + static::assertSame([1, 2, null], $point->toArray()); + static::assertSame(CoordinateDimensionEnum::XYZ, $point->getDimension()); } /** diff --git a/tests/Unit/Types/CompoundCurveValidationTest.php b/tests/Unit/Types/CompoundCurveValidationTest.php index 09ba7b6..8a8ef9a 100644 --- a/tests/Unit/Types/CompoundCurveValidationTest.php +++ b/tests/Unit/Types/CompoundCurveValidationTest.php @@ -83,6 +83,28 @@ public function testCommonCurveImplementation(): void static::assertFalse($compound->isClosed()); } + /** Booleans are not normalized into numeric component ordinates. */ + public function testComponentRejectsBooleanOrdinate(): void + { + $component = static::createStub(CurveInterface::class); + $component->method('toArray')->willReturn([[0, false]]); + $this->expectException(InvalidValueException::class); + $this->expectExceptionMessage('A compound curve component must expose numeric or null ordinates.'); + + new CompoundCurve([$component]); + } + + /** Strings remain invalid component ordinates when null is accepted. */ + public function testComponentRejectsNumericStringOrdinate(): void + { + $component = static::createStub(CurveInterface::class); + $component->method('toArray')->willReturn([[0, '2']]); + $this->expectException(InvalidValueException::class); + $this->expectExceptionMessage('A compound curve component must expose numeric or null ordinates.'); + + new CompoundCurve([$component]); + } + /** Indexed access follows the existing empty-aggregate exception contract. */ public function testEmptyIndexedAccess(): void { diff --git a/tests/Unit/Types/EmptyPointTest.php b/tests/Unit/Types/EmptyPointTest.php index 2a4ced1..3822bc8 100644 --- a/tests/Unit/Types/EmptyPointTest.php +++ b/tests/Unit/Types/EmptyPointTest.php @@ -18,7 +18,6 @@ use LongitudeOne\Core\Enum\CoordinateDimensionEnum; use LongitudeOne\Core\Enum\SpatialModelEnum; -use LongitudeOne\SpatialTypes\Exception\InvalidValueException; use LongitudeOne\SpatialTypes\Factory\FromIndexedArrayFactory; use LongitudeOne\SpatialTypes\Interfaces\PointInterface; use LongitudeOne\SpatialTypes\Types\Dimension2\Geometry\Point as GeometryPoint; @@ -156,14 +155,14 @@ public static function provideFamiliesAndDimensions(): \Generator } /** - * Point constructors reject an incomplete coordinate tuple. + * Point constructors preserve an incomplete coordinate tuple. */ - public function testPointConstructorRejectsPartialCoordinates(): void + public function testPointConstructorPreservesPartialCoordinates(): void { - self::expectException(InvalidValueException::class); - self::expectExceptionMessageIsOrContains('All point coordinates must be provided'); + $point = new GeometryPoint(null, 2); - new GeometryPoint(null, 2); + static::assertFalse($point->isEmpty()); + static::assertSame([null, 2], $point->toArray()); } /** diff --git a/tests/Unit/Value/CoordinatesTest.php b/tests/Unit/Value/CoordinatesTest.php index 71f6129..fc930db 100644 --- a/tests/Unit/Value/CoordinatesTest.php +++ b/tests/Unit/Value/CoordinatesTest.php @@ -30,7 +30,7 @@ class CoordinatesTest extends TestCase { /** - * Coordinates cannot carry only part of the ordinates required by their dimension. + * Coordinates reject supplied ordinates outside their dimension. * * @param \Closure $operation invalid coordinate value construction */ @@ -46,11 +46,7 @@ public function testConstructorRejectsIncompatibleOrdinates(\Closure $operation) */ public static function provideIncompatibleCoordinates(): \Generator { - yield 'XYM without measure' => [static fn () => new Coordinates(CoordinateDimensionEnum::XYM, 1, 2)]; - yield 'XYZ with measure instead of elevation' => [static fn () => new Coordinates(CoordinateDimensionEnum::XYZ, 1, 2, null, 3)]; - - yield 'XYZM without measure' => [static fn () => new Coordinates(CoordinateDimensionEnum::XYZM, 1, 2, 3)]; } /** @@ -64,6 +60,42 @@ public function testConstructorRejectsUnexpectedElevationForTwoDimensionalCoordi new Coordinates(CoordinateDimensionEnum::XY, 1, 2, 3); } + /** Null replacement preserves the layout and the original coordinate value. */ + public function testElevationCoordinatesAcceptNullReplacements(): void + { + $coordinates = Coordinates::xyz(1, 2, 3); + $replacement = $coordinates->withX(null)->withY(null)->withZ(null); + + static::assertNotSame($coordinates, $replacement); + static::assertSame([1, 2, 3], $coordinates->toArray()); + static::assertSame([null, null, null], $replacement->toArray()); + static::assertSame(CoordinateDimensionEnum::XYZ, $replacement->dimension); + } + + /** Null replacement preserves the layout and the original coordinate value. */ + public function testFourDimensionalCoordinatesAcceptNullReplacements(): void + { + $coordinates = Coordinates::xyzm(1, 2, 3, 4); + $replacement = $coordinates->withX(null)->withY(null)->withZ(null)->withM(null); + + static::assertNotSame($coordinates, $replacement); + static::assertSame([1, 2, 3, 4], $coordinates->toArray()); + static::assertSame([null, null, null, null], $replacement->toArray()); + static::assertSame(CoordinateDimensionEnum::XYZM, $replacement->dimension); + } + + /** Null replacement preserves the layout and the original coordinate value. */ + public function testMeasureCoordinatesAcceptNullReplacements(): void + { + $coordinates = Coordinates::xym(1, 2, 3); + $replacement = $coordinates->withX(null)->withY(null)->withM(null); + + static::assertNotSame($coordinates, $replacement); + static::assertSame([1, 2, 3], $coordinates->toArray()); + static::assertSame([null, null, null], $replacement->toArray()); + static::assertSame(CoordinateDimensionEnum::XYM, $replacement->dimension); + } + /** * Missing ordinates are rejected on read as well as on update. * @@ -146,4 +178,16 @@ public function testToArrayUsesCoordinateDimensionOrder(): void static::assertSame([1, 2, 3], Coordinates::xyz(1, 2, 3)->toArray()); static::assertSame([1, 2, 3, 4], Coordinates::xyzm(1, 2, 3, 4)->toArray()); } + + /** Null replacement preserves the layout and the original coordinate value. */ + public function testTwoDimensionalCoordinatesAcceptNullReplacements(): void + { + $coordinates = Coordinates::xy(1, 2); + $replacement = $coordinates->withX(null)->withY(null); + + static::assertNotSame($coordinates, $replacement); + static::assertSame([1, 2], $coordinates->toArray()); + static::assertSame([null, null], $replacement->toArray()); + static::assertSame(CoordinateDimensionEnum::XY, $replacement->dimension); + } }