Skip to content

Commit 4ec31b7

Browse files
MatanYadaevMatan Yadaev
and
Matan Yadaev
authored
SRID support (#47)
* Add PhpStorm run configurations * Add test to `whereSrid` * Add tests to Point * Add tests to MultiPoint * Add tests to LineString * Add tests to MultiLineString * Add tests to Polygon * Add tests to MultiPolygon * Add tests to GeometryCollection * Implement `whereSrid` * Implement SRID * fixes * Fixes * update docs Co-authored-by: Matan Yadaev <[email protected]>
1 parent 55119fb commit 4ec31b7

23 files changed

+768
-72
lines changed

.run/Fix formatting.run.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<component name="ProjectRunConfigurationManager">
2+
<configuration default="false" name="Fix formatting" type="ComposerRunConfigurationType" factoryName="Composer Script">
3+
<option name="commandLineParameters" value="" />
4+
<option name="pathToComposerJson" value="$PROJECT_DIR$/composer.json" />
5+
<option name="script" value="php-cs-fixer" />
6+
<method v="2" />
7+
</configuration>
8+
</component>

.run/Static code analysis.run.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<component name="ProjectRunConfigurationManager">
2+
<configuration default="false" name="Static code analysis" type="ComposerRunConfigurationType" factoryName="Composer Script">
3+
<option name="commandLineParameters" value="" />
4+
<option name="pathToComposerJson" value="$PROJECT_DIR$/composer.json" />
5+
<option name="script" value="phpstan" />
6+
<method v="2" />
7+
</configuration>
8+
</component>

.run/Test.run.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<component name="ProjectRunConfigurationManager">
2+
<configuration default="false" name="Test" type="PestRunConfigurationType">
3+
<option name="pestRunnerSettings">
4+
<PestRunner method="" scope="ConfigurationFile" />
5+
</option>
6+
<option name="runnerSettings">
7+
<PhpTestRunnerSettings method="" scope="ConfigurationFile" />
8+
</option>
9+
<method v="2" />
10+
</configuration>
11+
</component>

API.md

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,28 @@
11
# API
22

3-
## Available spatial classes
3+
## Available geometry classes
44

5-
* `Point(float $latitude, float $longitude)` - [MySQL Point](https://dev.mysql.com/doc/refman/8.0/en/gis-class-point.html)
6-
* `MultiPoint(Point[] | Collection<Point>)` - [MySQL MultiPoint](https://dev.mysql.com/doc/refman/8.0/en/gis-class-multipoint.html)
7-
* `LineString(Point[] | Collection<Point>)` - [MySQL LineString](https://dev.mysql.com/doc/refman/8.0/en/gis-class-linestring.html)
8-
* `MultiLineString(LineString[] | Collection<LineString>)` - [MySQL MultiLineString](https://dev.mysql.com/doc/refman/8.0/en/gis-class-multilinestring.html)
9-
* `Polygon(LineString[] | Collection<LineString>)` - [MySQL Polygon](https://dev.mysql.com/doc/refman/8.0/en/gis-class-polygon.html)
10-
* `MultiPolygon(Polygon[] | Collection<Polygon>)` - [MySQL MultiPolygon](https://dev.mysql.com/doc/refman/8.0/en/gis-class-multipolygon.html)
11-
* `GeometryCollection(Geometry[] | Collection<Geometry>)` - [MySQL GeometryCollection](https://dev.mysql.com/doc/refman/8.0/en/gis-class-geometrycollection.html)
5+
* `Point(float $latitude, float $longitude, int $srid = 0)` - [MySQL Point](https://dev.mysql.com/doc/refman/8.0/en/gis-class-point.html)
6+
* `MultiPoint(Point[] | Collection<Point>, int $srid = 0)` - [MySQL MultiPoint](https://dev.mysql.com/doc/refman/8.0/en/gis-class-multipoint.html)
7+
* `LineString(Point[] | Collection<Point>, int $srid = 0)` - [MySQL LineString](https://dev.mysql.com/doc/refman/8.0/en/gis-class-linestring.html)
8+
* `MultiLineString(LineString[] | Collection<LineString>, int $srid = 0)` - [MySQL MultiLineString](https://dev.mysql.com/doc/refman/8.0/en/gis-class-multilinestring.html)
9+
* `Polygon(LineString[] | Collection<LineString>, int $srid = 0)` - [MySQL Polygon](https://dev.mysql.com/doc/refman/8.0/en/gis-class-polygon.html)
10+
* `MultiPolygon(Polygon[] | Collection<Polygon>, int $srid = 0)` - [MySQL MultiPolygon](https://dev.mysql.com/doc/refman/8.0/en/gis-class-multipolygon.html)
11+
* `GeometryCollection(Geometry[] | Collection<Geometry>, int $srid = 0)` - [MySQL GeometryCollection](https://dev.mysql.com/doc/refman/8.0/en/gis-class-geometrycollection.html)
1212

13-
## Available spatial functions
13+
Geometry classes can be also created by these static methods:
1414

15-
Every geometry class has these functions:
15+
* `fromJson(string $geoJson, int $srid = 0)` - Creates a geometry object from a [GeoJSON](https://en.wikipedia.org/wiki/GeoJSON) string.
16+
* `fromWkt(string $wkt, int $srid = 0)` - Creates a geometry object from a [WKT](https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry).
17+
* `fromWkb(string $wkb, int $srid = 0)` - Creates a geometry object from a [WKB](https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry#Well-known_binary).
18+
19+
## Available geometry class methods
1620

1721
* `toArray()` - Serializes the geometry object into a GeoJSON associative array.
1822
* `toJson()` - Serializes the geometry object into an GeoJSON string.
19-
* `fromJson(string $geoJson)` - Deserializes a geometry object from a GeoJSON string. (static method)
2023
* `toFeatureCollectionJson()` - Serializes the geometry object into an GeoJSON's FeatureCollection string.
24+
* `toWkt()` - Serializes the geometry object into a WKT.
25+
* `toWkb()` - Serializes the geometry object into a WKB.
2126
* `getCoordinates()` - Returns the coordinates of the geometry object.
2227

2328
In addition, `GeometryCollection` also has these functions:
@@ -40,7 +45,7 @@ $geometryCollection = new GeometryCollection([
4045
]);
4146

4247
echo $geometryCollection->getGeometries()[1]->latitude; // 180
43-
// can also access as an array:
48+
// or access as an array:
4449
echo $geometryCollection[1]->latitude; // 180
4550
```
4651

@@ -59,6 +64,7 @@ echo $geometryCollection[1]->latitude; // 180
5964
* [whereCrosses](#whereCrosses)
6065
* [whereDisjoint](#whereDisjoint)
6166
* [whereEquals](#whereEquals)
67+
* [whereSrid](#whereSrid)
6268

6369
### withDistance
6470

@@ -372,3 +378,24 @@ Place::query()
372378
```
373379
</details>
374380

381+
### whereSrid
382+
383+
Filters records by the [ST_Srid](https://dev.mysql.com/doc/refman/8.0/en/gis-general-property-functions.html#function_st-srid) function.
384+
385+
| parameter name | type
386+
| ------------------ | --------------------
387+
| `$column` | `string`
388+
| `$operator` | `string`
389+
| `$value` | `int`
390+
391+
<details><summary>Example</summary>
392+
393+
```php
394+
Place::create(['location' => new Point(0, 0, 4326)]);
395+
396+
Place::query()
397+
->whereSrid('location', '=', 4326)
398+
->exists(); // true
399+
```
400+
</details>
401+

README.md

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,12 @@ use MatanYadaev\EloquentSpatial\Objects\Point;
102102

103103
$londonEye = Place::create([
104104
'name' => 'London Eye',
105-
'location' => new Point(51.5032973, -0.1195537)
105+
'location' => new Point(51.5032973, -0.1217424),
106+
]);
107+
108+
$whiteHouse = Place::create([
109+
'name' => 'White House',
110+
'location' => new Point(38.8976763, -77.0365298, 4326), // with SRID
106111
]);
107112

108113
$vaticanCity = Place::create([
@@ -119,16 +124,18 @@ $vaticanCity = Place::create([
119124
new Point(12.457734346389769, 41.905918239316286),
120125
new Point(12.45572805404663, 41.90637337450963),
121126
new Point(12.455363273620605, 41.90746728266806),
122-
])
123-
])
127+
]),
128+
]),
124129
])
125130
```
126131

127132
Retrieve a record with spatial data:
128133

129134
```php
130135
echo $londonEye->location->latitude; // 51.5032973
131-
echo $londonEye->location->longitude; // -0.1195537
136+
echo $londonEye->location->longitude; // -0.1217424
137+
138+
echo $whiteHouse->location->srid; // 4326
132139

133140
echo $vacationCity->area->toJson(); // {"type":"Polygon","coordinates":[[[41.90746728266806,12.455363273620605],[41.906636872349075,12.450309991836548],[41.90197359839437,12.445632219314575],[41.90027269624499,12.447413206100464],[41.90000118654431,12.457906007766724],[41.90281205461268,12.458517551422117],[41.903107507989986,12.457584142684937],[41.905918239316286,12.457734346389769],[41.90637337450963,12.45572805404663],[41.90746728266806,12.455363273620605]]]}
134141
```
@@ -170,13 +177,12 @@ Place::query()->whereDistance(...); // This is IDE-friendly
170177
Place::whereDistance(...); // This is not
171178
```
172179

173-
## Tests
180+
## Development
174181

175-
``` bash
176-
composer phpunit
177-
# or with coverage
178-
composer phpunit-coverage
179-
```
182+
* Test: `composer pest`
183+
* Test with coverage: `composer pest-coverage`
184+
* Type check: `composer phpstan`
185+
* Format: `composer php-cs-fixer`
180186

181187
## Changelog
182188

src/Factory.php

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,8 @@
2525

2626
class Factory
2727
{
28-
public static function parse(string $value, bool $isWkb): Geometry
28+
public static function parse(string $value): Geometry
2929
{
30-
if ($isWkb) {
31-
// MySQL adds 4 NULL bytes at the start of the WKB
32-
$value = substr($value, 4);
33-
}
34-
3530
try {
3631
/** @var geoPHPGeometry|false $geoPHPGeometry */
3732
$geoPHPGeometry = geoPHP::load($value);
@@ -46,14 +41,14 @@ public static function parse(string $value, bool $isWkb): Geometry
4641

4742
protected static function createFromGeometry(geoPHPGeometry $geometry): Geometry
4843
{
44+
$srid = is_int($geometry->getSRID()) ? $geometry->getSRID() : 0;
45+
4946
if ($geometry instanceof geoPHPPoint) {
5047
if ($geometry->coords[0] === null || $geometry->coords[1] === null) {
51-
if (! isset($geoPHPGeometry) || ! $geoPHPGeometry) {
52-
throw new InvalidArgumentException('Invalid spatial value');
53-
}
48+
throw new InvalidArgumentException('Invalid spatial value');
5449
}
5550

56-
return new Point($geometry->coords[1], $geometry->coords[0]);
51+
return new Point($geometry->coords[1], $geometry->coords[0], $srid);
5752
}
5853

5954
/** @var geoPHPGeometryCollection $geometry */
@@ -63,25 +58,25 @@ protected static function createFromGeometry(geoPHPGeometry $geometry): Geometry
6358
});
6459

6560
if ($geometry::class === geoPHPMultiPoint::class) {
66-
return new MultiPoint($components);
61+
return new MultiPoint($components, $srid);
6762
}
6863

6964
if ($geometry::class === geoPHPLineString::class) {
70-
return new LineString($components);
65+
return new LineString($components, $srid);
7166
}
7267

7368
if ($geometry::class === geoPHPPolygon::class) {
74-
return new Polygon($components);
69+
return new Polygon($components, $srid);
7570
}
7671

7772
if ($geometry::class === geoPHPMultiLineString::class) {
78-
return new MultiLineString($components);
73+
return new MultiLineString($components, $srid);
7974
}
8075

8176
if ($geometry::class === geoPHPMultiPolygon::class) {
82-
return new MultiPolygon($components);
77+
return new MultiPolygon($components, $srid);
8378
}
8479

85-
return new GeometryCollection($components);
80+
return new GeometryCollection($components, $srid);
8681
}
8782
}

src/GeometryCast.php

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,56 +27,64 @@ public function __construct(string $className)
2727
/**
2828
* @param Model $model
2929
* @param string $key
30-
* @param string|Expression|null $wkbOrWKt
30+
* @param string|Expression|null $value
3131
* @param array<string, mixed> $attributes
3232
* @return Geometry|null
3333
*/
34-
public function get($model, string $key, $wkbOrWKt, array $attributes): ?Geometry
34+
public function get($model, string $key, $value, array $attributes): ?Geometry
3535
{
36-
if (! $wkbOrWKt) {
36+
if (! $value) {
3737
return null;
3838
}
3939

40-
if ($wkbOrWKt instanceof Expression) {
41-
$wkt = $this->extractWktFromExpression($wkbOrWKt);
40+
if ($value instanceof Expression) {
41+
$wkt = $this->extractWktFromExpression($value);
42+
$srid = $this->extractSridFromExpression($value);
4243

43-
return $this->className::fromWkt($wkt);
44+
return $this->className::fromWkt($wkt, $srid);
4445
}
4546

46-
return $this->className::fromWkb($wkbOrWKt);
47+
return $this->className::fromWkb($value);
4748
}
4849

4950
/**
5051
* @param Model $model
5152
* @param string $key
52-
* @param Geometry|mixed|null $geometry
53+
* @param Geometry|mixed|null $value
5354
* @param array<string, mixed> $attributes
5455
* @return Expression|null
5556
*
5657
* @throws InvalidArgumentException
5758
*/
58-
public function set($model, string $key, $geometry, array $attributes): Expression|null
59+
public function set($model, string $key, $value, array $attributes): Expression|null
5960
{
60-
if (! $geometry) {
61+
if (! $value) {
6162
return null;
6263
}
6364

64-
if (! ($geometry instanceof $this->className)) {
65-
$geometryType = is_object($geometry) ? $geometry::class : gettype($geometry);
65+
if (! ($value instanceof $this->className)) {
66+
$geometryType = is_object($value) ? $value::class : gettype($value);
6667
throw new InvalidArgumentException(
6768
sprintf('Expected %s, %s given.', static::class, $geometryType)
6869
);
6970
}
7071

71-
$wkt = $geometry->toWkt(withFunction: true);
72+
$wkt = $value->toWkt();
7273

73-
return DB::raw("ST_GeomFromText('{$wkt}')");
74+
return DB::raw("ST_GeomFromText('{$wkt}', {$value->srid})");
7475
}
7576

7677
private function extractWktFromExpression(Expression $expression): string
7778
{
78-
preg_match('/ST_GeomFromText\(\'(.+)\'\)/', (string) $expression, $match);
79+
preg_match('/ST_GeomFromText\(\'(.+)\', .+\)/', (string) $expression, $match);
7980

8081
return $match[1];
8182
}
83+
84+
private function extractSridFromExpression(Expression $expression): int
85+
{
86+
preg_match('/ST_GeomFromText\(\'.+\', (.+)\)/', (string) $expression, $match);
87+
88+
return (int) $match[1];
89+
}
8290
}

src/Objects/Geometry.php

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
abstract class Geometry implements Castable, Arrayable, Jsonable, JsonSerializable
2020
{
21+
public int $srid = 0;
22+
2123
abstract public function toWkt(bool $withFunction = true): string;
2224

2325
/**
@@ -33,10 +35,14 @@ public function toJson($options = 0): string
3335

3436
public function toWkb(): string
3537
{
36-
$geoPHPGeometry = geoPHP::load($this->toWkt());
38+
$geoPHPGeometry = geoPHP::load($this->toJson());
39+
40+
$sridInBinary = pack('L', $this->srid);
3741

3842
// @phpstan-ignore-next-line
39-
return (new geoPHPWkb)->write($geoPHPGeometry, true);
43+
$wkbWithoutSrid = (new geoPHPWkb)->write($geoPHPGeometry);
44+
45+
return $sridInBinary.$wkbWithoutSrid;
4046
}
4147

4248
/**
@@ -47,7 +53,14 @@ public function toWkb(): string
4753
*/
4854
public static function fromWkb(string $wkb): static
4955
{
50-
$geometry = Factory::parse($wkb, true);
56+
$srid = substr($wkb, 0, 4);
57+
// @phpstan-ignore-next-line
58+
$srid = unpack('L', $srid)[1];
59+
60+
$wkb = substr($wkb, 4);
61+
62+
$geometry = Factory::parse($wkb);
63+
$geometry->srid = $srid;
5164

5265
if (! ($geometry instanceof static)) {
5366
throw new InvalidArgumentException(
@@ -60,13 +73,15 @@ public static function fromWkb(string $wkb): static
6073

6174
/**
6275
* @param string $wkt
76+
* @param int $srid
6377
* @return static
6478
*
6579
* @throws InvalidArgumentException
6680
*/
67-
public static function fromWkt(string $wkt): static
81+
public static function fromWkt(string $wkt, int $srid = 0): static
6882
{
69-
$geometry = Factory::parse($wkt, false);
83+
$geometry = Factory::parse($wkt);
84+
$geometry->srid = $srid;
7085

7186
if (! ($geometry instanceof static)) {
7287
throw new InvalidArgumentException(
@@ -79,13 +94,15 @@ public static function fromWkt(string $wkt): static
7994

8095
/**
8196
* @param string $geoJson
97+
* @param int $srid
8298
* @return static
8399
*
84100
* @throws InvalidArgumentException
85101
*/
86-
public static function fromJson(string $geoJson): static
102+
public static function fromJson(string $geoJson, int $srid = 0): static
87103
{
88-
$geometry = Factory::parse($geoJson, false);
104+
$geometry = Factory::parse($geoJson);
105+
$geometry->srid = $srid;
89106

90107
if (! ($geometry instanceof static)) {
91108
throw new InvalidArgumentException(

0 commit comments

Comments
 (0)