From 623014fa18dbba53f83921b64f0ea305e6546e0c Mon Sep 17 00:00:00 2001 From: Mustafa Talaeezadeh Khouzani Date: Wed, 12 Jun 2019 01:51:56 +0430 Subject: [PATCH 1/2] Custom spherical radius & SpatialTrait annotations --- src/Eloquent/SpatialTrait.php | 141 ++++++++++++++++++++++++++++++---- 1 file changed, 127 insertions(+), 14 deletions(-) diff --git a/src/Eloquent/SpatialTrait.php b/src/Eloquent/SpatialTrait.php index c1bf101c..23590b39 100755 --- a/src/Eloquent/SpatialTrait.php +++ b/src/Eloquent/SpatialTrait.php @@ -119,7 +119,16 @@ public function getSpatialFields() } } - public function isColumnAllowed($geometryColumn) + /** + * Checks whether a column name exists in a SpatialTrait::$geometries + * + * @param string $geometryColumn + * + * @throws SpatialFieldsNotDefinedException + * + * @return bool + */ + public function isColumnAllowed(string $geometryColumn) { if (!in_array($geometryColumn, $this->getSpatialFields())) { throw new SpatialFieldsNotDefinedException(); @@ -128,7 +137,19 @@ public function isColumnAllowed($geometryColumn) return true; } - public function scopeDistance($query, $geometryColumn, $geometry, $distance) + /** + * Queries a geometry column to have less than or equal distance to a geometry object on a flat surface + * + * @param EloquentBuilder $query + * @param string $geometryColumn + * @param Geometry $geometry + * @param float $distance + * + * @throws SpatialFieldsNotDefinedException + * + * @return EloquentBuilder + */ + public function scopeDistance(EloquentBuilder $query, string $geometryColumn, Geometry $geometry, float $distance) { $this->isColumnAllowed($geometryColumn); @@ -140,7 +161,19 @@ public function scopeDistance($query, $geometryColumn, $geometry, $distance) return $query; } - public function scopeDistanceExcludingSelf($query, $geometryColumn, $geometry, $distance) + /** + * Queries a geometry column to have less than or equal distance to a geometry object on a flat surface, excluding the geometry object from result set + * + * @param EloquentBuilder $query + * @param string $geometryColumn + * @param Geometry $geometry + * @param float $distance + * + * @throws SpatialFieldsNotDefinedException + * + * @return EloquentBuilder + */ + public function scopeDistanceExcludingSelf(EloquentBuilder $query, string $geometryColumn, Geometry $geometry, float $distance) { $this->isColumnAllowed($geometryColumn); @@ -153,7 +186,18 @@ public function scopeDistanceExcludingSelf($query, $geometryColumn, $geometry, $ return $query; } - public function scopeDistanceValue($query, $geometryColumn, $geometry) + /** + * Queries a table for distance between a geometry column and a geometry object on a flat surface + * + * @param EloquentBuilder $query + * @param string $geometryColumn + * @param Geometry $geometry + * + * @throws SpatialFieldsNotDefinedException + * + * @return EloquentBuilder + */ + public function scopeDistanceValue(EloquentBuilder $query, string $geometryColumn, Geometry $geometry) { $this->isColumnAllowed($geometryColumn); @@ -168,32 +212,72 @@ public function scopeDistanceValue($query, $geometryColumn, $geometry) ]); } - public function scopeDistanceSphere($query, $geometryColumn, $geometry, $distance) + /** + * Queries a geometry column to have less than or equal distance to a geometry object on a sphere + * + * @param EloquentBuilder $query + * @param string $geometryColumn + * @param Geometry $geometry + * @param float $distance + * @param float $radius + * + * @throws SpatialFieldsNotDefinedException + * + * @return EloquentBuilder + */ + public function scopeDistanceSphere(EloquentBuilder $query, string $geometryColumn, Geometry $geometry, float $distance, float $radius = 6371008.7714150598325213221998779) { $this->isColumnAllowed($geometryColumn); - $query->whereRaw("st_distance_sphere(`$geometryColumn`, ST_GeomFromText(?)) <= ?", [ + $query->whereRaw("st_distance_sphere(`$geometryColumn`, ST_GeomFromText(?), ?) <= ?", [ $geometry->toWkt(), + $radius, $distance, ]); return $query; } - public function scopeDistanceSphereExcludingSelf($query, $geometryColumn, $geometry, $distance) + /** + * Queries a geometry column to have less than or equal distance to a geometry object on a sphere, excluding the geometry object from result set + * + * @param EloquentBuilder $query + * @param string $geometryColumn + * @param Geometry $geometry + * @param float $distance + * @param float $radius + * + * @throws SpatialFieldsNotDefinedException + * + * @return EloquentBuilder + */ + public function scopeDistanceSphereExcludingSelf(EloquentBuilder $query, string $geometryColumn, Geometry $geometry, float $distance, float $radius = 6371008.7714150598325213221998779) { $this->isColumnAllowed($geometryColumn); $query = $this->scopeDistanceSphere($query, $geometryColumn, $geometry, $distance); - $query->whereRaw("st_distance_sphere($geometryColumn, ST_GeomFromText(?)) != 0", [ + $query->whereRaw("st_distance_sphere($geometryColumn, ST_GeomFromText(?), ?) != 0", [ $geometry->toWkt(), + $radius, ]); return $query; } - public function scopeDistanceSphereValue($query, $geometryColumn, $geometry) + /** + * Queries a table for distance between a geometry column and a geometry object on a flat surface + * + * @param EloquentBuilder $query + * @param string $geometryColumn + * @param Geometry $geometry + * @param float $radius + * + * @throws SpatialFieldsNotDefinedException + * + * @return EloquentBuilder + */ + public function scopeDistanceSphereValue(EloquentBuilder $query, string $geometryColumn, Geometry $geometry, float $radius = 6371008.7714150598325213221998779) { $this->isColumnAllowed($geometryColumn); @@ -202,12 +286,26 @@ public function scopeDistanceSphereValue($query, $geometryColumn, $geometry) if (!$columns) { $query->select('*'); } - $query->selectRaw("st_distance_sphere(`$geometryColumn`, ST_GeomFromText(?)) as distance", [ + $query->selectRaw("st_distance_sphere(`$geometryColumn`, ST_GeomFromText(?), ?) as distance", [ $geometry->toWkt(), + $radius, ]); } - public function scopeComparison($query, $geometryColumn, $geometry, $relationship) + /** + * Description of `scopeComparison` + * + * @param EloquentBuilder $query + * @param string $geometryColumn + * @param Geometry $geometry + * @param string $relationship + * + * @throws SpatialFieldsNotDefinedException + * @throws UnknownSpatialRelationFunction + * + * @return EloquentBuilder + */ + public function scopeComparison(EloquentBuilder $query, string $geometryColumn, Geometry $geometry, string $relationship) { $this->isColumnAllowed($geometryColumn); @@ -279,11 +377,26 @@ public function scopeOrderBySpatial($query, $geometryColumn, $geometry, $orderFu public function scopeOrderByDistance($query, $geometryColumn, $geometry, $direction = 'asc') { - return $this->scopeOrderBySpatial($query, $geometryColumn, $geometry, 'distance', $direction); + $this->isColumnAllowed($geometryColumn); + + $query->orderByRaw("st_distance(`$geometryColumn`, ST_GeomFromText(?)) {$direction}", [ + $geometry->toWkt(), + ]); + + return $query; + // return $this->scopeOrderBySpatial($query, $geometryColumn, $geometry, 'distance', $direction); } - public function scopeOrderByDistanceSphere($query, $geometryColumn, $geometry, $direction = 'asc') + public function scopeOrderByDistanceSphere($query, $geometryColumn, $geometry, $direction = 'asc', float $radius = 6371008.7714150598325213221998779) { - return $this->scopeOrderBySpatial($query, $geometryColumn, $geometry, 'distance_sphere', $direction); + $this->isColumnAllowed($geometryColumn); + + $query->orderByRaw("st_distance_sphere(`$geometryColumn`, ST_GeomFromText(?), ?) {$direction}", [ + $geometry->toWkt(), + $radius, + ]); + + return $query; + // return $this->scopeOrderBySpatial($query, $geometryColumn, $geometry, 'distance_sphere', $direction); } } From 86b7ac79120ef6fd379de5971e5a4fbe3eedc542 Mon Sep 17 00:00:00 2001 From: Mustafa Talaeezadeh Khouzani Date: Wed, 26 Feb 2020 18:14:05 +0330 Subject: [PATCH 2/2] Adding MySQL compatible SRID --- src/Types/Factory.php | 16 ++++++++-------- src/Types/Geometry.php | 28 ++++++++++++++++++++++++++-- src/Types/GeometryCollection.php | 3 ++- src/Types/MultiLineString.php | 4 ++-- src/Types/MultiPolygon.php | 4 ++-- src/Types/Point.php | 3 ++- src/Types/PointCollection.php | 4 ++-- 7 files changed, 44 insertions(+), 18 deletions(-) diff --git a/src/Types/Factory.php b/src/Types/Factory.php index 087348fa..ed04ac2d 100755 --- a/src/Types/Factory.php +++ b/src/Types/Factory.php @@ -6,41 +6,41 @@ class Factory implements \GeoIO\Factory { public function createPoint($dimension, array $coordinates, $srid = null) { - return new Point($coordinates['y'], $coordinates['x']); + return new Point($coordinates['y'], $coordinates['x'], $srid); } public function createLineString($dimension, array $points, $srid = null) { - return new LineString($points); + return new LineString($points, $srid); } public function createLinearRing($dimension, array $points, $srid = null) { - return new LineString($points); + return new LineString($points, $srid); } public function createPolygon($dimension, array $lineStrings, $srid = null) { - return new Polygon($lineStrings); + return new Polygon($lineStrings, $srid); } public function createMultiPoint($dimension, array $points, $srid = null) { - return new MultiPoint($points); + return new MultiPoint($points, $srid); } public function createMultiLineString($dimension, array $lineStrings, $srid = null) { - return new MultiLineString($lineStrings); + return new MultiLineString($lineStrings, $srid); } public function createMultiPolygon($dimension, array $polygons, $srid = null) { - return new MultiPolygon($polygons); + return new MultiPolygon($polygons, $srid); } public function createGeometryCollection($dimension, array $geometries, $srid = null) { - return new GeometryCollection($geometries); + return new GeometryCollection($geometries, $srid); } } diff --git a/src/Types/Geometry.php b/src/Types/Geometry.php index c0df8ec3..b578c684 100644 --- a/src/Types/Geometry.php +++ b/src/Types/Geometry.php @@ -19,6 +19,8 @@ abstract class Geometry implements GeometryInterface, Jsonable, \JsonSerializabl 7 => GeometryCollection::class, ]; + protected $srid = 0; + public static function getWKTArgument($value) { $left = strpos($value, '('); @@ -54,8 +56,25 @@ public static function getWKTClass($value) public static function fromWKB($wkb) { - // mysql adds 4 NUL bytes at the start of the binary - $wkb = substr($wkb, 4); + // mysql adds 4 bytes at the start of the binary as SRID + // $srid = @unpack('V', substr($wkb, 0, 4))[1]; + // $wkb = substr($wkb, 4); + + $bom = unpack('C', substr($wkb, 4, 5)); + $formatter = $bom ? 'V' : 'N'; + $srid = unpack($formatter, substr($wkb, 0, 4))[1]; + $type = unpack($formatter, substr($wkb, 5, 9))[1]; + $wkb = substr($wkb, 9); + if ($srid) + { + $type |= Parser::MASK_SRID; + $wkb = pack('C', $bom) . pack($formatter, $type) . pack($formatter, $srid) . $wkb; + } + else + { + $wkb = pack('C', $bom) . pack($formatter, $type) . $wkb; + } + $parser = new Parser(new Factory()); return $parser->parse($wkb); @@ -91,4 +110,9 @@ public function toJson($options = 0) { return json_encode($this, $options); } + + public function getSRID() + { + return $this->srid; + } } diff --git a/src/Types/GeometryCollection.php b/src/Types/GeometryCollection.php index 2e1f0321..d11d395c 100755 --- a/src/Types/GeometryCollection.php +++ b/src/Types/GeometryCollection.php @@ -26,7 +26,7 @@ class GeometryCollection extends Geometry implements IteratorAggregate, ArrayAcc * * @throws InvalidArgumentException */ - public function __construct(array $geometries) + public function __construct(array $geometries, $srid = null) { $validated = array_filter($geometries, function ($value) { return $value instanceof GeometryInterface; @@ -37,6 +37,7 @@ public function __construct(array $geometries) } $this->items = $geometries; + $this->srid = $srid; } public function getGeometries() diff --git a/src/Types/MultiLineString.php b/src/Types/MultiLineString.php index dd3342fd..2cc30380 100644 --- a/src/Types/MultiLineString.php +++ b/src/Types/MultiLineString.php @@ -12,7 +12,7 @@ class MultiLineString extends GeometryCollection /** * @param LineString[] $lineStrings */ - public function __construct(array $lineStrings) + public function __construct(array $lineStrings, $srid = null) { if (count($lineStrings) < 1) { throw new InvalidArgumentException('$lineStrings must contain at least one entry'); @@ -26,7 +26,7 @@ public function __construct(array $lineStrings) throw new InvalidArgumentException('$lineStrings must be an array of LineString'); } - parent::__construct($lineStrings); + parent::__construct($lineStrings, $srid); } public function getLineStrings() diff --git a/src/Types/MultiPolygon.php b/src/Types/MultiPolygon.php index aba2b6d1..52d89dc9 100644 --- a/src/Types/MultiPolygon.php +++ b/src/Types/MultiPolygon.php @@ -12,7 +12,7 @@ class MultiPolygon extends GeometryCollection /** * @param Polygon[] $polygons */ - public function __construct(array $polygons) + public function __construct(array $polygons, $srid = null) { $validated = array_filter($polygons, function ($value) { return $value instanceof Polygon; @@ -21,7 +21,7 @@ public function __construct(array $polygons) if (count($polygons) !== count($validated)) { throw new InvalidArgumentException('$polygons must be an array of Polygon'); } - parent::__construct($polygons); + parent::__construct($polygons, $srid); } public function toWKT() diff --git a/src/Types/Point.php b/src/Types/Point.php index 40719af4..91052171 100644 --- a/src/Types/Point.php +++ b/src/Types/Point.php @@ -12,10 +12,11 @@ class Point extends Geometry protected $lng; - public function __construct($lat, $lng) + public function __construct($lat, $lng, $srid = null) { $this->lat = (float) $lat; $this->lng = (float) $lng; + $this->srid = (int) $srid; } public function getLat() diff --git a/src/Types/PointCollection.php b/src/Types/PointCollection.php index af586b28..1585ab29 100755 --- a/src/Types/PointCollection.php +++ b/src/Types/PointCollection.php @@ -10,7 +10,7 @@ abstract class PointCollection extends GeometryCollection /** * @param Point[] $points */ - public function __construct(array $points) + public function __construct(array $points, $srid = null) { if (count($points) < 2) { throw new InvalidArgumentException('$points must contain at least two entries'); @@ -24,7 +24,7 @@ public function __construct(array $points) throw new InvalidArgumentException('$points must be an array of Points'); } - parent::__construct($points); + parent::__construct($points, $srid); } public function toPairList()