dev1191 / laravel-terra-draw
Interactive geospatial map drawing and GeoJSON form integration for Laravel using Terra Draw and MapLibre GL
Fund package maintenance!
Requires
- php: ^8.3 || ^8.4
- illuminate/contracts: ^11.0||^12.0||^13.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^11.0.0||^10.0.0||^9.0.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- spatie/laravel-ray: ^1.35
README
Seamless geospatial drawing, GeoJSON form integration, validation, and Eloquent model casting for Laravel powered by Terra Draw and MapLibre GL.
Easily embed interactive vector maps into Blade templates, forms, and admin panels with automatic GeoJSON data synchronization and server-side validation.
Features
- πΊοΈ Easy Blade Component: Use
<x-laravel-terra-draw::terra-draw />anywhere in your views. - βοΈ Comprehensive Drawing Modes: Support for Polygon, Rectangle, Circle, LineString, Freehand, Point, and Select / Edit.
- π Automatic Form Sync: Syncs drawn GeoJSON features directly into a hidden input for seamless Laravel form submission and Livewire binding.
- π‘οΈ Server-Side Validation Rule: Powerful
ValidGeoJsonrule with coordinate checking and fluent geometry restrictions (onlyPolygons(),onlyPoints(),minFeatures(), etc.). - π¦ Eloquent Model Cast: Built-in
AsGeoJsoncast for effortless database array serialization. - π¨ Built-in Toolbar: Sleek, customizable drawing controls with active mode indicators and clear canvas buttons.
- β‘ Zero-Config Directives: Load assets in seconds using
@terraDrawStylesand@terraDrawScripts. - βοΈ Fully Configurable: Customizable initial coordinates, zoom levels, MapLibre tile styles, and mode permissions.
Installation
Install the package via Composer:
composer require dev1191/laravel-terra-draw
Publish the configuration file (optional):
php artisan vendor:publish --tag="laravel-terra-draw-config"
Publish the Blade views (optional):
php artisan vendor:publish --tag="laravel-terra-draw-views"
Quick Start
1. Embed Map in Blade View
<!DOCTYPE html> <html> <head> <title>My Map Form</title> {{-- Include MapLibre & Toolbar Styles --}} @terraDrawStyles </head> <body> <form method="POST" action="{{ route('locations.store') }}"> @csrf {{-- Terra Draw Component --}} <x-laravel-terra-draw::terra-draw name="boundary" :center="[85.3240, 27.7172]" :zoom="12" height="500px" /> <button type="submit">Save Boundary</button> </form> {{-- Include Scripts --}} @terraDrawScripts </body> </html>
2. Validate in Controller (ValidGeoJson)
Use the ValidGeoJson rule to validate the incoming GeoJSON payload and enforce geometry constraints:
use DevRajThapa\LaravelTerraDraw\Rules\ValidGeoJson; use Illuminate\Http\Request; public function store(Request $request) { $request->validate([ // Enforce valid GeoJSON structure and coordinates 'boundary' => ['required', new ValidGeoJson()], // Or use fluent geometry constraints: // 'boundary' => ['required', ValidGeoJson::make()->onlyPolygons()], // 'marker' => ['required', ValidGeoJson::make()->onlyPoints()], // 'road' => ['required', ValidGeoJson::make()->onlyLineStrings()], // 'area' => ['required', ValidGeoJson::make()->allowedModes(['polygon', 'rectangle', 'circle'])], // 'zones' => ['required', ValidGeoJson::make()->minFeatures(1)->maxFeatures(5)], ]); Location::create([ 'name' => $request->input('name'), 'boundary' => $request->input('boundary'), ]); }
3. Cast on Eloquent Model (AsGeoJson)
Add the AsGeoJson cast to your Eloquent model for automatic array casting:
namespace App\Models; use DevRajThapa\LaravelTerraDraw\Casts\AsGeoJson; use Illuminate\Database\Eloquent\Model; class Location extends Model { protected $fillable = ['name', 'boundary']; protected function casts(): array { return [ 'boundary' => AsGeoJson::class, ]; } }
Now you can interact with boundary as a native PHP array:
$location = Location::find(1); // Array of GeoJSON features $features = $location->boundary['features']; // Mutate and save $location->boundary = $updatedGeoJsonArray; $location->save();
Component Props Reference
| Prop | Type | Default | Description |
|---|---|---|---|
name |
string |
'geometry' |
Name of the hidden input field submitted with the form. |
id |
string |
Auto-generated | Unique identifier for the map container and input. |
value |
string|array |
null |
Initial GeoJSON payload to render on load (e.g. $location->boundary). |
center |
array |
[0, 0] |
Center coordinates [longitude, latitude]. |
zoom |
int|float |
2 |
Initial map zoom level. |
height |
string |
'450px' |
Height of the map canvas (e.g. '500px', '70vh'). |
modes |
array |
['polygon', ...] |
Array of enabled drawing modes. |
initialMode |
string |
'polygon' |
Mode activated immediately when the map loads. |
mapStyle |
string |
Demo Tiles | URL to MapLibre style JSON. |
toolbar |
bool |
true |
Show or hide the top drawing toolbar. |
editable |
bool |
true |
When false, renders in read-only / static mode. |
Pre-loading Existing GeoJSON
Pass existing GeoJSON data into the :value prop:
<x-laravel-terra-draw::terra-draw name="boundary" :value="$location->boundary" :center="[85.3240, 27.7172]" :zoom="13" />
TerraDraw Facade & Helpers
The TerraDraw Facade provides convenient server-side geospatial utilities:
use DevRajThapa\LaravelTerraDraw\Facades\TerraDraw; // Count total features $count = TerraDraw::getFeatureCount($geojson); // Get unique geometry types (e.g. ['Polygon', 'Point']) $types = TerraDraw::getGeometryTypes($geojson); // Extract raw coordinates array $coordinates = TerraDraw::extractCoordinates($geojson); // Check if empty if (TerraDraw::isEmpty($geojson)) { // ... } // Validate against allowed modes $isValid = TerraDraw::validate($geojson, ['polygon', 'rectangle']);
JavaScript Events & Interoperability
The component dispatches DOM events for vanilla JavaScript, Alpine.js, and Livewire:
const mapElement = document.getElementById('my-map-id'); mapElement.addEventListener('terra-draw:change', (event) => { const { snapshot, type, ids } = event.detail; console.log('GeoJSON updated:', snapshot); }); mapElement.addEventListener('terra-draw:ready', (event) => { const { map, draw } = event.detail; console.log('MapLibre and TerraDraw ready!', map, draw); });
Configuration (config/terra-draw.php)
return [ 'modes' => [ 'polygon', 'rectangle', 'circle', 'linestring', 'freehand', 'point', 'select', ], 'initial_mode' => 'polygon', 'map_style' => 'https://demotiles.maplibre.org/style.json', 'center' => [0, 0], 'zoom' => 2, 'height' => '450px', 'toolbar' => true, ];
Testing
Run the test suite using Pest:
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Credits
- DevRajThapa
- James Milner (Creator of Terra Draw)
- All Contributors
License
The MIT License (MIT). Please see License File for more information.