diff --git a/README.md b/README.md index 9cd51f5..362baf5 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,11 @@ assert(!$dimension->isCurve()); `GeometryTypeEnum` names the concrete shape of a spatial value, such as a point, line string, polygon, or geometry collection. It exposes a type's homogeneous component and topological dimension, making -it useful for creating and validating multipart geometries. +it useful for creating and validating multipart geometries. Its `isInstantiable()` method returns +`false` for the abstract `SURFACE` type and `true` for every other enum case, including `MULTICURVE` +and `MULTISURFACE`. The classification considers OGC Simple Feature Access 1.2.1 sections 6.1.2, +6.1.10, and 6.1.13, and ISO/IEC CD 13249-3:201x(E) sections 4.2, 4.2.8, 4.2.16, and 4.2.18; the ISO +source is a Committee Draft, not a published ISO edition. ```php use LongitudeOne\Core\Enum\GeometryTypeEnum; @@ -160,6 +164,7 @@ use LongitudeOne\Core\Enum\TopologicalDimensionEnum; $type = GeometryTypeEnum::MULTIPOLYGON; assert($type->isMulti()); +assert($type->isInstantiable()); assert(GeometryTypeEnum::POLYGON === $type->componentType()); assert(TopologicalDimensionEnum::SURFACE === $type->topologicalDimension()); ``` diff --git a/lib/LongitudeOne/Core/Enum/GeometryTypeEnum.php b/lib/LongitudeOne/Core/Enum/GeometryTypeEnum.php index 17cbb58..63ba88c 100644 --- a/lib/LongitudeOne/Core/Enum/GeometryTypeEnum.php +++ b/lib/LongitudeOne/Core/Enum/GeometryTypeEnum.php @@ -24,9 +24,15 @@ enum GeometryTypeEnum: string /** A route made of linear and circular segments. */ case COMPOUNDCURVE = 'CompoundCurve'; + /** An abstract curve geometry type. */ + case CURVE = 'Curve'; + /** An area bounded by curves. */ case CURVEPOLYGON = 'CurvePolygon'; + /** The generic geometry type. */ + case GEOMETRY = 'Geometry'; + /** A geometry collection. */ case GEOMETRYCOLLECTION = 'GeometryCollection'; @@ -57,6 +63,12 @@ enum GeometryTypeEnum: string /** A surface composed of polygon patches. */ case POLYHEDRALSURFACE = 'PolyhedralSurface'; + /** A solid geometry type. */ + case SOLID = 'Solid'; + + /** An abstract surface geometry type. */ + case SURFACE = 'Surface'; + /** A triangulated irregular network. */ case TIN = 'TIN'; @@ -73,12 +85,7 @@ public function componentType(): ?self self::POLYGON, self::MULTILINESTRING, self::TRIANGLE => self::LINESTRING, self::MULTIPOLYGON, self::POLYHEDRALSURFACE => self::POLYGON, self::TIN => self::TRIANGLE, - self::COMPOUNDCURVE, - self::CURVEPOLYGON, - self::GEOMETRYCOLLECTION, - self::MULTICURVE, - self::MULTISURFACE, - self::POINT => null, + default => null, }; } @@ -90,6 +97,22 @@ public function isCollection(): bool return self::GEOMETRYCOLLECTION === $this; } + /** + * Return whether this geometry type can be instantiated. + * + * GEOMETRY, CURVE, SOLID, and SURFACE are non-instantiable; every other enum case is instantiable. + */ + public function isInstantiable(): bool + { + return match ($this) { + self::CURVE, + self::GEOMETRY, + self::SOLID, + self::SURFACE => false, + default => true, + }; + } + /** * Return whether this is a multi-geometry type. */ @@ -106,7 +129,7 @@ public function isMulti(): bool } /** - * Return the topological dimension, or null for a heterogeneous collection. + * Return the topological dimension, or null when it is not specific to a geometry type. */ public function topologicalDimension(): ?TopologicalDimensionEnum { @@ -114,6 +137,7 @@ public function topologicalDimension(): ?TopologicalDimensionEnum self::POINT, self::MULTIPOINT => TopologicalDimensionEnum::POINT, self::CIRCULARSTRING, self::COMPOUNDCURVE, + self::CURVE, self::LINESTRING, self::MULTICURVE, self::MULTILINESTRING => TopologicalDimensionEnum::CURVE, @@ -122,9 +146,11 @@ public function topologicalDimension(): ?TopologicalDimensionEnum self::MULTISURFACE, self::POLYGON, self::POLYHEDRALSURFACE, + self::SURFACE, self::TIN, self::TRIANGLE => TopologicalDimensionEnum::SURFACE, - self::GEOMETRYCOLLECTION => null, + self::SOLID => TopologicalDimensionEnum::VOLUME, + default => null, }; } } diff --git a/lib/LongitudeOne/Core/Enum/TopologicalDimensionEnum.php b/lib/LongitudeOne/Core/Enum/TopologicalDimensionEnum.php index b87bd06..7e264e7 100644 --- a/lib/LongitudeOne/Core/Enum/TopologicalDimensionEnum.php +++ b/lib/LongitudeOne/Core/Enum/TopologicalDimensionEnum.php @@ -34,6 +34,9 @@ enum TopologicalDimensionEnum: int /** The dimension of a surface geometry. */ case SURFACE = 2; + /** The dimension of a solid geometry. */ + case VOLUME = 3; + /** * Return whether this represents a curve geometry. */ @@ -65,4 +68,12 @@ public function isSurface(): bool { return self::SURFACE === $this; } + + /** + * Return whether this represents a volume geometry. + */ + public function isVolume(): bool + { + return self::VOLUME === $this; + } } diff --git a/tests/LongitudeOne/Core/Tests/Unit/Enum/GeometryTypeEnumTest.php b/tests/LongitudeOne/Core/Tests/Unit/Enum/GeometryTypeEnumTest.php index 1a8d281..7b4808f 100644 --- a/tests/LongitudeOne/Core/Tests/Unit/Enum/GeometryTypeEnumTest.php +++ b/tests/LongitudeOne/Core/Tests/Unit/Enum/GeometryTypeEnumTest.php @@ -62,6 +62,34 @@ public function testCurvedArea(): void self::assertFalse($type->isMulti()); } + /** + * Generic, abstract, and volumetric types have no homogeneous component. + */ + public function testGenericAndVolumetricTypes(): void + { + $geometry = GeometryTypeEnum::GEOMETRY; + $curve = GeometryTypeEnum::CURVE; + $solid = GeometryTypeEnum::SOLID; + + self::assertNull($geometry->componentType()); + self::assertNull($geometry->topologicalDimension()); + self::assertFalse($geometry->isCollection()); + self::assertFalse($geometry->isMulti()); + self::assertFalse($geometry->isInstantiable()); + + self::assertNull($curve->componentType()); + self::assertSame(TopologicalDimensionEnum::CURVE, $curve->topologicalDimension()); + self::assertFalse($curve->isCollection()); + self::assertFalse($curve->isMulti()); + self::assertFalse($curve->isInstantiable()); + + self::assertNull($solid->componentType()); + self::assertSame(TopologicalDimensionEnum::VOLUME, $solid->topologicalDimension()); + self::assertFalse($solid->isCollection()); + self::assertFalse($solid->isMulti()); + self::assertFalse($solid->isInstantiable()); + } + /** * Generic multipart curves and surfaces retain their respective dimensions. */ @@ -78,6 +106,41 @@ public function testGenericMultipartGeometries(): void self::assertTrue($surfaces->isMulti()); } + /** + * Every geometry type has an explicit instantiability classification. + */ + public function testInstantiabilityClassification(): void + { + $expected = [ + GeometryTypeEnum::CIRCULARSTRING->name => true, + GeometryTypeEnum::COMPOUNDCURVE->name => true, + GeometryTypeEnum::CURVE->name => false, + GeometryTypeEnum::CURVEPOLYGON->name => true, + GeometryTypeEnum::GEOMETRY->name => false, + GeometryTypeEnum::GEOMETRYCOLLECTION->name => true, + GeometryTypeEnum::LINESTRING->name => true, + GeometryTypeEnum::MULTICURVE->name => true, + GeometryTypeEnum::MULTILINESTRING->name => true, + GeometryTypeEnum::MULTIPOINT->name => true, + GeometryTypeEnum::MULTIPOLYGON->name => true, + GeometryTypeEnum::MULTISURFACE->name => true, + GeometryTypeEnum::POINT->name => true, + GeometryTypeEnum::POLYGON->name => true, + GeometryTypeEnum::POLYHEDRALSURFACE->name => true, + GeometryTypeEnum::SOLID->name => false, + GeometryTypeEnum::SURFACE->name => false, + GeometryTypeEnum::TIN->name => true, + GeometryTypeEnum::TRIANGLE->name => true, + ]; + + self::assertCount(count(GeometryTypeEnum::cases()), $expected); + + foreach (GeometryTypeEnum::cases() as $type) { + self::assertArrayHasKey($type->name, $expected); + self::assertSame($expected[$type->name], $type->isInstantiable()); + } + } + /** * A location is a point and has no component geometry. */ diff --git a/tests/LongitudeOne/Core/Tests/Unit/Enum/TopologicalDimensionEnumTest.php b/tests/LongitudeOne/Core/Tests/Unit/Enum/TopologicalDimensionEnumTest.php index 96044d1..16e2596 100644 --- a/tests/LongitudeOne/Core/Tests/Unit/Enum/TopologicalDimensionEnumTest.php +++ b/tests/LongitudeOne/Core/Tests/Unit/Enum/TopologicalDimensionEnumTest.php @@ -76,4 +76,18 @@ public function testRoute(): void self::assertFalse($geometry->isPoint()); self::assertFalse($geometry->isSurface()); } + + /** + * A solid is represented by a volume. + */ + public function testVolume(): void + { + $geometry = TopologicalDimensionEnum::VOLUME; + + self::assertTrue($geometry->isVolume()); + self::assertFalse($geometry->isEmpty()); + self::assertFalse($geometry->isPoint()); + self::assertFalse($geometry->isCurve()); + self::assertFalse($geometry->isSurface()); + } }