RGeo is a gem for writing location-based applications in Ruby programming language.
It is used to:
- Represent spatial and geolocation data objects such as points, lines, and polygons.
- Perform standard spatial analysis operations such as finding intersections, creating buffers, and computing lengths and areas.
- Correctly handle spherical geometry, and compute geographic projections for map display and data analysis.
- Read and write location data in the WKT and WKB representations used by spatial databases.
Prerequisites:
- Rails app.
- PostgreSQL database with PostGIS extension enabled.
- rgeo gem.
- activerecord-postgis-adapter gem.
Installing the rgeo gem:
Before we install rgeo gem, we need to install Geos.
For installing geos on Mac, execute brew install geos.
Add rgeo in the Gemfile and execute bundle install.
To verify that rgeo is installed correctly, we need to execute RGeo::Geos.supported? in the Rails console
(rails c in the terminal).
If the response is true, it is installed successfully.
RGeo Calculations:
To store spatial data, we must create column with spatial type. PostGIS provides various spatial datatypes like point, linestring, polygon, etc.
The activerecord-postgis-adapter gem supports the above spatial type in
ActiveRecord’s migration.
We can create spatial datatype columns in migration as below
create_table :spatial_table do |t|
t.st_point :latlong, geographic: true
t.geometry :area
t.linestring :route
end We will create a simple model Locality with columns name and latlong as below.
rails generate model Locality name:string latlon:pointThe migration file looks as below
class CreateLocalities < ActiveRecord::Migration
def change
create_table :localities do |t|
t.string :name
t.point :latlon
t.timestamps
end
end
endDistance between two points
We create two Locality entries as below.
saeloun_locality = Locality.create(
name: "Saeloun Office",
latlon: "POINT(-71.4313134 42.5891898)"
)
liberty_locality = Locality.create(
name: "Statue of Liberty",
latlon: "POINT(-74.0466891 40.6892494)"
) We can get the distance between the above two coordinates by using the distance method as below
saeloun_locality.distance(liberty_locality)
=> 303408.38530644414The distance method returns the shortest distance in meters between two points.
To the POINT function, we pass two numeric values longitude and then latitude.
Querying by location
The PostGIS database supports querying on spatial datatype.
We can figure out which of the above two localities created are closer to a particular coordinate.
For eg., Boston Public Garden is around 36.3 miles (58.42 km) from Saeloun locality and around 215 miles (346 km) from Statue of Liberty. Using the coordinates of Boston Public Garden we can identify which localities are within 62 miles (100 km) and which are greater than 62 miles.
Boston Public Garden coordinates are 42.3541675, -71.0726166.
Where 42.3541675 is latitude and -71.0726166 is longitude.
Locality.where("ST_Distance(latlon, "+
"'POINT(-71.0726166 42.3541675)') < 100000").
map{ |ar| ar.name }
=> ["Saeloun Office"]
Locality.where("ST_Distance(latlon, "+
"'POINT(-71.0726166 42.3541675)') > 100000").
map{ |ar| ar.name }
=> ["Statue Of Liberty"] Here we have used ST_Distance function which calculates the distance between two points.
Distance between points using Rgeo factory
We can calculate the distance between two points by using
their coordinates and calling the distance function.
point1 = RGeo::Geographic.spherical_factory.point(-74.0466891, 40.6892494)
point2 = RGeo::Geographic.spherical_factory.point(-71.4313134, 42.5891898)
point1.distance(point2)
=> 303408.38530644414The point function expects longitude and latitude as the first and second parameter respectively.
Point Inside a polygon or not
PostGIS database also supports storing polygon data in a table.
We will add the polygon datatype column to Locality model via migration as shown below:
rails generate migration add_polygon_to_localities
class AddPolygonToLocality < ActiveRecord::Migration[5.0]
def change
add_column :localities, :site_polygon, :st_polygon
end
end Let’s say we create New York locality in Locality table which has polygon endpoints at
- Saeloun Office
- Statue of Liberty
- Syracuse
Syracuse coordinates are (43.0352286, -76.1742994)
We create the New York locality in Locality table as below
site_polygon = "POLYGON ((-71.4313134 42.5891898, -74.0466891 40.6892494, -76.1742994 43.0352286,-71.4313134 42.5891898))"
new_york_locality = Locality.create(name: "New York", site_polygon: site_polygon)Kingston lies between the polygon created above. And Virginia lies outside this polygon.
So we can check whether a point lies inside a polygon or not
using contains? method as below
kingston_coordinate = RGeo::Geographic.spherical_factory.point(-74.0166785, 41.9274042)
new_york_locality.site_polygon.contains?(kingston_coordinate)
=> true
virginia_coordinate = RGeo::Geographic.spherical_factory.point(-81.6640155, 37.9820796)
new_york_locality.site_polygon.contains?(virginia_coordinate)
=> falseWe can also check whether a coordinate lies within any Locality, using the below query.
Locality.where("st_contains(site_polygon, '#{kingston_coordinate}')").first
=> #<Locality:0x00007fd0d74136b0
id: 7,
name: "New York",
latlon: nil,
created_at: Wed, 18 Sep 2019 11:25:40 EDT -04:00,
updated_at: Wed, 18 Sep 2019 11:25:40 EDT -04:00,
site_polygon: #<RGeo::Geos::CAPIPolygonImpl:0x3fe86b9d12e4 "POLYGON ((-71.4313134 42.5891898, -74.0466891 40.6892494, -76.1742994 43.0352286, -71.4313134 42.5891898))">>
Locality.where("st_contains(site_polygon, '#{virginia_coordinate}')").first
=> nilNote we can index these spatial datatype colum to improve query performance.
