Test Setup Failed
Branch testing (d1ef2e)
by Hennik
02:20
created

SpatialTrait::scopeWithin()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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