Thursday, February 19, 2015

RGeo Problem on Production Environment

BIP Informed Partnership Project is a national project that aims to decrease winter mortality of managed honeybee colonies by helping beekeepers keep colonies alive through surveys and data collection. Beekeepers can register a hive, assign it to a scale and track daily cycles such as weight, humidity and temperature. Over time, the data collected on the new Web application will become a research tool for scientists to use to discover patterns that could shed some light on this significant problem.
As a very basic use case, we needed to implement a feature for creating a Bee Hive. Here is the scenario:
  1. The beekeeper logs in.
  2. He adds the information for his hive.
  3. Using Google Maps API, he chooses the hive location on a map.
  4. He saves the newly created hive.
  5. On the next page he sees his hive information in addition to a Google Map Marker pinned to the hive location on a map.
To facilitate implementing mentioned scenario we used RGeo Gem which is a Geospatial data library for Ruby. Using RGeo we are able to work with Geospatial objects directly from our Ruby models. Further, using activerecord-postgis-adapter gem we can save our geospatial objects in PostgreSQL using PostGIS addon. The process is pretty straightforward.
Basically, in our hive model we use:
class Hive < ActiveRecord::Base
    set_rgeo_factory_for_column(:current_location, 
                              RGeo::Geographic.spherical_factory(:srid => 4326))
    ...
end
This defines current_location attribute on Hive model as a spherical object. Spherical objects have latitudes and longitude accessors.
Then in our location creator, we have the following code:
require_relative "./coordinate_builder"
require 'rgeo'

class LocationCreator
  DEFAULT = "default"

  def self.create(lat_lng)
    return nil if lat_lng == DEFAULT

    lng, lat = CoordinateBuilder.parse(lat_lng)
    geographic_factory = RGeo::Geographic.spherical_factory
    geographic_factory.point(lng, lat)
  end
end
LocationCreator receives an string which is comprised of latitude and longitude and then it tries to create a spherical object. Later during hive creation, we assigned the return value of 'create' method to @hive.current_location:
def create
    Hive.transaction do
      @hive = Hive.new(hive_params)
      @hive.current_location = LocationCreator.create(params[:hive][:latlng])
      respond_to_create
    end
end
Later in the views, we use the following piece of code to fetch the latitude and longitude of the points in order to create a Google Maps marker for the hive and pin it on a map:
<% if @hive.current_location.present? %>
  var myLatlng = new google.maps.LatLng(<%= @hive.current_location.latitude %>, <%=@hive.current_location.longitude %>);
  var mapOptions = {
    zoom: 16,
    center: myLatlng,
    mapTypeId: google.maps.MapTypeId.ROADMAP
  }
<% end  %>
We implemented the feature. Everyone was happy. The code was working on production and development environments. Our test was passing flawlessly on the development system. Also, the feature worked perfectly on every production server. Later we decided to host our project on Heroku. Here was when the problem with RGeo Objects started.
On Heroku, initially we used good naive Webrick which comes as the default web server. But Webrick is not scalable. For the sake of scalability, we considered switching our production server to Puma.
When switched to Puma, calling @hive.current_location.lon and @hive.current_location.lat in my Javascript views throws the error:
ActionView::Template::Error (undefined method 'lon' and 'lat' for #<RGeo::Cartesian::PointImpl:0x007f700846c970>)
Meanwhile, we tried to access the current_location latitude and longitude via heroku run rails console. The interesting thing was that fetching lat and long values on Heroku console returns correct values that we expected which meant that objects are correctly saved in the database. So the problem should be on the presentation side.

Workaround:  I don't know exactly why in the Javascript codes the SphericalPointImpl converts to Cartesian::PointImpl. we changed the javascript calls in the views from @hive.current_location.lat to @hive.current_location.y. Also for latitude, we did the same: @hive.current_location.lon to @hive.current_location.x.
due to the fact that RGeo::Cartesian::PointImpl supports .y and .x methods and not .latitude and .longitude methods.
This fixed the problem for us. So if you ran into this problem don't forget to check method availability on your objects.

No comments:

Post a Comment