Passed
Push — master ( 5a9173...228961 )
by Hennik
06:27
created

SpatialTrait::scopeDistanceSphereValue()   A

Complexity

Conditions 5
Paths 10

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 9
nc 10
nop 3
dl 0
loc 18
ccs 10
cts 10
cp 1
crap 5
rs 9.6111
c 0
b 0
f 0
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 array $attributes
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
 */
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 48
    public function newEloquentBuilder($query)
63
    {
64 48
        return new Builder($query);
65
    }
66
67 11
    public function setRawAttributes(array $attributes, $sync = false)
68
    {
69 11
        $spatial_fields = $this->getSpatialFields();
70
71 11
        foreach ($attributes as $attribute => &$value) {
72 11
            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 11
                if ($this->getConnection() instanceof MySqlConnection && substr($value, 0, 4) == "\0\0\0\0") {
75 5
                    $value = substr($value, 4);
76 6
                } elseif ($this->getConnection() instanceof PostgresConnection) {
77 5
                    $value = pack('H*', $value);
78
                }
79
80
                try {
81 11
                    $value = GeoJson::jsonUnserialize(json_decode(geoPHP::load($value, 'wkb')->out('json')));
82
                } catch (\Exception $e) {
83 11
                    throw new \Exception("Can't parse WKB {$value}: {$e->getMessage()}", $e->getCode(), $e);
84
                }
85
            }
86
        }
87
88 11
        parent::setRawAttributes($attributes, $sync);
89 11
    }
90
91 50
    public function getSpatialFields()
92
    {
93 50
        if (property_exists($this, 'spatialFields') && !empty($this->spatialFields)) {
94 47
            return $this->spatialFields;
95
        } else {
96 3
            throw new SpatialFieldsNotDefinedException(__CLASS__ . ' has to define $spatialFields');
97
        }
98
    }
99
100 46
    protected function toWkt(Geometry $value)
101
    {
102 46
        return ($this->getConnection() instanceof PostgresConnection ? 'SRID=4326;' : '') .
103 46
                geoPHP::load(json_decode(json_encode($value->jsonSerialize()), false), 'json')->out('wkt');
104
    }
105
106 31
    protected function performInsert(EloquentBuilder $query, array $options = [])
107
    {
108 31
        foreach ($this->attributes as $key => $value) {
109 31
            if ($value instanceof Geometry && $this->isColumnAllowed($key)) {
110 29
                $this->geometries[$key] = $value; // Preserve the geometry objects prior to the insert
111 29
                $this->attributes[$key] = $this->getConnection()->raw("ST_GeomFromText('{$this->toWkt($value)}')");
112
            }
113
        }
114
115 29
        $insert = parent::performInsert($query, $options);
116
117 29
        foreach ($this->geometries as $key => $value) {
118 29
            $this->attributes[$key] = $value; // Retrieve the geometry objects so they can be used in the model
119
        }
120
121 29
        return $insert; // Return the result of the parent insert
122
    }
123
124 48
    public function isColumnAllowed($geometryColumn)
125
    {
126 48
        if (!in_array($geometryColumn, $this->getSpatialFields())) {
127
            throw new SpatialFieldsNotDefinedException();
128
        }
129
130 46
        return true;
131
    }
132
133 4
    public function scopeDistance($query, $geometryColumn, $geometry, $distance)
134
    {
135 4
        if ($this->isColumnAllowed($geometryColumn)) {
136 4
            $geometryColumn .= $this->getConnection() instanceof PostgresConnection ? '::geometry' : '';
137 4
            $query->whereRaw("ST_Distance({$geometryColumn}, ST_GeomFromText(?)) <= ?", [
138 4
                $this->toWkt($geometry),
139 4
                $distance,
140
            ]);
141
        }
142
143 4
        return $query;
144
    }
145
146 3
    public function scopeDistanceExcludingSelf($query, $geometryColumn, $geometry, $distance)
147
    {
148 3
        if ($this->isColumnAllowed($geometryColumn)) {
149 3
            $query = $this->scopeDistance($query, $geometryColumn, $geometry, $distance);
150
151 3
            $geometryColumn .= $this->getConnection() instanceof PostgresConnection ? '::geometry' : '';
152 3
            $query->whereRaw("ST_Distance({$geometryColumn}, ST_GeomFromText(?)) != 0", [
153 3
                $this->toWkt($geometry),
154
            ]);
155
        }
156
157 3
        return $query;
158
    }
159
160 4
    public function scopeDistanceValue($query, $geometryColumn, $geometry)
161
    {
162 4
        if ($this->isColumnAllowed($geometryColumn)) {
163 4
            $columns = $query->getQuery()->columns;
164
165 4
            if (!$columns) {
166 3
                $query->select('*');
167
            }
168
169 4
            $geometryColumn .= $this->getConnection() instanceof PostgresConnection ? '::geometry' : '';
170 4
            $query->selectRaw("ST_Distance({$geometryColumn}, ST_GeomFromText(?)) as distance", [
171 4
                $this->toWkt($geometry),
172
            ]);
173
        }
174
175 4
        return $query;
176
    }
177
178 4
    public function scopeDistanceSphere($query, $geometryColumn, $geometry, $distance)
179
    {
180 4
        $distFunc = $this->getConnection() instanceof PostgresConnection ? 'ST_DistanceSphere' : 'ST_Distance_Sphere';
181
182 4
        if ($this->isColumnAllowed($geometryColumn)) {
183 4
            $geometryColumn .= $this->getConnection() instanceof PostgresConnection ? '::geometry' : '';
184 4
            $query->whereRaw("{$distFunc}({$geometryColumn}, ST_GeomFromText(?)) <= ?", [
185 4
                $this->toWkt($geometry),
186 4
                $distance,
187
            ]);
188
        }
189
190 4
        return $query;
191
    }
192
193 3
    public function scopeDistanceSphereExcludingSelf($query, $geometryColumn, $geometry, $distance)
194
    {
195 3
        $distFunc = $this->getConnection() instanceof PostgresConnection ? 'ST_DistanceSphere' : 'ST_Distance_Sphere';
196
197 3
        if ($this->isColumnAllowed($geometryColumn)) {
198 3
            $query = $this->scopeDistanceSphere($query, $geometryColumn, $geometry, $distance);
199
200 3
            $geometryColumn .= $this->getConnection() instanceof PostgresConnection ? '::geometry' : '';
201 3
            $query->whereRaw("{$distFunc}({$geometryColumn}, ST_GeomFromText(?)) != 0", [
202 3
                $this->toWkt($geometry),
203
            ]);
204
        }
205
206 3
        return $query;
207
    }
208
209 4
    public function scopeDistanceSphereValue($query, $geometryColumn, $geometry)
210
    {
211 4
        $distFunc = $this->getConnection() instanceof PostgresConnection ? 'ST_DistanceSphere' : 'ST_Distance_Sphere';
212
213 4
        if ($this->isColumnAllowed($geometryColumn)) {
214 4
            $columns = $query->getQuery()->columns;
215
216 4
            if (!$columns) {
217 3
                $query->select('*');
218
            }
219
220 4
            $geometryColumn .= $this->getConnection() instanceof PostgresConnection ? '::geometry' : '';
221 4
            $query->selectRaw("{$distFunc}({$geometryColumn}, ST_GeomFromText(?)) as distance", [
222 4
                $this->toWkt($geometry),
223
            ]);
224
        }
225
226 4
        return $query;
227
    }
228
229 9
    public function scopeComparison($query, $geometryColumn, $geometry, $relationship)
230
    {
231 9
        if ($this->isColumnAllowed($geometryColumn)) {
232 9
            $relationship = ucfirst(strtolower($relationship));
233
234 9
            if (!in_array($relationship, $this->stRelations)) {
235
                throw new UnknownSpatialRelationFunction($relationship);
236
            }
237
238 9
            $geometryColumn .= $this->getConnection() instanceof PostgresConnection ? '::geometry' : '';
239 9
            $query->whereRaw("ST_{$relationship}(`{$geometryColumn}`, ST_GeomFromText(?))", [
240 9
                $this->toWkt($geometry),
241
            ]);
242
        }
243
244 9
        return $query;
245
    }
246
247 1
    public function scopeWithin($query, $geometryColumn, $polygon)
248
    {
249 1
        return $this->scopeComparison($query, $geometryColumn, $polygon, 'within');
250
    }
251
252 1
    public function scopeCrosses($query, $geometryColumn, $geometry)
253
    {
254 1
        return $this->scopeComparison($query, $geometryColumn, $geometry, 'crosses');
255
    }
256
257 1
    public function scopeContains($query, $geometryColumn, $geometry)
258
    {
259 1
        return $this->scopeComparison($query, $geometryColumn, $geometry, 'contains');
260
    }
261
262 1
    public function scopeDisjoint($query, $geometryColumn, $geometry)
263
    {
264 1
        return $this->scopeComparison($query, $geometryColumn, $geometry, 'disjoint');
265
    }
266
267 1
    public function scopeEquals($query, $geometryColumn, $geometry)
268
    {
269 1
        return $this->scopeComparison($query, $geometryColumn, $geometry, 'equals');
270
    }
271
272 1
    public function scopeIntersects($query, $geometryColumn, $geometry)
273
    {
274 1
        return $this->scopeComparison($query, $geometryColumn, $geometry, 'intersects');
275
    }
276
277 1
    public function scopeOverlaps($query, $geometryColumn, $geometry)
278
    {
279 1
        return $this->scopeComparison($query, $geometryColumn, $geometry, 'overlaps');
280
    }
281
282 1
    public function scopeDoesTouch($query, $geometryColumn, $geometry)
283
    {
284 1
        return $this->scopeComparison($query, $geometryColumn, $geometry, 'touches');
285
    }
286
}
287