Skip to content

Commit 7242665

Browse files
feat(#456): add convenience factory methods to WktSpatialData (#459)
1 parent e953bdc commit 7242665

File tree

3 files changed

+262
-0
lines changed

3 files changed

+262
-0
lines changed

docs/SPATIAL-TYPES.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,77 @@ Two enums drive normalization so the code and docs remain consistent:
2727

2828
Regex patterns for geometry type detection and dimensional modifier handling are built from these enums instead of hardcoded strings.
2929

30+
## Creating Spatial Data
31+
32+
The `WktSpatialData` value object provides multiple ways to create spatial data:
33+
34+
### From WKT String (Traditional)
35+
```php
36+
use MartinGeorgiev\Doctrine\DBAL\Types\ValueObject\WktSpatialData;
37+
38+
// Parse complete WKT/EWKT strings
39+
$point = WktSpatialData::fromWkt('POINT(1 2)');
40+
$pointWithSrid = WktSpatialData::fromWkt('SRID=4326;POINT(-122.4194 37.7749)');
41+
$line = WktSpatialData::fromWkt('LINESTRING(0 0, 1 1, 2 2)');
42+
```
43+
44+
### From Components (Programmatic)
45+
```php
46+
use MartinGeorgiev\Doctrine\DBAL\Types\ValueObject\WktSpatialData;
47+
use MartinGeorgiev\Doctrine\DBAL\Types\ValueObject\GeometryType;
48+
use MartinGeorgiev\Doctrine\DBAL\Types\ValueObject\DimensionalModifier;
49+
50+
// Build from individual components
51+
$point = WktSpatialData::fromComponents(
52+
GeometryType::POINT,
53+
'1 2'
54+
);
55+
56+
// With SRID
57+
$pointWithSrid = WktSpatialData::fromComponents(
58+
GeometryType::POINT,
59+
'-122.4194 37.7749',
60+
4326
61+
);
62+
63+
// With dimensional modifier
64+
$line3d = WktSpatialData::fromComponents(
65+
GeometryType::LINESTRING,
66+
'0 0 1, 1 1 2, 2 2 3',
67+
null,
68+
DimensionalModifier::Z
69+
);
70+
71+
// With all parameters
72+
$polygon4d = WktSpatialData::fromComponents(
73+
GeometryType::POLYGON,
74+
'0 0 0 1, 0 1 0 1, 1 1 0 1, 1 0 0 1, 0 0 0 1',
75+
4326,
76+
DimensionalModifier::ZM
77+
);
78+
```
79+
80+
### Convenience Methods for Points
81+
```php
82+
use MartinGeorgiev\Doctrine\DBAL\Types\ValueObject\WktSpatialData;
83+
84+
// Simple 2D point
85+
$point = WktSpatialData::point(1, 2);
86+
// Result: POINT(1 2)
87+
88+
// Point with SRID (common for geographic coordinates)
89+
$location = WktSpatialData::point(-122.4194, 37.7749, 4326);
90+
// Result: SRID=4326;POINT(-122.4194 37.7749)
91+
92+
// 3D point with elevation
93+
$point3d = WktSpatialData::point3d(-122.4194, 37.7749, 100);
94+
// Result: POINT Z(-122.4194 37.7749 100)
95+
96+
// 3D point with SRID
97+
$location3d = WktSpatialData::point3d(-122.4194, 37.7749, 100, 4326);
98+
// Result: SRID=4326;POINT Z(-122.4194 37.7749 100)
99+
```
100+
30101
## Supported Geometry Types
31102

32103
The library supports all PostGIS geometry types through the `GeometryType` enum:

src/MartinGeorgiev/Doctrine/DBAL/Types/ValueObject/WktSpatialData.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,67 @@ public static function fromWkt(string $wkt): self
8787
return new self($srid, $geometryType, $body, $dimensionalModifier);
8888
}
8989

90+
/**
91+
* Create spatial data from individual components.
92+
*
93+
* This is a convenience method for programmatically building spatial data
94+
* without manually constructing WKT strings.
95+
*
96+
* @param GeometryType $geometryType The geometry type (e.g., POINT, LINESTRING)
97+
* @param string $coordinates The coordinate data (e.g., "1 2" for a point, "0 0, 1 1" for a line)
98+
* @param int|null $srid Optional spatial reference system identifier
99+
* @param DimensionalModifier|null $dimensionalModifier Optional dimensional modifier (Z, M, or ZM)
100+
*
101+
* @throws InvalidWktSpatialDataException If coordinates are empty
102+
*/
103+
public static function fromComponents(
104+
GeometryType $geometryType,
105+
string $coordinates,
106+
?int $srid = null,
107+
?DimensionalModifier $dimensionalModifier = null
108+
): self {
109+
$coordinates = \trim($coordinates);
110+
if ($coordinates === '') {
111+
throw InvalidWktSpatialDataException::forEmptyCoordinateSection();
112+
}
113+
114+
return new self($srid, $geometryType, $coordinates, $dimensionalModifier);
115+
}
116+
117+
/**
118+
* Create a POINT geometry from longitude and latitude.
119+
*
120+
* Convenience method for the most common use case: creating geographic points.
121+
*
122+
* @param float|int|string $longitude The longitude (X coordinate)
123+
* @param float|int|string $latitude The latitude (Y coordinate)
124+
* @param int|null $srid Optional SRID (commonly 4326 for WGS84)
125+
*/
126+
public static function point(
127+
float|int|string $longitude,
128+
float|int|string $latitude,
129+
?int $srid = null
130+
): self {
131+
return new self($srid, GeometryType::POINT, \sprintf('%s %s', $longitude, $latitude));
132+
}
133+
134+
/**
135+
* Create a 3D POINT geometry with elevation.
136+
*
137+
* @param float|int|string $longitude The longitude (X coordinate)
138+
* @param float|int|string $latitude The latitude (Y coordinate)
139+
* @param float|int|string $elevation The elevation (Z coordinate)
140+
* @param int|null $srid Optional SRID (commonly 4326 for WGS84)
141+
*/
142+
public static function point3d(
143+
float|int|string $longitude,
144+
float|int|string $latitude,
145+
float|int|string $elevation,
146+
?int $srid = null
147+
): self {
148+
return new self($srid, GeometryType::POINT, \sprintf('%s %s %s', $longitude, $latitude, $elevation), DimensionalModifier::Z);
149+
}
150+
90151
public function getSrid(): ?int
91152
{
92153
return $this->srid;

tests/Unit/MartinGeorgiev/Doctrine/DBAL/Types/ValueObject/WktSpatialDataTest.php

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,4 +167,134 @@ public static function provideDimensionalModifierRoundTripCases(): array
167167
'srid with polygon zm' => ['SRID=4326;POLYGON ZM((-122.5 37.7 0 1, -122.5 37.8 0 1, -122.4 37.8 0 1, -122.4 37.7 0 1, -122.5 37.7 0 1))'],
168168
];
169169
}
170+
171+
#[DataProvider('provideFromComponentsData')]
172+
#[Test]
173+
public function can_create_from_components(
174+
GeometryType $geometryType,
175+
string $coordinates,
176+
?int $srid,
177+
?DimensionalModifier $dimensionalModifier,
178+
string $expectedWkt
179+
): void {
180+
$wktSpatialData = WktSpatialData::fromComponents($geometryType, $coordinates, $srid, $dimensionalModifier);
181+
182+
$this->assertSame($geometryType, $wktSpatialData->getGeometryType());
183+
$this->assertSame($srid, $wktSpatialData->getSrid());
184+
$this->assertSame($dimensionalModifier, $wktSpatialData->getDimensionalModifier());
185+
$this->assertSame($expectedWkt, $wktSpatialData->getWkt());
186+
}
187+
188+
/**
189+
* @return array<string, array{GeometryType, string, int|null, DimensionalModifier|null, string}>
190+
*/
191+
public static function provideFromComponentsData(): array
192+
{
193+
return [
194+
'simple point' => [
195+
GeometryType::POINT,
196+
'1 2',
197+
null,
198+
null,
199+
'POINT(1 2)',
200+
],
201+
'point with srid' => [
202+
GeometryType::POINT,
203+
'-122.4194 37.7749',
204+
4326,
205+
null,
206+
'SRID=4326;POINT(-122.4194 37.7749)',
207+
],
208+
'linestring with dimensional modifier' => [
209+
GeometryType::LINESTRING,
210+
'0 0 1, 1 1 2',
211+
null,
212+
DimensionalModifier::M,
213+
'LINESTRING M(0 0 1, 1 1 2)',
214+
],
215+
'polygon with all parameters' => [
216+
GeometryType::POLYGON,
217+
'0 0 0 1, 0 1 0 1, 1 1 0 1, 1 0 0 1, 0 0 0 1',
218+
4326,
219+
DimensionalModifier::ZM,
220+
'SRID=4326;POLYGON ZM(0 0 0 1, 0 1 0 1, 1 1 0 1, 1 0 0 1, 0 0 0 1)',
221+
],
222+
];
223+
}
224+
225+
#[DataProvider('provideInvalidCoordinatesData')]
226+
#[Test]
227+
public function from_components_throws_exception_for_invalid_coordinates(string $invalidCoordinates): void
228+
{
229+
$this->expectException(InvalidWktSpatialDataException::class);
230+
WktSpatialData::fromComponents(GeometryType::POINT, $invalidCoordinates);
231+
}
232+
233+
/**
234+
* @return array<string, array{string}>
235+
*/
236+
public static function provideInvalidCoordinatesData(): array
237+
{
238+
return [
239+
'empty string' => [''],
240+
'whitespace only' => [' '],
241+
];
242+
}
243+
244+
#[DataProvider('providePointData')]
245+
#[Test]
246+
public function can_create_point(
247+
float|int|string $longitude,
248+
float|int|string $latitude,
249+
?int $srid,
250+
string $expectedWkt
251+
): void {
252+
$wktSpatialData = WktSpatialData::point($longitude, $latitude, $srid);
253+
254+
$this->assertSame(GeometryType::POINT, $wktSpatialData->getGeometryType());
255+
$this->assertSame($srid, $wktSpatialData->getSrid());
256+
$this->assertNull($wktSpatialData->getDimensionalModifier());
257+
$this->assertSame($expectedWkt, $wktSpatialData->getWkt());
258+
}
259+
260+
/**
261+
* @return array<string, array{float|int|string, float|int|string, int|null, string}>
262+
*/
263+
public static function providePointData(): array
264+
{
265+
return [
266+
'integer coordinates' => [1, 2, null, 'POINT(1 2)'],
267+
'float coordinates' => [-122.4194, 37.7749, null, 'POINT(-122.4194 37.7749)'],
268+
'string coordinates' => ['-122.4194', '37.7749', null, 'POINT(-122.4194 37.7749)'],
269+
'with srid' => [-122.4194, 37.7749, 4326, 'SRID=4326;POINT(-122.4194 37.7749)'],
270+
];
271+
}
272+
273+
#[DataProvider('providePoint3dData')]
274+
#[Test]
275+
public function can_create_3d_point(
276+
float|int|string $longitude,
277+
float|int|string $latitude,
278+
float|int|string $elevation,
279+
?int $srid,
280+
string $expectedWkt
281+
): void {
282+
$wktSpatialData = WktSpatialData::point3d($longitude, $latitude, $elevation, $srid);
283+
284+
$this->assertSame(GeometryType::POINT, $wktSpatialData->getGeometryType());
285+
$this->assertSame($srid, $wktSpatialData->getSrid());
286+
$this->assertSame(DimensionalModifier::Z, $wktSpatialData->getDimensionalModifier());
287+
$this->assertSame($expectedWkt, $wktSpatialData->getWkt());
288+
}
289+
290+
/**
291+
* @return array<string, array{float|int|string, float|int|string, float|int|string, int|null, string}>
292+
*/
293+
public static function providePoint3dData(): array
294+
{
295+
return [
296+
'simple 3d point' => [1, 2, 3, null, 'POINT Z(1 2 3)'],
297+
'with srid' => [-122.4194, 37.7749, 100, 4326, 'SRID=4326;POINT Z(-122.4194 37.7749 100)'],
298+
];
299+
}
170300
}

0 commit comments

Comments
 (0)