Easy Way of Finding Coordinates in Polygon

Muratatak
4 min readJan 26, 2020

--

(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 :

https://github.com/geokit/geokit

https://github.com/apneadiving/Google-Maps-for-Rails

https://github.com/alexreisner/geocoder

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 :

https://github.com/geokit/geokit/blob/master/lib/geokit/polygon.rb

https://github.com/geokit/geokit/blob/master/lib/geokit/lat_lng.rb

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
end
end

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
end
privatedef 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
end
class Point
attr_accessor :lat, :lng
def initialize(lat, lng)
self.lat = lat.to_f
self.lng = lng.to_f
end
end
end

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: downtown
two:
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 geofence
Finished 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.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Muratatak
Muratatak

Written by Muratatak

Software Engineer, Rubyist, Follow Rails Way

No responses yet

Write a response