Passed
Push — master ( ef3aeb...986e68 )
by Yaroslav
04:23
created

HasCoordinates::scopeNearestInKilometers()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 4
crap 1
1
<?php
2
3
namespace LaraGeoData\Models;
4
5
use Illuminate\Database\Eloquent\Builder;
6
use Illuminate\Support\Facades\DB;
7
use LaraGeoData\Contracts\ModelWithCoordinates;
8
9
trait HasCoordinates
10
{
11
12
    /**
13
     * Table column name for latitude.
14
     *
15
     * @return string
16
     */
17 2
    public function latitudeColName(): string
18
    {
19 2
        return $this->latitudeColumnName ?? 'lat';
20
    }
21
22
    /**
23
     * Table column name for longitude.
24
     *
25
     * @return string
26
     */
27 2
    public function longitudeColName(): string
28
    {
29 2
        return $this->longitudeColumnName ?? 'lng';
30
    }
31
32
    /**
33
     * Filed name for distance. This field will be created only id use "nearest" scope.
34
     *
35
     * @return string
36
     */
37 2
    public function distanceColName(): string
38
    {
39 2
        return $this->distanceColumnName ?? 'distance';
40
    }
41
42
    /**
43
     * Haversine formula (from Google solution example).
44
     * By default use radius in kilometers.
45
     *
46
     * @param Builder $query
47
     * @param float   $lat
48
     * @param float   $lng
49
     * @param float   $radius
50
     * @param int     $coef
51
     * @return Builder
52
     */
53 2
    public function scopeNearest(Builder $query, float $lat, float $lng, float $radius, int $coef = ModelWithCoordinates::HAVERSINE_COEF_KILOMETERS)
54
    {
55 2
        $latColName        = $this->latitudeColName();
56 2
        $lngColName        = $this->longitudeColName();
57 2
        $distanceFieldName = $this->distanceColName();
58
59 2
        return $query->select([
60 2
            '*',
61 2
            DB::raw("
62 2
               ( {$coef} *
63 2
               acos(cos(radians({$lat})) *
64 2
               cos(radians({$latColName})) *
65 2
               cos(radians({$lngColName}) -
66 2
               radians({$lng})) +
67 2
               sin(radians({$lat})) *
68 2
               sin(radians({$latColName})))
69 2
            ) AS {$distanceFieldName} "),
70 2
        ])->having($distanceFieldName, '<=', $radius);
71
    }
72
73 1
    public function scopeNearestInKilometers(Builder $query, float $lat, float $lng, float $radius)
74
    {
75 1
        return $this->scopeNearest($query, $lat, $lng, $radius, ModelWithCoordinates::HAVERSINE_COEF_KILOMETERS);
76
    }
77
78 1
    public function scopeNearestInMiles(Builder $query, float $lat, float $lng, float $radius)
79
    {
80 1
        return $this->scopeNearest($query, $lat, $lng, $radius, ModelWithCoordinates::HAVERSINE_COEF_MILES);
81
    }
82
83
    /**
84
     * Order query results by distance.
85
     *
86
     * @param Builder $query
87
     * @param string  $direction
88
     * @return Builder
89
     */
90 2
    public function scopeOrderByNearest(Builder $query, string $direction = 'asc')
91
    {
92 2
        return $query->orderBy($this->distanceColName(), $direction);
93
    }
94
95
    /**
96
     * Field has value only is use "nearest" scope.
97
     *
98
     * @return float
99
     */
100 2
    public function getDistanceAttribute(): float
101
    {
102 2
        if (array_key_exists($this->distanceColName(), $this->attributes)) {
103 2
            return (float) $this->attributes[$this->distanceColName()];
104
        }
105
106
        return 0;
107
    }
108
}
109