Skip to content

Commit 23dd3a6

Browse files
author
Adam Hutchison
committed
Adds orderByDistanceFrom method
1 parent fe27820 commit 23dd3a6

File tree

8 files changed

+136
-16
lines changed

8 files changed

+136
-16
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ php artisan vendor:publish --tag=geoscope
2323
GeoScope includes the `Netsells\GeoScope\Traits\GeoScopeTrait` that can be added to your models. The trait contains two scopes,
2424
`withinDistanceOf` and `orWithinDistanceOf`. `withinDistanceOf` will add a `where` clause to your query and `orWithinDistanceOf`
2525
will add an `orWhere`. Both of these methods accept 3 parameters, a latitude, longitude and distance. Both the latitude
26-
and longitude should be given in degrees.
26+
and longitude should be given in degrees. GeoScope with then use these to query against the specified lat long fields on that model.
2727

2828
```php
2929
<?php

src/BuilderScopes/AbstractBuilderScope.php

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
namespace Netsells\GeoScope\BuilderScopes;
44

55
use Netsells\GeoScope\ScopeDriverFactory;
6+
use Netsells\GeoScope\Exceptions\InvalidOrderDirectionParameter;
67

78
abstract class AbstractBuilderScope
89
{
9-
const DISTANCE_UNITS_MILES = 'miles';
10-
const DISTANCE_UNITS_METERS = 'meters';
11-
const DISTANCE_UNITS_KILOMETERS = 'kilometers';
10+
private const DISTANCE_UNITS_MILES = 'miles';
11+
private const DISTANCE_UNITS_METERS = 'meters';
12+
private const DISTANCE_UNITS_KILOMETERS = 'kilometers';
1213

13-
const DISTANCE_CONVERSION_FROM_METERS = [
14+
public const DISTANCE_CONVERSION_FROM_METERS = [
1415
self::DISTANCE_UNITS_MILES => 0.000621371,
1516
self::DISTANCE_UNITS_METERS => 1,
1617
self::DISTANCE_UNITS_KILOMETERS => 0.001
@@ -51,6 +52,15 @@ public function orWithinDistanceOf(float $lat, float $long, float $distance)
5152
return $this->scopeDriver->orWithinDistanceOf($lat, $long, $distance);
5253
}
5354

55+
/**
56+
* @throws InvalidOrderDirectionParameter
57+
* @return mixed
58+
*/
59+
public function orderByDistanceFrom(float $lat, float $long, string $orderDirection = 'asc')
60+
{
61+
return $this->scopeDriver->orderByDistanceFrom($lat, $long, $orderDirection);
62+
}
63+
5464
/**
5565
* @param $driver
5666
* @return $this

src/ScopeDrivers/AbstractScopeDriver.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public function setQuery($query): AbstractScopeDriver
4848
*/
4949
protected function checkOrderDirectionIdentifier(string $orderDirection): void
5050
{
51-
if (!in_array($orderDirection, $this->ALLOWED_ORDER_DIRECTION_IDENTIFIERS)) {
51+
if (!in_array($orderDirection, self::ALLOWED_ORDER_DIRECTION_IDENTIFIERS)) {
5252
throw new InvalidOrderDirectionParameter("{$orderDirection} is not a valid order direction");
5353
}
5454
}

src/ScopeDrivers/MySQLScopeDriver.php

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ final class MySQLScopeDriver extends AbstractScopeDriver
1111
*/
1212
public function withinDistanceOf(float $lat, float $long, float $distance)
1313
{
14-
return $this->query->whereRaw($this->getSQL(), [
14+
return $this->query->whereRaw($this->getWithinDistanceSQL(), [
1515
$long,
1616
$lat,
1717
$distance,
@@ -25,33 +25,52 @@ public function withinDistanceOf(float $lat, float $long, float $distance)
2525
*/
2626
public function orWithinDistanceOf(float $lat, float $long, float $distance)
2727
{
28-
return $this->query->orWhereRaw($this->getSQL(), [
28+
return $this->query->orWhereRaw($this->getWithinDistanceSQL(), [
2929
$long,
3030
$lat,
3131
$distance,
3232
]);
3333
}
3434

3535
/**
36+
* @throws InvalidOrderDirectionParameter
3637
* @param float $lat
3738
* @param float $long
3839
* @param float $orderDirection
3940
*/
4041
public function orderByDistanceFrom(float $lat, float $long, string $orderDirection = 'asc')
4142
{
42-
return $this->query;
43+
$this->checkOrderDirectionIdentifier($orderDirection);
44+
45+
return $this->query->orderByRaw($this->getOrderByDistanceSQL($orderDirection), [
46+
$long,
47+
$lat,
48+
]);
4349
}
4450

4551
/**
4652
* @return string
4753
*/
48-
private function getSQL(): string
54+
private function getWithinDistanceSQL(): string
4955
{
5056
return <<<EOD
5157
ST_Distance_Sphere(
5258
point({$this->config['long-column']}, {$this->config['lat-column']}),
5359
point(?, ?)
5460
) * {$this->conversion} < ?
61+
EOD;
62+
}
63+
64+
/**
65+
* @return string
66+
*/
67+
private function getOrderByDistanceSQL(string $orderDirection): string
68+
{
69+
return <<<EOD
70+
ST_Distance_Sphere(
71+
point({$this->config['long-column']}, {$this->config['lat-column']}),
72+
point(?, ?)
73+
) * {$this->conversion} {$orderDirection}
5574
EOD;
5675
}
5776
}

src/ScopeDrivers/PostgreSQLScopeDriver.php

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ final class PostgreSQLScopeDriver extends AbstractScopeDriver
1111
*/
1212
public function withinDistanceOf(float $lat, float $long, float $distance)
1313
{
14-
return $this->query->whereRaw($this->getSQL(), [
14+
return $this->query->whereRaw($this->getWithinDistanceSQL(), [
1515
$lat,
1616
$long,
1717
$distance,
@@ -25,23 +25,52 @@ public function withinDistanceOf(float $lat, float $long, float $distance)
2525
*/
2626
public function orWithinDistanceOf(float $lat, float $long, float $distance)
2727
{
28-
return $this->query->orWhereRaw($this->getSQL(), [
28+
return $this->query->orWhereRaw($this->getWithinDistanceSQL(), [
2929
$lat,
3030
$long,
3131
$distance,
3232
]);
3333
}
3434

35+
/**
36+
* @throws InvalidOrderDirectionParameter
37+
* @param float $lat
38+
* @param float $long
39+
* @param float $orderDirection
40+
*/
41+
public function orderByDistanceFrom(float $lat, float $long, string $orderDirection = 'asc')
42+
{
43+
$this->checkOrderDirectionIdentifier($orderDirection);
44+
45+
return $this->query->orderByRaw($this->getOrderByDistanceSQL($orderDirection), [
46+
$long,
47+
$lat,
48+
]);
49+
}
50+
3551
/**
3652
* @return string
3753
*/
38-
private function getSQL(): string
54+
private function getWithinDistanceSQL(): string
3955
{
4056
return <<<EOD
4157
earth_distance(
4258
ll_to_earth({$this->config['lat-column']}, {$this->config['long-column']}),
4359
ll_to_earth(?, ?)
4460
) * {$this->conversion} < ?
61+
EOD;
62+
}
63+
64+
/**
65+
* @return string
66+
*/
67+
private function getOrderByDistanceSQL(string $orderDirection): string
68+
{
69+
return <<<EOD
70+
earth_distance(
71+
ll_to_earth({$this->config['lat-column']}, {$this->config['long-column']}),
72+
ll_to_earth(?, ?)
73+
) * {$this->conversion} {$orderDirection}
4574
EOD;
4675
}
4776
}

src/ScopeDrivers/SQLServerScopeDriver.php

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ final class SQLServerScopeDriver extends AbstractScopeDriver
1111
*/
1212
public function withinDistanceOf(float $lat, float $long, float $distance)
1313
{
14-
return $this->query->whereRaw($this->getSQL(), [
14+
return $this->query->whereRaw($this->getWithinDistanceSQL(), [
1515
$lat,
1616
$long,
1717
$distance,
@@ -25,22 +25,50 @@ public function withinDistanceOf(float $lat, float $long, float $distance)
2525
*/
2626
public function orWithinDistanceOf(float $lat, float $long, float $distance)
2727
{
28-
return $this->query->orWhereRaw($this->getSQL(), [
28+
return $this->query->orWhereRaw($this->getWithinDistanceSQL(), [
2929
$lat,
3030
$long,
3131
$distance,
3232
]);
3333
}
3434

35+
/**
36+
* @throws InvalidOrderDirectionParameter
37+
* @param float $lat
38+
* @param float $long
39+
* @param float $orderDirection
40+
*/
41+
public function orderByDistanceFrom(float $lat, float $long, string $orderDirection = 'asc')
42+
{
43+
$this->checkOrderDirectionIdentifier($orderDirection);
44+
45+
return $this->query->orderByRaw($this->getOrderByDistanceSQL($orderDirection), [
46+
$long,
47+
$lat,
48+
]);
49+
}
50+
3551
/**
3652
* @return string
3753
*/
38-
private function getSQL(): string
54+
private function getWithinDistanceSQL(): string
3955
{
4056
return <<<EOD
4157
(GEOGRAPHY::Point(?, ?, 4326)
4258
.STDistance(GEOGRAPHY::Point({$this->config["lat-column"]}, {$this->config["long-column"]}, 4326)))
4359
* {$this->conversion} < ?
60+
EOD;
61+
}
62+
63+
/**
64+
* @return string
65+
*/
66+
private function getOrderByDistanceSQL(string $orderDirection): string
67+
{
68+
return <<<EOD
69+
(GEOGRAPHY::Point(?, ?, 4326)
70+
.STDistance(GEOGRAPHY::Point({$this->config["lat-column"]}, {$this->config["long-column"]}, 4326)))
71+
* {$this->conversion} {$orderDirection}
4472
EOD;
4573
}
4674
}

src/Traits/GeoScopeTrait.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,23 @@ public function scopeOrWithinDistanceOf(
5151
'configOption' => $configOption,
5252
])->orWithinDistanceOf($lat, $long, $distance);
5353
}
54+
55+
/**
56+
* @param Builder $query
57+
* @param float $lat
58+
* @param float $long
59+
* @param float $orderDirection
60+
* @return mixed
61+
* @throws InvalidOrderDirectionParameter
62+
*/
63+
public function scopeOrderByDistanceFrom(
64+
Builder $query,
65+
float $lat,
66+
float $long,
67+
string $orderDirection = 'asc'
68+
) {
69+
return app(EloquentBuilderScope::class, [
70+
'query' => $query,
71+
])->orderByDistanceFrom($lat, $long, $orderDirection);
72+
}
5473
}

tests/Integration/ScopeDrivers/Traits/ScopeDriverEloquentBuilderTests.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,21 @@ public function or_within_distance_of_returns_correct_results()
4646
$this->assertEqualCollections($expected, $actual);
4747
}
4848

49+
/**
50+
* @test
51+
*/
52+
public function order_by_distance_from_returns_correct_results()
53+
{
54+
$centralPoint = $this->getLatLongs()->get('central_point');
55+
56+
factory(Test::class, 30)->create();
57+
58+
$results1 = Test::orderByDistanceFrom($centralPoint['latitude'], $centralPoint['longitude'], 'asc')->get();
59+
$results2 = Test::orderByDistanceFrom($centralPoint['latitude'], $centralPoint['longitude'], 'desc')->get();
60+
61+
$this->assertEquals($results1->pluck('id'), $results2->reverse()->pluck('id'));
62+
}
63+
4964
/**
5065
* @test
5166
*/

0 commit comments

Comments
 (0)