【发布时间】:2018-07-31 13:02:43
【问题描述】:
我正在尝试按events 与用户提交的邮政编码和距离的距离来订购。
我附上了我的数据库表及其关系的示例,如您所见,geom 通过postcode 与多个地址相关联,并且地址可以与多个表相关联(在本例中为事件表)。
我从最终用户那里获取邮政编码以及以英里为单位的半径来检索适当的事件,这是我如何在 Eloquent 中实现这一目标的示例。
/**
* Extend locale method which initially only gets lat/long for given postcode to search
*
* @param \Illuminate\Database\Eloquent\Builder $query The query builder
* @param \App\Http\Requests\SearchRequest $request The search request
* @return void
*/
protected function locale(Builder $query, SearchRequest $request)
{
$postcode = $this->formatPostcode($request->postcode);
$geom = Geom::query()->where('postcode', $postcode)->first();
if (! $geom || Cache::has('postcodeAPIFailed')) {
return;
}
$lat = $geom->geo_location['lat'];
$long = $geom->geo_location['long'];
// Top-left point of bounding box
$lat1 = $lat - ($request->within / 69);
$long1 = $long - $request->within / abs(cos(deg2rad($lat)) * 69);
// Bottom-right point of bounding box
$lat2 = $lat + ($request->within / 69);
$long2 = $long + $request->within / abs(cos(deg2rad($lat)) * 69);
$query->whereHas('address', function (Builder $query) use ($request, $lat, $long, $lat1, $long1, $lat2, $long2) {
$query->whereHas('geom', function (Builder $query) use ($request, $lat, $long, $lat1, $long1, $lat2, $long2) {
$query->whereRaw('st_within(geo_location, envelope(linestring(point(?, ?), point(?, ?))))', [$long1, $lat1, $long2, $lat2]);
});
});
}
在控制器中检索到搜索结果后,我们计算每个结果的距离。
if ($request->has('postcode')) {
$postcodeDistances = $this->getDistances($results, $request);
}
这会产生一个带有key of postcode 和value of distance 的数组,即$postcodeDistances['L1 0AA'] = '3';,我们将这个数组发送到视图。
然后在视图中,我们使用以下逻辑在适用的记录上显示距离
@if($postcodeDistances)
<span>
{{ $postcodeDistances[$result->address->postcode] }}
mile{{ $postcodeDistances[$result->address->postcode] != 1 ? 's' : '' }} away
</span>
@endif
我尝试了几种方法,但无法更新我的function locale() 以按距离进行排序。我考虑过也许我可以将距离附加到集合并使用 Laravel 方法以这种方式对集合进行排序,但如果后者甚至可能的话,从数据库层实现这一点将是理想的。
我的第一次尝试是在whereHas('geom') 之后添加选择距离字段并按新字段排序
$query->addSelect(\DB::raw("ST_DISTANCE_SPHERE(geo_location, POINT({$long}, {$lat})) AS distance"));
我收到以下错误:
SQLSTATE[21000]: Cardinality violation: 1241 Operand should contain 2 column(s) (SQL: select count(*) as aggregate from `event` where (select count(*) from `address` where `address`.`addressable_id` = `event`.`id` and `address`.`addressable_type` = event and (select count(*), ST_DISTANCE_SPHERE(geo_location, POINT(-2.717472, 53.427078)) AS distance from `geom` where `geom`.`postcode` = `address`.`postcode` and st_within(geo_location, envelope(linestring(point(-3.6903924055016, 52.847367855072), point(-1.7445515944984, 54.006788144928))))) >= 1) >= 1 and (select count(*) from `organisation` where `event`.`organisation_id` = `organisation`.`id` and `status` = 1) >= 1 and `event_template_id` is not null and `date_start` >= 2018-07-31 00:00:00 and `status` in (1, 5))
我也尝试在同一个地方使用 orderByRaw,虽然我没有收到错误,但结果没有相应地排序。
$query->orderByRaw('ST_DISTANCE_SPHERE(geo_location, POINT(?, ?)) ASC', [$long, $lat]);
【问题讨论】:
-
将 geo_location(点)拆分为纬度和经度有多难?我使用一个公式计算超过 2200 个地理位置的距离。它仅使用 sql 测量从任何点到任何点的任何距离。在一瞬间。按距离排序。但它在表格中有经纬度单元格。保存地理数据的方式不适用于公式。
-
@DimitriMostrey 你提出了一个很好的观点,我不认为将它分成两列会很困难,很高兴知道我有这个解决方案(我以前没有考虑过)。
标签: laravel-5 eloquent geometry sql-order-by point