netsells / laravel-geoscope
GeoScope 是一个 Laravel 扩展包,允许您根据经纬度轻松执行距离查询和地理围栏。
Requires
- php: >=7.2
- laravel/framework: >=5.3
Requires (Dev)
- laravel/framework: ^6.6
- mockery/mockery: ^1.3
- orchestra/testbench: ^4.4
- phpunit/phpunit: ^8.4
README
GeoScope 是一个 Laravel 扩展包,允许您根据经纬度轻松执行距离查询和地理围栏。
由 Netsells 团队 创建和维护
主要功能
- 支持 Laravel 5.3 及以上版本
- 使用模型特性或 DB 查询构建器,查找给定经纬度范围内所有记录。
- 可以自动按给定经纬度的距离对结果进行排序。
- 可以完全配置以与单个模型/表上的多个经纬度列一起工作。
- 使用原生数据库函数以实现最快可能的性能 - 无更多慢速 havensine 查询。
- 默认支持 MySql、Mariadb、Postgres 和 SQLServer。
- 可以设置为自动将
distance
和distance_units
字段添加到模型结果中。 - 距离单位完全可配置。
安装
使用 composer
$ composer require netsells/laravel-geoscope
然后使用以下 artisan 命令发布配置文件
php artisan vendor:publish --tag=geoscope
使用方法
基本用法
GeoScope 包含 Netsells\GeoScope\Traits\GeoScopeTrait
,可以添加到您的模型中。该特性包含两个作用域,withinDistanceOf
和 orWithinDistanceOf
。 withinDistanceOf
将添加一个 where
子句到您的查询中,而 orWithinDistanceOf
将添加一个 orWhere
。这两个方法都接受三个参数,一个纬度、一个经度和一个距离。GeoScope 将使用这些参数来查询该模型指定的经纬度字段。
<?php namespace App; use Illuminate\Database\Eloquent\Model; use Netsells\GeoScope\Traits\GeoScopeTrait; class Job extends Model { use GeoScopeTrait; // }
然后可以将这些作用域应用于任何模型查询
// Gets all jobs within 20 miles of the given latitude and longitude $jobs = Job::withinDistanceOf(53.957962, -1.085485, 20)->get(); // Gets all jobs within 20 miles of the first lat and long or within 20 miles // of the second lat long $jobs = Job::withinDistanceOf(53.957962, -1.085485, 20) ->orWithinDistanceOf(52.143542, -2.08556, 20);
GeoScope 还包含一个 orderByDistanceFrom()
方法,允许您按指定经纬度的距离对结果进行排序。
// order by distance in ascending order $results = Job::orderByDistanceFrom(30.1234, -71.2176, 'asc')->get(); // order by distance in descending order $results = Job::orderByDistanceFrom(30.1234, -71.2176, 'desc')->get();
可以使用 addDistanceFromField()
方法将计算出的距离字段添加到每个返回的结果中。
$results = Job::addDistanceFromField(30.1234, -71.2176)->get();
在 addDistanceFromField()
应用之前
{ "id": 1, "email": "vita.frami@example.com", "latitude": 39.95, "longitude": -76.74, "created_at": "2020-02-29 19:13:08", "updated_at": "2020-02-29 19:13:08", }
在 addDistanceFromField()
应用之后
{ "id": 1, "email": "vita.frami@example.com", "latitude": 39.95, "longitude": -76.74, "created_at": "2020-02-29 19:13:08", "updated_at": "2020-02-29 19:13:08", "distance": 0.2, "dist_units": "miles" }
如果该名称已在 geoscope.php 配置文件中的 whitelisted-distance-from-field-names
数组中注册,则可以将自定义字段名称作为 addDistanceFromField()
方法的第三个参数传递。距离字段将具有默认名称 distance
,而单位字段将具有默认名称 distance_units
。
addDistanceFromField()
方法仅通过 GeoScopeTrait 提供。它不在数据库构建器上提供。
'whitelisted-distance-from-field-names' => [ 'custom_field_name' ]
$results = Job::addDistanceFromField(30.1234, -71.2176, 'custom_field_name')->get();
{ "id": 1, "email": "vita.frami@example.com", "latitude": 39.95, "longitude": -76.74, "created_at": "2020-02-29 19:13:08", "updated_at": "2020-02-29 19:13:08", "custom_field_name": 0.2, "custom_field_name_units": "miles" }
配置
将 GeoScopeTrait
添加到您的模型时,您可以在 geoscope.php 配置文件中定义该特性使用的纬度、经度和距离单位。
'models' => [ App\Job::class => [ 'lat-column' => 'custom-lat-column-name', 'long-column' => 'custom-long-column-name', 'units' => 'meters' ] ]
如果您希望在同一模型上使用多个纬度和经度列的作用域,您可以在同一模型键内创建多个配置。
'models' => [ App\Job::class => [ 'location1' => [ 'lat-column' => 'custom-lat-column-name', 'long-column' => 'custom-long-column-name', 'units' => 'meters' ], 'location2' => [ 'lat-column' => 'custom-lat-column-name', 'long-column' => 'custom-long-column-name', 'units' => 'meters' ] ] ]
然后可以将要使用的模型配置键作为第四个参数传递给 withinDistanceOf
和 orWithinDistanceOf
作用域。
$jobs = Job::withinDistanceOf(53.957962, -1.085485, 20, 'location1')->get(); $jobs2 = Job::withinDistanceOf(53.957962, -1.085485, 20, 'location1') ->orWithinDistanceOf(52.143542, -2.08556, 20, 'location2');
您还可以将配置项数组作为第四个参数传递给 withinDistanceOf
和 orWithinDistanceOf
作用域。
$jobs = Job::withinDistanceOf(53.957962, -1.085485, 20, [ 'lat-column' => 'lat-column-1', 'long-column' => 'long-column-1', 'units' => 'meters' ])->get(); $jobs2 = Job::withinDistanceOf(53.957962, -1.085485, 20, 'location1') ->orWithinDistanceOf(52.143542, -2.08556, 20, [ 'units' => 'meters' ])->get();
任何缺失的配置选项都将被替换为 config('geoscope.defaults')
中定义的默认值。 传递无效的配置键也会导致 GeoScope 为所有配置字段回退到这些默认值。
数据库查询构建器
Geoscope还允许您直接从数据库查询构建器调用withinDistanceOf()
、orWithinDistanceOf()
和orderByDistanceFrom()
方法。
$results = DB::table('users') ->withinDistanceOf(30.1234, -71.2176, 20) ->join('jobs', 'jobs.user_id', '=', 'users.id') ->get();
如果您想修改配置选项,可以将数组作为withinDistanceOf()
和orWithinDistanceOf()
方法的第四个参数传递。
$results = DB::table('users')->withinDistanceOf(30.1234, -71.2176, 20, [ 'lat-column' => 'lat-column-1', 'long-column' => 'long-column-1', 'units' => 'meters' ])->get();
按距离排序示例
$results = DB::table('users')->orderByDistanceFrom(30.1234, -71.2176, 'asc')->get();
范围驱动程序
在底层,GeoScope使用不同的驱动程序以确保距离查询针对所使用的数据库连接进行了优化。范围驱动程序对应于Laravel使用的数据库驱动程序。GeoScope将自动检测Laravel使用的数据库驱动程序,并为其选择正确的范围驱动程序。默认情况下,GeoScope包括一个MySQL范围驱动程序,它使用ST_Distance_Sphere()
函数,一个PostgreSQL范围驱动程序,它使用earth_distance
,以及一个SQL Server驱动程序,它使用STDistance
。
注意:PostgreSQL驱动程序要求您安装postgres的earthdistance
模块,可以通过执行以下SQL来完成:
create extension if not exists cube; create extension if not exists earthdistance;
创建自定义范围驱动程序
GeoScope允许您定义和注册自定义范围驱动程序。要创建自定义范围驱动程序,请创建一个继承自Netsells\GeoScope\ScopeDrivers\AbstractScopeDriver
的类。然后,新驱动程序必须实现Netsells\GeoScope\Interfaces\ScopeDriverInterface
中概述的方法(如下)。
<?php namespace Netsells\GeoScope\Interfaces; interface ScopeDriverInterface { /** * @param float $lat * @param float $long * @param float $distance * @return mixed * Should return query instance */ public function withinDistanceOf(float $lat, float $long, float $distance); /** * @param float $lat * @param float $long * @param float $distance * @return mixed * Should return query instance */ public function orWithinDistanceOf(float $lat, float $long, float $distance); /** * @param float $lat * @param float $long * @param string $orderDirection - asc or desc * @return mixed * Should return query instance */ public function orderByDistanceFrom(float $lat, float $long, string $orderDirection = 'asc'); /** * @param float $lat * @param float $long * @param string $fieldName * @return mixed * Should return query instance */ public function addDistanceFromField(float $lat, float $long, string $fieldName); }
查询构建器实例通过您的驱动程序中的$this->query
属性可用,任何传递的配置选项都可通过$this->config
属性访问。
注册自定义范围驱动程序
可以使用ScopeDriverFactory
类上的registerDriverStrategy
方法注册自定义范围驱动程序。注册通常应在服务提供商的register
方法中完成。
<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use Netsells\GeoScope\ScopeDriverFactory; use App\Services\GeoScope\ScopeDrivers\PostgreSQLScopeDriver; class AppServiceProvider extends ServiceProvider { /** * Register any application services. * * @return void */ public function register() { app(ScopeDriverFactory::class)->registerDriverStrategy('pgsql', PostgreSQLScopeDriver::class); } /** * Bootstrap any application services. * * @return void */ public function boot() { // } }
如果您想强制使用特定的范围驱动程序,可以设置可选的scope-driver
配置键。
注意 - 如果您使用MariaDB,则必须将scope-driver
字段设置为mariadb
。这是因为Laravel为MariaDB和MySQL使用相同的数据库连接设置,因此无法使用数据库连接来区分它们。
'models' => [ App\Job::class => [ 'lat-column' => 'custom-lat-column-name', 'long-column' => 'custom-long-column-name', 'units' => 'meters', 'scope-driver' => 'mysql' ] ]
如果您创建了一个自定义范围驱动程序,请考虑将其提交为Pull Request,以便其他人可以使用它。
范围驱动程序安全
由于GeoScope运行的查询的性质,使用了whereRaw()
和orWhereRaw
方法。默认包含的驱动程序可以防止SQL注入攻击(使用预编译语句并检查有效的经纬度列配置值)。在创建自定义范围驱动程序时,您还需要考虑对任何直接传递给它的用户输入进行此考虑。