Passed
Push — testing ( 57c125...199f7f )
by Hennik
03:23
created

SpatialTrait::scopeDistanceSphereExcludingSelf()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
c 0
b 0
f 0
dl 0
loc 14
rs 10
cc 4
nc 6
nop 4
1
<?php
2
3
namespace LaravelSpatial\Eloquent;
4
5
use GeoJson\GeoJson;
6
use GeoJSON\Geometry\Geometry;
7
use geoPHP\geoPHP;
8
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
9
use Illuminate\Database\MySqlConnection;
10
use Illuminate\Database\PostgresConnection;
11
use LaravelSpatial\Exceptions\SpatialFieldsNotDefinedException;
12
13
/**
14
 * Trait SpatialTrait.
15
 *
16
 * @property $attributes array
17
 * @method static distance($geometryColumn, $geometry, $distance)
18
 * @method static distanceExcludingSelf($geometryColumn, $geometry, $distance)
19
 * @method static distanceSphere($geometryColumn, $geometry, $distance)
20
 * @method static distanceSphereExcludingSelf($geometryColumn, $geometry, $distance)
21
 * @method static comparison($geometryColumn, $geometry, $relationship)
22
 * @method static within($geometryColumn, $polygon)
23
 * @method static crosses($geometryColumn, $geometry)
24
 * @method static contains($geometryColumn, $geometry)
25
 * @method static disjoint($geometryColumn, $geometry)
26
 * @method static equals($geometryColumn, $geometry)
27
 * @method static intersects($geometryColumn, $geometry)
28
 * @method static overlaps($geometryColumn, $geometry)
29
 * @method static doesTouch($geometryColumn, $geometry)
30
 */
0 ignored issues
show
Documentation Bug introduced by
The doc comment $attributes at position 0 could not be parsed: Unknown type name '$attributes' at position 0 in $attributes.
Loading history...
31
trait SpatialTrait
32
{
33
    /*
34
     * The attributes that are spatial representations.
35
     * To use this Trait, add the following array to the model class
36
     *
37
     * @var array
38
     *
39
     * protected $spatialFields = [];
40
     */
41
42
    public $geometries = [];
43
44
    protected $stRelations = [
45
        'Within',
46
        'Crosses',
47
        'Contains',
48
        'Disjoint',
49
        'Equals',
50
        'Intersects',
51
        'Overlaps',
52
        'Touches',
53
    ];
54
55
    /**
56
     * Create a new Eloquent query builder for the model.
57
     *
58
     * @param  \Illuminate\Database\Query\Builder $query
59
     * @return \LaravelSpatial\Eloquent\Builder
60
     */
61
    public function newEloquentBuilder($query)
62
    {
63
        return new Builder($query);
64
    }
65
66
    public function setRawAttributes(array $attributes, $sync = false)
67
    {
68
        $spatial_fields = $this->getSpatialFields();
69
70
        foreach ($attributes as $attribute => &$value) {
71
            if (in_array($attribute, $spatial_fields) && is_string($value) && strlen($value) >= 15) {
72
                // MySQL adds 4 NULL bytes at the start of the binary
73
                if ($this->getConnection() instanceof MySqlConnection && substr($value, 0, 4) == "\0\0\0\0") {
74
                    $value = substr($value, 4);
75
                } elseif ($this->getConnection() instanceof PostgresConnection) {
76
                    $value = pack('H*', $value);
77
                }
78
79
                try {
80
                    $value = GeoJson::jsonUnserialize(json_decode(geoPHP::load($value, 'wkb')->out('json')));
81
                } catch (\Exception $e) {
82
                    throw new \Exception("Can't parse WKB {$value}: {$e->getMessage()}", $e->getCode(), $e);
83
                }
84
            }
85
        }
86
87
        parent::setRawAttributes($attributes, $sync);
88
    }
89
90
    public function getSpatialFields()
91
    {
92
        if (property_exists($this, 'spatialFields') && !empty($this->spatialFields)) {
93
            return $this->spatialFields;
94
        } else {
95
            throw new SpatialFieldsNotDefinedException(__CLASS__ . ' has to define $spatialFields');
96
        }
97
    }
98
99
    protected function toWkt(Geometry $value)
100
    {
101
        return ($this->getConnection() instanceof PostgresConnection ? 'SRID=4326;' : '') .
102
                geoPHP::load(json_decode(json_encode($value->jsonSerialize()), false), 'json')->out('wkt');
103
    }
104
105
    protected function performInsert(EloquentBuilder $query, array $options = [])
106
    {
107
        foreach ($this->attributes as $key => $value) {
108
            if ($value instanceof Geometry && $this->isColumnAllowed($key)) {
109
                $this->geometries[$key] = $value; // Preserve the geometry objects prior to the insert
110
                $this->attributes[$key] = $this->getConnection()->raw("ST_GeomFromText('{$this->toWkt($value)}')");
111
            }
112
        }
113
114
        $insert = parent::performInsert($query, $options);
115
116
        foreach ($this->geometries as $key => $value) {
117
            $this->attributes[$key] = $value; // Retrieve the geometry objects so they can be used in the model
118
        }
119
120
        return $insert; // Return the result of the parent insert
121
    }
122
123
    public function isColumnAllowed($geometryColumn)
124
    {
125
        if (!in_array($geometryColumn, $this->getSpatialFields())) {
126
            throw new SpatialFieldsNotDefinedException();
127
        }
128
129
        return true;
130
    }
131
132
    public function scopeDistance($query, $geometryColumn, $geometry, $distance)
133
    {
134
        if ($this->isColumnAllowed($geometryColumn)) {
135
            $geometryColumn .= $this->getConnection() instanceof PostgresConnection ? '::geometry' : '';
136
            $query->whereRaw("ST_Distance({$geometryColumn}, ST_GeomFromText(?)) <= ?", [
137
                $this->toWkt($geometry),
138
                $distance,
139
            ]);
140
        }
141
142
        return $query;
143
    }
144
145
    public function scopeDistanceExcludingSelf($query, $geometryColumn, $geometry, $distance)
146
    {
147
        if ($this->isColumnAllowed($geometryColumn)) {
148
            $query = $this->scopeDistance($query, $geometryColumn, $geometry, $distance);
149
150
            $geometryColumn .= $this->getConnection() instanceof PostgresConnection ? '::geometry' : '';
151
            $query->whereRaw("ST_Distance({$geometryColumn}, ST_GeomFromText(?)) != 0", [
152
                $this->toWkt($geometry),
153
            ]);
154
        }
155
156
        return $query;
157
    }
158
159
    public function scopeDistanceValue($query, $geometryColumn, $geometry)
160
    {
161
        if ($this->isColumnAllowed($geometryColumn)) {
162
            $columns = $query->getQuery()->columns;
163
164
            if (!$columns) {
165
                $query->select('*');
166
            }
167
168
            $geometryColumn .= $this->getConnection() instanceof PostgresConnection ? '::geometry' : '';
169
            $query->selectRaw("ST_Distance({$geometryColumn}, ST_GeomFromText(?)) as distance", [
170
                $this->toWkt($geometry),
171
            ]);
172
        }
173
174
        return $query;
175
    }
176
177
    public function scopeDistanceSphere($query, $geometryColumn, $geometry, $distance)
178
    {
179
        $distFunc = $this->getConnection() instanceof PostgresConnection ? 'ST_DistanceSphere' : 'ST_Distance_Sphere';
180
181
        if ($this->isColumnAllowed($geometryColumn)) {
182
            $geometryColumn .= $this->getConnection() instanceof PostgresConnection ? '::geometry' : '';
183
            $query->whereRaw("{$distFunc}({$geometryColumn}, ST_GeomFromText(?)) <= ?", [
184
                $this->toWkt($geometry),
185
                $distance,
186
            ]);
187
        }
188
189
        return $query;
190
    }
191
192
    public function scopeDistanceSphereExcludingSelf($query, $geometryColumn, $geometry, $distance)
193
    {
194
        $distFunc = $this->getConnection() instanceof PostgresConnection ? 'ST_DistanceSphere' : 'ST_Distance_Sphere';
195
196
        if ($this->isColumnAllowed($geometryColumn)) {
197
            $query = $this->scopeDistanceSphere($query, $geometryColumn, $geometry, $distance);
198
199
            $geometryColumn .= $this->getConnection() instanceof PostgresConnection ? '::geometry' : '';
200
            $query->whereRaw("{$distFunc}({$geometryColumn}, ST_GeomFromText(?)) != 0", [
201
                $this->toWkt($geometry),
202
            ]);
203
        }
204
205
        return $query;
206
    }
207
208
    public function scopeDistanceSphereValue($query, $geometryColumn, $geometry)
209
    {
210
        $distFunc = $this->getConnection() instanceof PostgresConnection ? 'ST_DistanceSphere' : 'ST_Distance_Sphere';
211
212
        if ($this->isColumnAllowed($geometryColumn)) {
213
            $columns = $query->getQuery()->columns;
214
215
            if (!$columns) {
216
                $query->select('*');
217
            }
218
219
            $geometryColumn .= $this->getConnection() instanceof PostgresConnection ? '::geometry' : '';
220
            $query->selectRaw("{$distFunc}({$geometryColumn}, ST_GeomFromText(?)) as distance", [
221
                $this->toWkt($geometry),
222
            ]);
223
        }
224
225
        return $query;
226
    }
227
228
    public function scopeComparison($query, $geometryColumn, $geometry, $relationship)
229
    {
230
        if ($this->isColumnAllowed($geometryColumn)) {
231
            $relationship = ucfirst(strtolower($relationship));
232
233
            if (!in_array($relationship, $this->stRelations)) {
234
                throw new UnknownSpatialRelationFunction($relationship);
0 ignored issues
show
Bug introduced by
The type LaravelSpatial\Eloquent\...SpatialRelationFunction was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
235
            }
236
237
            $geometryColumn .= $this->getConnection() instanceof PostgresConnection ? '::geometry' : '';
238
            $query->whereRaw("ST_{$relationship}(`{$geometryColumn}`, ST_GeomFromText(?))", [
239
                $this->toWkt($geometry),
240
            ]);
241
        }
242
243
        return $query;
244
    }
245
246
    public function scopeWithin($query, $geometryColumn, $polygon)
247
    {
248
        return $this->scopeComparison($query, $geometryColumn, $polygon, 'within');
249
    }
250
251
    public function scopeCrosses($query, $geometryColumn, $geometry)
252
    {
253
        return $this->scopeComparison($query, $geometryColumn, $geometry, 'crosses');
254
    }
255
256
    public function scopeContains($query, $geometryColumn, $geometry)
257
    {
258
        return $this->scopeComparison($query, $geometryColumn, $geometry, 'contains');
259
    }
260
261
    public function scopeDisjoint($query, $geometryColumn, $geometry)
262
    {
263
        return $this->scopeComparison($query, $geometryColumn, $geometry, 'disjoint');
264
    }
265
266
    public function scopeEquals($query, $geometryColumn, $geometry)
267
    {
268
        return $this->scopeComparison($query, $geometryColumn, $geometry, 'equals');
269
    }
270
271
    public function scopeIntersects($query, $geometryColumn, $geometry)
272
    {
273
        return $this->scopeComparison($query, $geometryColumn, $geometry, 'intersects');
274
    }
275
276
    public function scopeOverlaps($query, $geometryColumn, $geometry)
277
    {
278
        return $this->scopeComparison($query, $geometryColumn, $geometry, 'overlaps');
279
    }
280
281
    public function scopeDoesTouch($query, $geometryColumn, $geometry)
282
    {
283
        return $this->scopeComparison($query, $geometryColumn, $geometry, 'touches');
284
    }
285
}
286