CakePHP 从数据库中查询最近的经纬度

Posted

技术标签:

【中文标题】CakePHP 从数据库中查询最近的经纬度【英文标题】:CakePHP query closest latitude longitude from database 【发布时间】:2016-11-22 14:17:50 【问题描述】:

在 Cakephp (v3) 应用程序中,如何根据传递的 lat lng 值检索最接近的结果?

我想让它们作为原生 CakePHP 实体返回,所以像这样:

public function closest($lat, $lng) 

    $sightings = //records within given lat lng 

    $this->set(compact('sightings'));
    $this->set('_serialize', ['sightings']);

我知道这条 SQL 有效:

SELECT *,
       ( 3959 * acos( cos( radians(50.7) ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians(-1.8) ) + sin( radians(50.7) ) * sin( radians( latitude ) ) ) ) AS distance
  FROM sightings
HAVING distance < 10
 ORDER BY distance
 LIMIT 0 , 20

努力将两者结合起来。

更新

我已经添加了

class Sighting extends Entity 
    public $_virtual = ['distance'];
    //rest of class...

所以现在distance 出现在我的 json 输出中(现在预期的值为 null,但我觉得这是一个步骤)。

我看过这里:http://www.mrthun.com/2014/11/26/search-distance-cakephp/ 这似乎是我想要达到的目标,所以假设是这样的:

$latitude = 51.145;
$longitude = -1.45;
$distance = 100;

$this->Sightings->virtualFields
    = array('Sightings.distance'
     => '(3959 * acos (cos ( radians('.$latitude.') )
    * cos( radians( Sightings.latitude ) )
    * cos( radians( Sightings.longitude )
    - radians('.$longitude.') )
    + sin ( radians('.$latitude.') )
    * sin( radians( Sightings.latitude ) )))');

$sightings = $this->Sightings->find('all', [
    'conditions' => ['Sightings.distance <' => $distance]
]);

$this->set(compact('sightings'));
$this->set('_serialize', ['sightings']);

结果:Column not found: 1054 Unknown column 'Sightings.distance' in 'where clause'

不确定 CakePHP v2 与 v3 相比是否可行?

【问题讨论】:

到目前为止,这是我的方法,但我没有做任何距离的事情,我不确定如何将该 sql 插入到 cakes 中。公共函数 findClosest($lat, $lng) $sightings = $this->Sightings->find('all') ->where(['Sightings.created >' => new Time('-12 hour')] ) -> 限制 (5); $this->set(compact('sightings')); $this->set('_serialize', ['sightings']); 提示:dereuromark.de/2012/06/12/geocoding-with-cakephp 这是蛋糕 2.x 的,无法将其用于 3.x 请提供 CakePHP 生成的 SQL。 不,这也适用于 3.x,如果您阅读它,您会看到 :) 它将链接到 github.com/dereuromark/cakephp-geo,这是一个 cake 3 插件。众所周知的awesome cakephp list 中也提到了它,在进行任何其他研究之前,它应该始终是您的第一站。 【参考方案1】:

蛋糕 3 中没有更多的虚拟字段,但您仍然可以为计算字段创建别名

按照@ndm 的建议,您最好绑定$latitude$longitude 以防止SQL 注入

$distanceField = '(3959 * acos (cos ( radians(:latitude) )
    * cos( radians( Sightings.latitude ) )
    * cos( radians( Sightings.longitude )
    - radians(:longitude) )
    + sin ( radians(:latitude) )
    * sin( radians( Sightings.latitude ) )))';

使用位置

$sightings = $this->Sightings->find()
    ->select([
        'distance' => $distanceField
    ])
    ->where(["$distanceField < " => $distance])
    ->bind(':latitude', $latitude, 'float')
    ->bind(':longitude', $longitude, 'float')
    ->contain(['Photos', 'Tags']);

使用拥有

$sightings = $this->Sightings->find()
    ->select([
        'distance' => $distanceField
    ])
    ->having(['distance < ' => $distance])
    ->bind(':latitude', $latitude, 'float')
    ->bind(':longitude', $longitude, 'float')
    ->contain(['Photos', 'Tags']);

【讨论】:

请确保在此示例中显式转换或引用$latitude$longitude,或者使用绑定,否则这可能是SQL 注入漏洞! @ndm 你是对的。我会在接下来的几天里改进我的答案 这真的很接近我的需要,它选择了正确的行,但它似乎只包含距离值,有没有办法将实体水合为“正常”? 您当然可以选择所有需要的字段。手册解释了如何选择模型的所有字段 尝试-&gt;autoFields(true) 选择所有字段【参考方案2】:

您需要在这里使用 ConnectionManager。

例如:

use Cake\Datasource\ConnectionManager;  // Mention this first, preferably on top of the page.

$connection = ConnectionManager::get('default');

$results = $connection
  ->execute('SELECT * FROM articles WHERE id = :id', ['id' => 1])
  ->fetchAll('assoc');

您可以尝试以这种方式设置您的查询。一定会成功的。

以此为参考:

SELECT Statements with CakePHP 3

【讨论】:

确实有效,谢谢。有没有办法将 ['contain' => ['Photos', 'Tags'] 对象连同它一起包含在内,还是我必须编写连接?这基本上是确切的查询...我只需要按 lat/lng 公共函数 index($lat, $lng) $sightings = $this->Sightings->find('all', ['contain' => ['Photos', 'Tags']]) ->where(['Sightings.created >' => new Time('-12 hour')]) ->order(['Sightings.created' => ' ASC']) ->限制(5); $this->set(compact('sightings')); $this->set('_serialize', ['sightings']); 我认为使用 join 会是一个更好的选择。当您以这种方式使用 SELECT 时,它不会识别模型关联,因此对 contains 的使用持怀疑态度。 真的很接近。我将输出呈现为 json,问题是相关表不再显示为相关的 json 对象。例如,连接表中的id 正在覆盖查询的主主表中的id。这看起来非常接近,gist.github.com/SwabTheDeck/0056d6785377d86deaa8 可能看起来像蛋糕 2.0,我不确定如何实现它,建议?【参考方案3】:

有几种方法可以实现您的结果。

1。原始查询

您可以执行raw query?

$conn = ConnectionManager::get('my_connection');

然后您可以运行自定义查询,例如:

$results = $conn->execute('MY RAW SQL HERE');

2。带有急切加载的原始查询

如果要运行查询和eager load the relations,则需要使用 contains 方法,例如:

$query = $articles->find('all', ['contain' => ['Authors', 'Comments']]);

3。使用虚拟字段

如果您想使用上面示例中的虚拟字段,则需要将其添加到您的实体模型类中,例如:

protected function _getDistance()

    //access model members using $this->_properties e.g.
    //return $this->_properties['latitude'] . '  ' .
    //    $this->_properties['longitude'];

这应该确保您的距离在您的 json 中不再为空。

您可以使用CakephpGeo library 的距离方法来构造查询

public function distance(array $pointX, array $pointY) 
    $res = $this->calculateDistance($pointX, $pointY);
    return ceil($res);


public static function calculateDistance($pointX, $pointY) 
    $res = 69.09 * rad2deg(acos(sin(deg2rad($pointX['lat'])) * sin(deg2rad($pointY['lat']))
        + cos(deg2rad($pointX['lat'])) * cos(deg2rad($pointY['lat'])) * cos(deg2rad($pointX['lng']
        - $pointY['lng']))));
    return $res;

4。原始 PHP

最后,不推荐,您可以检索您需要的所有信息并使用 PHP 来缩小结果范围。痛苦、缓慢,不推荐。

【讨论】:

【参考方案4】:

我终于找到了解决方案。下面是工作示例

$distance = 25;
$start_latitude = 12.920479;
$start_longitude = 77.670547;

$contents = $this->Sightings->find()->select(['latitude', 'longitude', 'distance' => 'SQRT(
    POW(69.1 * (latitude - '.$start_latitude.'), 2) +
    POW(69.1 * ('.$start_longitude.' - longitude) * COS(latitude / 57.3), 2))'])->having(["distance < " => $distance]);

输出

   "lat": xx.920479,
    "lng": xx.670547,
    "distance": "0"
   

【讨论】:

【参考方案5】:

人们可能想看看CakePHP GeoDistance Plugin:

CakePHP-GeoDistance 是 CakePHP 3 的一种行为,用于使用球余弦定律根据制图距离查询地理编码数据。它非常适合“查找我最近的 X”或“查找我附近的 Y”类型查询。

https://github.com/chris48s/cakephp-geodistance

完成了任务。

【讨论】:

以上是关于CakePHP 从数据库中查询最近的经纬度的主要内容,如果未能解决你的问题,请参考以下文章

TP5查询最近景区

TP5查询最近景区

CakePHP:如何使用内部联接从两个表中检索数据?

如何检查(在 php 或 cakephp 中)郊区的特定纬度/经度位于特定州?

如何从数据库中读出经纬度,然后在地图上进行标注

如何绕过 CakePHP 中 requestAction 的 ACL 查询?