如何在Laravel QueryBuilder / MySQL Spatial包中按距离对查询结果进行排序?

时间:2017-10-02 17:57:14

标签: php mysql laravel laravel-5 eloquent

首先,我想向您展示当前的数据库结构。有三个表:

菜肴(身份证,姓名)

位置(ID,名称,坐标(POINT))

dish_location (location_id,dish_id)

现在我想实现一个获取用户位置(纬度,经度)的API,并返回按距离(km)排序的菜单列表。我已经有一种方法,需要两个纬度和两个经度,并给我一个距离。但我相信你可以告诉我一种方法,这是一种在MySQL查询中直接执行此操作的更高效方法。

其他:我想在API中加载更多" -function。所以我传递已经收到的项目的数量,或者在这种情况下如何解决这个问题?

我将其用于MySQL Spatial package

3 个答案:

答案 0 :(得分:3)

首先,让我们看看如何使用基本查询构建器执行此操作。然后,我们将讨论如何使用Eloquent模型执行此查询:

function paginateDishesFromPoint(Point $point, $pageSize) 
{
    $distanceField = "ST_Distance_Sphere(locations.coordinates, "
        . "ST_GeomFromText('{$point->toWKT()}') AS distance"; 

    return DB::table('dishes') 
        ->select('dishes.*', DB::raw($distanceField))
        ->join('dish_locations', 'dish_locations.dish_id', '=', 'dishes.id')
        ->join('locations', 'locations.id', '=', 'dish_locations.location_id')
        ->orderBy('distance') 
        ->paginate($pageSize);
}

ST_Distance_Sphere()函数计算我们可以对结果进行排序的距离。 Laravel的paginate()方法使用通过请求URL传递的page参数为我们执行自动分页。有关详细信息,请阅读pagination docs。使用上面的函数,我们可以按如下方式获取分页结果集:

$point = new Point($latitude, $longitude); 
$sortedDishes = paginateDishesFromPoint($point, 15); 

...其中Point是我们正在使用the packageGrimzy\LaravelMysqlSpatial\Types\Point类,而15是每页的结果数。

现在,让我们尝试使用Eloquent模型。我们将使用local query scope来封装创建执行排序的查询部分所需的逻辑:

class Dish extends Model 
{
    ...

    public function locations() 
    {
        return $this->belongsToMany(App\Location::class);
    }

    public function scopeOrderByDistanceFrom($query, Point $point) 
    {
        $relation = $this->locations();
        $locationsTable = $relation->getRelated()->getTable();
        $distanceField = "ST_Distance_Sphere($locationsTable.coordinates, "
        . "ST_GeomFromText('{$point->toWKT()}') AS distance";

        return $query
            ->select($this->getTable() . '.*', DB::raw($distanceField))
            ->join(
                $relation->getTable(), 
                $relation->getQualifiedForeignKeyName(), 
                '=', 
                $relation->getQualifiedParentKeyName()
            )
            ->join(
                $locationsTable,
                $relation->getRelated()->getQualifiedKeyName(),
                '=', 
                $relation->getQualifiedRelatedKeyName()
            )
            ->orderBy('distance');
    }
}

此实现使用模型上的元数据将表和字段名称添加到查询中,因此如果更改此方法,我们就不需要更新此方法。现在我们可以使用模型获取有序集:

$point = new Point($latitude, $longitude); 
$sortedDishes = Dish::orderByDistanceFrom($point)->paginate($pageSize);

$sortedDishes是Laravel LengthAwarePaginator的一个实例,它包含了Collection个模型。如果我们将结果传递给视图,请按照以下方式在Blade模板中显示它们:

<ul>
    @foreach($sortedDishes as $dish) 
        <li>{{ $dish->name }} is {{ $dish->distance }} meters away.</li>
    @endforeach
</ul>

<a href="{{ $sortedDishes->nextPageUrl() }}">Load more...</a>

如上所示,分页器提供convenience methods,我们可以使用它来轻松地在分页结果之间移动。

或者,我们可以使用AJAX请求来加载结果。请务必在请求数据的page参数中传递当前页面+ 1

答案 1 :(得分:1)

<强> SQL

SELECT d.name AS `Dish`,
       l.name AS `Location`,
       ST_Distance(l.coordinates, POINT(<<<longitude>>>, <<<latitude>>>)) AS `Distance`
FROM dish_locations dl
JOIN dishes d
ON d.id = dl.dish_id
JOIN locations l
ON l.id = dl.location_id
ORDER BY `Distance`
LIMIT <<<n-1>>>, <<<page size>>>;

...从用户的位置插入<<<longitude>>><<<latitude>>><<<n-1>>>从已检索到的行数减去1 <<<page size>>>作为所需的下一行要检索的行数。

<强>演示

http://rextester.com/YAEBF16430

<强>解释

答案 2 :(得分:1)

Postgis有两个运营商(&lt; - &gt;和&lt;#&gt;)来工作距离并计算KNN(最近邻居)。您可以使用它们而不是st_distance。 Operators 查询将类似于:

WITH index_query AS (
 SELECT ST_Distance(geom, 'SRID=3005;POINT(1011102 450541)'::geometry) as d,edabbr, vaabbr
FROM va2005
ORDER BY geom <-> 'SRID=3005;POINT(1011102 450541)'::geometry LIMIT 100)
SELECT *
   FROM index_query
ORDER BY d limit 10;

使用距离作为d,您可以订购。