mirror of
https://github.com/pacnpal/thrillwiki_laravel.git
synced 2025-12-20 09:11:10 -05:00
Refactor Location model to integrate PostGIS for spatial data, add legacy coordinate fields for compatibility, and enhance documentation for geocoding service implementation.
This commit is contained in:
178
app/Traits/HasLocation.php
Normal file
178
app/Traits/HasLocation.php
Normal file
@@ -0,0 +1,178 @@
|
||||
<?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]);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user