Easy Way of Finding Coordinates in Polygon





(Ruby on Rails 6 only API, Postgres, Minitest)
Hi everybody,

Sometimes, we need specific features in our application. One of them may be “How to find Coordinates in Polygon” :)
We suppose, we have some areas in our application. We define these areas by using Google Maps and every area has some specific attributes that are name , price, and coordinates. For example, we need information like this, when we click on a point on the map, what are the properties of this point? How to find if the dots are between the corresponding coordinates?

GEMS

If we are using Ruby on Rails, we think firstly that “is there any Gem about finding these points? Yes, there are some Gems. Like :
But these gems little huge :) Actually, we don’t need to use completely a gem. Because we just find a point in a polygon, technically. There is a cost of a gem for our application. That's why I select a module from Geokit gem :
I think these files are enough for this feature.

LETS DEVELOP

Today, I will develop in Ruby on Rails 6 only API. DB is Postgres.
Of course, we will proceed with the TDD method by using Minitest.
For fast development, I prefer “scaffold”
rails g scaffold geofence area_name:string — apiRunning via Spring preloader in process 37838
      invoke  active_record
      create    db/migrate/20200126041210_create_geofences.rb
      create    app/models/geofence.rb
      invoke    test_unit
      create      test/models/geofence_test.rb
      create      test/fixtures/geofences.yml
      invoke  resource_route
       route    resources :geofences
      invoke  scaffold_controller
      create    app/controllers/geofences_controller.rb
      invoke    test_unit
      create      test/controllers/geofences_controller_test.rb
We created geofence.rb model and just one attribute area_name and other processes.
We need to add coordinates array to the geofence model.
“db/migrate/20200126041210_create_geofences.rb”
class CreateGeofences < ActiveRecord::Migration[6.0]
  def change
    create_table :geofences do |t|
      t.float :coordinates, :array => true
      t.string :area_name
      t.timestamps
    end
  end
end
and “db/schema “ :
ActiveRecord::Schema.define(version: 2020_01_23_041217) do# These are extensions that must be enabled in order to support this database
  enable_extension "plpgsql"create_table "geofences", force: :cascade do |t|
    t.float "coordinates", array: true
    t.string "area_name"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  endend
We need to check tests :
rails test test/controllers/geofences_controller_test.rb
Little magical. We didn't do anything but works all tests :)
Finished in 0.554706s, 12.6193 runs/s, 19.8303 assertions/s.
7 runs, 11 assertions, 0 failures, 0 errors, 0 skips
We define a new method which name is “find_by coordinate” in Geofence Controller.rb to find about we wanting coordinates.
we can add firstly in routes file :
Rails.application.routes.draw do
  resources :geofences
  post 'find_by_coordinate', to: 'geofences#find_by_coordinate#'
end
And Geofence Controller.rb :
def find_by_coordinate
    target_coords = params[:target_coordinates]
    data = @geofence.find_target_coordinates target_coords
    if data.present?
      render json: @geofence 
    else
      render json: @geofence.errors, status: :unprocessable_entity
    end
  end
I am seeing a method in geofence.rb class :
@geofence.find_target_coordinates target_coords
geofence.rb :
require "#{Rails.root}/lib/geokit/lat_lng.rb"
require "#{Rails.root}/lib/geokit/polygon.rb"class Geofence < ApplicationRecordattr_accessor :lat, :lngdef find_target_coordinates target_coordinates
  @lat = target_coordinates[:lat]
  @lng = target_coordinates[:lng]
  if contains_point?
   puts "Geofence Model - Success -  We found geofence by lat : #{lat} / lng : #{@lng} in Geofence  [ ID : #{self.id} / coordinated : #{self.coordinates} ] "
   self
  else
   puts "Geofence Model - Warning  -  We did not found geofence by lat : #{lat} / lng : #{@lng} in Geofence  [ ID : #{self.id} / coordinated : #{self.coordinates} ] "
   errors.add(:base, "We did not found geofence.")
   nil
  end
 endprivatedef contains_point?
  points = []
  self.coordinates.each do |coord|
   points << Geokit::LatLng.new(coord[0], coord[1])
  end
  polygon = Geokit::Polygon.new(points)
  point = Point.new(@lat, @lng)
  polygon.contains? point
 endclass Point
  attr_accessor :lat, :lng
  def initialize(lat, lng)
   self.lat = lat.to_f
   self.lng = lng.to_f
  end
 endend
As seen, We have 2 requires files. I copy and paste the Geokit module in my application lib folders. And I just use to find the required area in a polygon.
Firstly, I add the geofence model coordinates to LatLng.rb class.
points << Geokit::LatLng.new(coord[0], coord[1])
Then, I created “Point.rb” class for target coordinates. We need this class, because the Polygon module wants Point objects.
point  = Point.new(@lat, @lng)class Point
  attr_accessor :lat, :lng
  def initialize(lat, lng)
   self.lat = lat.to_f
   self.lng = lng.to_f
  end
 end

TESTING

We need more 2 tests for testing finding the right and wrong coordinates.
We can add a test in test/controllers/geofences_controller_test.rb
test "should find_by_coordinate_url geofence" do
    puts "Testing should find geofence"
    data = {}
    data[:id] = @geofence.id
    data[:target_coordinates] = {"lat": 37.7615389, "lng":  -122.4144601}
    post find_by_coordinate_url, params: data, as: :json
    assert_response 200
    res =  JSON.parse(response.body)
    assert_equal @geofence.area_name, res['area_name']
  end
This method has “target coordinates” that is a customer click these points for getting specific area properties.
We got a geofence model :
setup do
 @geofence = geofences(:one) #getting geofence.yml a record 
end
from “/fixtures/geofence.yml”
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.htmlone:
  coordinates: [[37.796285118327, -122.557322342739], [37.8277482679855, -122.380854447231], [37.7170254390543, -122.333475907192], [37.6876887080233, -122.552515824184]]
  area_name: downtowntwo:
  coordinates: [[37.7116950858012, -122.329346944102], [37.6812693981973, -122.551820088633], [37.5539984566077, -122.565552998789], [37.3828740274489, -122.46804933668], [37.4722999873452, -121.994263936289]]
  area_name: san mateo
runnings test :
➜ rails test test/controllers/geofences_controller_test.rb
Running via Spring preloader in process 38195
Run options: --seed 20388# Running:Testing index
Testing show
Testing update
..Testing create
Testing destroy
...Testing should find geofence
Geofence Model - Success -  We found geofence by lat : 37.7615389 / lng : -122.4144601 in Geofence  [ ID : 980190962 / coordinated : [[37.796285118327, -122.557322342739], [37.8277482679855, -122.380854447231], [37.7170254390543, -122.333475907192], [37.6876887080233, -122.552515824184]] ]
.Testing should find geofenceFinished in 0.554706s, 12.6193 runs/s, 19.8303 assertions/s.
7 runs, 11 assertions, 0 failures, 0 errors, 0 skips
and what will happen with the wrong data :
test "should not find_by_coordinate_url geofence" do
    puts "Testing should find geofence"
    data = {}
    data[:id] = @geofence.id
    data[:target_coordinates] = {"lat": 39.7615389, "lng":  -122.4144601}
    post find_by_coordinate_url, params: data, as: :json
    assert_response 422
 end
result :
➜ rails test test/controllers/geofences_controller_test.rb
Running via Spring preloader in process 38195
Run options: --seed 20388# Running:Testing index
Testing show
Testing update
..Testing create
Testing destroy
...Testing should find geofence

Geofence Model - Warning  -  We did not found geofence by lat : 39.7615389 / lng : -122.4144601 in Geofence  [ ID : 980190962 / coordinated : [[37.796285118327, -122.557322342739], [37.8277482679855, -122.380854447231], [37.7170254390543, -122.333475907192], [37.6876887080233, -122.552515824184]] ]
.Finished in 0.554706s, 12.6193 runs/s, 19.8303 assertions/s.
7 runs, 11 assertions, 0 failures, 0 errors, 0 skips
works!
You can reach this source my GitHub repository :
https://github.com/muratatak77/api_find_cooords_in_polygon
Thanks for reading

Comments

Popular posts from this blog

Design a notification system

NETFLIX HIGH-LEVEL SYSTEM DESIGN

URL Shortener System Design