In a CakePHP (v3) application, how can I retrieve the closest results based on passed lat lng values?
I\'d like to have them back as native CakePHP entities, so some
There are several approaches to achieve your result.
Instead of struggling with the ORM, you could perform a raw query?
$conn = ConnectionManager::get('my_connection');
And then you can run a custom query like:
$results = $conn->execute('MY RAW SQL HERE');
If you want to run a query and eager load the relations, you will need to use the contains method like:
$query = $articles->find('all', ['contain' => ['Authors', 'Comments']]);
If you want to use virtual fields like in your example above, you need to add it in your entity model class like:
protected function _getDistance()
{
//access model members using $this->_properties e.g.
//return $this->_properties['latitude'] . ' ' .
// $this->_properties['longitude'];
}
This should ensure your distance is no longer null in your json.
And you could use with the distance method from CakephpGeo library to construct a query
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;
}
Lastly, and not recommended, you could retrieve all the info you need and use PHP to narrow down your results. Painful, slow and not recommended.
there are no more virtualFields in cake 3 but you still can create an alias for your calculated field
As suggested by @ndm you'd better bind $latitude
and $longitude
to prevent SQL injections
$distanceField = '(3959 * acos (cos ( radians(:latitude) )
* cos( radians( Sightings.latitude ) )
* cos( radians( Sightings.longitude )
- radians(:longitude) )
+ sin ( radians(:latitude) )
* sin( radians( Sightings.latitude ) )))';
using where
$sightings = $this->Sightings->find()
->select([
'distance' => $distanceField
])
->where(["$distanceField < " => $distance])
->bind(':latitude', $latitude, 'float')
->bind(':longitude', $longitude, 'float')
->contain(['Photos', 'Tags']);
using having
$sightings = $this->Sightings->find()
->select([
'distance' => $distanceField
])
->having(['distance < ' => $distance])
->bind(':latitude', $latitude, 'float')
->bind(':longitude', $longitude, 'float')
->contain(['Photos', 'Tags']);
People might want to look at the CakePHP GeoDistance Plugin:
CakePHP-GeoDistance is a CakePHP 3 behavior for querying geocoded data based on cartographic distance using the spherical cosine law. It is great for 'find my nearest X' or 'find Y near me' type queries.
https://github.com/chris48s/cakephp-geodistance
Did the job.
finally i got the solution for this. below is the working example
$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]);
output
{"lat": xx.920479,
"lng": xx.670547,
"distance": "0"
}
You need to use ConnectionManager here.
For example:
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');
You could try and set up your query in this fashion. It will definitely work.
Use this as a reference:
SELECT Statements with CakePHP 3