donsteffenski
donsteffenski

Reputation: 477

Setting single coordinates for RGeo Point

RGeo provides built in methods for POINT features, for example getter methods lat() and lon() to pull latitude and longitude values from a POINT object. Unfortunately, these don't work as setters. For example:

point = RGeo::Geographic.spherical_factory(:srid => 4326).point(3,5)     // => #<RGeo::Geographic::SphericalPointImpl:0x817e521c "POINT (3.0 5.0)">

I can do this:

point.lat      // => 5.0
point.lon      // => 3.0

But I can't do:

point.lat = 4    // => NoMethodError: undefined method `lat=' for #<RGeo::Geographic::SphericalPointImpl:0x00000104024770>

Any suggestions as to how to implement setter methods? Would you do it in the Model or extend the Feature class?

Upvotes: 7

Views: 5000

Answers (3)

Matt Kent
Matt Kent

Reputation: 146

I ended up doing something like this in my model:

class MyModel < ActiveRecord::Base

  attr_accessor :longitude, :latitude
  attr_accessible :longitude, :latitude

  validates :longitude, numericality: { greater_than_or_equal_to: -180, less_than_or_equal_to: 180 }, allow_blank: true
  validates :latitude, numericality: { greater_than_or_equal_to: -90, less_than_or_equal_to: 90 }, allow_blank: true

  before_save :update_gps_location

  def update_gps_location
    if longitude.present? || latitude.present?
      long = longitude || self.gps_location.longitude
      lat = latitude || self.gps_location.latitude
      self.gps_location = RGeo::Geographic.spherical_factory(srid: 4326).point(long, lat)
    end
  end
end

Then you can just update the position like so:

my_model.update_attributes(longitude: -122, latitude: 37)

I didn't load up longitude/latitude in an after_initialize block because in my app we never need to read the data, only write it. You could easily add that though.

Credit to this answer for the validations.

Upvotes: 0

Daniel Azuma
Daniel Azuma

Reputation: 839

I'm the author of RGeo, so you can consider this answer authoritative on that basis.

In short, PLEASE AVOID DOING THIS. RGeo objects intentionally have no setter methods because they are meant to be immutable objects. This is so that they can be cached, used as hash keys, used across threads, etc. Some of the RGeo calculations assume that the value of a feature object will never change, so making changes like this could have unexpected and unpredictable consequences.

If you really want a "changed" value, create a new object. For example:

p1 = my_create_a_point()
p2 = p1.factory.point(p1.lon + 20.0, p2.lat)

Upvotes: 33

donsteffenski
donsteffenski

Reputation: 477

I have found something that works, although there might be more elegant solutions.

In my Location model I have added theses methods:

  after_initialize :init


  def init
    self.latlon ||= Location.rgeo_factory_for_column(:latlon).point(0, 0)
  end

  def latitude
    self.latlon.lat
  end

  def latitude=(value)
    lon = self.latlon.lon
    self.latlon = Location.rgeo_factory_for_column(:latlon).point(lon, value)
  end

  def longitude
    self.latlon.lon
  end

  def longitude=(value)
    lat = self.latlon.lat
    self.latlon = Location.rgeo_factory_for_column(:latlon).point(value, lat)
  end

Upvotes: 3

Related Questions