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

HasCoordinates   A

Complexity

Total Complexity 9

Size/Duplication

Total Lines 98
Duplicated Lines 0 %

Test Coverage

Coverage 96.88%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 9
eloc 25
c 1
b 0
f 0
dl 0
loc 98
ccs 31
cts 32
cp 0.9688
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A distanceColName() 0 3 1
A latitudeColName() 0 3 1
A longitudeColName() 0 3 1
A scopeNearest() 0 18 1
A getDistanceAttribute() 0 7 2
A scopeNearestInKilometers() 0 3 1
A scopeNearestInMiles() 0 3 1
A scopeOrderByNearest() 0 3 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