mirror of
https://github.com/pacnpal/thrillwiki_laravel.git
synced 2025-12-20 07:31:09 -05:00
178 lines
4.7 KiB
PHP
178 lines
4.7 KiB
PHP
<?php
|
|
|
|
namespace App\Traits;
|
|
|
|
use App\Models\Location;
|
|
use Illuminate\Database\Eloquent\Relations\MorphOne;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
trait HasLocation
|
|
{
|
|
/**
|
|
* Boot the trait.
|
|
*/
|
|
public static function bootHasLocation(): void
|
|
{
|
|
static::deleting(function ($model) {
|
|
// Delete associated location when model is deleted
|
|
$model->location?->delete();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get the model's location.
|
|
*/
|
|
public function location(): MorphOne
|
|
{
|
|
return $this->morphOne(Location::class, 'locatable');
|
|
}
|
|
|
|
/**
|
|
* Get the model's coordinates.
|
|
*
|
|
* @return array<string, float|null>
|
|
*/
|
|
public function getCoordinatesAttribute(): array
|
|
{
|
|
if (!$this->location?->coordinates) {
|
|
return [
|
|
'lat' => null,
|
|
'lng' => null,
|
|
];
|
|
}
|
|
|
|
return [
|
|
'lat' => $this->location->coordinates->getLat(),
|
|
'lng' => $this->location->coordinates->getLng(),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get the model's formatted address.
|
|
*
|
|
* @return string|null
|
|
*/
|
|
public function getFormattedAddressAttribute(): ?string
|
|
{
|
|
return $this->location?->formatted_address;
|
|
}
|
|
|
|
/**
|
|
* Get the model's map URL.
|
|
*
|
|
* @return string|null
|
|
*/
|
|
public function getMapUrlAttribute(): ?string
|
|
{
|
|
return $this->location?->map_url;
|
|
}
|
|
|
|
/**
|
|
* Update or create the model's location.
|
|
*
|
|
* @param array<string, mixed> $attributes
|
|
* @return \App\Models\Location
|
|
*/
|
|
public function updateLocation(array $attributes): Location
|
|
{
|
|
if ($this->location) {
|
|
$this->location->update($attributes);
|
|
return $this->location;
|
|
}
|
|
|
|
return $this->location()->create($attributes);
|
|
}
|
|
|
|
/**
|
|
* Set the model's coordinates.
|
|
*
|
|
* @param float $latitude
|
|
* @param float $longitude
|
|
* @param float|null $elevation
|
|
* @return \App\Models\Location
|
|
*/
|
|
public function setCoordinates(float $latitude, float $longitude, ?float $elevation = null): Location
|
|
{
|
|
return $this->updateLocation([
|
|
'coordinates' => DB::raw("ST_SetSRID(ST_MakePoint($longitude, $latitude), 4326)"),
|
|
'elevation' => $elevation,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Set the model's address.
|
|
*
|
|
* @param array<string, string> $components
|
|
* @return \App\Models\Location
|
|
*/
|
|
public function setAddress(array $components): Location
|
|
{
|
|
return $this->updateLocation($components);
|
|
}
|
|
|
|
/**
|
|
* Calculate distance to another model with location.
|
|
*
|
|
* @param mixed $model Model using HasLocation trait
|
|
* @param string $unit 'km' or 'mi'
|
|
* @return float|null
|
|
*/
|
|
public function distanceTo($model, string $unit = 'km'): ?float
|
|
{
|
|
if (!$this->location || !$model->location) {
|
|
return null;
|
|
}
|
|
|
|
return $this->location->distanceTo(
|
|
$model->location->latitude,
|
|
$model->location->longitude,
|
|
$unit
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Scope a query to find models near coordinates.
|
|
*
|
|
* @param \Illuminate\Database\Eloquent\Builder $query
|
|
* @param float $latitude
|
|
* @param float $longitude
|
|
* @param float $radius
|
|
* @param string $unit
|
|
* @return \Illuminate\Database\Eloquent\Builder
|
|
*/
|
|
public function scopeNearby($query, float $latitude, float $longitude, float $radius, string $unit = 'km')
|
|
{
|
|
$point = DB::raw("ST_SetSRID(ST_MakePoint($longitude, $latitude), 4326)");
|
|
$distance = $unit === 'mi' ? $radius * 1609.34 : $radius * 1000;
|
|
|
|
return $query->whereHas('location', function ($query) use ($point, $distance) {
|
|
$query->whereRaw(
|
|
"ST_DWithin(coordinates::geography, ?::geography, ?)",
|
|
[$point, $distance]
|
|
);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Scope a query to find models within bounds.
|
|
*
|
|
* @param \Illuminate\Database\Eloquent\Builder $query
|
|
* @param array<string, float> $ne Northeast corner [lat, lng]
|
|
* @param array<string, float> $sw Southwest corner [lat, lng]
|
|
* @return \Illuminate\Database\Eloquent\Builder
|
|
*/
|
|
public function scopeInBounds($query, array $ne, array $sw)
|
|
{
|
|
$bounds = DB::raw(
|
|
"ST_MakeEnvelope(
|
|
{$sw['lng']}, {$sw['lat']},
|
|
{$ne['lng']}, {$ne['lat']},
|
|
4326
|
|
)"
|
|
);
|
|
|
|
return $query->whereHas('location', function ($query) use ($bounds) {
|
|
$query->whereRaw("ST_Within(coordinates::geometry, ?::geometry)", [$bounds]);
|
|
});
|
|
}
|
|
} |