+4

Geocoder - A complete geocoding solution for Ruby

Geocoder.png

Geocoder

Geocoder là một giải pháp mã hóa địa lý hoàn chỉnh cho Ruby. Nó cho phép chuyển đổi tên các địa điểm thành tọa độ địa lý và ngược lại, thậm chí có thể chuyển đổi địa chỉ IP thành các địa chỉ đường phố. Nó cũng cho phép bạn tìm kiếm những địa điểm gần đó với khoảng cách và chỉ dẫn và rất nhiều tính năng hữu ích khác.

Khả năng tương thích

  • Hỗ trợ nhiều bản Ruby: Ruby 1.9.3, 2.x, JRuby, and Rubinius.
  • Hỗ trợ nhiều database: MySQL, PostgreSQL, SQLite, and MongoDB (1.7.0 and cao hơn).
  • Hỗ trợ Rails 3, 4 và 5. Nếu bạn cần sử dụng với Rails 2, hãy xem rails2 branch (không còn maintained và bị giới hạn tính năng)
  • Làm việc rất tốt bên ngoài Rails, bạn chỉ cần cài đặt json (cho MRI) và json_pure (cho JRuby) gem

Cài đặt

Chạy lệnh sau trong terminal

gem install geocoder

hoặc thêm gem sau vào Gemfile:

gem 'geocoder'

rồi chạy lệnh bundle install

Demo

Sau đây, tôi sẽ tạo một ví dụ để các bạn hiểu rõ hơn về các tính năng của Geocoder nhé.

Đầu tiên, tạo cho mình một ứng dụng Rails:

rails new GeocoderTest -T

Tiếp theo, tạo một Location model với scaffold bao gồm các trường address, latitudelongitude.

rails g scaffold Location address:string latitude:float longitude:float

Các trường latitudelongitude, Geocoder sẽ sử dụng để lưu trữ tọa độ của địa điểm. Đừng quên migrate database của bạn để tạo bảng locations mới nhé: rake db:migrate

Bây giờ chúng ta đã có thể tạo một địa điểm mới như sau

geocoder1.png

Tiếp theo, chúng ta sẽ sửa đổi Location model một chút, thêm một geocoded_by vào một thuộc tính mà chúng ta muốn Geocoder chuyển đổi geocoding, trong trường hợp này là trường address.

#/app/models/location.rb
class Location < ActiveRecord::Base
  geocoded_by :address
end

Khi một địa điểm được tạo hoặc được cập nhật, chúng ta cần phải gọi phương thức geocode để thực hiện việc mã hóa địa chỉ (geocoding). Mọi người thường phải sử dụng một after_validation callback để làm được điều này.

#/app/models/location.rb
class Location < ActiveRecord::Base
  geocoded_by :address
  after_validation :geocode
end

Phương thức Geocode sẽ gửi một request đến một API bên ngoài, mặc định là Google Maps API.

Chúng ta có thể khởi động ứng dụng bây giờ và thử Geocoder xem. Khi chúng ta thử nhật một địa điểm mới, ví dụ như "Keangnam Hanoi Landmark Tower", mà không có latitudelongitude, Geocoder sẽ lấy những dữ liệu đó từ API bên ngoài và thêm nó vào địa điểm này.

geocoder2.png

API bên ngoài sẽ được gọi mỗi khi chúng ta cập nhật địa điểm nhưng nó chỉ nên xảy ra mỗi khi trường address bị thay đổi. Vì vậy, chúng ta nên có một thay đổi đơn giản cho after_validation callback như sau:

#/app/models/location.rb
class Location < ActiveRecord::Base
  geocoded_by :address
  after_validation :geocode, if: :address_changed?
end

Nó sẽ chỉ tìm tọa độ mỗi khi address có sự thay đổi so với lần lưu cuối cùng.

Chúng ta cũng có thể sử dụng phương thức reverse_geocoded_by để chuyển đổi một latitudelongitude thành một address. Nó cũng làm việc tương tự như phương thức geocoded_by.

Tìm kiếm những địa điểm gần

Sẽ rất hữu ích khi chúng ta xem một địa điểm lại có thể nhìn thấy danh sách những địa điểm gần nó. Tôi có một số địa điểm trong database, bây giờ, chúng ta sẽ thêm tính năng này cho trang hiển thị một địa điểm.

Tôi muốn liệt kê mỗi vị trí với một khoảng cách nhất định và Geocoder tạo cho việc lấy các địa điểm lân cận một cách dễ dàng với phương thức nearbys của nó. Theo mặc định, nó sẽ trả về những địa điểm với bán kính là 20 dặm, nhưng ở đây tôi sẽ giới hạn nó đến 10 dặm thôi.

# /app/views/locations/show.html.erb
<p id="notice"><%= notice %></p>

<h1>Location</h1>
<p>
  <strong>Address:</strong>
  <%= @location.address %>
</p>
<p>
  <strong>Latitude:</strong>
  <%= @location.latitude %>
</p>
<p>
  <strong>Longitude:</strong>
  <%= @location.longitude %>
</p>

<h3>Nearby Locations</h3>
<ul>
<% @location.nearbys(10).each do |location| %>
  <li><%= link_to location.address, location %> (<%= location.distance.round(2) %> miles)</li>
<% end %>
</ul>

<p>
  <%= link_to "Edit", edit_location_path(@location) %> |
  <%= link_to "Destroy", @location, confirm: 'Are you sure?', method: :delete %> |
  <%= link_to "View All", locations_path %>
</p>

Mỗi khi chúng ta view một địa điểm, chúng ta sẽ thấy những địa điểm lân cận với nó trong khoảng cách 10 dặm, như sau:

Geocoder4.png

Tiếp theo, chúng ta sẽ thêm một search box trên trang locations index để có thể tìm kiếm những địa điểm lân cận với địa điểm đó.

#/app/views/locations/index.html.erb
<%= form_tag locations_path, :method => :get do %>
  <%= text_field_tag :search, params[:search] %>
  <%= submit_tag "Search Near", name: nil %>
<% end %>
<!-- phần còn lại của trang -->
#/app/controllers/locations_controller.rb
def index
  if params[:search].present?
    @locations = Location.near(params[:search], 10, units: :km)
  else
    @locations = Location.all
  end
end

Hãy thử tìm kiếm với từ khóa "Tổng cục Hải Quan", và xem kết quả hiện thị như sau:

geocoder5.png

Thêm bản đồ

Khi làm việc với các vị trí địa lý, sẽ rất hữu ích khi ta sử dụng một bản đồ cho mỗi địa điểm. Google Maps API cung cấp nhiều cách để thêm bản đồ tới một trang web, nhưng vì đơn giản nên ở đây tôi sẽ sử dụng một Maps API tĩnh. Bản đồ được thêm vào như một thẻ image_tag mà URL có các tham số xác định các thông số của bản đồ, bao gồm kinh độ, vĩ độ, kích thước, zoom, vv...

#/app/views/locations/show.html.erb
<%= image_tag "http://maps.google.com/maps/api/staticmap?size=450x300&sensor=false&zoom=16&markers=#{@location.latitude}%2C#{@location.longitude}" %>

Khi đó, trang hiển thị địa điểm sẽ như sau

geocoder 6.png

Nếu bạn muốn sử dụng bản đồ động, hãy thử tham khảo Google Maps For Rails gem.

Trên đây, tôi đã trình bày một số tính năng của gem Geocoder thông qua một ví dụ nho nhỏ, hi vọng nó sẽ giúp ích được cho bạn đọc 😄

Tài liệu tham khảo

  1. https://github.com/alexreisner/geocoder
  2. http://railscasts.com/episodes/273-geocoder?view=asciicast
  3. http://www.rubygeocoder.com/

All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.