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 你是对的。我会在接下来的几天里改进我的答案
这真的很接近我的需要,它选择了正确的行,但它似乎只包含距离值,有没有办法将实体水合为“正常”?
您当然可以选择所有需要的字段。手册解释了如何选择模型的所有字段
尝试->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 从数据库中查询最近的经纬度的主要内容,如果未能解决你的问题,请参考以下文章