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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,18 @@ The shared WKT/EWKT lexer uses `SpatialTypeImplementationStatus` from
keywords. `CIRCULARSTRING`, `COMPOUNDCURVE`, `CURVEPOLYGON`, `MULTICURVE`,
`MULTISURFACE`, and `TIN` raise `NotYetImplementedException`. Non-instantiable
enum types raise the decoder's `InvalidArgumentException`. Implemented types
continue to the existing parsers; implementation in the spatial model does not
by itself imply parser support (for example, `TRIANGLE` and `POLYHEDRALSURFACE`).
continue to the representation-specific parsers. EWKT supports `POINT`,
`LINESTRING`, `POLYGON`, `TRIANGLE`, `POLYHEDRALSURFACE`, `MULTIPOINT`,
`MULTILINESTRING`, `MULTIPOLYGON`, and `GEOMETRYCOLLECTION`, including nested
collections and supported EMPTY values. Standard WKT retains its existing
geometry set and does not decode `TRIANGLE` or `POLYHEDRALSURFACE`.

EWKT triangles support XY, XYZ, XYM, and XYZM. Polyhedral surfaces require XYZ
or XYZM, including for EMPTY values; XY and XYM raise `InvalidArgumentException`,
not `NotYetImplementedException`. Surface construction validates triangle rings
and polyhedral patches using the spatial model constraints, reporting invalid
structures through the decoder's `InvalidArgumentException`. Root SRID,
coordinates, dimensions, and the geometry family are preserved recursively.

Classification happens during lexical scanning, before grammar validation,
including inside collections. A recognized unimplemented keyword therefore
Expand Down
6 changes: 3 additions & 3 deletions lib/Strategy/Wkt/Parser/MultiPolygonWktParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
use LongitudeOne\SpatialDecoder\Strategy\Wkt\Factory\WktPolygonFactory;
use LongitudeOne\SpatialDecoder\Strategy\Wkt\WktCoordinateReader;
use LongitudeOne\SpatialDecoder\Strategy\Wkt\WktTokenCursor;
use LongitudeOne\SpatialTypes\Interfaces\SpatialInterface;
use LongitudeOne\SpatialTypes\Interfaces\MultiPolygonInterface;

/**
* Parses WKT multi-polygon representations.
Expand Down Expand Up @@ -55,9 +55,9 @@ public function __construct(
*
* @param CoordinateDimensionEnum|null $inheritedDimension dimension inherited from a parent collection
*
* @return SpatialInterface the decoded multi-polygon
* @return MultiPolygonInterface the decoded multi-polygon
*/
public function parse(?CoordinateDimensionEnum $inheritedDimension = null): SpatialInterface
public function parse(?CoordinateDimensionEnum $inheritedDimension = null): MultiPolygonInterface
{
$dimension = $this->coordinateReader->consumeDimension($inheritedDimension);
if ($this->cursor->isNextToken(Lexer::T_EMPTY)) {
Expand Down
6 changes: 3 additions & 3 deletions lib/Strategy/Wkt/Parser/PolygonWktParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
use LongitudeOne\SpatialDecoder\Strategy\Wkt\Factory\WktPolygonFactory;
use LongitudeOne\SpatialDecoder\Strategy\Wkt\WktCoordinateReader;
use LongitudeOne\SpatialDecoder\Strategy\Wkt\WktTokenCursor;
use LongitudeOne\SpatialTypes\Interfaces\SpatialInterface;
use LongitudeOne\SpatialTypes\Interfaces\PolygonInterface;

/**
* Parses WKT polygon representations.
Expand Down Expand Up @@ -76,9 +76,9 @@ public function consumePolygon(?CoordinateDimensionEnum $dimension): array
*
* @param CoordinateDimensionEnum|null $inheritedDimension dimension inherited from a parent collection
*
* @return SpatialInterface the decoded polygon
* @return PolygonInterface the decoded polygon
*/
public function parse(?CoordinateDimensionEnum $inheritedDimension = null): SpatialInterface
public function parse(?CoordinateDimensionEnum $inheritedDimension = null): PolygonInterface
{
$dimension = $this->coordinateReader->consumeDimension($inheritedDimension);
if ($this->cursor->isNextToken(Lexer::T_EMPTY)) {
Expand Down
61 changes: 61 additions & 0 deletions lib/Strategy/Wkt/Parser/PolyhedralSurfaceEwktParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php
/**
* This file is part of the spatial-decoder project.
*
* PHP 8.4 | 8.5
*
* Copyright Alexandre Tranchant <alexandre.tranchant@gmail.com> 2026
* Copyright Longitude One 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\SpatialDecoder\Strategy\Wkt\Parser;

use LongitudeOne\Core\Enum\CoordinateDimensionEnum;
use LongitudeOne\SpatialDecoder\Exception\InvalidArgumentException;
use LongitudeOne\SpatialTypes\Exception\SpatialTypeExceptionInterface;
use LongitudeOne\SpatialTypes\Interfaces\PolyhedralSurfaceInterface;
use LongitudeOne\SpatialTypes\Types\Dimension3z\Geometry\PolyhedralSurface as PolyhedralSurfaceXYZ;
use LongitudeOne\SpatialTypes\Types\Dimension4zm\Geometry\PolyhedralSurface as PolyhedralSurfaceXYZM;

/**
* Decode an EWKT polyhedral surface using the shared polygonal grammar.
*
* @internal
*/
final class PolyhedralSurfaceEwktParser implements WktGeometryParserInterface
{
/**
* Construct the parser over the shared polygonal grammar.
*
* @param MultiPolygonWktParser $parser parser for the polygonal body
*/
public function __construct(private MultiPolygonWktParser $parser)
{
}

/**
* Parse and validate the concrete surface before returning it.
*
* @param CoordinateDimensionEnum|null $inheritedDimension dimension inherited from a collection
*/
public function parse(?CoordinateDimensionEnum $inheritedDimension = null): PolyhedralSurfaceInterface
{
$geometry = $this->parser->parse($inheritedDimension);

try {
return match ($geometry->getDimension()) {
CoordinateDimensionEnum::XYZ => new PolyhedralSurfaceXYZ($geometry->getElements()),
CoordinateDimensionEnum::XYZM => new PolyhedralSurfaceXYZM($geometry->getElements()),
default => throw new InvalidArgumentException('EWKT POLYHEDRALSURFACE requires a coordinate layout containing Z.'),
};
} catch (SpatialTypeExceptionInterface $exception) {
throw new InvalidArgumentException($exception->getMessage(), 0, $exception);
}
}
}
64 changes: 64 additions & 0 deletions lib/Strategy/Wkt/Parser/TriangleEwktParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php
/**
* This file is part of the spatial-decoder project.
*
* PHP 8.4 | 8.5
*
* Copyright Alexandre Tranchant <alexandre.tranchant@gmail.com> 2026
* Copyright Longitude One 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\SpatialDecoder\Strategy\Wkt\Parser;

use LongitudeOne\Core\Enum\CoordinateDimensionEnum;
use LongitudeOne\SpatialDecoder\Exception\InvalidArgumentException;
use LongitudeOne\SpatialTypes\Exception\SpatialTypeExceptionInterface;
use LongitudeOne\SpatialTypes\Interfaces\TriangleInterface;
use LongitudeOne\SpatialTypes\Types\Dimension2\Geometry\Triangle as TriangleXY;
use LongitudeOne\SpatialTypes\Types\Dimension3m\Geometry\Triangle as TriangleXYM;
use LongitudeOne\SpatialTypes\Types\Dimension3z\Geometry\Triangle as TriangleXYZ;
use LongitudeOne\SpatialTypes\Types\Dimension4zm\Geometry\Triangle as TriangleXYZM;

/**
* Decode an EWKT triangle using the shared polygonal grammar.
*
* @internal
*/
final class TriangleEwktParser implements WktGeometryParserInterface
{
/**
* Construct the parser over the shared polygonal grammar.
*
* @param PolygonWktParser $parser parser for the polygonal body
*/
public function __construct(private PolygonWktParser $parser)
{
}

/**
* Parse and validate the concrete surface before returning it.
*
* @param CoordinateDimensionEnum|null $inheritedDimension dimension inherited from a collection
*/
public function parse(?CoordinateDimensionEnum $inheritedDimension = null): TriangleInterface
{
$geometry = $this->parser->parse($inheritedDimension);

try {
return match ($geometry->getDimension()) {
CoordinateDimensionEnum::XY => new TriangleXY($geometry->getRings()),
CoordinateDimensionEnum::XYZ => new TriangleXYZ($geometry->getRings()),
CoordinateDimensionEnum::XYM => new TriangleXYM($geometry->getRings()),
CoordinateDimensionEnum::XYZM => new TriangleXYZM($geometry->getRings()),
};
} catch (SpatialTypeExceptionInterface $exception) {
throw new InvalidArgumentException($exception->getMessage(), 0, $exception);
}
}
}
8 changes: 8 additions & 0 deletions lib/Strategy/Wkt/Parser/WktParserFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ public function createFromCursor(WktTokenCursor $cursor, WktDialect $dialect = W
new GeometryCollectionWktParser($cursor, $coordinateReader, new WktGeometryCollectionFactory(), $registry)
);

if (WktDialect::EWKT === $dialect) {
$registry->register(Lexer::T_TRIANGLE, new TriangleEwktParser(new PolygonWktParser($cursor, $coordinateReader, $polygonFactory)));
$registry->register(
Lexer::T_POLYHEDRALSURFACE,
new PolyhedralSurfaceEwktParser(new MultiPolygonWktParser($cursor, $coordinateReader, new WktMultiPolygonFactory($polygonFactory), $polygonFactory))
);
}

return new WktParser($cursor, $registry);
}
}
Loading
Loading