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
46 changes: 46 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Repository Guidelines

## Project Structure & Module Organization

`longitude-one/spatial-core` provides shared PHP primitives for LongitudeOne spatial libraries.
PSR-4 maps `LongitudeOne\Core\` to `lib/`, which contains `Enum/`, `Exception/`, and `Diagnostic/`.
Tests live in `tests/Unit/` and `tests/Contract/` under `LongitudeOne\Core\Tests\`.
Quality configurations and isolated tool dependencies live in `quality/`; CI lives in `.github/workflows/`.
Consult `README.md` for public behavior and `CONTRIBUTING.md` for the full contribution checklist.

## Build, Test, and Development Commands

Use PHP 8.3+ with `mbstring` and Composer. This library has no application server or asset build.

- `composer install`: install library development dependencies.
- `composer upgrade-tools`: install or update project and quality-tool dependencies; inspect resulting changes.
- `composer validate --strict`: validate package metadata.
- `composer test`: run PHPUnit.
- `composer test-coverage`: generate coverage reports under `.phpunit.cache/`; requires a coverage driver.
- `composer quality`: run PHP-CS-Fixer checks, PHPStan level 9, PHPMD, and PHP_CodeSniffer.
- `composer quality-fix`: apply PHP-CS-Fixer formatting; rerun quality checks afterward.
- `composer markdownlint`: lint Markdown; install Node.js and `markdownlint-cli2` first.
- `git diff --check`: detect whitespace errors before committing.

## Coding Style & Naming Conventions

Use four-space indentation, LF endings, strict types, explicit types, and the existing copyright header.
Follow the configured Symfony/PHP-CS-Fixer and PSR-2-based rules.
Use PascalCase class names, camelCase methods and variables, and uppercase enum cases.
Match filenames to symbols, such as `GeometryTypeEnum.php`; preserve established enum backing values.

## Testing Guidelines

Use PHPUnit 12.5 with `*Test.php` classes and descriptive `test...` methods.
Mirror source organization in unit tests; place public-contract checks in `tests/Contract/`.
Run focused tests with `composer test -- --filter GeometryTypeEnumTest`.
Cover boundary cases, exceptions, and regressions. When extending an enum, verify every method for the added cases.
Generate coverage for executable changes; no numeric coverage minimum is configured in PHPUnit.

## Commit & Pull Request Guidelines

Follow Conventional Commits, as in history: `feat: ...`, `fix: ...`, or `chore: ...`.
Use optional scopes, for example `fix(range): report the correct axis error`.
Link the issue, explain behavioral changes, and report validation results in the PR.
All required checks must pass before pushing. Obtain independent review and Alexandre’s merge decision.
Do not mark work Done before merge. Escalate unresolved public API, compatibility, architecture, or normative decisions to Alexandre.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ All notable changes to this project will be documented in this file. See [commit

### ✨ New Features

* Implement DiagnosticValueFormatter ([#4](https://github.com/longitude-one/spatial-core/issues/4)) ([db6f205](https://github.com/longitude-one/spatial-core/commit/db6f205768ace5ae3c9bbc1a3736f4926681799b)), closes [#3](https://github.com/longitude-one/spatial-core/issues/3)
* Implement DiagnosticValueFormatter ([#4](https://github.com/longitude-one/spatial-core/issues/4)) ([db6f205](https://github.com/longitude-one/spatial-core/commit/db6f205768ace5ae3c9bbc1a3736f4926681799b))

### 🔧 Maintenance

Expand Down
21 changes: 14 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,20 @@ assert(!$dimension->isCurve());

### `GeometryTypeEnum`

`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. 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.
`GeometryTypeEnum` identifies geometry types and exposes their homogeneous component and topological
dimension. Its `isInstantiable()` method returns `false` for `GEOMETRY`, `CURVE`, `SOLID`, and `SURFACE`,
and `true` for every other case, including `MULTICURVE` and `MULTISURFACE`.

The additional types `BREPSOLID`, `CIRCLE`, `CLOTHOID`, `COMPOUNDSURFACE`, `ELLIPTICALCURVE`,
`GEODESICSTRING`, `NURBSCURVE`, and `SPIRALCURVE` are instantiable. `BREPSOLID` has volume dimension,
`COMPOUNDSURFACE` has surface dimension, and the remaining types have curve dimension. None is a
multi-geometry or a geometry collection. `CIRCLE` and `GEODESICSTRING` have `POINT` components.
The other additions have no single concrete component type exposed by this enum: compound surfaces
and solid shells can use different surface subtypes, while NURBS curves use weighted control points
and knots, and the other curves use constructive parameters.

These additions follow ISO/IEC DIS 13249-3, fifth-edition ballot text N2593 dated 2015-01-25,
clauses 4.2.7–4.2.12, 4.2.20, 4.2.22, 7.4.1–7.9.1, 8.7.1, and 9.2.1. Enum membership does not imply concrete geometry classes or support for encoding or decoding these types.

```php
use LongitudeOne\Core\Enum\GeometryTypeEnum;
Expand Down
37 changes: 34 additions & 3 deletions lib/Enum/GeometryTypeEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,36 @@
*/
enum GeometryTypeEnum: string
{
/** A solid bounded by exterior and interior surface shells. */
case BREPSOLID = 'BRepSolid';

/** A simple closed circular curve. */
case CIRCLE = 'Circle';

/** A route formed from circular arcs. */
case CIRCULARSTRING = 'CircularString';

/** A curve with clothoid interpolation. */
case CLOTHOID = 'Clothoid';

/** A route made of linear and circular segments. */
case COMPOUNDCURVE = 'CompoundCurve';

/** A surface formed from connected surface patches. */
case COMPOUNDSURFACE = 'CompoundSurface';

/** An abstract curve geometry type. */
case CURVE = 'Curve';

/** An area bounded by curves. */
case CURVEPOLYGON = 'CurvePolygon';

/** A curve with elliptical interpolation. */
case ELLIPTICALCURVE = 'EllipticalCurve';

/** A route formed from geodesic segments. */
case GEODESICSTRING = 'GeodesicString';

/** The generic geometry type. */
case GEOMETRY = 'Geometry';

Expand All @@ -54,6 +72,9 @@ enum GeometryTypeEnum: string
/** A collection of surfaces. */
case MULTISURFACE = 'MultiSurface';

/** A non-uniform rational B-spline curve. */
case NURBSCURVE = 'NURBSCurve';

/** A location. */
case POINT = 'Point';

Expand All @@ -66,6 +87,9 @@ enum GeometryTypeEnum: string
/** A solid geometry type. */
case SOLID = 'Solid';

/** A curve with spiral interpolation. */
case SPIRALCURVE = 'SpiralCurve';

/** An abstract surface geometry type. */
case SURFACE = 'Surface';

Expand All @@ -81,7 +105,7 @@ enum GeometryTypeEnum: string
public function componentType(): ?self
{
return match ($this) {
self::CIRCULARSTRING, self::LINESTRING, self::MULTIPOINT => self::POINT,
self::CIRCLE, self::CIRCULARSTRING, self::GEODESICSTRING, self::LINESTRING, self::MULTIPOINT => self::POINT,
self::POLYGON, self::MULTILINESTRING, self::TRIANGLE => self::LINESTRING,
self::MULTIPOLYGON, self::POLYHEDRALSURFACE => self::POLYGON,
self::TIN => self::TRIANGLE,
Expand Down Expand Up @@ -135,12 +159,19 @@ public function topologicalDimension(): ?TopologicalDimensionEnum
{
return match ($this) {
self::POINT, self::MULTIPOINT => TopologicalDimensionEnum::POINT,
self::CIRCLE,
self::CIRCULARSTRING,
self::CLOTHOID,
self::COMPOUNDCURVE,
self::CURVE,
self::ELLIPTICALCURVE,
self::GEODESICSTRING,
self::LINESTRING,
self::MULTICURVE,
self::MULTILINESTRING => TopologicalDimensionEnum::CURVE,
self::MULTILINESTRING,
self::NURBSCURVE,
self::SPIRALCURVE => TopologicalDimensionEnum::CURVE,
self::COMPOUNDSURFACE,
self::CURVEPOLYGON,
self::MULTIPOLYGON,
self::MULTISURFACE,
Expand All @@ -149,7 +180,7 @@ public function topologicalDimension(): ?TopologicalDimensionEnum
self::SURFACE,
self::TIN,
self::TRIANGLE => TopologicalDimensionEnum::SURFACE,
self::SOLID => TopologicalDimensionEnum::VOLUME,
self::BREPSOLID, self::SOLID => TopologicalDimensionEnum::VOLUME,
default => null,
};
}
Expand Down
3 changes: 2 additions & 1 deletion quality/markdownlint/.markdownlint-cli2.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"config": {
"MD013": {
"line_length": 200
}
},
"MD024": false
},
"globs": [
"**/*.md"
Expand Down
39 changes: 39 additions & 0 deletions tests/Unit/Enum/GeometryTypeEnumTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,37 @@
*/
final class GeometryTypeEnumTest extends TestCase
{
/**
* Additional ISO geometry types retain their identity and complete classification.
*/
public function testAdditionalGeometryTypes(): void
{
$expected = [
['BREPSOLID', 'BRepSolid', TopologicalDimensionEnum::VOLUME, null],
['CIRCLE', 'Circle', TopologicalDimensionEnum::CURVE, GeometryTypeEnum::POINT],
['CLOTHOID', 'Clothoid', TopologicalDimensionEnum::CURVE, null],
['COMPOUNDSURFACE', 'CompoundSurface', TopologicalDimensionEnum::SURFACE, null],
['ELLIPTICALCURVE', 'EllipticalCurve', TopologicalDimensionEnum::CURVE, null],
['GEODESICSTRING', 'GeodesicString', TopologicalDimensionEnum::CURVE, GeometryTypeEnum::POINT],
['NURBSCURVE', 'NURBSCurve', TopologicalDimensionEnum::CURVE, null],
['SPIRALCURVE', 'SpiralCurve', TopologicalDimensionEnum::CURVE, null],
];

foreach ($expected as [$name, $value, $dimension, $component]) {
$type = GeometryTypeEnum::from($value);

self::assertSame($name, $type->name);
self::assertSame($value, $type->value);
self::assertSame($type, GeometryTypeEnum::tryFrom($value));
self::assertContains($type, GeometryTypeEnum::cases());
self::assertSame($dimension, $type->topologicalDimension());
self::assertSame($component, $type->componentType());
self::assertTrue($type->isInstantiable());
self::assertFalse($type->isCollection());
self::assertFalse($type->isMulti());
}
}

/**
* A circular route remains a curve made from point locations.
*/
Expand Down Expand Up @@ -112,6 +143,14 @@ public function testGenericMultipartGeometries(): void
public function testInstantiabilityClassification(): void
{
$expected = [
GeometryTypeEnum::SPIRALCURVE->name => true,
GeometryTypeEnum::NURBSCURVE->name => true,
GeometryTypeEnum::GEODESICSTRING->name => true,
GeometryTypeEnum::ELLIPTICALCURVE->name => true,
GeometryTypeEnum::COMPOUNDSURFACE->name => true,
GeometryTypeEnum::CLOTHOID->name => true,
GeometryTypeEnum::CIRCLE->name => true,
GeometryTypeEnum::BREPSOLID->name => true,
GeometryTypeEnum::CIRCULARSTRING->name => true,
GeometryTypeEnum::COMPOUNDCURVE->name => true,
GeometryTypeEnum::CURVE->name => false,
Expand Down
Loading