Skip to content
Open
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
5 changes: 5 additions & 0 deletions src/elements/Address.php
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,11 @@ public static function addressAttributeLabel(string $attribute, string $countryC
*/
public ?string $longitude = null;

/**
* @var float|null Distance in meters
*/
public ?float $distance = null;

/**
* @inheritdoc
*/
Expand Down
76 changes: 76 additions & 0 deletions src/elements/db/AddressQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use craft\db\Table;
use craft\elements\Address;
use craft\helpers\Db;
use yii\db\Expression;

/**
* AddressQuery represents a SELECT SQL statement for categories in a way that is independent of DBMS.
Expand Down Expand Up @@ -316,6 +317,26 @@ class AddressQuery extends ElementQuery implements NestedElementQueryInterface
*/
public ?string $lastName = null;

/**
* @var null|array Narrows the query results based on the distance to a set of coordinates.
* ---
* ```php
* // Fetch addresses by distance to the given coordinates
* $addresses = \craft\elements\Address::find()
* ->distanceTo(['latitude' => 44.0473,'longitude' => -121.3338])
* ->all();
* ```
* ```twig
* {# Fetch addresses by distance to the given coordinates #}
* {% set addresses = craft.addresses()
* .distanceTo({ latitude: 44.0473, longitude: -121.3338 })
* .all() %}
* ```
*
* @used-by distanceTo()
*/
public ?array $distanceTo = null;

/**
* Narrows the query results based on the country the addresses belong to.
*
Expand Down Expand Up @@ -863,6 +884,31 @@ public function lastName(?string $value): static
return $this;
}

/**
* Calculate the distance of the address to a given set of coordinates, and
* optionally narrows the query results by minimum or maximum distance.
* Excludes addresses without valid coordinates.
*
* The coordinates should be provided as an associative array with the following keys:
*
* | Key | Value
* | - | -
* | `latitude` | Required, the latitude of the point to measure distance to.
* | `longitude` | Required, the longitude of the point to measure distance to.
* | `min` | Minimum distance in meters to narrow results by.
* | `max` | Maximum distance in meters to narrow results by.
*
* @return static self reference
*
* @uses $distanceTo
*/
public function distanceTo(array $coordinates): static
{
$this->distanceTo = $coordinates;

return $this;
}

/**
* @inheritdoc
*/
Expand Down Expand Up @@ -965,6 +1011,36 @@ protected function beforePrepare(): bool
$this->subQuery->andWhere(Db::parseParam('addresses.fullName', $this->fullName));
}

if ($this->distanceTo) {
$latitude = $this->distanceTo['latitude'];
$longitude = $this->distanceTo['longitude'];
$min = $this->distanceTo['min'] ?? null;
$max = $this->distanceTo['max'] ?? null;

$distance = new Expression(
'ST_Distance_Sphere(
POINT([[addresses.longitude]], [[addresses.latitude]]),
POINT(:lng, :lat)
)',
[':lng' => $longitude, ':lat' => $latitude]
);
$this->subQuery->addSelect(['distance' => $distance]);
$this->query->addSelect(['distance' => $distance]);

$this->subQuery->andWhere(['not', ['[[addresses.latitude]]' => null]]);
$this->subQuery->andWhere(['not', ['[[addresses.longitude]]' => null]]);

if ($min) {
$this->query->andWhere(['>=', $distance, $min]);
$this->subQuery->andWhere(['>=', $distance, $min]);
}

if ($max) {
$this->query->andWhere(['<=', $distance, $max]);
$this->subQuery->andWhere(['<=', $distance, $max]);
}
}

return true;
}

Expand Down
Loading