Skip to content
This repository was archived by the owner on Nov 4, 2019. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Basically, the behavior will add:
* two new columns to your model (`latitude` and `longitude`);
* four new methods to the _ActiveRecord_ API (`getDistanceTo()`, `isGeocoded()`,
`getCoordinates()`, and `setCoordinates()`);
* two new methods to the _ActiveQuery_ API (`filterByDistanceFrom()`,
* three new methods to the _ActiveQuery_ API (`withDistance()`, `filterByDistanceFrom()`,
`filterNear()`).


Expand All @@ -58,6 +58,18 @@ and longitude values.

### ActiveQuery API ###

`withDistance()` takes three arguments:

* a latitude value;
* a longitude value;
* a measure unit (`KILOMETERS_UNIT`, `MILES_UNIT`, or `NAUTICAL_MILES_UNIT`
defined in the `Peer` class of the geocoded model);

It will add a `Distance` column on your current query and returns itself for
fluid interface.
Example use: combine with `orderByDistance()` and `limit()` to return closest
matches.

`filterByDistanceFrom()` takes five arguments:

* a latitude value;
Expand Down
24 changes: 15 additions & 9 deletions src/GeocodableBehaviorQueryBuilderModifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,27 +29,21 @@ public function __construct(Behavior $behavior)
public function queryMethods($builder)
{
$script = '';
$script .= $this->addWithDistance($builder);
$script .= $this->addFilterByDistanceFrom($builder);
$script .= $this->addFilterNear($builder);

return $script;
}

public function addFilterByDistanceFrom($builder)
public function addWithDistance($builder)
{
$table = $this->behavior->getTable();
foreach ($table->getColumns() as $col) {
if ($col->isPrimaryKey()) {
$pks[] = "\$this->getModelAliasOrName().'.".$col->getPhpName()."'";
}
}

$builder->declareClass('Criteria', 'PDO');

$queryClassName = $builder->getStubQueryBuilder()->getClassname();
$peerClassName = $builder->getStubPeerBuilder()->getClassname();

return $this->behavior->renderTemplate('queryFilterByDistanceFrom', array(
return $this->behavior->renderTemplate('queryWithDistance', array(
'queryClassName' => $queryClassName,
'defaultUnit' => $this->getDefaultUnit($builder),
'peerClassName' => $peerClassName,
Expand All @@ -58,6 +52,18 @@ public function addFilterByDistanceFrom($builder)
));
}

public function addFilterByDistanceFrom($builder)
{
$builder->declareClass('Criteria', 'PDO');

$queryClassName = $builder->getStubQueryBuilder()->getClassname();

return $this->behavior->renderTemplate('queryFilterByDistanceFrom', array(
'queryClassName' => $queryClassName,
'defaultUnit' => $this->getDefaultUnit($builder)
));
}

public function addFilterNear($builder)
{
$builder->declareClassFromBuilder($builder->getStubObjectBuilder());
Expand Down
23 changes: 2 additions & 21 deletions src/templates/queryFilterByDistanceFrom.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,8 @@
*/
public function filterByDistanceFrom($latitude, $longitude, $distance, $unit = <?php echo $defaultUnit ?>, $comparison = Criteria::LESS_THAN)
{
if (<?php echo $peerClassName ?>::MILES_UNIT === $unit) {
$earthRadius = 3959;
} elseif (<?php echo $peerClassName ?>::NAUTICAL_MILES_UNIT === $unit) {
$earthRadius = 3440;
} else {
$earthRadius = 6371;
}

$sql = 'ABS(%s * ACOS(%s * COS(RADIANS(%s)) * COS(RADIANS(%s) - %s) + %s * SIN(RADIANS(%s))))';
$preparedSql = sprintf($sql,
$earthRadius,
cos(deg2rad($latitude)),
$this->getAliasedColName(<?php echo $latitudeColumnConstant ?>),
$this->getAliasedColName(<?php echo $longitudeColumnConstant ?>),
deg2rad($longitude),
sin(deg2rad($latitude)),
$this->getAliasedColName(<?php echo $latitudeColumnConstant ?>)
);

return $this
->withColumn($preparedSql, 'Distance')
->where(sprintf('%s %s ?', $preparedSql, $comparison), $distance, PDO::PARAM_STR)
->withDistance($latitude, $longitude, $unit)
->where(sprintf('Distance %s ?', $comparison), $distance, PDO::PARAM_STR)
;
}
34 changes: 34 additions & 0 deletions src/templates/queryWithDistance.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@

/**
* Adds distance from a given origin column to query.
*
* @param double $latitude The latitude of the origin point.
* @param double $longitude The longitude of the origin point.
* @param double $unit The unit measure.
*
* @return <?php echo $queryClassName ?> The current query, for fluid interface
*/
public function withDistance($latitude, $longitude, $unit = <?php echo $defaultUnit ?>)
{
if (<?php echo $peerClassName ?>::MILES_UNIT === $unit) {
$earthRadius = 3959;
} elseif (<?php echo $peerClassName ?>::NAUTICAL_MILES_UNIT === $unit) {
$earthRadius = 3440;
} else {
$earthRadius = 6371;
}

$sql = 'ABS(%s * ACOS(%s * COS(RADIANS(%s)) * COS(RADIANS(%s) - %s) + %s * SIN(RADIANS(%s))))';
$preparedSql = sprintf($sql,
$earthRadius,
cos(deg2rad($latitude)),
$this->getAliasedColName(<?php echo $latitudeColumnConstant ?>),
$this->getAliasedColName(<?php echo $longitudeColumnConstant ?>),
deg2rad($longitude),
sin(deg2rad($latitude)),
$this->getAliasedColName(<?php echo $latitudeColumnConstant ?>)
);

return $this
->withColumn($preparedSql, 'Distance');
}
25 changes: 25 additions & 0 deletions tests/GeocodableBehaviorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ public function testObjectMethods()

public function testQueryMethods()
{
$this->assertTrue(method_exists('GeocodedObjectQuery', 'withDistance'));
$this->assertTrue(method_exists('GeocodedObjectQuery', 'filterByDistanceFrom'));
$this->assertTrue(method_exists('GeocodedObjectQuery', 'filterNear'));
}
Expand Down Expand Up @@ -224,6 +225,30 @@ public function testIsGeocoded()
$this->assertTrue($obj->isGeocoded());
}

public function testWithDistanceAddsColumn()
{
GeocodedObjectPeer::doDeleteAll();

$geo1 = new GeocodedObject();
$geo1->setName('Aulnat Area');
$geo1->setCity('Aulnat');
$geo1->setCountry('France');
$geo1->save();

$geo2 = new GeocodedObject();
$geo2->setName('Lyon Area');
$geo2->setCity('Lyon');
$geo2->setCountry('France');
$geo2->save();

$object = GeocodedObjectQuery::create()
->withDistance($geo1->getLatitude(), $geo1->getLongitude())
->filterByName('Lyon Area')
->findOne()
;
$this->assertTrue((float)$object->getDistance() > 0);
}

public function testFilterByDistanceFromReturnsNoObjects()
{
GeocodedObjectPeer::doDeleteAll();
Expand Down