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
https://github.com/muratatak77/api_find_cooords_in_polygon
Thanks for reading
Comments
Post a Comment