mostafaznv/nova-map-field

Map Field for Laravel Nova

4.1.0 2024-12-20 07:31 UTC

README

License Packagist Downloads Latest Version on Packagist

Using this package, you can use spatial fields in Laravel Nova.

nova-map-field.mp4

I am on an open-source journey 🚀, and I wish I could solely focus on my development path without worrying about my financial situation. However, as life is not perfect, I have to consider other factors.

Therefore, if you decide to use my packages, please kindly consider making a donation. Any amount, no matter how small, goes a long way and is greatly appreciated. 🍺

Donate

Requirements:

  • PHP 8.1 or higher
  • Laravel 10.* or higher

Laravel Compatibility

Installation

  1. Install the package via composer:
    composer require mostafaznv/nova-map-field
  2. Publish config and assets:
    php artisan vendor:publish --provider="Mostafaznv\NovaMapField\NovaMapFieldServiceProvider"
  3. Done

Usage

  1. Create table with spatial fields
    <?php
    
    return new class extends Migration
    {
        public function up()
        {
            Schema::create('locations', function (Blueprint $table) {
                $table->id();
                $table->string('title', 150);
                
                # laravel 10
                $table->point('location')->nullable();
                $table->polygon('area')->nullable();
                $table->multiPolygon('areas')->nullable();
                
                # laravel 11 and higher
                $table->geometry('location', subtype: 'point')->nullable();
                $table->geometry('area', subtype: 'polygon')->nullable();
                $table->geometry('areas', subtype: 'multipolygon')->nullable();
    
                $table->timestamps();
            });
        }
    };
  2. Add HasSpatial trait to model
    <?php
    
    namespace App\Models;
    
    use MatanYadaev\EloquentSpatial\Traits\HasSpatial;
    
    
    class Location extends Model
    {
        use HasSpatial;
    }
  3. Define spatial columns of model
    <?php
    
    namespace App\Models;
    
    use MatanYadaev\EloquentSpatial\Objects\MultiPolygon;
    use MatanYadaev\EloquentSpatial\Objects\Point;
    use MatanYadaev\EloquentSpatial\Objects\Polygon;
    use MatanYadaev\EloquentSpatial\Traits\HasSpatial;
    
    
    class Location extends Model
    {
        use HasSpatial;
    
        protected $casts = [
            'location' => Point::class,
            'area'     => Polygon::class,
            'areas'    => MultiPolygon::class
        ];
    }
  4. Add map fields to resource
    <?php
    
    namespace App\Nova\Resources;
    
    use Mostafaznv\NovaMapField\Fields\MapMultiPolygonField;
    use Mostafaznv\NovaMapField\Fields\MapPointField;
    use Mostafaznv\NovaMapField\Fields\MapPolygonField;
    
    
    class Location extends Resource
    {
        public function fields(Request $request): array
        {
            return [
                MapPointField::make('location'),
                MapPolygonField::make('area'),
                MapMultiPolygonField::make('areas'),
            ];
        }
    }
  5. Done

Map Field Methods

Config Properties

Capture Screenshot

Utilizing this method enables the capture of a screenshot that mirrors the current state of the map field, subsequently saving it to the filesystem. It is important to note that this feature is experimental and its performance may vary. The underlying mechanism operates by generating an image on the client's machine each time the client/admin modifies the map field state. Subsequently, this image is transmitted to the server, where it is then stored in the filesystem.

To enable this feature, you must first add the capture method to your map field. This method accepts a single argument, which is an instance of the Capture class.

<?php

namespace App\Nova\Resources;

use App\Nova\Resource;
use Illuminate\Http\Request;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\Image;
use App\Models\Location as Model;
use Mostafaznv\NovaMapField\DTOs\Capture;
use Mostafaznv\NovaMapField\Fields\MapPointField;

class Location extends Resource
{
    public static string $model = Model::class;

    public function fields(Request $request): array
    {
        return [
            ID::make()->sortable(),

            MapPointField::make(trans('Location'), 'location')
                ->capture(
                     Capture::make('location_screenshot', 600, 600)
                        ->disk('location')
                        ->maxZoom(9)
                        ->padding([10, 10, 10, 10])
                        ->nearest(true)
                        ->prunable(true)
                ),

            Image::make(trans('Location Screenshot'), 'location_screenshot')
                ->disk('location')
                ->prunable()
                ->exceptOnForms(),
        ];
    }
}

Note

Activating this feature is as straightforward as calling the static make function of Capture. Nevertheless, for those seeking to tailor the behavior of this functionality, alternative methods are available for customization.

Using Spatial Columns over Application

This package uses Laravel Eloquent Spatial under the hood. to use columns and querying them over the application, please read Laravel Eloquent Spatial documentation

Tricks

Transform Polygons

To transform polygons, You should press Alt (Option ) button and drag that polygon everywhere you want.

Select Polygons

To select polygons (and modify them), You can press Alt (Option ) button and then click on the polygon. By pressing the Alt (Option ) key, drawing mode will be disabled, and you can select every polygon you want.

Complete Example

<?php

namespace App\Nova\Resources;

use App\Nova\Resource;
use Illuminate\Http\Request;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\Text;
use App\Models\Location as Model;
use Mostafaznv\NovaMapField\DTOs\Capture;
use Mostafaznv\NovaMapField\DTOs\PointValue;
use Mostafaznv\NovaMapField\Enums\MapSearchBoxType;
use Mostafaznv\NovaMapField\Enums\MapSearchProvider;
use Mostafaznv\NovaMapField\Fields\MapPointField;

class Location extends Resource
{
    public static string $model = Model::class;

    public function fields(Request $request): array
    {
        return [
            ID::make()->sortable(),

            Text::make('Title')
                ->sortable()
                ->rules('required', 'max:255'),
                
            MapPointField::make(trans('Location'), 'location')
                ->templateUrl('https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png')
                ->projection('EPSG:3857')
                ->srid(3857)
                ->defaultLatitude(35.6978527)
                ->defaultLongitude(51.4037269)
                ->zoom(14)
                ->withoutZoomControl()
                ->withoutZoomSlider()
                ->withoutUndoControl()
                ->withFullScreenControl()
                ->mapHeight(360)
                ->hideDetailButton(false)
                ->markerIcon(3)
                ->searchProvider(MapSearchProvider::OSM)
                ->searchProviderApiKey('api-key')
                ->withAutocompleteSearch()
                ->searchAutocompleteMinLength(4)
                ->searchAutocompleteTimeout(500)
                ->searchLanguage('fa-IR')
                ->searchPlaceholder('Placeholder ...')
                ->searchBoxType(MapSearchBoxType::BUTTON)
                ->searchResultLimit(3)
                ->searchResultKeepOpen(true)
                ->withTransformation()
                ->transformScale()
                ->transformRotate()
                ->transformStretch()
                ->required()
                ->requiredOnCreate()
                ->requiredOnUpdate()
                ->stacked()
                ->capture(
                    Capture::make('location_screenshot', 600, 600)
                        ->disk('location')
                        ->maxZoom(9)
                        ->padding([10, 10, 10, 10])
                        ->nearest(true)
                        ->prunable(true)
                    
                )
                ->default(
                    PointValue::make(51.5887845, 4.7760237)
                ),
        ];
    }
}

Deployment

The map field uses a web worker that loads data from blob values, and if you have a strict Content-Security-Policy header (e.g. with default 'none'), you will need to give permission to load data via this method by adding a worker-src directive to your CSP:

worker-src 'self' blob:;

Migration

From 3.* to 4.*

  • Support for matanyadaev/laravel-eloquent-spatial versions 2 and 3 has been dropped. The package now exclusively supports version 4 and higher.
  • The HasSpatialColumns trait has been removed from the package. Instead, use the HasSpatial trait from the laravel-eloquent-spatial package.
  • Both MapSearchBoxType and MapSearchProvider custom enums have been refactored and are now located in the Mostafaznv\NovaMapField\Enums namespace, utilizing PHP 8.1 enums. This update affects:
    • The searchBoxType and searchProvider methods across all map field types (MapPointField, MapPolygonField, MapMultiPolygonField)
    • The configuration file properties search.box-type and search.provider.

I am on an open-source journey 🚀, and I wish I could solely focus on my development path without worrying about my financial situation. However, as life is not perfect, I have to consider other factors.

Therefore, if you decide to use my packages, please kindly consider making a donation. Any amount, no matter how small, goes a long way and is greatly appreciated. 🍺

Donate

License

This software is released under The MIT License (MIT).

Sponsors

JetBrains Logo (Main) logo