GEO Spatial MySQL in Laravel 5
Overview Ngày nay, tính năng bản đồ được sử dụng rất nhiều ở hầu hết các ứng dụng. Từ version 5.6, MySQL đã giới thiệu một số chức năng thú vị về thao tác và lưu trữ dữ liệu địa lý. Nhưng trong Laravel ORM lại không được hỗ trợ tính năng thao tác dữ liệu kiểu này. Trong bài này tôi sẽ giới ...
Overview
Ngày nay, tính năng bản đồ được sử dụng rất nhiều ở hầu hết các ứng dụng. Từ version 5.6, MySQL đã giới thiệu một số chức năng thú vị về thao tác và lưu trữ dữ liệu địa lý. Nhưng trong Laravel ORM lại không được hỗ trợ tính năng thao tác dữ liệu kiểu này.
Trong bài này tôi sẽ giới thiệu tới các bạn việc làm thế nào chúng ta có thể tích hợp được tính năng hay như vậy vào ứng dụng của tôi.
Ví dụ dưới đây tôi sẽ giới thiệu việc lưu trữ cá Geo. Bạn sẽ thấy nó được ứng dụng như thế nào trong việc lưu trữ, phối hợp và truy vấn dữ liệu dựa trên khoảng cách sử dụng các chức năng cơ bản MySQL.
Coding Standard
Migration
Việc đầu tiên chúng ta cần tạo 1 table bằng lệnh tạo migration
php artisan make:migration create_table_items --create="items"
Sau đó chúng ta sẽ tạo bảng items cơ bản như sau
public function up() { Schema::create('items', function(Blueprint $table) { $table->increments('id'); $table->string('title'); $table->timestamps(); }); DB::statement('ALTER TABLE items ADD location POINT' ); }
Đến đây, chắc hẳn bạn đã thấy dòng cuối có đoạn add column location Point. Vâng, để hiểu được vì sao bạn hãy tiếp tục đọc đến model tôi viết bên dưới
Make model
Chạy lệnh artisan tạo model Item
php artisan make:model Item
Tôi sẽ viết code vào model mà tôi vừa tạo như sau
class Item extends Model { protected $geofields = array('location'); public function setLocationAttribute($value) { $this->attributes['location'] = DB::raw("POINT($value)"); } public function getLocationAttribute($value){ $loc = substr($value, 6); $loc = preg_replace('/[ ,]+/', ',', $loc, 1); return substr($loc,0,-1); } public function newQuery($excludeDeleted = true) { $raw='; foreach($this->geofields as $column){ $raw .= ' astext('.$column.') as '.$column.' '; } return parent::newQuery($excludeDeleted)->addSelect('*',DB::raw($raw)); } }
Đoạn code trên tôi đã sử dụng một số phương thức khá quan trọng. Bạn thấy đấy newQuery() mà tôi sử dụng ở trên chính là nhân tố của vấn đề. Tiếp theo là sử dụng $geofields như một mảng lưu ở dạng map hệ nhị phân. Đây chính là điều tôi muốn nói với bạn, nếu không có phương pháp này chúng ta không thể đọc được các trường nhị phân
Còn có 2 dạng thức khác là accessors và getter mà bạn có thể sử dụng. Ở đây, bạn có thể khai báo kết quả trả về nếu như mà bạn muốn. Tôi ví dụ 2 lựa chọn kết quả trả về dạng lat,lng (đây là dạng khá phổ biến được sử dụng dạng POINT trong map google)
Distance Filter
Bậy giờ, tôi có thể sử dụng tính năng truy vấn POINT location dễ dàng thông qua phạm vi truy vấn trong Laravel:
public function scopeDistance($query,$dist,$location) { return $query->whereRaw('st_distance(location,POINT('.$location.')) < '.$dist); }
Đoạn trên sẽ build ra được đoạn mã sql như thế này
Item::distance(1,'42.32232,12.31312121')->get()
Test a simple MAP
Và lúc này, bạn có thể kiểu tra được kết quả thực hiện của nó trong ứng dụng cụ thể. Tôi sẽ tạo ra một bộ lọc bản đồ bởi khoảng cách từ một điểm nhất định
php artisan make:controller ItemsController –plain
Định nghĩa đường dẫn map trong the route.php file
Route::get('map','ItemsController@map');
Viết phương thức map() function trong Controller
public function map() { $items = Item::all(); //$items = Item::distance(0.1,'45.05,7.6667')->get(); return view('items.map')->with(['items'=>$items]); }
Tạo một blade map.blade.php in views folder, và content như sau
@extends('app') @section('content') <div class="container"> <div class="row"> <div id="map-canvas"></div> </div> </div> <script type="text/javascript"> var mapOptions = { zoom: 4, center: new google.maps.LatLng({{$items[0]->location}}) } var map = new google.maps.Map(document.getElementById("map-canvas"), mapOptions); @foreach($items as $item) var marker{{$item->id}} = new google.maps.Marker({ position: new google.maps.LatLng({{$item->location}}), map: map, title: "{{$item->title}}" }); @endforeach </script> @endsection
Bạn có thể download dữ liệu seeder với 1000 địa chỉ của Italian ở đây DataSeeder.php