Skip to content
No description, website, or topics provided.
PHP
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
src Adds support for DB query builder Jan 14, 2020
tests
.gitattributes Initial commit Dec 1, 2019
.gitignore Adds support for DB query builder Jan 14, 2020
CHANGELOG.md Updates changelog Jan 14, 2020
README.md Adds support for DB query builder Jan 14, 2020
composer.json
phpunit.xml Adds further tests Dec 2, 2019

README.md

Laravel GeoScope

GeoScope is a laravel package that allows you to easily add distance based query restrictions to your models.

Installation

using composer:

$ composer require netsells/laravel-geo-scope

Then publish the config file using the following artisan command:

php artisan vendor:publish --provider Netsells\GeoScope\GeoScopeServiceProvider

Usage

Basic Usage

GeoScope includes the Netsells\GeoScope\Traits\GeoScopeTrait that can be added to your models. The trait contains two scopes, withinDistanceOf and orWithinDistanceOf. withinDistanceOf will add a where clause to your query and orWithinDistanceOf will add an orWhere. Both of these methods accept 3 parameters, a latitude, longitude and distance. Both the latitude and longitude should be given in degrees.

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Netsells\GeoScope\Traits\GeoScopeTrait;

class Job extends Model
{
    use GeoScopeTrait;
    //
}

the scopes can then be applied to any model query:


// 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);

Configuration

When adding the GeoScopeTrait to your model you can define the latitude, longitude and distance units to be used by the trait in the geoscope.php config file.

 'models' => [
        App\Job::class => [
           'lat-column' => 'custom-lat-column-name',
           'long-column' => 'custom-long-column-name',
           'units' => 'meters'
        ]
    ]

Should you wish to use the scope for multiple latitude and longitude columns on the same model you can do so by creating multiple configurations within the same model key.

    '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' 
            ]
        ]
    ]

The key for the model config you wish to use can then be passed as a fourth parameter to both the withinDistanceOf and orWithinDistanceOf scopes.


$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');

You may also pass in an array of config items as the fourth parameter to both the withinDistanceOf and orWithinDistanceOf scopes.


$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();

Any missing config options will be replaced with the defaults defined in config('geoscope.defaults'). Passing invalid config keys will also cause GeoScope to fallback to these defaults for all config fields.

Database Query Builder

Geoscope also allows you to call the withinDistanceOf() and orWithinDistanceOf() directly off the DB query builder:

    $results =  DB::table('users')
                    ->withinDistanceOf(30.1234, -71.2176, 20)
                    ->join('jobs', 'jobs.user_id', '=', 'users.id')
                    ->get();

if you wish to alter the config options then you may pass an array as the fourth parameter to the withinDistanceOf() and orWithinDistanceOf() methods:

    $results =  DB::table('users')->withinDistanceOf(30.1234, -71.2176, 20, [
                         'lat-column' => 'lat-column-1',
                         'long-column' => 'long-column-1',
                         'units' => 'meters'
                    ])->get();

Scope Drivers

Under the hood, GeoScope uses different drivers to ensure that the distance queries are optimised to the database connection being used. Scope drivers correspond to the database drivers used by Laravel. GeoScope will automatically detect the database driver being used by Laravel and choose the correct scope driver for it. Out of the box GeoScope includes a MySQL scope driver which uses ST_Distance_Sphere() function, a PostgreSQL scope driver which uses earth_distance and a SQL Server driver which uses STDistance.

NOTE: The PostgreSQL driver requires you to have the postgres earthdistance module installed which can be done by executing the following SQL

create extension if not exists cube;
create extension if not exists earthdistance;

Creating Custom Scope Drivers

GeoScope allows you to define and register custom scope drivers. To create a custom scope driver create a class that extends Netsells\GeoScope\ScopeDrivers\AbstractScopeDriver . The new driver must then implement the methods outlined in Netsells\GeoScope\Interfaces\ScopeDriverInterface (below).

<?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);
}

The Query Builder instance is available within your driver via the $this->query property.

Registering Custom Scope Drivers

Custom scope drivers can be registered using the registerDriverStrategy method on the ScopeDriverFactory class. Registration should normally be done within the register method of a service provider.

<?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()
    {
        //
    }
}

You may set an optional scope-driver config key if you wish to force a specific scope driver to be used.

 'models' => [
        App\Job::class => [
           'lat-column' => 'custom-lat-column-name',
           'long-column' => 'custom-long-column-name',
           'units' => 'meters',
           'scope-driver' => 'mysql'
        ]
    ]

If you create a custom scope driver, please consider putting in a pull Request to add it to the package so it may be used by others.

Scope Driver Security

Due to the nature of the queries being run by GeoScope, both the whereRaw() and orWhereRaw methods are used. The drivers included by default protect against sql injection attacks (using prepared statements and by checking for valid lat long column config values). It is important that when creating custom scope drivers, that you also take this into consideration for any user input that you pass directly to it.

You can’t perform that action at this time.