Tìm vị trí với gem Geocoder

1, Giới thiệu

Trong một dự án tôi đã từng làm có một bài toán liên quan tới tìm vị trí như sau:
Người dùng nhập mã bưu điện postal code, hệ thống sẽ tự động tìm ra thông tin vị trí tương ứng với postal code mà người dùng đã nhập như: tên quốc gia, tên tỉnh thành, quận huyện, xã phường. Thoạt đầu tôi có một chút lo lắng liệu không biết có một thư viện nào hỗ trợ điều này không, nó có đủ lớn để tìm vị trí trên nhiều nước khác nhau không, thông tin nó trả về có đầy đủ không. Trường hợp xấu nhất theo tôi là phải dùng Google map api để get thông tin vị trí, nhưng như thế hơi thủ công và mất nhiều thời gian. Thật may cuối cùng tôi đã tìm được gem Geocoder nó đáp ứng đầy đủ các yêu cầu của bài toán tôi đang gặp phải.
Trong bài viết này tôi xin giới thiệu với các bạn cách dùng gem Geocoder để tìm thông tin vị trí thông qua mã bưu điện postal code giống như bài toán tôi gặp phải.

2, Cách sử dụng gem Geocoder giải quyết bài toán tìm vị trí qua postal code

2.1, Cài đặt

a, Cài gem Geocoder

Trong file Gemfile thêm dòng sau:

gem 'geocoder'

Và chạy lệnh

bundle install

b, Thêm thư viện AngularJs

Tôi sử dụng thư viện AngularJs để xử lý các logic trên client, cũng như là dùng ajax để thực hiện request tới server khi người dùng thao tác với hệ thống

  • Bạn download file angular.min.js từ url: https://code.angularjs.org/1.5.6/angular.min.js sau đó thêm nó tới thư mục app/assets/javascripts/lib

  • Để sử dụng thư viện này bạn cần require nó trong file application.js như sau:

//= require angular.min

2.2, Thêm view và tạo ajax request

Bạn tạo file pages/index.html.erb tương ứng với index action của pages controller
File có nội dung như sau:

<div class="container" ng-app="geocoderApp" ng-controller="addressController as vm">
  <div class="page-header">
    <h3>Geocoder Search With Postal Code</h3>
  </div>

  <div class="row">
    <div class="col-md-6">
      <%= text_field_tag :postal_code, nil, placeholder: "Enter postal code",
      class: "form-control", "ng-change": "vm.changePostalCode()", "ng-model": "vm.postalCode" %>
    </div>
  </div>

  <div class="row">
    <div class="col-md-6">
      <%= text_field_tag :country, nil, placeholder: "Country Name",
        readonly: true, class: "form-control", "ng-model": "vm.addressInfo.country" %>
    </div>
    <div class="col-md-6">
      <%= text_field_tag :area, nil, placeholder: "Area Name",
        readonly: true, class: "form-control", "ng-model": "vm.addressInfo.area" %>
    </div>
  </div>

  <div class="row">
    <div class="col-md-6">
      <%= text_field_tag :address, nil, placeholder: "Address", readonly: true,
        class: "form-control", "ng-model": "vm.addressInfo.address" %>
    </div>
  </div>
</div>

Kết quả như hình bên dưới:

report-t9-1a.png

Giao diện rất đơn giản chỉ gồm 4 textfiles, textfile đầu tiên để người dùng nhập postal code Trong file view này, tôi cũng đã định nghĩa angular app và controller, cùng các models và event để đảm bảo ứng dụng bắt được sự kiện người dùng nhập postal code và gửi postal code đó tới server để lấy data về.

Sau đó chúng ta thêm file assets/javascripts/angular/apps/geocoder_app.js, để định nghĩa angular app, file có nội dung như sau:

angular.module('geocoderApp', []);

Tiếp theo, chúng ta thêm file assets/javascripts/angular/controllers/address_controller.js để tạo một angular controller, file có nội dung như sau:

'use strict';

angular.module('geocoderApp').controller('addressController', addressController);
addressController.$inject = ['$http'];

function addressController($http) {
  var vm = this;
  vm.addressInfo = {country: null, area: null, address: null};

  vm.changePostalCode = function() {
    var promise = $http.get('/address_info', {postal_code: vm.postalCode});
    promise.success(function(data){
      vm.addressInfo = data;
    });
  };
}

Trong nội dung của controller này, chúng ta có thể thấy phần gọi ajax request để lấy thông tin address. Để gọi một ajax request trong angular, tôi sử dụng $http service

2.3, Xử lý dữ liệu trên server

Ở phần trên, chúng ta đã thực hiện gửi một ajax tới server để yêu cầu có thông tin về address tương ứng với postal code chúng ta đã nhập
Tiếp theo, chúng ta sẽ thêm code để server lấy postal code từ client gửi lên và dùng thư viện geocoder để lấy về dữ liệu tương ứng

Thêm dòng sau tới routes.rb file để định nghĩa controller

get "/address_info" => "addresses#index"

Thêm dòng sau tới application.rb

config.autoload_paths += %W(#{config.root}/lib)

Thêm file lib/myapp/geocoder.rb có nội dung như sau:

class Myapp::Geocoder
  TYPES = {
    country: "country",
    area: "administrative_area_level_1",
    address_1_part_1: %w(locality political),
    address_1_part_2: "sublocality_level_1"
  }

  def initialize search_term, language
    @search_term = search_term
    @language = language
    @address_info = search_address
  end

  def full_address_info_hash
    {
      country: get_location,
      area: get_area,
      address: get_first_address
    }
  end

  private
  attr_reader :search_term, :language, :address_info

  def search_address
    address = Geocoder.search search_term, params: {language: language}
    return {} unless address.present?
    address.first.data["address_components"]
  end

  def choose_address_item_by type
    address_info.find do |entry|
      type.is_a?(Array) ? entry["types"] == type : type.in?(entry["types"])
    end.try(:[], "long_name").to_s
  end

  def get_location
    choose_address_item_by TYPES[:country]
  end

  def get_area
    choose_address_item_by TYPES[:area]
  end

  def get_first_address
    separate_char = language == :ja ? "" : " "
    [choose_address_item_by(TYPES[:address_1_part_1]),
      choose_address_item_by(TYPES[:address_1_part_2])].join separate_char
  end
end

Thêm file app/controllers/addresses_controller.rb có nội dung như sau:

class AddressesController < ApplicationController
  def index
    render json: Myapp::Geocoder.new(params[:postal_code], "en").full_address_info_hash
  end
end

Đến đây thì ứng dụng của chúng ta đã hoàn thành, bạn có thể chạy ứng dụng và xem kết quả

3, Kết luận

Geocoder gem thực sự là một gem hỗ trợ cực tốt cho việc tìm các thông tin addess theo nhiều thể loại thông tin đầu vào. Ở bài viết này tôi mới chỉ áp dụng nó vào việc tìm kiếm address information theo postal code, geocoder còn rất nhiều tính năng hữu dụng khác, bạn có thể tham khảo tại: https://github.com/alexreisner/geocoder
Cảm ơn các bạn đã quan tâm đến bài viết của tôi, hẹn gặp lại ở các bài viết tiếp theo.
</br> Tài liệu tham khảo: https://github.com/alexreisner/geocoder
http://railscasts.com/episodes/273-geocoder?view=asciicast


All Rights Reserved